diff --git a/app/client/packages/design-system/theming/src/index.ts b/app/client/packages/design-system/theming/src/index.ts index 4ef348340f..4c6d63d561 100644 --- a/app/client/packages/design-system/theming/src/index.ts +++ b/app/client/packages/design-system/theming/src/index.ts @@ -1,7 +1,5 @@ export { ThemeProvider } from "./components/ThemeProvider"; -export { TokensAccessor, ColorsAccessor } from "./utils"; +export * from "./utils"; export { default as defaultTokens } from "./tokens/defaultTokens.json"; export { default as themeTokens } from "./tokens/themeTokens.json"; - -export type { ThemeTokens } from "./utils"; diff --git a/app/client/packages/design-system/theming/src/tokens/defaultTokens.json b/app/client/packages/design-system/theming/src/tokens/defaultTokens.json index 7804cb8068..6dccb7ed82 100644 --- a/app/client/packages/design-system/theming/src/tokens/defaultTokens.json +++ b/app/client/packages/design-system/theming/src/tokens/defaultTokens.json @@ -1,8 +1,7 @@ { "seedColor": "#553de9", - "focusColor": "#2a82ea", "rootUnit": 4, - "colorScheme": "light", + "colorMode": "light", "borderRadius": { "1": "0px" }, diff --git a/app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkScheme.ts b/app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkModeTheme.ts similarity index 73% rename from app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkScheme.ts rename to app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkModeTheme.ts index f063695ca9..65996d0d36 100644 --- a/app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkScheme.ts +++ b/app/client/packages/design-system/theming/src/utils/TokensAccessor/DarkModeTheme.ts @@ -1,23 +1,26 @@ import { contrast, lighten, setLch } from "../colorUtils"; -import type { ColorTypes } from "colorjs.io/types/src/color"; import { ColorsAccessor } from "../ColorsAccessor"; -export class DarkScheme { +import type { ColorTypes } from "colorjs.io/types/src/color"; +import type { ColorModeTheme } from "./types"; + +export class DarkModeTheme implements ColorModeTheme { private readonly seedColor: string; private readonly seedLightness: number; private readonly seedChroma: number; private readonly seedHue: number; private readonly seedIsVeryDark: boolean; + private readonly seedIsAchromatic: boolean; constructor(private color: ColorTypes) { - const { chroma, hex, hue, isVeryDark, lightness } = new ColorsAccessor( - color, - ); + const { chroma, hex, hue, isAchromatic, isVeryDark, lightness } = + new ColorsAccessor(color); this.seedColor = hex; this.seedLightness = lightness; this.seedChroma = chroma; this.seedHue = hue; this.seedIsVeryDark = isVeryDark; + this.seedIsAchromatic = isAchromatic; } public getColors = () => { @@ -44,6 +47,13 @@ export class DarkScheme { * Background colors */ private get bg() { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, @@ -78,7 +88,7 @@ export class DarkScheme { }); } - if (this.seedChroma > 0.112) { + if (this.seedChroma > 0.112 && !this.seedIsAchromatic) { currentColor = setLch(currentColor, { c: 0.112, }); @@ -99,6 +109,13 @@ export class DarkScheme { * Foreground colors */ private get fg() { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.965, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.965, c: 0.024, @@ -107,6 +124,13 @@ export class DarkScheme { private get fgAccent() { if (contrast(this.seedColor, this.bg) <= 60) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.79, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.79, c: 0.136, @@ -118,12 +142,26 @@ export class DarkScheme { private get fgOnAccent() { if (contrast(this.seedColor, this.bg) <= 40) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.985, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.985, c: 0.016, }); } + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, @@ -132,6 +170,13 @@ export class DarkScheme { private get bdAccent() { if (contrast(this.seedColor, this.bg) <= 15) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.985, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.985, c: 0.016, @@ -142,12 +187,19 @@ export class DarkScheme { } private get bdNeutral() { - if (contrast(this.seedColor, this.bg) >= -25) { + if (contrast(this.seedColor, this.bg) >= -25 && !this.seedIsAchromatic) { return setLch(this.seedColor, { c: 0.008, }); } + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, diff --git a/app/client/packages/design-system/theming/src/utils/TokensAccessor/LightScheme.ts b/app/client/packages/design-system/theming/src/utils/TokensAccessor/LightModeTheme.ts similarity index 71% rename from app/client/packages/design-system/theming/src/utils/TokensAccessor/LightScheme.ts rename to app/client/packages/design-system/theming/src/utils/TokensAccessor/LightModeTheme.ts index 36221b39c2..5801b057fa 100644 --- a/app/client/packages/design-system/theming/src/utils/TokensAccessor/LightScheme.ts +++ b/app/client/packages/design-system/theming/src/utils/TokensAccessor/LightModeTheme.ts @@ -1,19 +1,26 @@ import { contrast, lighten, setLch } from "../colorUtils"; -import type { ColorTypes } from "colorjs.io/types/src/color"; import { ColorsAccessor } from "../ColorsAccessor"; -export class LightScheme { +import type { ColorTypes } from "colorjs.io/types/src/color"; +import type { ColorModeTheme } from "./types"; + +export class LightModeTheme implements ColorModeTheme { private readonly seedColor: string; private readonly seedLightness: number; private readonly seedChroma: number; private readonly seedHue: number; + private readonly seedIsAchromatic: boolean; + private readonly seedIsCold: boolean; constructor(private color: ColorTypes) { - const { chroma, hex, hue, lightness } = new ColorsAccessor(color); + const { chroma, hex, hue, isAchromatic, isCold, lightness } = + new ColorsAccessor(color); this.seedColor = hex; this.seedLightness = lightness; this.seedChroma = chroma; this.seedHue = hue; + this.seedIsAchromatic = isAchromatic; + this.seedIsCold = isCold; } public getColors = () => { @@ -40,9 +47,16 @@ export class LightScheme { * Background colors */ private get bg() { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.985, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.985, - c: 0.016, + c: this.seedIsCold ? 0.006 : 0.004, }); } @@ -74,7 +88,7 @@ export class LightScheme { }); } - if (this.seedChroma > 0.16) { + if (this.seedChroma > 0.16 && !this.seedIsAchromatic) { currentColor = setLch(currentColor, { c: 0.16, }); @@ -95,6 +109,13 @@ export class LightScheme { * Foreground colors */ private get fg() { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.12, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.12, c: 0.032, @@ -103,6 +124,13 @@ export class LightScheme { private get fgAccent() { if (contrast(this.seedColor, this.bg) >= -60) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.25, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.25, c: 0.064, @@ -114,12 +142,26 @@ export class LightScheme { private get fgOnAccent() { if (contrast(this.seedColor, this.bg) <= -60) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.985, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.985, c: 0.016, }); } + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, @@ -131,6 +173,13 @@ export class LightScheme { */ private get bdAccent() { if (contrast(this.seedColor, this.bg) >= -25) { + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, @@ -141,12 +190,19 @@ export class LightScheme { } private get bdNeutral() { - if (contrast(this.seedColor, this.bg) <= -25) { + if (contrast(this.seedColor, this.bg) <= -25 && !this.seedIsAchromatic) { return setLch(this.seedColor, { c: 0.016, }); } + if (this.seedIsAchromatic) { + return setLch(this.seedColor, { + l: 0.15, + c: 0, + }); + } + return setLch(this.seedColor, { l: 0.15, c: 0.064, diff --git a/app/client/packages/design-system/theming/src/utils/TokensAccessor/TokensAccessor.ts b/app/client/packages/design-system/theming/src/utils/TokensAccessor/TokensAccessor.ts index 038f03cbe4..656ce01643 100644 --- a/app/client/packages/design-system/theming/src/utils/TokensAccessor/TokensAccessor.ts +++ b/app/client/packages/design-system/theming/src/utils/TokensAccessor/TokensAccessor.ts @@ -1,54 +1,70 @@ -import type { ColorTypes } from "colorjs.io/types/src/color"; -import { defaultTokens } from "../../"; import range from "lodash/range"; import kebabCase from "lodash/kebabCase"; -import { LightScheme } from "./LightScheme"; -import { DarkScheme } from "./DarkScheme"; +import { DarkModeTheme } from "./DarkModeTheme"; +import { LightModeTheme } from "./LightModeTheme"; -type TokenType = - | "sizing" - | "color" - | "spacing" - | "borderRadius" - | "boxShadow" - | "borderWidth" - | "opacity"; - -type ColorScheme = "light" | "dark"; - -type Token = { - value: string | number; - type: TokenType; -}; - -export type ThemeTokens = { - [key in TokenType]: { [key: string]: Token }; -}; - -type TokenObj = { [key: string]: string | number }; +import type { + ColorMode, + TokenObj, + TokenSource, + ThemeTokens, + TokenType, + ColorTypes, +} from "./types"; export class TokensAccessor { - constructor( - private color: ColorTypes = defaultTokens.seedColor, - private colorScheme: ColorScheme = defaultTokens.colorScheme as ColorScheme, - private rootUnit: number = defaultTokens.rootUnit, - private borderRadius: TokenObj = defaultTokens.borderRadius, - private boxShadow: TokenObj = defaultTokens.boxShadow, - private borderWidth: TokenObj = defaultTokens.borderWidth, - private opacity: TokenObj = defaultTokens.opacity, - ) {} + private seedColor?: ColorTypes; + private colorMode?: ColorMode; + private borderRadius?: TokenObj; + private rootUnit?: number; + private boxShadow?: TokenObj; + private borderWidth?: TokenObj; + private opacity?: TokenObj; + + constructor({ + borderRadius, + borderWidth, + boxShadow, + colorMode, + opacity, + rootUnit, + seedColor, + }: TokenSource) { + this.seedColor = seedColor; + this.colorMode = colorMode; + this.rootUnit = rootUnit; + this.borderRadius = borderRadius; + this.boxShadow = boxShadow; + this.borderWidth = borderWidth; + this.opacity = opacity; + } updateSeedColor = (color: ColorTypes) => { - this.color = color; + this.seedColor = color; }; - updateColorScheme = (colorScheme: ColorScheme) => { - this.colorScheme = colorScheme; + updateColorMode = (colorMode: ColorMode) => { + this.colorMode = colorMode; }; updateBorderRadius = (borderRadius: TokenObj) => { this.borderRadius = borderRadius; - this.createTokenObject(this.borderRadius, "borderRadius"); + }; + + updateRootUnit = (rootUnit: number) => { + this.rootUnit = rootUnit; + }; + + updateBoxShadow = (boxShadow: TokenObj) => { + this.boxShadow = boxShadow; + }; + + updateBorderWidth = (borderWidth: TokenObj) => { + this.borderWidth = borderWidth; + }; + + updateOpacity = (opacity: TokenObj) => { + this.opacity = opacity; }; getAllTokens = () => { @@ -64,26 +80,30 @@ export class TokensAccessor { }; getColors = () => { + if (this.seedColor == null) return {} as ThemeTokens; + switch (true) { case this.isLightMode: return this.createTokenObject( - new LightScheme(this.color).getColors(), + new LightModeTheme(this.seedColor).getColors(), "color", ); case this.isDarkMode: return this.createTokenObject( - new DarkScheme(this.color).getColors(), + new DarkModeTheme(this.seedColor).getColors(), "color", ); default: return this.createTokenObject( - new LightScheme(this.color).getColors(), + new LightModeTheme(this.seedColor).getColors(), "color", ); } }; getSizing = () => { + if (this.rootUnit == null) return {} as ThemeTokens; + const sizing = { rootUnit: `${this.rootUnit}px`, }; @@ -92,10 +112,12 @@ export class TokensAccessor { }; getSpacing = (count = 6) => { + if (this.rootUnit == null) return {} as ThemeTokens; + const spacing = range(count).reduce((acc, value, index) => { return { ...acc, - [index]: `${this.rootUnit * value}px`, + [index]: `${(this.rootUnit as number) * value}px`, }; }, {}); @@ -103,27 +125,35 @@ export class TokensAccessor { }; getBorderRadius = () => { + if (this.borderRadius == null) return {} as ThemeTokens; + return this.createTokenObject(this.borderRadius, "borderRadius"); }; getBoxShadow = () => { + if (this.boxShadow == null) return {} as ThemeTokens; + return this.createTokenObject(this.boxShadow, "boxShadow"); }; getBorderWidth = () => { + if (this.borderWidth == null) return {} as ThemeTokens; + return this.createTokenObject(this.borderWidth, "borderWidth"); }; getOpacity = () => { + if (this.opacity == null) return {} as ThemeTokens; + return this.createTokenObject(this.opacity, "opacity"); }; private get isLightMode() { - return this.colorScheme === "light"; + return this.colorMode === "light"; } private get isDarkMode() { - return this.colorScheme === "dark"; + return this.colorMode === "dark"; } private createTokenObject = ( diff --git a/app/client/packages/design-system/theming/src/utils/TokensAccessor/index.ts b/app/client/packages/design-system/theming/src/utils/TokensAccessor/index.ts index 8a9f249211..8193f7b2e0 100644 --- a/app/client/packages/design-system/theming/src/utils/TokensAccessor/index.ts +++ b/app/client/packages/design-system/theming/src/utils/TokensAccessor/index.ts @@ -1,3 +1,3 @@ export { TokensAccessor } from "./TokensAccessor"; -export type { ThemeTokens } from "./TokensAccessor"; +export type { ThemeTokens, TokenSource } from "./types"; diff --git a/app/client/packages/design-system/theming/src/utils/TokensAccessor/types.ts b/app/client/packages/design-system/theming/src/utils/TokensAccessor/types.ts new file mode 100644 index 0000000000..71b8400fe9 --- /dev/null +++ b/app/client/packages/design-system/theming/src/utils/TokensAccessor/types.ts @@ -0,0 +1,54 @@ +import type { ColorTypes } from "colorjs.io/types/src/color"; +export type { ColorTypes } from "colorjs.io/types/src/color"; + +export type ThemeTokens = { + [key in TokenType]: { [key: string]: Token }; +}; + +export type TokenType = + | "sizing" + | "color" + | "spacing" + | "borderRadius" + | "boxShadow" + | "borderWidth" + | "opacity"; + +export interface Token { + value: string | number; + type: TokenType; +} + +export interface TokenSource { + seedColor?: ColorTypes; + colorMode?: ColorMode; + rootUnit?: number; + borderRadius?: TokenObj; + boxShadow?: TokenObj; + borderWidth?: TokenObj; + opacity?: TokenObj; +} + +export type TokenObj = { [key: string]: string | number }; + +export type ColorMode = "light" | "dark"; + +export interface ColorModeTheme { + getColors: () => { + bg: string; + bgAccent: string; + bgAccentHover: string; + bgAccentActive: string; + bgAccentSubtleHover: string; + bgAccentSubtleActive: string; + fg: string; + fgAccent: string; + fgOnAccent: string; + bdAccent: string; + bdFocus: string; + bdNeutral: string; + bdNeutralHover: string; + bdNegative: string; + bdNegativeHover: string; + }; +} diff --git a/app/client/packages/design-system/theming/src/utils/buildTokens.ts b/app/client/packages/design-system/theming/src/utils/buildTokens.ts index 99fe8fa5ac..949066784a 100644 --- a/app/client/packages/design-system/theming/src/utils/buildTokens.ts +++ b/app/client/packages/design-system/theming/src/utils/buildTokens.ts @@ -1,7 +1,12 @@ import fs from "fs"; -import { TokensAccessor } from "../"; +import { TokensAccessor, defaultTokens } from "../"; +import type { TokenSource } from "./"; fs.writeFileSync( `${__dirname}/../tokens/themeTokens.json`, - `${JSON.stringify(new TokensAccessor().getAllTokens(), null, 2)}\r\n`, + `${JSON.stringify( + new TokensAccessor(defaultTokens as TokenSource).getAllTokens(), + null, + 2, + )}\r\n`, ); diff --git a/app/client/packages/design-system/theming/src/utils/colorUtils/setLch.ts b/app/client/packages/design-system/theming/src/utils/colorUtils/setLch.ts index b2be855dd6..8a893d9402 100644 --- a/app/client/packages/design-system/theming/src/utils/colorUtils/setLch.ts +++ b/app/client/packages/design-system/theming/src/utils/colorUtils/setLch.ts @@ -13,15 +13,15 @@ export const setLch = ( const { c, h, l } = lch; let currentColor = parse(color); - if (l) { + if (l != null) { currentColor = setLightness(currentColor, l); } - if (c) { + if (c != null) { currentColor = setChroma(currentColor, c); } - if (h) { + if (h != null) { currentColor = setHue(currentColor, h); } diff --git a/app/client/packages/design-system/theming/src/utils/index.ts b/app/client/packages/design-system/theming/src/utils/index.ts index a7910c7a4e..762c1647bc 100644 --- a/app/client/packages/design-system/theming/src/utils/index.ts +++ b/app/client/packages/design-system/theming/src/utils/index.ts @@ -2,4 +2,4 @@ export { contrast, parse, lighten, setLch } from "./colorUtils"; export { TokensAccessor } from "./TokensAccessor"; export { ColorsAccessor } from "./ColorsAccessor"; -export type { ThemeTokens } from "./TokensAccessor"; +export type { ThemeTokens, TokenSource } from "./TokensAccessor"; diff --git a/app/client/packages/design-system/widgets/src/components/TestComponent/PrimaryButonTest.stories.mdx b/app/client/packages/design-system/widgets/src/components/TestComponent/PrimaryButonTest.stories.mdx index 03c9f2a2a0..0888d3154c 100644 --- a/app/client/packages/design-system/widgets/src/components/TestComponent/PrimaryButonTest.stories.mdx +++ b/app/client/packages/design-system/widgets/src/components/TestComponent/PrimaryButonTest.stories.mdx @@ -7,7 +7,7 @@ import { Button } from "../Button"; component={TestComponent} /> -export const Template = (args, { globals: { colorScheme } }) => ( +export const Template = (args, { globals: { colorMode } }) => ( <>