Skip to content

Commit 3254c96

Browse files
authored
Themessss (codesandbox#3424)
* component inventory * theeming v1 * the things i do for typescript * make it worse to make it better?
1 parent 9bbc3d9 commit 3254c96

File tree

4 files changed

+167
-56
lines changed

4 files changed

+167
-56
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,5 @@ Thanks goes to these wonderful people
255255

256256
<!-- markdownlint-enable -->
257257
<!-- prettier-ignore-end -->
258+
258259
<!-- ALL-CONTRIBUTORS-LIST:END -->

packages/common/src/themes/codesandbox-black.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const colors = {
2626
background: tokens.blues[600],
2727
foreground: tokens.white,
2828
border: tokens.blues[600],
29-
hoverBackground: tokens.blues[600],
3029
},
3130
dropdown: {
3231
background: tokens.grays[700],
@@ -210,7 +209,7 @@ const colors = {
210209
},
211210
sideBar: {
212211
background: tokens.grays[700],
213-
hoverBackground: tokens.green,
212+
hoverBackground: tokens.grays[600],
214213
border: tokens.grays[600],
215214
foreground: tokens.grays[200],
216215
},

packages/components/src/components/ThemeProvider/index.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
* polyfill - color tokens missing from vscode
77
*/
88
import React from 'react';
9-
import dot from 'dot-object';
109
import deepmerge from 'deepmerge';
1110
import designLanguage from '@codesandbox/common/lib/design-language';
1211
import { ThemeProvider as BaseThemeProvider } from 'styled-components';
@@ -21,19 +20,16 @@ export const getThemes = () => {
2120

2221
return results.filter(a => a);
2322
};
24-
export const makeTheme = (vsCodeTheme = { colors: {} }, name?: string) => {
25-
// black is the default, it would be helpful to use storybook-addon-themes
26-
// to test our components across multiple themes
27-
// convert vscode colors to dot notation so that we can use them in tokens
28-
const vsCodeColors = dot.object({ ...vsCodeTheme.colors });
29-
23+
export const makeTheme = (vsCodeTheme, name?: string) => {
3024
// Our interface does not map 1-1 with vscode.
3125
// To add styles that remain themeable, we add
3226
// some polyfills to the theme tokens.
33-
const polyfilledVSCodeColors = polyfillTheme(vsCodeColors);
27+
const polyfilledVSCodeColors = polyfillTheme(vsCodeTheme);
3428

3529
// merge the design language and vscode theme
36-
const theme = deepmerge(designLanguage, { colors: polyfilledVSCodeColors });
30+
const theme = deepmerge(designLanguage, {
31+
colors: polyfilledVSCodeColors,
32+
});
3733

3834
if (name) {
3935
return {
Lines changed: 160 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,180 @@
11
/**
2-
* Our interface does not map 1-1 with vscode
3-
* To add styles that remain themeable, we add
4-
* some _polyfills_ to the theme tokens.
5-
* These are mapped to existing variables from the vscode theme
6-
* that always exists - editor, sidebar.
72
*
8-
* These are our best guesses.
3+
* switch color for light theme
4+
* secondary button color
5+
* collapsible icon
6+
*
97
*/
8+
import dot from 'dot-object';
9+
import deepmerge from 'deepmerge';
10+
import Color from 'color';
11+
import designLanguage from '@codesandbox/common/lib/design-language/theme';
12+
import codesandboxBlack from '../themes/codesandbox-black';
13+
import codesandboxLight from '../themes/codesandbox-light.json';
1014

11-
// TODO: For themes that we officially support, we have the option
12-
// to modify the theme and add our custom keys
13-
// which we can use when the polyfill is a bad alternate.
14-
// In that case, we should check if it exists before overriding it
15+
const polyfillTheme = vsCodeTheme => {
16+
/**
17+
*
18+
* In order of importance, this is the value we use:
19+
* 1. Value from theme
20+
* 2. or inferred value from theme
21+
* 3. or value from codesandbox black/light
22+
*
23+
* The steps required to get there are -
24+
* 1. Take vscode theme
25+
* 2. Fill missing values based on existing values or codesandbox dark/light
26+
* 3. Infer values that are not defined by vscode theme
27+
*
28+
*/
29+
let uiColors: any = {
30+
// initialise objects to avoid null checks later
31+
editor: {},
32+
button: {},
33+
input: {},
34+
inputOption: {},
35+
sideBar: {},
36+
};
1537

16-
import deepmerge from 'deepmerge';
17-
import designLanguage from '@codesandbox/common/lib/design-language';
38+
const type = vsCodeTheme.type || guessType(vsCodeTheme);
1839

19-
const polyfillTheme = vsCodeTheme =>
20-
deepmerge(vsCodeTheme, {
21-
sideBar: {
22-
hoverBackground: (vsCodeTheme.sideBar || {}).border || 'red',
23-
},
24-
// this works for codesandbox-black but I doubt other themes define this
25-
mutedForeground: vsCodeTheme.input.placeholderForeground,
26-
// putting this here so that we remember to polyfill it
27-
input: {
28-
placeholderForeground: vsCodeTheme.input.placeholderForeground,
29-
},
30-
inputOption: {
31-
activeBorder:
32-
(vsCodeTheme.inputOption || {}).activeBorder ||
33-
vsCodeTheme.input.placeholderForeground,
34-
},
35-
avatar: {
36-
border: (vsCodeTheme.sideBar || {}).border || 'red',
37-
},
40+
// Step 1: Initialise with vscode theme
41+
const vsCodeColors = dot.object(vsCodeTheme.colors || {});
42+
uiColors = deepmerge(uiColors, vsCodeColors);
43+
44+
// Step 2: Fill missing values from existing values or codesandbox dark/light
45+
46+
const codesandboxColors = ['dark', 'lc'].includes(type)
47+
? dot.object(codesandboxBlack.colors)
48+
: dot.object(codesandboxLight.colors);
49+
50+
// 2.1 First, lets fill in core values that are used to infer other values
51+
52+
uiColors.foreground = uiColors.foreground || codesandboxColors.foreground;
53+
uiColors.errorForeground =
54+
uiColors.errorForeground || codesandboxColors.errorForeground;
55+
56+
uiColors.sideBar = {
57+
background:
58+
uiColors.sideBar.background ||
59+
uiColors.editor.background ||
60+
codesandboxColors.sideBar.background,
61+
foreground:
62+
uiColors.sideBar.foreground ||
63+
uiColors.editor.foreground ||
64+
codesandboxColors.sideBar.foreground,
65+
border:
66+
uiColors.sideBar.border ||
67+
uiColors.editor.hoverHighlightBackground ||
68+
codesandboxColors.sideBar.border,
69+
};
70+
71+
uiColors.input = {
72+
background: uiColors.input.background || uiColors.sideBar.border,
73+
foreground: uiColors.input.foreground || uiColors.sideBar.foreground,
74+
border: uiColors.input.border || uiColors.sideBar.border,
75+
placeholderForeground:
76+
uiColors.input.placeholderForeground ||
77+
codesandboxColors.input.placeholderForeground,
78+
};
79+
80+
uiColors.inputOption.activeBorder =
81+
uiColors.inputOption.activeBorder || uiColors.input.placeholderForeground;
82+
83+
uiColors.button = {
84+
background:
85+
uiColors.button.background || codesandboxColors.button.background,
86+
foreground:
87+
uiColors.button.foreground || codesandboxColors.button.foreground,
88+
};
89+
90+
// Step 3. Infer values that are not defined by vscode theme
91+
92+
// Step 3.1
93+
// As all VSCode themes are built for a code editor,
94+
// the design decisions made in them might not work well
95+
// for an interface like ours which has other ui elements as well.
96+
// To make sure the UI looks great, we change some of these design decisions
97+
// made by the theme author
98+
99+
const decreaseContrast = type === 'dark' ? lighten : darken;
100+
101+
if (uiColors.sideBar.border === uiColors.sideBar.background) {
102+
uiColors.sideBar.border = decreaseContrast(
103+
uiColors.sideBar.background,
104+
0.25
105+
);
106+
}
107+
108+
if (uiColors.sideBar.hoverBackground === uiColors.sideBar.background) {
109+
uiColors.sideBar.hoverBackground = decreaseContrast(
110+
uiColors.sideBar.background,
111+
0.25
112+
);
113+
}
114+
115+
// Step 3.2
116+
// On the same theme of design decisions for interfaces,
117+
// we add a bunch of extra elements and interaction.
118+
// To make these elements look natural with the theme,
119+
// we infer them from the theme
120+
121+
const addedColors = {
122+
mutedForeground: withContrast(
123+
uiColors.input.placeholderForeground,
124+
uiColors.sideBar.background,
125+
type
126+
),
127+
avatar: { border: uiColors.sideBar.border },
128+
sideBar: { hoverBackground: uiColors.sideBar.border },
38129
button: {
39-
// this key is can defined by vscode, but not always present
40-
// we add a 30% overlay on top of the background color using gradient
41-
hoverBackground: `linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), ${vsCodeTheme.button.background}`,
130+
hoverBackground: `linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), ${uiColors.button.background}`,
42131
},
43132
secondaryButton: {
44-
background: vsCodeTheme.input.background,
45-
foreground: vsCodeTheme.input.foreground,
46-
hoverBackground: `linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), ${vsCodeTheme.input.background}`,
133+
background: uiColors.input.background,
134+
foreground: uiColors.input.foreground,
135+
hoverBackground: `linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), ${uiColors.sideBar.border}`,
47136
},
48137
dangerButton: {
49-
// @ts-ignore: The colors totally exist, our typings are incorrect
50138
background: designLanguage.colors.reds[300],
51-
foreground: '#fff',
52-
// @ts-ignore: The colors totally exist, our typings are incorrect
139+
foreground: 'white',
53140
hoverBackground: `linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), ${designLanguage.colors.reds[300]}`,
54141
},
55142
switch: {
56-
// @ts-ignore
57-
background: designLanguage.colors.grays[900],
58-
// @ts-ignore
143+
background: uiColors.sideBar.border,
59144
foregroundOff: designLanguage.colors.white,
60-
// @ts-ignore
61145
foregroundOn: designLanguage.colors.green,
62146
},
63-
});
147+
};
148+
149+
uiColors = deepmerge(uiColors, addedColors);
150+
151+
return uiColors;
152+
};
64153

65154
export default polyfillTheme;
155+
156+
const guessType = theme => {
157+
if (theme.name && theme.name.toLowerCase().includes('light')) return 'light';
158+
return 'dark';
159+
};
160+
161+
const lighten = (color, value) =>
162+
Color(color)
163+
.lighten(value)
164+
.hex();
165+
166+
const darken = (color, value) =>
167+
Color(color)
168+
.darken(value)
169+
.hex();
170+
171+
const withContrast = (color, background, type) => {
172+
if (Color(color).contrast(Color(background)) > 4.5) return color;
173+
174+
// can't fix that
175+
if (color === '#FFFFFF' || color === '#000000') return color;
176+
177+
// recursively increase contrast
178+
const increaseContrast = type === 'dark' ? lighten : darken;
179+
return withContrast(increaseContrast(color, 0.1), background, type);
180+
};

0 commit comments

Comments
 (0)