Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Theming - Inline svgs #1651

Merged
merged 46 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
17c3399
Split out consts
bmingles Nov 22, 2023
685c1d5
Fixed issue with button tests
bmingles Nov 22, 2023
fabbea2
Snapshot updates
bmingles Nov 22, 2023
486b163
Snapshot updates
bmingles Nov 22, 2023
0084781
Snapshot updates
bmingles Nov 22, 2023
67e72fe
Added missing awaits
bmingles Nov 22, 2023
a864db3
Updated css vars
bmingles Nov 16, 2023
91b0f56
Spectrum color updates
bmingles Nov 16, 2023
626ebaf
Updated snapshots color var changes
bmingles Nov 16, 2023
444f810
Styleguide consts
bmingles Nov 22, 2023
9ab16ff
Made some inline svgs themeable
bmingles Nov 17, 2023
5f46c84
Color var type
bmingles Nov 17, 2023
336a9b1
Split out a mixin for image mask
bmingles Nov 17, 2023
67d9d7c
Golden layout popout icon
bmingles Nov 17, 2023
0c3c722
Changed inline SVG approach
bmingles Nov 20, 2023
10c8c52
Removed unnecessary preload
bmingles Nov 20, 2023
405b52e
Remaining GL icons
bmingles Nov 20, 2023
611c03b
Cleanup inline svgs
bmingles Nov 20, 2023
95fc684
Changed svg color changing approach
bmingles Nov 20, 2023
1d5c5a1
Reset svg overrides
bmingles Nov 20, 2023
74f3537
Select state colors
bmingles Nov 20, 2023
9f5da37
Form control icons
bmingles Nov 20, 2023
72a40c0
Time select icon
bmingles Nov 20, 2023
8201058
Updated e2e snapshots
bmingles Nov 21, 2023
b18a5de
Split out mixin into utils
bmingles Nov 21, 2023
47b458b
Custom select caret + semantics
bmingles Nov 21, 2023
f31a76c
Custom time select caret
bmingles Nov 21, 2023
3579b1d
Combobox caret
bmingles Nov 21, 2023
dc01784
Removed unnecessary styles
bmingles Nov 21, 2023
4d63f36
Updated e2e snapshots
bmingles Nov 21, 2023
4831e8c
Fixed Firefox caret size
bmingles Nov 21, 2023
5e2c358
Mozilla disabled style
bmingles Nov 21, 2023
6bd24f5
Added disabled / invalid states to styleguide
bmingles Nov 21, 2023
bd078fc
Unit tests
bmingles Nov 21, 2023
9797342
Fixed reversed icons + tests
bmingles Nov 22, 2023
3369462
Fixed mimimised icon
bmingles Nov 22, 2023
abab14a
Update snapshots
bmingles Nov 22, 2023
00bb7b7
Update snapshots
bmingles Nov 22, 2023
636207c
Test coverage
bmingles Nov 22, 2023
49e623b
Moved scss util and fixed css warning
bmingles Nov 22, 2023
0eeb9e2
Added comment
bmingles Nov 22, 2023
a164b70
Comments
bmingles Nov 22, 2023
941f880
Merge branch 'main' into 1634-inline-svgs
bmingles Nov 27, 2023
43c2f11
Updated test count
bmingles Nov 27, 2023
13663d9
Updated snapshot
bmingles Nov 27, 2023
5e7846b
Moved styleguide color utils
bmingles Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/code-studio/src/styleguide/GoldenLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable react/jsx-props-no-spreading */
import React from 'react';
import classnames from 'classnames';
import { sampleSectionIdAndClasses } from './utils';

const tabs = ['Tab 1', 'Tab 2', 'Tab 3'];

function Tab({
isActive,
title,
}: {
isActive: boolean;
title: string;
}): JSX.Element {
return (
<li className={classnames('lm_tab', isActive && 'lm_active')}>
<span className="lm_title_before" />
<span className="lm_title">{title}</span>
<span className="lm_close_tab" />
</li>
);
}

export function GoldenLayout(): JSX.Element {
return (
<div {...sampleSectionIdAndClasses('golden-layout')}>
<h2 className="ui-title">Golden Layout</h2>
{[false, true].map(isMaximised => (
<React.Fragment key={String(isMaximised)}>
<h5>{isMaximised ? 'Minimized' : 'Maximised'}</h5>
<div
style={{ position: 'relative', border: 'none' }}
className={isMaximised ? 'lm_maximised' : undefined}
>
<div className="lm_header">
<ul className="lm_tabs">
{tabs.map((tab, i) => (
<Tab key={tab} isActive={i === 0} title={tab} />
))}
</ul>
<ul className="lm_controls">
<li className="lm_tabpreviousbutton" />
<li className="lm_tabnextbutton" />
<li className="lm_maximise" />
<li className="lm_popout" />
<li className="lm_tabdropdown" />
<li className="lm_close" />
</ul>
</div>
</div>
</React.Fragment>
))}
</div>
);
}

export default GoldenLayout;
6 changes: 4 additions & 2 deletions packages/code-studio/src/styleguide/Inputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,11 @@ function Inputs(): React.ReactElement {
validationError={!validateValue ? 'Value not set' : undefined}
isModified={!!validateValue}
id="validateInput1"
labelText="Input Field"
labelText={`Input Field${on ? ' (disabled)' : ''}`}
hintText="Hint text"
>
<input
disabled={on}
type="text"
className="form-control"
aria-describedby="emailHelp"
Expand All @@ -320,9 +321,10 @@ function Inputs(): React.ReactElement {
validateOption === 'Invalid' ? 'Invalid value' : undefined
}
id="validateLabelInput2"
labelText="Dropdown"
labelText={`Dropdown${on ? ' (disabled)' : ''}`}
>
<Select
disabled={on}
onChange={eventTargetValue =>
setValidateOption(eventTargetValue)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import SpectrumComponents from './SpectrumComponents';
import SamplesMenu, { SampleMenuCategory } from './SamplesMenu';
import GotoTopButton from './GotoTopButton';
import { HIDE_FROM_E2E_TESTS_CLASS } from './utils';
import { GoldenLayout } from './GoldenLayout';

const stickyProps = {
position: 'sticky',
Expand Down Expand Up @@ -85,6 +86,9 @@ function StyleGuide(): React.ReactElement {
<Colors />
<ThemeColors />

<SampleMenuCategory data-menu-category="Layout" />
<GoldenLayout />

<SampleMenuCategory data-menu-category="Components" />
<Buttons />
<Progress />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

.label {
display: flex;
align-items: end;
align-items: flex-end;
justify-content: space-between;
gap: 4px;
height: var(--swatch-height);
Expand Down
141 changes: 44 additions & 97 deletions packages/code-studio/src/styleguide/ThemeColors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import semanticGrid from '@deephaven/components/src/theme/theme-dark/theme-dark-
import components from '@deephaven/components/src/theme/theme-dark/theme-dark-components.css?inline';
import styles from './ThemeColors.module.scss';
import { sampleSectionIdAndClasses } from './utils';
import {
buildColorGroups,
contrastColor,
INVALID_COLOR_BORDER_STYLE,
} from './colorUtils';

// 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
Expand Down Expand Up @@ -39,6 +44,10 @@ const reassignVarGroups: Record<string, string> = {

// Mappings of variable groups to rename
const renameGroups = {
palette: {
black: 'gray',
white: 'gray',
},
editor: {
line: 'editor',
comment: 'code',
Expand Down Expand Up @@ -76,12 +85,37 @@ const renameGroups = {
};

const swatchDataGroups = {
'Theme Color Palette': buildColorGroups(palette, 1),
'Semantic Colors': buildColorGroups(semantic, 1, renameGroups.semantic),
'Chart Colors': buildColorGroups(chart, 2, renameGroups.chart),
'Editor Colors': buildColorGroups(semanticEditor, 2, renameGroups.editor),
'Grid Colors': buildColorGroups(semanticGrid, 2, renameGroups.grid),
'Component Colors': buildColorGroups(components, 1),
'Theme Color Palette': buildColorGroups(
palette,
1,
reassignVarGroups,
renameGroups.palette
),
'Semantic Colors': buildColorGroups(
semantic,
1,
reassignVarGroups,
renameGroups.semantic
),
'Chart Colors': buildColorGroups(
chart,
2,
reassignVarGroups,
renameGroups.chart
),
'Editor Colors': buildColorGroups(
semanticEditor,
2,
reassignVarGroups,
renameGroups.editor
),
'Grid Colors': buildColorGroups(
semanticGrid,
2,
reassignVarGroups,
renameGroups.grid
),
'Component Colors': buildColorGroups(components, 1, reassignVarGroups),
};

export function ThemeColors(): JSX.Element {
Expand Down Expand Up @@ -110,6 +144,10 @@ export function ThemeColors(): JSX.Element {
className={styles.swatch}
style={{
backgroundColor: value,
border:
value === '' && name.length > 0
? INVALID_COLOR_BORDER_STYLE
: undefined,
color: `var(--dh-color-${contrastColor(value)})`,
}}
>
Expand Down Expand Up @@ -137,94 +175,3 @@ export function ThemeColors(): JSX.Element {
}

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);
const varNames = styleText
.split(/[\n;]/g)
// Non-minified css will have leading 2 spaces in front of each css variable
// declaration. Minified has no prefix except for the first line which will
// have ':root{' prefix.
.map(line => /^(?:\s{2}|:root\{)?(--dh-color-(?:[^:]+))/.exec(line)?.[1])
.filter((match): match is string => Boolean(match));

return varNames
.map(varName => {
const value = computedStyle.getPropertyValue(varName);

// Chart colorway consists of multiple colors, so split into separate
// swatches for illustration. Note that this assumes the colors are hsl
// values. We'll need to make this more robust if we ever change the
// default themes to use non-hsl.
if (varName === '--dh-color-chart-colorway') {
const colorwayColors = value
.split('hsl')
.filter(Boolean)
.map(v => `hsl${v.trim()}`);

return colorwayColors.map((varExp, i) => ({
name: `${varName}-${i}`,
value: varExp,
}));
}

return {
name: varName,
value,
};
})
.flat();
}

/** 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;
}
105 changes: 105 additions & 0 deletions packages/code-studio/src/styleguide/colorUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ColorUtils } from '@deephaven/utils';

export const INVALID_COLOR_BORDER_STYLE =
'2px solid var(--dh-color-notice-default-bg)';

/** Return black or white contrast color */
export 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;
return ColorUtils.getBrightness([r, g, b]) >= 128 ? 'black' : 'white';
}

/** Extract an array of { name, value } pairs for css variables in a given string */
export function extractColorVars(
styleText: string
): { name: string; value: string }[] {
const computedStyle = getComputedStyle(document.documentElement);
const varNames = styleText
.split(/[\n;]/g)
// Non-minified css will have leading 2 spaces in front of each css variable
// declaration. Minified has no prefix except for the first line which will
// have ':root{' prefix.
.map(line => /^(?:\s{2}|:root\{)?(--dh-color-(?:[^:]+))/.exec(line)?.[1])
.filter((match): match is string => Boolean(match));

return varNames
.map(varName => {
const value = computedStyle.getPropertyValue(varName);

// Chart colorway consists of multiple colors, so split into separate
// swatches for illustration. Note that this assumes the colors are hsl
// values. We'll need to make this more robust if we ever change the
// default themes to use non-hsl.
if (varName === '--dh-color-chart-colorway') {
const colorwayColors = value
.split('hsl')
.filter(Boolean)
.map(v => `hsl${v.trim()}`);

return colorwayColors.map((varExp, i) => ({
name: `${varName}-${i}`,
value: varExp,
}));
}

return {
name: varName,
value,
};
})
.flat();
}

/** Group color data based on capture group value */
export function buildColorGroups(
styleText: string,
captureGroupI: number,
reassignVarGroups: Record<string, string> = {},
bmingles marked this conversation as resolved.
Show resolved Hide resolved
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] = [];
}

// Skip -hsl variables since they aren't actually colors yet
if (/^--dh-color-(.*?)-hsl$/.test(name)) {
return acc;
}

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

// Skip gray light/mid/dark as we are planning to remove them
if (/^--dh-color-gray-(light|mid|dark)$/.test(name)) {
return acc;
}

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

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

return groupData;
}
Loading
Loading