From 54b89fa5d0d581d57078429c598406034a4d1456 Mon Sep 17 00:00:00 2001 From: Raushen Date: Mon, 7 Sep 2026 19:09:20 +0300 Subject: [PATCH] Custom Accent support --- .../build/tokens/build-tokens.mjs | 27 +++++++- .../widgets/fluent-next/_accent-color.scss | 26 +++++++ .../widgets/fluent-next/_design-system.scss | 1 + .../devextreme-scss/tests/calc-budget.json | 8 +-- .../tests/fluent-next-naming.baseline.json | 69 ++++++++++++++++++- .../tests/fluent-next-naming.test.ts | 5 ++ packages/devextreme/project.json | 3 +- 7 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 packages/devextreme-scss/scss/widgets/fluent-next/_accent-color.scss diff --git a/packages/devextreme-scss/build/tokens/build-tokens.mjs b/packages/devextreme-scss/build/tokens/build-tokens.mjs index 750f5b0a4d45..7ffb69939e5c 100644 --- a/packages/devextreme-scss/build/tokens/build-tokens.mjs +++ b/packages/devextreme-scss/build/tokens/build-tokens.mjs @@ -238,6 +238,31 @@ StyleDictionary.registerFormat({ .join('\n'), }); +const ACCENT_PROPERTY = '--dx-accent-color'; +const PRIMARY_STEP_DECLARATION = /^(\s*)--dxds-primary-(\d+):\s*([^;]+);$/gm; + +StyleDictionary.registerFormat({ + name: 'dx/accent-palette', + format: async (args) => { + const palette = await StyleDictionary.hooks.formats['css/variables'](args); + let wrapped = 0; + const withAccentFallback = palette.replace( + PRIMARY_STEP_DECLARATION, + (line, indent, step, value) => { + wrapped += 1; + + return `${indent}--dxds-primary-${step}: var(${ACCENT_PROPERTY}-${step}, ${value});`; + }, + ); + + if (wrapped === 0) { + throw new Error('An accent palette without a single --dxds-primary-* step'); + } + + return withAccentFallback; + }, +}); + const FILE_OPTIONS = { outputReferences: true, themeable: true, @@ -288,7 +313,7 @@ const createConfig = (name, files, platformFiles) => ({ const createPaletteConfig = (palette) => createConfig(palette, [`base/colors/palettes/${THEME_NAME}/${palette}`], [ { destination: `${THEME_NAME}/accents/${palette}.scss`, - format: 'css/variables', + format: 'dx/accent-palette', filter: (token) => normalizeFilePath(token).includes(`${THEME_NAME}/${palette}.json`), options: FILE_OPTIONS, }, diff --git a/packages/devextreme-scss/scss/widgets/fluent-next/_accent-color.scss b/packages/devextreme-scss/scss/widgets/fluent-next/_accent-color.scss new file mode 100644 index 000000000000..ebd9a41b1e29 --- /dev/null +++ b/packages/devextreme-scss/scss/widgets/fluent-next/_accent-color.scss @@ -0,0 +1,26 @@ +@supports (color: oklch(from red l c h)) { + :root { + --dx-accent-color-source: var(--dx-accent-color); + --dx-accent-lightness-max: 0.95; + --dx-accent-lightness-min: 0.15; + --dx-accent-chroma-min: 0.04; + --dx-accent-color-10: oklch(from var(--dx-accent-color-source) calc(l + 9 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 9 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-20: oklch(from var(--dx-accent-color-source) calc(l + 8 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 8 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-30: oklch(from var(--dx-accent-color-source) calc(l + 7 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 7 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-40: oklch(from var(--dx-accent-color-source) calc(l + 6 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 6 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-50: oklch(from var(--dx-accent-color-source) calc(l + 5 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 5 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-60: oklch(from var(--dx-accent-color-source) calc(l + 4 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 4 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-70: oklch(from var(--dx-accent-color-source) calc(l + 3 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 3 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-80: oklch(from var(--dx-accent-color-source) calc(l + 2 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 2 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-90: oklch(from var(--dx-accent-color-source) calc(l + 1 * (var(--dx-accent-lightness-max) - min(l, var(--dx-accent-lightness-max))) / 9) calc(c - 1 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 9) h); + --dx-accent-color-100: oklch(from var(--dx-accent-color-source) l c h); + --dx-accent-color-110: oklch(from var(--dx-accent-color-source) calc(l - 1 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 1 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-120: oklch(from var(--dx-accent-color-source) calc(l - 2 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 2 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-130: oklch(from var(--dx-accent-color-source) calc(l - 3 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 3 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-140: oklch(from var(--dx-accent-color-source) calc(l - 4 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 4 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-150: oklch(from var(--dx-accent-color-source) calc(l - 5 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 5 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-160: oklch(from var(--dx-accent-color-source) calc(l - 6 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 6 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-170: oklch(from var(--dx-accent-color-source) calc(l - 7 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 7 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + --dx-accent-color-180: oklch(from var(--dx-accent-color-source) calc(l - 8 * (max(l, var(--dx-accent-lightness-min)) - var(--dx-accent-lightness-min)) / 8) calc(c - 8 * (max(c, var(--dx-accent-chroma-min)) - var(--dx-accent-chroma-min)) / 8) h); + } +} diff --git a/packages/devextreme-scss/scss/widgets/fluent-next/_design-system.scss b/packages/devextreme-scss/scss/widgets/fluent-next/_design-system.scss index e255d380ae46..61953d380912 100644 --- a/packages/devextreme-scss/scss/widgets/fluent-next/_design-system.scss +++ b/packages/devextreme-scss/scss/widgets/fluent-next/_design-system.scss @@ -15,6 +15,7 @@ $accent: colors.$color; @include meta.load-css("../../_design-system/base"); @include meta.load-css("../../_design-system/fluent/base"); @include meta.load-css("../../_design-system/fluent/accents/#{$accent}"); +@include meta.load-css("accent-color"); @include meta.load-css("../../_design-system/fluent/semantic/typography"); @include meta.load-css("../../_design-system/fluent/semantic/box-shadow"); @include meta.load-css("../../_design-system/fluent/semantic/colors/#{colors.$mode}"); diff --git a/packages/devextreme-scss/tests/calc-budget.json b/packages/devextreme-scss/tests/calc-budget.json index 903e8e4de13a..46abd4afbc97 100644 --- a/packages/devextreme-scss/tests/calc-budget.json +++ b/packages/devextreme-scss/tests/calc-budget.json @@ -1,18 +1,18 @@ { "dx.fluent-next.blue.dark.compact.css": { - "calcOccurrences": 359, + "calcOccurrences": 393, "declarationsWithDeepCalc": 5 }, "dx.fluent-next.blue.dark.css": { - "calcOccurrences": 359, + "calcOccurrences": 393, "declarationsWithDeepCalc": 5 }, "dx.fluent-next.blue.light.compact.css": { - "calcOccurrences": 359, + "calcOccurrences": 393, "declarationsWithDeepCalc": 5 }, "dx.fluent-next.blue.light.css": { - "calcOccurrences": 359, + "calcOccurrences": 393, "declarationsWithDeepCalc": 5 } } diff --git a/packages/devextreme-scss/tests/fluent-next-naming.baseline.json b/packages/devextreme-scss/tests/fluent-next-naming.baseline.json index a014a0aa37b2..3521bda01eb6 100644 --- a/packages/devextreme-scss/tests/fluent-next-naming.baseline.json +++ b/packages/devextreme-scss/tests/fluent-next-naming.baseline.json @@ -492,6 +492,28 @@ "crossWidgetStarImports": [], "deadVariables": [], "publicSurfaceUnused": [ + "--dx-accent-chroma-min", + "--dx-accent-color-10", + "--dx-accent-color-100", + "--dx-accent-color-110", + "--dx-accent-color-120", + "--dx-accent-color-130", + "--dx-accent-color-140", + "--dx-accent-color-150", + "--dx-accent-color-160", + "--dx-accent-color-170", + "--dx-accent-color-180", + "--dx-accent-color-20", + "--dx-accent-color-30", + "--dx-accent-color-40", + "--dx-accent-color-50", + "--dx-accent-color-60", + "--dx-accent-color-70", + "--dx-accent-color-80", + "--dx-accent-color-90", + "--dx-accent-color-source", + "--dx-accent-lightness-max", + "--dx-accent-lightness-min", "--dx-button-padding-inline", "--dx-color-shadow", "--dx-component-height", @@ -511,8 +533,53 @@ "--dx-toolbar-height" ], "publicSurfaceUndeclared": [], - "publicSurfaceDifferences": [], + "publicSurfaceDifferences": [ + "--dx-accent-chroma-min: only in fluent-next", + "--dx-accent-color-10: only in fluent-next", + "--dx-accent-color-100: only in fluent-next", + "--dx-accent-color-110: only in fluent-next", + "--dx-accent-color-120: only in fluent-next", + "--dx-accent-color-130: only in fluent-next", + "--dx-accent-color-140: only in fluent-next", + "--dx-accent-color-150: only in fluent-next", + "--dx-accent-color-160: only in fluent-next", + "--dx-accent-color-170: only in fluent-next", + "--dx-accent-color-180: only in fluent-next", + "--dx-accent-color-20: only in fluent-next", + "--dx-accent-color-30: only in fluent-next", + "--dx-accent-color-40: only in fluent-next", + "--dx-accent-color-50: only in fluent-next", + "--dx-accent-color-60: only in fluent-next", + "--dx-accent-color-70: only in fluent-next", + "--dx-accent-color-80: only in fluent-next", + "--dx-accent-color-90: only in fluent-next", + "--dx-accent-color-source: only in fluent-next", + "--dx-accent-lightness-max: only in fluent-next", + "--dx-accent-lightness-min: only in fluent-next" + ], "publicTierManualDeclarations": [ + "fluent-next/_accent-color.scss: --dx-accent-chroma-min", + "fluent-next/_accent-color.scss: --dx-accent-color-10", + "fluent-next/_accent-color.scss: --dx-accent-color-100", + "fluent-next/_accent-color.scss: --dx-accent-color-110", + "fluent-next/_accent-color.scss: --dx-accent-color-120", + "fluent-next/_accent-color.scss: --dx-accent-color-130", + "fluent-next/_accent-color.scss: --dx-accent-color-140", + "fluent-next/_accent-color.scss: --dx-accent-color-150", + "fluent-next/_accent-color.scss: --dx-accent-color-160", + "fluent-next/_accent-color.scss: --dx-accent-color-170", + "fluent-next/_accent-color.scss: --dx-accent-color-180", + "fluent-next/_accent-color.scss: --dx-accent-color-20", + "fluent-next/_accent-color.scss: --dx-accent-color-30", + "fluent-next/_accent-color.scss: --dx-accent-color-40", + "fluent-next/_accent-color.scss: --dx-accent-color-50", + "fluent-next/_accent-color.scss: --dx-accent-color-60", + "fluent-next/_accent-color.scss: --dx-accent-color-70", + "fluent-next/_accent-color.scss: --dx-accent-color-80", + "fluent-next/_accent-color.scss: --dx-accent-color-90", + "fluent-next/_accent-color.scss: --dx-accent-color-source", + "fluent-next/_accent-color.scss: --dx-accent-lightness-max", + "fluent-next/_accent-color.scss: --dx-accent-lightness-min", "fluent-next/_colors.scss: --dx-color-border", "fluent-next/_colors.scss: --dx-color-danger", "fluent-next/_colors.scss: --dx-color-icon", diff --git a/packages/devextreme-scss/tests/fluent-next-naming.test.ts b/packages/devextreme-scss/tests/fluent-next-naming.test.ts index 03df125e97bc..fd3f0d06d461 100644 --- a/packages/devextreme-scss/tests/fluent-next-naming.test.ts +++ b/packages/devextreme-scss/tests/fluent-next-naming.test.ts @@ -323,6 +323,10 @@ const RUNTIME_CONTRACT = new Set([ '--dx-scheduler-animation-top', ]); +const DECLARED_BY_APPLICATION = new Set([ + '--dx-accent-color', +]); + /** Every `--dx-*` read anywhere outside the theme sources, or null when the monorepo is unavailable. */ const publicNameConsumers = (): Set | null => { const roots = [ @@ -1174,6 +1178,7 @@ test('component tier: every var(--dx-…) read in the theme resolves to a declar const declared = new Set([ ...[...tierDeclared.keys()].map((variable) => `--dx-${variable.slice(1)}`), ...RUNTIME_CONTRACT, + ...DECLARED_BY_APPLICATION, ...findings.publicTierManualDeclarations.map((entry) => entry.slice(entry.indexOf(': ') + 2)), ]); const offenders = walk(themeRoot, '.scss').flatMap((file) => [ diff --git a/packages/devextreme/project.json b/packages/devextreme/project.json index bfc2f889037e..0c638d623ca9 100644 --- a/packages/devextreme/project.json +++ b/packages/devextreme/project.json @@ -13,8 +13,7 @@ "targetDirectory": "./artifacts", "excludePatterns": [ "./artifacts/css", - "./artifacts/npm/devextreme/package.json", - "./artifacts/npm/devextreme-dist" + "./artifacts/npm/devextreme/package.json" ] } },