Skip to content

Commit

Permalink
feat: Theming Iris Grid (#1568)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
bmingles authored Oct 17, 2023
1 parent cf1b368 commit ed8f4b7
Show file tree
Hide file tree
Showing 22 changed files with 758 additions and 285 deletions.
30 changes: 18 additions & 12 deletions packages/code-studio/src/styleguide/Colors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@ import classNames from 'classnames';

function Colors(): React.ReactElement {
const graySwatches = [
'100',
'200',
'300',
'400',
'500',
'600',
'700',
'800',
'900',
].map(swatch => (
['100', '900'],
['200', '800'],
['300', '700'],
['400', '600'],
['500', '500'],
['600', '500'],
['700', '400'],
['800', '300'],
['850', '200'],
['900', '75'],
].map(([swatch, dh]) => (
<div
key={swatch}
className={classNames('swatch', 'gray-swatch', `gray-swatch-${swatch}`)}
>
Gray-
{swatch}
<span>
Gray-
{swatch}
</span>
<span style={{ backgroundColor: `var(--dh-color-gray-${dh})` }}>
--dh-gray-{dh}
</span>
</div>
));

Expand Down
7 changes: 7 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ pre {
line-height: 2.5rem;
}

.gray-swatch {
display: flex;
span {
flex: 1 0 50%;
}
}

.swatch-content-bg {
border: 1px solid $gray-600;
margin-top: 2.5rem;
Expand Down
3 changes: 3 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Typograpy from './Typography';
import './StyleGuide.scss';
import DraggableLists from './DraggableLists';
import Navigations from './Navigations';
import ThemeColors from './ThemeColors';

function StyleGuide(): React.ReactElement {
return (
Expand All @@ -33,6 +34,8 @@ function StyleGuide(): React.ReactElement {

<Colors />

<ThemeColors />

<Buttons />

<Progress />
Expand Down
35 changes: 35 additions & 0 deletions packages/code-studio/src/styleguide/ThemeColors.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.themeColors {
--swatch-height: 35px;
--column-gap: 14px;

display: grid;
column-gap: var(--column-gap);
// Add as many columns as will fit in the container each 210px wide.
// Row height is set to the swatch height (35px) by dynamic `grid-row` style
// attributes set in ThemeColors.tsx.
grid-template-columns: repeat(auto-fit, 210px);

.label {
display: flex;
align-items: end;
justify-content: space-between;
gap: 4px;
height: var(--swatch-height);
text-transform: capitalize;
white-space: nowrap;
}

.swatch {
display: flex;
align-items: center;
height: var(--swatch-height);
justify-content: space-between;
padding: 0 10px;

span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
178 changes: 178 additions & 0 deletions packages/code-studio/src/styleguide/ThemeColors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { useMemo } from 'react';
import { Tooltip } from '@deephaven/components';
import { ColorUtils } from '@deephaven/utils';
import palette from '@deephaven/components/src/theme/theme-dark/theme-dark-palette.css?inline';
import semantic from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic.css?inline';
import semanticEditor from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic-editor.css?inline';
import semanticGrid from '@deephaven/components/src/theme/theme-dark/theme-dark-semantic-grid.css?inline';
import styles from './ThemeColors.module.scss';

// Group names are extracted from var names via a regex capture group. Most of
// them work pretty well, but some need to be remapped to a more appropriate
// group.
const reassignVarGroups: Record<string, string> = {
'--dh-color-black': 'gray',
'--dh-color-white': 'gray',
// Editor
'--dh-color-editor-bg': 'editor',
'--dh-color-editor-fg': 'editor',
'--dh-color-editor-context-menu-bg': 'menus',
'--dh-color-editor-context-menu-fg': 'menus',
'--dh-color-editor-menu-selection-bg': 'menus',
// Grid
'--dh-color-grid-bg': 'grid',
'--dh-color-grid-number-positive': 'Data Types',
'--dh-color-grid-number-negative': 'Data Types',
'--dh-color-grid-number-zero': 'Data Types',
'--dh-color-grid-date': 'Data Types',
'--dh-color-grid-string-null': 'Data Types',
};

// Mappings of variable groups to rename
const renameGroups = {
editor: {
line: 'editor',
comment: 'code',
string: 'code',
number: 'code',
delimiter: 'code',
identifier: 'code',
keyword: 'code',
operator: 'code',
storage: 'code',
predefined: 'code',
selection: 'state',
focus: 'state',
},
grid: { data: 'Data Bars', context: 'Context Menu' },
};

export function ThemeColors(): JSX.Element {
const swatchDataGroups = useMemo(
() => ({
'Theme Color Palette': buildColorGroups(palette, 1),
'Semantic Colors': buildColorGroups(semantic, 1),
'Editor Colors': buildColorGroups(semanticEditor, 2, renameGroups.editor),
'Grid Colors': buildColorGroups(semanticGrid, 2, renameGroups.grid),
}),
[]
);

return (
<>
{Object.entries(swatchDataGroups).map(([label, data]) => (
<div key={label}>
<h2 className="ui-title">{label}</h2>
<div className={styles.themeColors}>
{Object.entries(data).map(([group, swatchData]) => (
<div
key={group}
// This is the secret sauce for filling columns. The height of
// each swatch group spans multiple rows (the number of swatches
// + 1 for the label). This causes the grid to create rows
// based on the swatch height (35px), and each swatch (also the
// group label) neatly fits in a grid cell. The grid will put a
// group in each column and then wrap back around to the first
// until all groups are placed.
style={{ gridRow: `span ${swatchData.length + 1}` }}
>
<span className={styles.label}>{group}</span>
{swatchData.map(({ name, value }) => (
<div
key={name}
className={styles.swatch}
style={{
backgroundColor: value,
color: `var(--dh-color-${contrastColor(value)})`,
}}
>
<Tooltip>
<div>{name}</div>
<div>{value}</div>
<div>
{ColorUtils.normalizeCssColor(value).replace(
/^(#[a-f0-9]{6})ff$/,
'$1'
)}
</div>
</Tooltip>
<span>{name.replace('--dh-color-', '')}</span>
{name.endsWith('-hue') ? <span>{value}</span> : null}
</div>
))}
</div>
))}
</div>
</div>
))}
</>
);
}

export default ThemeColors;

/** Return black or white contrast color */
function contrastColor(color: string): 'black' | 'white' {
const rgba = ColorUtils.parseRgba(ColorUtils.asRgbOrRgbaString(color) ?? '');
if (rgba == null || rgba.a < 0.5) {
return 'white';
}

const { r, g, b } = rgba;
const y = (299 * r + 587 * g + 114 * b) / 1000;
return y >= 128 ? 'black' : 'white';
}

/** Extract an array of { name, value } pairs for css variables in a given string */
function extractColorVars(
styleText: string
): { name: string; value: string }[] {
const computedStyle = getComputedStyle(document.documentElement);

return styleText
.split('\n')
.map(line => /^\s{2}(--dh-color-(?:[^:]+))/.exec(line)?.[1])
.filter(Boolean)
.map(varName =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
({ name: varName!, value: computedStyle.getPropertyValue(varName!)! })
);
}

/** Group color data based on capture group value */
function buildColorGroups(
styleText: string,
captureGroupI: number,
groupRemap: Record<string, string> = {}
): Record<string, { name: string; value: string }[]> {
const swatchData = extractColorVars(styleText);

const groupData = swatchData.reduce(
(acc, { name, value }) => {
const match = /^--dh-color-([^-]+)(?:-([^-]+))?/.exec(name);
let group =
reassignVarGroups[name] ??
match?.[captureGroupI] ??
match?.[1] ??
'???';

group = groupRemap[group] ?? group;

if (acc[group] == null) {
acc[group] = [];
}

// Add a spacer for black / white
if (name === '--dh-color-black') {
acc[group].push({ name: '', value: '' });
}

acc[group].push({ name, value });

return acc;
},
{} as Record<string, { name: string; value: string }[]>
);

return groupData;
}
2 changes: 2 additions & 0 deletions packages/components/src/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './theme-dark';
export * from './theme-light';
export * from './ThemeModel';
export * from './ThemeProvider';
export * from './ThemeUtils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
--dh-color-gray-hue: 0deg;
--dh-color-gray-50: hsl(var(--dh-color-gray-hue) 6% 10%);
--dh-color-gray-75: hsl(var(--dh-color-gray-hue) 5% 13%);
--dh-color-gray-100: hsl(var(--dh-color-gray-hue), 5%, 17%);
--dh-color-gray-100: hsl(var(--dh-color-gray-hue) 5% 17%);
--dh-color-gray-200: hsl(var(--dh-color-gray-hue) 4% 19%);
--dh-color-gray-300: hsl(var(--dh-color-gray-hue) 4% 21%);
--dh-color-gray-400: hsl(var(--dh-color-gray-hue) 2% 25%);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
:root {
/* Editor */
--dh-color-editor-background: var(--dh-color-content-background);
--dh-color-editor-foreground: var(--dh-color-gray-900);
--dh-color-editor-error-foreground: var(--dh-color-visual-red);
--dh-color-editor-line-number-foreground: var(--dh-color-gray-700);
--dh-color-editor-bg: var(--dh-color-content-background);
--dh-color-editor-fg: var(--dh-color-gray-900);
--dh-color-editor-error-fg: var(--dh-color-visual-red);
--dh-color-editor-line-number-fg: var(--dh-color-gray-700);
--dh-color-editor-line-highlight-bg: var(--dh-color-gray-200);
--dh-color-editor-selection-background: var(--dh-color-text-highlight);
--dh-color-editor-selection-bg: var(--dh-color-text-highlight);

/* Code rules */
--dh-color-editor-string: var(--dh-color-visual-yellow);
--dh-color-editor-string-delim: var(--dh-color-gray-700);
--dh-color-editor-comment: var(--dh-color-gray-700);
--dh-color-editor-delimiter: var(--dh-color-gray-700);
--dh-color-editor-predefined: var(--dh-color-visual-green);
--dh-color-editor-identifier-js: var(--dh-color-visual-yellow);
--dh-color-editor-identifier-namespace: var(--dh-color-visual-red);
--dh-color-editor-identifier: var(--dh-color-gray-900);
--dh-color-editor-keyword: var(--dh-color-visual-cyan);
--dh-color-editor-storage: var(--dh-color-visual-red);
--dh-color-editor-number: var(--dh-color-visual-purple);
--dh-color-editor-operator: var(--dh-color-visual-red);
--dh-color-editor-identifier: var(--dh-color-gray-900);
--dh-color-editor-identifier-namespace: var(--dh-color-visual-red);
--dh-color-editor-identifier-js: var(--dh-color-visual-yellow);
--dh-color-editor-comment: var(--dh-color-gray-700);
--dh-color-editor-predefined: var(--dh-color-visual-green);
--dh-color-editor-storage: var(--dh-color-visual-red);
--dh-color-editor-string-delim: var(--dh-color-gray-700);
--dh-color-editor-string: var(--dh-color-visual-yellow);

/* Input */
--dh-color-editor-focus-border: var(--dh-color-focus-border);
--dh-color-editor-input-option-active-border: var(--dh-color-focus-ring);
--dh-color-editor-input-background: var(--dh-color-background);
--dh-color-editor-input-foreground: var(--dh-color-text);
--dh-color-editor-input-bg: var(--dh-color-background);
--dh-color-editor-input-fg: var(--dh-color-text);
--dh-color-editor-input-border: var(--dh-color-border);

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

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

/* Find */
--dh-color-editor-find-background: var(--dh-color-gray-200);
--dh-color-editor-find-match-background: var(--dh-color-highlight-selected);
--dh-color-editor-find-match-highlight-background: var(
--dh-color-editor-find-bg: var(--dh-color-gray-200);
--dh-color-editor-find-match-bg: var(--dh-color-highlight-selected);
--dh-color-editor-find-match-highlight-bg: var(
--dh-color-highlight-selected-hover
);
--dh-color-editor-find-option-active-background: var(--dh-color-accent-700);
--dh-color-editor-find-option-active-foreground: var(--dh-color-gray-900);
--dh-color-editor-find-option-active-bg: var(--dh-color-accent-700);
--dh-color-editor-find-option-active-fg: var(--dh-color-gray-900);

/* Suggest */
--dh-color-editor-suggest-background: var(--dh-color-gray-200);
--dh-color-editor-suggest-bg: var(--dh-color-gray-200);
--dh-color-editor-suggest-border: var(--dh-color-gray-400);
--dh-color-editor-suggest-foreground: var(--dh-color-gray-100);
--dh-color-editor-suggest-selected-background: var(
--dh-color-highlight-selected
);
--dh-color-editor-suggest-highlight-foreground: var(--dh-color-accent-700);
--dh-color-editor-suggest-hover-background: var(--dh-color-highlight-hover);
--dh-color-editor-suggest-fg: var(--dh-color-gray-100);
--dh-color-editor-suggest-selected-bg: var(--dh-color-highlight-selected);
--dh-color-editor-suggest-highlight-fg: var(--dh-color-accent-700);
--dh-color-editor-suggest-hover-bg: var(--dh-color-highlight-hover);

/* Links */
--dh-color-editor-link-foreground: var(--dh-color-accent-1000);
--dh-color-editor-link-active-foreground: var(--dh-color-accent-1100);
--dh-color-editor-link-active-fg: var(--dh-color-accent-1100);
}
Loading

0 comments on commit ed8f4b7

Please sign in to comment.