From 20e3647960f1fdab5df9eaa2b069c2c028a61ac1 Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 8 Feb 2024 09:25:00 -0500 Subject: [PATCH] Add basic support for theming on Web (#71) This adds basic support for overriding semantic tokens on Web via a CSS cascade layer, for which it was necessary to separate base and semantic tokens into different sets of files. Documentation for this theming mechanism will be provided over at the Compound Storybook repository, but it can be used like so: @import "@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css" layer(compound); @layer compound.custom { :root, [class*="cpd-theme-"] { --cpd-color-icon-accent-tertiary: var(--cpd-color-blue-800); } } --- assets/web/css/compound-design-tokens.css | 36 +++++----- .../{cpd-common.css => cpd-common-base.css} | 64 ----------------- assets/web/css/cpd-common-semantic.css | 66 ++++++++++++++++++ ...dark-mq.css => cpd-theme-dark-base-mq.css} | 2 - .../{cpd-dark.css => cpd-theme-dark-base.css} | 2 - ...c-mq.css => cpd-theme-dark-hc-base-mq.css} | 2 - ...dark-hc.css => cpd-theme-dark-hc-base.css} | 2 - .../web/css/cpd-theme-dark-hc-semantic-mq.css | 4 ++ assets/web/css/cpd-theme-dark-hc-semantic.css | 4 ++ assets/web/css/cpd-theme-dark-semantic-mq.css | 4 ++ assets/web/css/cpd-theme-dark-semantic.css | 4 ++ ...ght-mq.css => cpd-theme-light-base-mq.css} | 2 - ...cpd-light.css => cpd-theme-light-base.css} | 2 - ...-mq.css => cpd-theme-light-hc-base-mq.css} | 2 - ...ght-hc.css => cpd-theme-light-hc-base.css} | 2 - .../css/cpd-theme-light-hc-semantic-mq.css | 4 ++ .../web/css/cpd-theme-light-hc-semantic.css | 4 ++ .../web/css/cpd-theme-light-semantic-mq.css | 4 ++ assets/web/css/cpd-theme-light-semantic.css | 4 ++ build.ts | 2 + src/configs/getWebConfig.ts | 68 +++++++++---------- src/setupStyleDictionary.ts | 3 - src/utils/cssFileName.ts | 24 +++++++ src/utils/generateCssIndex.ts | 49 +++++++++++++ 24 files changed, 227 insertions(+), 133 deletions(-) rename assets/web/css/{cpd-common.css => cpd-common-base.css} (55%) create mode 100644 assets/web/css/cpd-common-semantic.css rename assets/web/css/{cpd-dark-mq.css => cpd-theme-dark-base-mq.css} (99%) rename assets/web/css/{cpd-dark.css => cpd-theme-dark-base.css} (99%) rename assets/web/css/{cpd-dark-hc-mq.css => cpd-theme-dark-hc-base-mq.css} (99%) rename assets/web/css/{cpd-dark-hc.css => cpd-theme-dark-hc-base.css} (99%) create mode 100644 assets/web/css/cpd-theme-dark-hc-semantic-mq.css create mode 100644 assets/web/css/cpd-theme-dark-hc-semantic.css create mode 100644 assets/web/css/cpd-theme-dark-semantic-mq.css create mode 100644 assets/web/css/cpd-theme-dark-semantic.css rename assets/web/css/{cpd-light-mq.css => cpd-theme-light-base-mq.css} (99%) rename assets/web/css/{cpd-light.css => cpd-theme-light-base.css} (99%) rename assets/web/css/{cpd-light-hc-mq.css => cpd-theme-light-hc-base-mq.css} (99%) rename assets/web/css/{cpd-light-hc.css => cpd-theme-light-hc-base.css} (99%) create mode 100644 assets/web/css/cpd-theme-light-hc-semantic-mq.css create mode 100644 assets/web/css/cpd-theme-light-hc-semantic.css create mode 100644 assets/web/css/cpd-theme-light-semantic-mq.css create mode 100644 assets/web/css/cpd-theme-light-semantic.css create mode 100644 src/utils/cssFileName.ts create mode 100644 src/utils/generateCssIndex.ts diff --git a/assets/web/css/compound-design-tokens.css b/assets/web/css/compound-design-tokens.css index c992cc60..e18db829 100644 --- a/assets/web/css/compound-design-tokens.css +++ b/assets/web/css/compound-design-tokens.css @@ -1,17 +1,21 @@ -/** - * TODO: Auto-generate this file to allow for dynamic themes to be generated - */ +/* Establish a layer order that allows semantic tokens to be customized, but not base tokens */ +@layer semantic, custom, base; -@import url("./cpd-common.css"); - -@import url("./cpd-light.css") screen; -@import url("./cpd-light-hc.css") screen; -@import url("./cpd-dark.css") screen; -@import url("./cpd-dark-hc.css") screen; - -@import url("./cpd-light-mq.css") screen and (prefers-color-scheme: light); -@import url("./cpd-light-hc-mq.css") screen and (prefers-color-scheme: light) - and (prefers-contrast: more); -@import url("./cpd-dark-mq.css") screen and (prefers-color-scheme: dark); -@import url("./cpd-dark-hc-mq.css") screen and (prefers-color-scheme: dark) and - (prefers-contrast: more); +@import url("./cpd-common-base.css") layer(base) screen; +@import url("./cpd-common-semantic.css") layer(semantic) screen; +@import url("./cpd-theme-light-base.css") layer(base) screen; +@import url("./cpd-theme-light-base-mq.css") layer(base) screen and (prefers-color-scheme: light); +@import url("./cpd-theme-light-semantic.css") layer(semantic) screen; +@import url("./cpd-theme-light-semantic-mq.css") layer(semantic) screen and (prefers-color-scheme: light); +@import url("./cpd-theme-light-hc-base.css") layer(base) screen; +@import url("./cpd-theme-light-hc-base-mq.css") layer(base) screen and (prefers-color-scheme: light) and (prefers-contrast: more); +@import url("./cpd-theme-light-hc-semantic.css") layer(semantic) screen; +@import url("./cpd-theme-light-hc-semantic-mq.css") layer(semantic) screen and (prefers-color-scheme: light) and (prefers-contrast: more); +@import url("./cpd-theme-dark-base.css") layer(base) screen; +@import url("./cpd-theme-dark-base-mq.css") layer(base) screen and (prefers-color-scheme: dark); +@import url("./cpd-theme-dark-semantic.css") layer(semantic) screen; +@import url("./cpd-theme-dark-semantic-mq.css") layer(semantic) screen and (prefers-color-scheme: dark); +@import url("./cpd-theme-dark-hc-base.css") layer(base) screen; +@import url("./cpd-theme-dark-hc-base-mq.css") layer(base) screen and (prefers-color-scheme: dark) and (prefers-contrast: more); +@import url("./cpd-theme-dark-hc-semantic.css") layer(semantic) screen; +@import url("./cpd-theme-dark-hc-semantic-mq.css") layer(semantic) screen and (prefers-color-scheme: dark) and (prefers-contrast: more); diff --git a/assets/web/css/cpd-common.css b/assets/web/css/cpd-common-base.css similarity index 55% rename from assets/web/css/cpd-common.css rename to assets/web/css/cpd-common-base.css index b74b4dd4..ad38c85e 100644 --- a/assets/web/css/cpd-common.css +++ b/assets/web/css/cpd-common-base.css @@ -37,70 +37,6 @@ --cpd-space-6x: 24px; --cpd-space-0x: 0px; --cpd-space-scale: 4px; - --cpd-color-icon-on-solid-primary: var(--cpd-color-theme-bg); - --cpd-color-icon-info-primary: var(--cpd-color-blue-900); - --cpd-color-icon-success-primary: var(--cpd-color-green-900); - --cpd-color-icon-critical-primary: var(--cpd-color-red-900); - --cpd-color-icon-accent-tertiary: var(--cpd-color-green-800); - --cpd-color-icon-quaternary-alpha: var(--cpd-color-alpha-gray-700); - --cpd-color-icon-tertiary-alpha: var(--cpd-color-alpha-gray-800); - --cpd-color-icon-secondary-alpha: var(--cpd-color-alpha-gray-900); - --cpd-color-icon-primary-alpha: var(--cpd-color-alpha-gray-1400); - --cpd-color-icon-disabled: var(--cpd-color-gray-700); - --cpd-color-icon-quaternary: var(--cpd-color-gray-700); - --cpd-color-icon-tertiary: var(--cpd-color-gray-800); - --cpd-color-icon-secondary: var(--cpd-color-gray-900); - --cpd-color-icon-primary: var(--cpd-color-gray-1400); - --cpd-color-border-info-subtle: var(--cpd-color-blue-500); - --cpd-color-border-success-subtle: var(--cpd-color-green-500); - --cpd-color-border-critical-subtle: var(--cpd-color-red-500); - --cpd-color-border-critical-hovered: var(--cpd-color-red-1000); - --cpd-color-border-critical-primary: var(--cpd-color-red-900); - --cpd-color-border-interactive-hovered: var(--cpd-color-gray-1100); - --cpd-color-border-interactive-secondary: var(--cpd-color-gray-600); - --cpd-color-border-interactive-primary: var(--cpd-color-gray-800); - --cpd-color-border-focused: var(--cpd-color-blue-900); - --cpd-color-border-disabled: var(--cpd-color-gray-500); - --cpd-color-bg-decorative-6: var(--cpd-color-alpha-orange-300); - --cpd-color-bg-decorative-5: var(--cpd-color-alpha-pink-300); - --cpd-color-bg-decorative-4: var(--cpd-color-alpha-purple-300); - --cpd-color-bg-decorative-3: var(--cpd-color-alpha-fuchsia-300); - --cpd-color-bg-decorative-2: var(--cpd-color-alpha-cyan-300); - --cpd-color-bg-decorative-1: var(--cpd-color-alpha-lime-300); - --cpd-color-bg-info-subtle: var(--cpd-color-blue-200); - --cpd-color-bg-success-subtle: var(--cpd-color-green-200); - --cpd-color-bg-critical-subtle-hovered: var(--cpd-color-red-300); - --cpd-color-bg-critical-subtle: var(--cpd-color-red-200); - --cpd-color-bg-critical-hovered: var(--cpd-color-red-1000); - --cpd-color-bg-critical-primary: var(--cpd-color-red-900); - --cpd-color-bg-action-secondary-pressed: var(--cpd-color-alpha-gray-300); - --cpd-color-bg-action-secondary-hovered: var(--cpd-color-alpha-gray-200); - --cpd-color-bg-action-secondary-rest: var(--cpd-color-theme-bg); - --cpd-color-bg-action-primary-disabled: var(--cpd-color-gray-700); - --cpd-color-bg-action-primary-pressed: var(--cpd-color-gray-1100); - --cpd-color-bg-action-primary-hovered: var(--cpd-color-gray-1200); - --cpd-color-bg-action-primary-rest: var(--cpd-color-gray-1400); - --cpd-color-bg-canvas-disabled: var(--cpd-color-gray-200); - --cpd-color-bg-canvas-default: var(--cpd-color-theme-bg); - --cpd-color-bg-subtle-secondary: var(--cpd-color-gray-300); - --cpd-color-bg-subtle-primary: var(--cpd-color-gray-400); - --cpd-color-text-decorative-6: var(--cpd-color-orange-1100); - --cpd-color-text-decorative-5: var(--cpd-color-pink-1100); - --cpd-color-text-decorative-4: var(--cpd-color-purple-1100); - --cpd-color-text-decorative-3: var(--cpd-color-fuchsia-1100); - --cpd-color-text-decorative-2: var(--cpd-color-cyan-1100); - --cpd-color-text-decorative-1: var(--cpd-color-lime-1100); - --cpd-color-text-on-solid-primary: var(--cpd-color-theme-bg); - --cpd-color-text-info-primary: var(--cpd-color-blue-900); - --cpd-color-text-success-primary: var(--cpd-color-green-900); - --cpd-color-text-critical-primary: var(--cpd-color-red-900); - --cpd-color-text-link-external: var(--cpd-color-blue-900); - --cpd-color-text-action-accent: var(--cpd-color-green-900); - --cpd-color-text-action-primary: var(--cpd-color-gray-1400); - --cpd-color-text-disabled: var(--cpd-color-gray-800); - --cpd-color-text-placeholder: var(--cpd-color-gray-800); - --cpd-color-text-secondary: var(--cpd-color-gray-900); - --cpd-color-text-primary: var(--cpd-color-gray-1400); --cpd-font-heading-xl-semibold: var(--cpd-font-weight-semibold) var(--cpd-font-size-heading-xl)/var(--cpd-font-line-height-tight) var(--cpd-font-family-sans); --cpd-font-heading-xl-regular: var(--cpd-font-weight-regular) var(--cpd-font-size-heading-xl)/var(--cpd-font-line-height-tight) var(--cpd-font-family-sans); --cpd-font-heading-lg-semibold: var(--cpd-font-weight-semibold) var(--cpd-font-size-heading-lg)/var(--cpd-font-line-height-tight) var(--cpd-font-family-sans); diff --git a/assets/web/css/cpd-common-semantic.css b/assets/web/css/cpd-common-semantic.css new file mode 100644 index 00000000..6c6d3b3c --- /dev/null +++ b/assets/web/css/cpd-common-semantic.css @@ -0,0 +1,66 @@ +:root, [class*="cpd-theme-"] { + --cpd-color-icon-on-solid-primary: var(--cpd-color-theme-bg); + --cpd-color-icon-info-primary: var(--cpd-color-blue-900); + --cpd-color-icon-success-primary: var(--cpd-color-green-900); + --cpd-color-icon-critical-primary: var(--cpd-color-red-900); + --cpd-color-icon-accent-tertiary: var(--cpd-color-green-800); + --cpd-color-icon-quaternary-alpha: var(--cpd-color-alpha-gray-700); + --cpd-color-icon-tertiary-alpha: var(--cpd-color-alpha-gray-800); + --cpd-color-icon-secondary-alpha: var(--cpd-color-alpha-gray-900); + --cpd-color-icon-primary-alpha: var(--cpd-color-alpha-gray-1400); + --cpd-color-icon-disabled: var(--cpd-color-gray-700); + --cpd-color-icon-quaternary: var(--cpd-color-gray-700); + --cpd-color-icon-tertiary: var(--cpd-color-gray-800); + --cpd-color-icon-secondary: var(--cpd-color-gray-900); + --cpd-color-icon-primary: var(--cpd-color-gray-1400); + --cpd-color-border-info-subtle: var(--cpd-color-blue-500); + --cpd-color-border-success-subtle: var(--cpd-color-green-500); + --cpd-color-border-critical-subtle: var(--cpd-color-red-500); + --cpd-color-border-critical-hovered: var(--cpd-color-red-1000); + --cpd-color-border-critical-primary: var(--cpd-color-red-900); + --cpd-color-border-interactive-hovered: var(--cpd-color-gray-1100); + --cpd-color-border-interactive-secondary: var(--cpd-color-gray-600); + --cpd-color-border-interactive-primary: var(--cpd-color-gray-800); + --cpd-color-border-focused: var(--cpd-color-blue-900); + --cpd-color-border-disabled: var(--cpd-color-gray-500); + --cpd-color-bg-decorative-6: var(--cpd-color-alpha-orange-300); + --cpd-color-bg-decorative-5: var(--cpd-color-alpha-pink-300); + --cpd-color-bg-decorative-4: var(--cpd-color-alpha-purple-300); + --cpd-color-bg-decorative-3: var(--cpd-color-alpha-fuchsia-300); + --cpd-color-bg-decorative-2: var(--cpd-color-alpha-cyan-300); + --cpd-color-bg-decorative-1: var(--cpd-color-alpha-lime-300); + --cpd-color-bg-info-subtle: var(--cpd-color-blue-200); + --cpd-color-bg-success-subtle: var(--cpd-color-green-200); + --cpd-color-bg-critical-subtle-hovered: var(--cpd-color-red-300); + --cpd-color-bg-critical-subtle: var(--cpd-color-red-200); + --cpd-color-bg-critical-hovered: var(--cpd-color-red-1000); + --cpd-color-bg-critical-primary: var(--cpd-color-red-900); + --cpd-color-bg-action-secondary-pressed: var(--cpd-color-alpha-gray-300); + --cpd-color-bg-action-secondary-hovered: var(--cpd-color-alpha-gray-200); + --cpd-color-bg-action-secondary-rest: var(--cpd-color-theme-bg); + --cpd-color-bg-action-primary-disabled: var(--cpd-color-gray-700); + --cpd-color-bg-action-primary-pressed: var(--cpd-color-gray-1100); + --cpd-color-bg-action-primary-hovered: var(--cpd-color-gray-1200); + --cpd-color-bg-action-primary-rest: var(--cpd-color-gray-1400); + --cpd-color-bg-canvas-disabled: var(--cpd-color-gray-200); + --cpd-color-bg-canvas-default: var(--cpd-color-theme-bg); + --cpd-color-bg-subtle-secondary: var(--cpd-color-gray-300); + --cpd-color-bg-subtle-primary: var(--cpd-color-gray-400); + --cpd-color-text-decorative-6: var(--cpd-color-orange-1100); + --cpd-color-text-decorative-5: var(--cpd-color-pink-1100); + --cpd-color-text-decorative-4: var(--cpd-color-purple-1100); + --cpd-color-text-decorative-3: var(--cpd-color-fuchsia-1100); + --cpd-color-text-decorative-2: var(--cpd-color-cyan-1100); + --cpd-color-text-decorative-1: var(--cpd-color-lime-1100); + --cpd-color-text-on-solid-primary: var(--cpd-color-theme-bg); + --cpd-color-text-info-primary: var(--cpd-color-blue-900); + --cpd-color-text-success-primary: var(--cpd-color-green-900); + --cpd-color-text-critical-primary: var(--cpd-color-red-900); + --cpd-color-text-link-external: var(--cpd-color-blue-900); + --cpd-color-text-action-accent: var(--cpd-color-green-900); + --cpd-color-text-action-primary: var(--cpd-color-gray-1400); + --cpd-color-text-disabled: var(--cpd-color-gray-800); + --cpd-color-text-placeholder: var(--cpd-color-gray-800); + --cpd-color-text-secondary: var(--cpd-color-gray-900); + --cpd-color-text-primary: var(--cpd-color-gray-1400); +} diff --git a/assets/web/css/cpd-dark-mq.css b/assets/web/css/cpd-theme-dark-base-mq.css similarity index 99% rename from assets/web/css/cpd-dark-mq.css rename to assets/web/css/cpd-theme-dark-base-mq.css index be5d1d28..ee764f48 100644 --- a/assets/web/css/cpd-dark-mq.css +++ b/assets/web/css/cpd-theme-dark-base-mq.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #181a1f; --cpd-color-gray-100: #14171b; --cpd-color-theme-bg: #101317; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); } diff --git a/assets/web/css/cpd-dark.css b/assets/web/css/cpd-theme-dark-base.css similarity index 99% rename from assets/web/css/cpd-dark.css rename to assets/web/css/cpd-theme-dark-base.css index 8be18793..26830618 100644 --- a/assets/web/css/cpd-dark.css +++ b/assets/web/css/cpd-theme-dark-base.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #181a1f; --cpd-color-gray-100: #14171b; --cpd-color-theme-bg: #101317; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); } diff --git a/assets/web/css/cpd-dark-hc-mq.css b/assets/web/css/cpd-theme-dark-hc-base-mq.css similarity index 99% rename from assets/web/css/cpd-dark-hc-mq.css rename to assets/web/css/cpd-theme-dark-hc-base-mq.css index 9ef76e24..7b0fb6a3 100644 --- a/assets/web/css/cpd-dark-hc-mq.css +++ b/assets/web/css/cpd-theme-dark-hc-base-mq.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #1d1f24; --cpd-color-gray-100: #181a1f; --cpd-color-theme-bg: #101317; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); } diff --git a/assets/web/css/cpd-dark-hc.css b/assets/web/css/cpd-theme-dark-hc-base.css similarity index 99% rename from assets/web/css/cpd-dark-hc.css rename to assets/web/css/cpd-theme-dark-hc-base.css index ffa3c3a2..ec807a3b 100644 --- a/assets/web/css/cpd-dark-hc.css +++ b/assets/web/css/cpd-theme-dark-hc-base.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #1d1f24; --cpd-color-gray-100: #181a1f; --cpd-color-theme-bg: #101317; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); } diff --git a/assets/web/css/cpd-theme-dark-hc-semantic-mq.css b/assets/web/css/cpd-theme-dark-hc-semantic-mq.css new file mode 100644 index 00000000..87a2af73 --- /dev/null +++ b/assets/web/css/cpd-theme-dark-hc-semantic-mq.css @@ -0,0 +1,4 @@ +:root { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); +} diff --git a/assets/web/css/cpd-theme-dark-hc-semantic.css b/assets/web/css/cpd-theme-dark-hc-semantic.css new file mode 100644 index 00000000..505191fe --- /dev/null +++ b/assets/web/css/cpd-theme-dark-hc-semantic.css @@ -0,0 +1,4 @@ +.cpd-theme-dark-hc.cpd-theme-dark-hc { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); +} diff --git a/assets/web/css/cpd-theme-dark-semantic-mq.css b/assets/web/css/cpd-theme-dark-semantic-mq.css new file mode 100644 index 00000000..87a2af73 --- /dev/null +++ b/assets/web/css/cpd-theme-dark-semantic-mq.css @@ -0,0 +1,4 @@ +:root { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); +} diff --git a/assets/web/css/cpd-theme-dark-semantic.css b/assets/web/css/cpd-theme-dark-semantic.css new file mode 100644 index 00000000..d1e68dcb --- /dev/null +++ b/assets/web/css/cpd-theme-dark-semantic.css @@ -0,0 +1,4 @@ +.cpd-theme-dark.cpd-theme-dark { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-theme-bg); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-gray-300); +} diff --git a/assets/web/css/cpd-light-mq.css b/assets/web/css/cpd-theme-light-base-mq.css similarity index 99% rename from assets/web/css/cpd-light-mq.css rename to assets/web/css/cpd-theme-light-base-mq.css index 46d79149..2e556e7d 100644 --- a/assets/web/css/cpd-light-mq.css +++ b/assets/web/css/cpd-theme-light-base-mq.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #f7f9fa; --cpd-color-gray-100: #fbfcfd; --cpd-color-theme-bg: #ffffff; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); } diff --git a/assets/web/css/cpd-light.css b/assets/web/css/cpd-theme-light-base.css similarity index 99% rename from assets/web/css/cpd-light.css rename to assets/web/css/cpd-theme-light-base.css index c23ae12b..d065211e 100644 --- a/assets/web/css/cpd-light.css +++ b/assets/web/css/cpd-theme-light-base.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #f7f9fa; --cpd-color-gray-100: #fbfcfd; --cpd-color-theme-bg: #ffffff; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); } diff --git a/assets/web/css/cpd-light-hc-mq.css b/assets/web/css/cpd-theme-light-hc-base-mq.css similarity index 99% rename from assets/web/css/cpd-light-hc-mq.css rename to assets/web/css/cpd-theme-light-hc-base-mq.css index 63b31c77..dabfaf7c 100644 --- a/assets/web/css/cpd-light-hc-mq.css +++ b/assets/web/css/cpd-theme-light-hc-base-mq.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #f0f2f5; --cpd-color-gray-100: #f7f9fa; --cpd-color-theme-bg: #ffffff; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); } diff --git a/assets/web/css/cpd-light-hc.css b/assets/web/css/cpd-theme-light-hc-base.css similarity index 99% rename from assets/web/css/cpd-light-hc.css rename to assets/web/css/cpd-theme-light-hc-base.css index 51c9c431..7d8b4acb 100644 --- a/assets/web/css/cpd-light-hc.css +++ b/assets/web/css/cpd-theme-light-hc-base.css @@ -308,6 +308,4 @@ --cpd-color-gray-200: #f0f2f5; --cpd-color-gray-100: #f7f9fa; --cpd-color-theme-bg: #ffffff; - --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); - --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); } diff --git a/assets/web/css/cpd-theme-light-hc-semantic-mq.css b/assets/web/css/cpd-theme-light-hc-semantic-mq.css new file mode 100644 index 00000000..57194576 --- /dev/null +++ b/assets/web/css/cpd-theme-light-hc-semantic-mq.css @@ -0,0 +1,4 @@ +:root { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); +} diff --git a/assets/web/css/cpd-theme-light-hc-semantic.css b/assets/web/css/cpd-theme-light-hc-semantic.css new file mode 100644 index 00000000..b310d2a1 --- /dev/null +++ b/assets/web/css/cpd-theme-light-hc-semantic.css @@ -0,0 +1,4 @@ +.cpd-theme-light-hc.cpd-theme-light-hc { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); +} diff --git a/assets/web/css/cpd-theme-light-semantic-mq.css b/assets/web/css/cpd-theme-light-semantic-mq.css new file mode 100644 index 00000000..57194576 --- /dev/null +++ b/assets/web/css/cpd-theme-light-semantic-mq.css @@ -0,0 +1,4 @@ +:root { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); +} diff --git a/assets/web/css/cpd-theme-light-semantic.css b/assets/web/css/cpd-theme-light-semantic.css new file mode 100644 index 00000000..b169e31c --- /dev/null +++ b/assets/web/css/cpd-theme-light-semantic.css @@ -0,0 +1,4 @@ +.cpd-theme-light.cpd-theme-light { + --cpd-color-bg-subtle-secondary-level-0: var(--cpd-color-gray-300); + --cpd-color-bg-canvas-default-level-1: var(--cpd-color-theme-bg); +} diff --git a/build.ts b/build.ts index d94818a7..dd2df5d6 100644 --- a/build.ts +++ b/build.ts @@ -19,12 +19,14 @@ import * as setupStyleDictionary from "./src/setupStyleDictionary"; import generateIconTokens from "./src/utils/generateIconTokens"; import fs from "fs-extra"; +import { generateCssIndex } from "./src/utils/generateCssIndex"; const themes: Theme[] = ["light", "light-hc", "dark", "dark-hc"]; const platforms: Platform[] = ["web", "android", "ios"]; (async () => { generateIconTokens(); + generateCssIndex(); for (const platform of platforms) { for (const theme of themes) { const sb = await setupStyleDictionary.themed(theme, platform); diff --git a/src/configs/getWebConfig.ts b/src/configs/getWebConfig.ts index d309d1bb..4fbcb738 100644 --- a/src/configs/getWebConfig.ts +++ b/src/configs/getWebConfig.ts @@ -18,8 +18,10 @@ import { Platform } from "style-dictionary/types/Platform"; import { Theme } from "../@types"; import { File } from "style-dictionary/types/File"; import _ from "lodash"; - -const COMPOUND_TOKENS_NAMESPACE = "cpd"; +import { isSharedAcrossTheme } from "../filters/isSharedAcrossTheme"; +import isCoreToken from "../filters/isCoreToken" +import { isCoreColor } from "../filters/isCoreColor"; +import { COMPOUND_TOKENS_NAMESPACE, cssFileName, Tier } from "../utils/cssFileName"; const basePxFontSize = 16; @@ -73,43 +75,41 @@ function getFilesFormat(theme: Theme, target: "css" | "js" | "ts"): File[] { }, ]; } else { - return [ - { - destination: `${COMPOUND_TOKENS_NAMESPACE}-common.css`, - format: "css/variables", - filter: "isSharedAcrossTheme", - options: { - showFileHeader: false, - outputReferences: true, - basePxFontSize, - selector: `:root, [class*="cpd-theme-"]`, - }, + const common = (tier: Tier): File => ({ + destination: cssFileName(null, tier, false), + format: "css/variables", + filter: t => isSharedAcrossTheme.matcher(t) && isCoreToken.matcher(t) === (tier === 'base'), + options: { + showFileHeader: false, + outputReferences: true, + basePxFontSize, + selector: `:root, [class*="cpd-theme-"]`, }, + }) + + const themed = (tier: Tier, mq: boolean): File => ({ + destination: cssFileName(theme, tier, mq), + format: "css/variables", + filter: t => isCoreColor.matcher(t) && isCoreToken.matcher(t) === (tier === 'base'), + options: { + showFileHeader: false, + outputReferences: true, + selector: mq ? undefined : `.${COMPOUND_TOKENS_NAMESPACE}-theme-${theme}.${COMPOUND_TOKENS_NAMESPACE}-theme-${theme}`, + basePxFontSize, + }, + }) + + return [ + common('base'), + common('semantic'), // Generates the theme under a scoped selector // e.g. .cpd-dark-hc { /* ... */ } - { - destination: `${COMPOUND_TOKENS_NAMESPACE}-${theme}.css`, - format: "css/variables", - filter: "isCoreColor", - options: { - showFileHeader: false, - outputReferences: true, - selector: `.${COMPOUND_TOKENS_NAMESPACE}-theme-${theme}.${COMPOUND_TOKENS_NAMESPACE}-theme-${theme}`, - basePxFontSize, - }, - }, + themed('base', false), + themed('semantic', false), // Generates the theme under the :root // This file is to be imported with a media query import - { - destination: `${COMPOUND_TOKENS_NAMESPACE}-${theme}-mq.css`, - format: "css/variables", - filter: "isCoreColor", - options: { - showFileHeader: false, - outputReferences: true, - basePxFontSize, - }, - }, + themed('base', true), + themed('semantic', true), ]; } } diff --git a/src/setupStyleDictionary.ts b/src/setupStyleDictionary.ts index 01072d52..0472cf5a 100644 --- a/src/setupStyleDictionary.ts +++ b/src/setupStyleDictionary.ts @@ -19,8 +19,6 @@ import { Core } from "style-dictionary"; import { Named } from "style-dictionary/types/_helpers"; import { Transform } from "style-dictionary/types/Transform"; import { registerTransforms } from "@tokens-studio/sd-transforms"; -import * as fs from "fs"; -import * as path from "path"; import camelCaseDecimal from "./transforms/camelCaseDecimal"; import pxToCGFloat from "./transforms/swift/pxToCGFloat"; @@ -48,7 +46,6 @@ import svgToDrawable from "./transforms/kotlin/svgToDrawable"; import iconsImport from "./transforms/css/iconsImport"; import iconTICamel from "./transforms/swift/iconTICamel"; import svgToImageView from "./transforms/swift/svgToImageView"; -import * as lodash from "lodash"; import { isSharedAcrossTheme } from "./filters/isSharedAcrossTheme"; async function setupDictionary(sb: Core) { diff --git a/src/utils/cssFileName.ts b/src/utils/cssFileName.ts new file mode 100644 index 00000000..7c94873f --- /dev/null +++ b/src/utils/cssFileName.ts @@ -0,0 +1,24 @@ +/* +Copyright 2024 New Vector Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Theme } from '../@types' + +export type Tier = 'base' | 'semantic' + +export const COMPOUND_TOKENS_NAMESPACE = "cpd"; + +export const cssFileName = (theme: Theme | null, tier: Tier, mq: boolean) => + `${COMPOUND_TOKENS_NAMESPACE}-${theme === null ? 'common' : `theme-${theme}`}-${tier}${mq ? '-mq' : ''}.css` diff --git a/src/utils/generateCssIndex.ts b/src/utils/generateCssIndex.ts new file mode 100644 index 00000000..4a2863d1 --- /dev/null +++ b/src/utils/generateCssIndex.ts @@ -0,0 +1,49 @@ +/* +Copyright 2024 New Vector Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import fs from 'fs-extra' +import path from 'path' +import { Theme } from '../@types' +import { cssFileName, Tier } from './cssFileName' + +const header = `/* Establish a layer order that allows semantic tokens to be customized, but not base tokens */ +@layer semantic, custom, base;` + +const themes: (Theme | null)[] = [null, 'light', 'light-hc', 'dark', 'dark-hc'] +const tiers: Tier[] = ['base', 'semantic'] + +export function generateCssIndex(): void { + const imports = [...(function* () { + for (const theme of themes) { + for (const tier of tiers) { + for (const mq of theme === null ? [false] : [false, true]) { + let mediaQuery = 'screen' + if (mq) { + mediaQuery += ` and (prefers-color-scheme: ${theme!.includes('light') ? 'light' : 'dark'})` + if (theme!.includes('-hc')) mediaQuery += ` and (prefers-contrast: more)` + } + yield `@import url("./${cssFileName(theme, tier, mq)}") layer(${tier}) ${mediaQuery};` + } + } + } + })()] + + fs.writeFileSync( + path.join('assets', 'web', 'css', 'compound-design-tokens.css'), + `${header}\n\n${imports.join('\n')}\n`, + 'utf-8', + ) +}