Skip to content

Commit ed8f4b7

Browse files
authored
feat: Theming Iris Grid (#1568)
- Created new base theme for Iris grid and mapped existing styles to it - Added color palettes to styleguide - IrisGrid now creates and resolves css variables when creating the default theme **Testing** - Easiest way to see the new color palettes is in the styleguide. - Iris Grid should look as it did before except for a few cases where the new color palette doesn't exactly align with the old one. aka. a color may have a subtle change in saturation but there shouldn't be any changes to the visual color. Current mapping of Bootstrap gray palette to new `--dh-color-gray` vars based on Don's POC. The Bootstrap variables will get updated in a future PR to pull from the new vars, but this is how things line up right now: <img width="350" alt="image" src="https://github.com/deephaven/web-client-ui/assets/1900643/ea9d3ea9-427c-4382-95ed-f0c47a16cd29"> BREAKING CHANGE: Enterprise will need ThemeProvider for the css variables to be available
1 parent cf1b368 commit ed8f4b7

22 files changed

Lines changed: 758 additions & 285 deletions

packages/code-studio/src/styleguide/Colors.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,28 @@ import classNames from 'classnames';
33

44
function Colors(): React.ReactElement {
55
const graySwatches = [
6-
'100',
7-
'200',
8-
'300',
9-
'400',
10-
'500',
11-
'600',
12-
'700',
13-
'800',
14-
'900',
15-
].map(swatch => (
6+
['100', '900'],
7+
['200', '800'],
8+
['300', '700'],
9+
['400', '600'],
10+
['500', '500'],
11+
['600', '500'],
12+
['700', '400'],
13+
['800', '300'],
14+
['850', '200'],
15+
['900', '75'],
16+
].map(([swatch, dh]) => (
1617
<div
1718
key={swatch}
1819
className={classNames('swatch', 'gray-swatch', `gray-swatch-${swatch}`)}
1920
>
20-
Gray-
21-
{swatch}
21+
<span>
22+
Gray-
23+
{swatch}
24+
</span>
25+
<span style={{ backgroundColor: `var(--dh-color-gray-${dh})` }}>
26+
--dh-gray-{dh}
27+
</span>
2228
</div>
2329
));
2430

packages/code-studio/src/styleguide/StyleGuide.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ pre {
1818
line-height: 2.5rem;
1919
}
2020

21+
.gray-swatch {
22+
display: flex;
23+
span {
24+
flex: 1 0 50%;
25+
}
26+
}
27+
2128
.swatch-content-bg {
2229
border: 1px solid $gray-600;
2330
margin-top: 2.5rem;

packages/code-studio/src/styleguide/StyleGuide.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import Typograpy from './Typography';
2121
import './StyleGuide.scss';
2222
import DraggableLists from './DraggableLists';
2323
import Navigations from './Navigations';
24+
import ThemeColors from './ThemeColors';
2425

2526
function StyleGuide(): React.ReactElement {
2627
return (
@@ -33,6 +34,8 @@ function StyleGuide(): React.ReactElement {
3334

3435
<Colors />
3536

37+
<ThemeColors />
38+
3639
<Buttons />
3740

3841
<Progress />
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.themeColors {
2+
--swatch-height: 35px;
3+
--column-gap: 14px;
4+
5+
display: grid;
6+
column-gap: var(--column-gap);
7+
// Add as many columns as will fit in the container each 210px wide.
8+
// Row height is set to the swatch height (35px) by dynamic `grid-row` style
9+
// attributes set in ThemeColors.tsx.
10+
grid-template-columns: repeat(auto-fit, 210px);
11+
12+
.label {
13+
display: flex;
14+
align-items: end;
15+
justify-content: space-between;
16+
gap: 4px;
17+
height: var(--swatch-height);
18+
text-transform: capitalize;
19+
white-space: nowrap;
20+
}
21+
22+
.swatch {
23+
display: flex;
24+
align-items: center;
25+
height: var(--swatch-height);
26+
justify-content: space-between;
27+
padding: 0 10px;
28+
29+
span {
30+
overflow: hidden;
31+
white-space: nowrap;
32+
text-overflow: ellipsis;
33+
}
34+
}
35+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React, { useMemo } from 'react';
2+
import { Tooltip } from '@deephaven/components';
3+
import { ColorUtils } from '@deephaven/utils';
4+
import palette from '@deephaven/components/src/theme/theme-dark/theme-dark-palette.css?inline';
5+
import semantic from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic.css?inline';
6+
import semanticEditor from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic-editor.css?inline';
7+
import semanticGrid from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic-grid.css?inline';
8+
import styles from './ThemeColors.module.scss';
9+
10+
// Group names are extracted from var names via a regex capture group. Most of
11+
// them work pretty well, but some need to be remapped to a more appropriate
12+
// group.
13+
const reassignVarGroups: Record<string, string> = {
14+
'--dh-color-black': 'gray',
15+
'--dh-color-white': 'gray',
16+
// Editor
17+
'--dh-color-editor-bg': 'editor',
18+
'--dh-color-editor-fg': 'editor',
19+
'--dh-color-editor-context-menu-bg': 'menus',
20+
'--dh-color-editor-context-menu-fg': 'menus',
21+
'--dh-color-editor-menu-selection-bg': 'menus',
22+
// Grid
23+
'--dh-color-grid-bg': 'grid',
24+
'--dh-color-grid-number-positive': 'Data Types',
25+
'--dh-color-grid-number-negative': 'Data Types',
26+
'--dh-color-grid-number-zero': 'Data Types',
27+
'--dh-color-grid-date': 'Data Types',
28+
'--dh-color-grid-string-null': 'Data Types',
29+
};
30+
31+
// Mappings of variable groups to rename
32+
const renameGroups = {
33+
editor: {
34+
line: 'editor',
35+
comment: 'code',
36+
string: 'code',
37+
number: 'code',
38+
delimiter: 'code',
39+
identifier: 'code',
40+
keyword: 'code',
41+
operator: 'code',
42+
storage: 'code',
43+
predefined: 'code',
44+
selection: 'state',
45+
focus: 'state',
46+
},
47+
grid: { data: 'Data Bars', context: 'Context Menu' },
48+
};
49+
50+
export function ThemeColors(): JSX.Element {
51+
const swatchDataGroups = useMemo(
52+
() => ({
53+
'Theme Color Palette': buildColorGroups(palette, 1),
54+
'Semantic Colors': buildColorGroups(semantic, 1),
55+
'Editor Colors': buildColorGroups(semanticEditor, 2, renameGroups.editor),
56+
'Grid Colors': buildColorGroups(semanticGrid, 2, renameGroups.grid),
57+
}),
58+
[]
59+
);
60+
61+
return (
62+
<>
63+
{Object.entries(swatchDataGroups).map(([label, data]) => (
64+
<div key={label}>
65+
<h2 className="ui-title">{label}</h2>
66+
<div className={styles.themeColors}>
67+
{Object.entries(data).map(([group, swatchData]) => (
68+
<div
69+
key={group}
70+
// This is the secret sauce for filling columns. The height of
71+
// each swatch group spans multiple rows (the number of swatches
72+
// + 1 for the label). This causes the grid to create rows
73+
// based on the swatch height (35px), and each swatch (also the
74+
// group label) neatly fits in a grid cell. The grid will put a
75+
// group in each column and then wrap back around to the first
76+
// until all groups are placed.
77+
style={{ gridRow: `span ${swatchData.length + 1}` }}
78+
>
79+
<span className={styles.label}>{group}</span>
80+
{swatchData.map(({ name, value }) => (
81+
<div
82+
key={name}
83+
className={styles.swatch}
84+
style={{
85+
backgroundColor: value,
86+
color: `var(--dh-color-${contrastColor(value)})`,
87+
}}
88+
>
89+
<Tooltip>
90+
<div>{name}</div>
91+
<div>{value}</div>
92+
<div>
93+
{ColorUtils.normalizeCssColor(value).replace(
94+
/^(#[a-f0-9]{6})ff$/,
95+
'$1'
96+
)}
97+
</div>
98+
</Tooltip>
99+
<span>{name.replace('--dh-color-', '')}</span>
100+
{name.endsWith('-hue') ? <span>{value}</span> : null}
101+
</div>
102+
))}
103+
</div>
104+
))}
105+
</div>
106+
</div>
107+
))}
108+
</>
109+
);
110+
}
111+
112+
export default ThemeColors;
113+
114+
/** Return black or white contrast color */
115+
function contrastColor(color: string): 'black' | 'white' {
116+
const rgba = ColorUtils.parseRgba(ColorUtils.asRgbOrRgbaString(color) ?? '');
117+
if (rgba == null || rgba.a < 0.5) {
118+
return 'white';
119+
}
120+
121+
const { r, g, b } = rgba;
122+
const y = (299 * r + 587 * g + 114 * b) / 1000;
123+
return y >= 128 ? 'black' : 'white';
124+
}
125+
126+
/** Extract an array of { name, value } pairs for css variables in a given string */
127+
function extractColorVars(
128+
styleText: string
129+
): { name: string; value: string }[] {
130+
const computedStyle = getComputedStyle(document.documentElement);
131+
132+
return styleText
133+
.split('\n')
134+
.map(line => /^\s{2}(--dh-color-(?:[^:]+))/.exec(line)?.[1])
135+
.filter(Boolean)
136+
.map(varName =>
137+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
138+
({ name: varName!, value: computedStyle.getPropertyValue(varName!)! })
139+
);
140+
}
141+
142+
/** Group color data based on capture group value */
143+
function buildColorGroups(
144+
styleText: string,
145+
captureGroupI: number,
146+
groupRemap: Record<string, string> = {}
147+
): Record<string, { name: string; value: string }[]> {
148+
const swatchData = extractColorVars(styleText);
149+
150+
const groupData = swatchData.reduce(
151+
(acc, { name, value }) => {
152+
const match = /^--dh-color-([^-]+)(?:-([^-]+))?/.exec(name);
153+
let group =
154+
reassignVarGroups[name] ??
155+
match?.[captureGroupI] ??
156+
match?.[1] ??
157+
'???';
158+
159+
group = groupRemap[group] ?? group;
160+
161+
if (acc[group] == null) {
162+
acc[group] = [];
163+
}
164+
165+
// Add a spacer for black / white
166+
if (name === '--dh-color-black') {
167+
acc[group].push({ name: '', value: '' });
168+
}
169+
170+
acc[group].push({ name, value });
171+
172+
return acc;
173+
},
174+
{} as Record<string, { name: string; value: string }[]>
175+
);
176+
177+
return groupData;
178+
}

packages/components/src/theme/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export * from './theme-dark';
2+
export * from './theme-light';
13
export * from './ThemeModel';
24
export * from './ThemeProvider';
35
export * from './ThemeUtils';

packages/components/src/theme/theme-dark/theme-dark-palette.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
--dh-color-gray-hue: 0deg;
44
--dh-color-gray-50: hsl(var(--dh-color-gray-hue) 6% 10%);
55
--dh-color-gray-75: hsl(var(--dh-color-gray-hue) 5% 13%);
6-
--dh-color-gray-100: hsl(var(--dh-color-gray-hue), 5%, 17%);
6+
--dh-color-gray-100: hsl(var(--dh-color-gray-hue) 5% 17%);
77
--dh-color-gray-200: hsl(var(--dh-color-gray-hue) 4% 19%);
88
--dh-color-gray-300: hsl(var(--dh-color-gray-hue) 4% 21%);
99
--dh-color-gray-400: hsl(var(--dh-color-gray-hue) 2% 25%);
Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
:root {
22
/* Editor */
3-
--dh-color-editor-background: var(--dh-color-content-background);
4-
--dh-color-editor-foreground: var(--dh-color-gray-900);
5-
--dh-color-editor-error-foreground: var(--dh-color-visual-red);
6-
--dh-color-editor-line-number-foreground: var(--dh-color-gray-700);
3+
--dh-color-editor-bg: var(--dh-color-content-background);
4+
--dh-color-editor-fg: var(--dh-color-gray-900);
5+
--dh-color-editor-error-fg: var(--dh-color-visual-red);
6+
--dh-color-editor-line-number-fg: var(--dh-color-gray-700);
77
--dh-color-editor-line-highlight-bg: var(--dh-color-gray-200);
8-
--dh-color-editor-selection-background: var(--dh-color-text-highlight);
8+
--dh-color-editor-selection-bg: var(--dh-color-text-highlight);
99

1010
/* Code rules */
11-
--dh-color-editor-string: var(--dh-color-visual-yellow);
12-
--dh-color-editor-string-delim: var(--dh-color-gray-700);
11+
--dh-color-editor-comment: var(--dh-color-gray-700);
1312
--dh-color-editor-delimiter: var(--dh-color-gray-700);
14-
--dh-color-editor-predefined: var(--dh-color-visual-green);
13+
--dh-color-editor-identifier-js: var(--dh-color-visual-yellow);
14+
--dh-color-editor-identifier-namespace: var(--dh-color-visual-red);
15+
--dh-color-editor-identifier: var(--dh-color-gray-900);
1516
--dh-color-editor-keyword: var(--dh-color-visual-cyan);
16-
--dh-color-editor-storage: var(--dh-color-visual-red);
1717
--dh-color-editor-number: var(--dh-color-visual-purple);
1818
--dh-color-editor-operator: var(--dh-color-visual-red);
19-
--dh-color-editor-identifier: var(--dh-color-gray-900);
20-
--dh-color-editor-identifier-namespace: var(--dh-color-visual-red);
21-
--dh-color-editor-identifier-js: var(--dh-color-visual-yellow);
22-
--dh-color-editor-comment: var(--dh-color-gray-700);
19+
--dh-color-editor-predefined: var(--dh-color-visual-green);
20+
--dh-color-editor-storage: var(--dh-color-visual-red);
21+
--dh-color-editor-string-delim: var(--dh-color-gray-700);
22+
--dh-color-editor-string: var(--dh-color-visual-yellow);
2323

2424
/* Input */
2525
--dh-color-editor-focus-border: var(--dh-color-focus-border);
2626
--dh-color-editor-input-option-active-border: var(--dh-color-focus-ring);
27-
--dh-color-editor-input-background: var(--dh-color-background);
28-
--dh-color-editor-input-foreground: var(--dh-color-text);
27+
--dh-color-editor-input-bg: var(--dh-color-background);
28+
--dh-color-editor-input-fg: var(--dh-color-text);
2929
--dh-color-editor-input-border: var(--dh-color-border);
3030

3131
/* Menus */
32-
--dh-color-editor-context-menu-background: var(--dh-color-gray-300);
33-
--dh-color-editor-context-menu-foreground: var(--dh-color-gray-900);
34-
--dh-color-editor-menu-selection-background: var(--dh-color-highlight-hover);
32+
--dh-color-editor-context-menu-bg: var(--dh-color-gray-300);
33+
--dh-color-editor-context-menu-fg: var(--dh-color-gray-900);
34+
--dh-color-editor-menu-selection-bg: var(--dh-color-highlight-hover);
3535

3636
/* Logging */
3737
--dh-color-editor-log-date: var(--dh-color-gray-700);
@@ -43,25 +43,23 @@
4343
--dh-color-editor-log-trace: var(--dh-color-visual-green);
4444

4545
/* Find */
46-
--dh-color-editor-find-background: var(--dh-color-gray-200);
47-
--dh-color-editor-find-match-background: var(--dh-color-highlight-selected);
48-
--dh-color-editor-find-match-highlight-background: var(
46+
--dh-color-editor-find-bg: var(--dh-color-gray-200);
47+
--dh-color-editor-find-match-bg: var(--dh-color-highlight-selected);
48+
--dh-color-editor-find-match-highlight-bg: var(
4949
--dh-color-highlight-selected-hover
5050
);
51-
--dh-color-editor-find-option-active-background: var(--dh-color-accent-700);
52-
--dh-color-editor-find-option-active-foreground: var(--dh-color-gray-900);
51+
--dh-color-editor-find-option-active-bg: var(--dh-color-accent-700);
52+
--dh-color-editor-find-option-active-fg: var(--dh-color-gray-900);
5353

5454
/* Suggest */
55-
--dh-color-editor-suggest-background: var(--dh-color-gray-200);
55+
--dh-color-editor-suggest-bg: var(--dh-color-gray-200);
5656
--dh-color-editor-suggest-border: var(--dh-color-gray-400);
57-
--dh-color-editor-suggest-foreground: var(--dh-color-gray-100);
58-
--dh-color-editor-suggest-selected-background: var(
59-
--dh-color-highlight-selected
60-
);
61-
--dh-color-editor-suggest-highlight-foreground: var(--dh-color-accent-700);
62-
--dh-color-editor-suggest-hover-background: var(--dh-color-highlight-hover);
57+
--dh-color-editor-suggest-fg: var(--dh-color-gray-100);
58+
--dh-color-editor-suggest-selected-bg: var(--dh-color-highlight-selected);
59+
--dh-color-editor-suggest-highlight-fg: var(--dh-color-accent-700);
60+
--dh-color-editor-suggest-hover-bg: var(--dh-color-highlight-hover);
6361

6462
/* Links */
6563
--dh-color-editor-link-foreground: var(--dh-color-accent-1000);
66-
--dh-color-editor-link-active-foreground: var(--dh-color-accent-1100);
64+
--dh-color-editor-link-active-fg: var(--dh-color-accent-1100);
6765
}

0 commit comments

Comments
 (0)