@solexllc/usx-theme 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/build.js +74 -7
- package/dist/_themes-registry.scss +948 -0
- package/dist/themes/all.css +677 -0
- package/dist/themes/aurora.css +77 -0
- package/dist/themes/borealis.css +104 -0
- package/dist/themes/carbon.css +79 -0
- package/dist/themes/forest.css +52 -0
- package/dist/themes/gov-uk.css +72 -0
- package/dist/themes/midnight.css +79 -0
- package/dist/themes/nasa.css +72 -0
- package/dist/themes/ocean.css +52 -0
- package/dist/themes/sunset.css +52 -0
- package/dist/themes/vads.css +38 -0
- package/package.json +16 -1
- package/src/_themes.scss +120 -0
- package/src/_variables.scss +0 -1
- package/src/derive.js +233 -0
- package/src/presets.js +348 -0
- package/src/theme-manifest.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solexllc/usx-theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Design tokens package for USX.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -26,11 +26,22 @@
|
|
|
26
26
|
"./hooks": {
|
|
27
27
|
"sass": "./dist/_hooks.scss"
|
|
28
28
|
},
|
|
29
|
+
"./themes": {
|
|
30
|
+
"sass": "./src/_themes.scss"
|
|
31
|
+
},
|
|
32
|
+
"./themes/all.css": "./dist/themes/all.css",
|
|
33
|
+
"./themes/*.css": "./dist/themes/*.css",
|
|
29
34
|
"./theme.css": "./dist/theme.css",
|
|
30
35
|
"./theme-manifest": {
|
|
31
36
|
"default": "./src/theme-manifest.js"
|
|
32
37
|
},
|
|
33
38
|
"./theme-manifest.json": "./dist/theme-manifest.json",
|
|
39
|
+
"./derive": {
|
|
40
|
+
"default": "./src/derive.js"
|
|
41
|
+
},
|
|
42
|
+
"./presets": {
|
|
43
|
+
"default": "./src/presets.js"
|
|
44
|
+
},
|
|
34
45
|
"./system-colors": {
|
|
35
46
|
"default": "./src/system-colors.generated.js"
|
|
36
47
|
},
|
|
@@ -41,9 +52,13 @@
|
|
|
41
52
|
"src",
|
|
42
53
|
"build.js"
|
|
43
54
|
],
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"sass": "^1.98.0"
|
|
57
|
+
},
|
|
44
58
|
"scripts": {
|
|
45
59
|
"build": "node build.js",
|
|
46
60
|
"dev": "node build.js --watch",
|
|
61
|
+
"test": "node build.js && node test/themes-check.mjs",
|
|
47
62
|
"generate:system-colors": "node scripts/generate-system-colors.js"
|
|
48
63
|
}
|
|
49
64
|
}
|
package/src/_themes.scss
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// _themes.scss — opt-in prebuilt (and custom) themes for the themed USX build.
|
|
2
|
+
//
|
|
3
|
+
// Only the themes you list are emitted, so unused presets never reach your
|
|
4
|
+
// bundle. Themes apply via a `data-theme` attribute (`<html data-theme="forest">`);
|
|
5
|
+
// `$default` also applies on `:root` with no attribute, and `$prefersdark`
|
|
6
|
+
// applies on `:root` under `prefers-color-scheme: dark`. Pair with
|
|
7
|
+
// `@solexllc/usx-theme/theme.css` (the :root defaults) as usual.
|
|
8
|
+
//
|
|
9
|
+
// @use 'pkg:@solexllc/usx-theme/themes' with (
|
|
10
|
+
// $themes: (forest, carbon, ocean),
|
|
11
|
+
// $default: forest,
|
|
12
|
+
// $prefersdark: carbon,
|
|
13
|
+
// );
|
|
14
|
+
//
|
|
15
|
+
// `$themes` may also be a map to tweak a prebuilt theme or add your own —
|
|
16
|
+
// the Theme Playground's "Sass" export produces a ready-to-paste entry:
|
|
17
|
+
//
|
|
18
|
+
// $themes: (
|
|
19
|
+
// forest: (),
|
|
20
|
+
// acme: (
|
|
21
|
+
// color-scheme: light,
|
|
22
|
+
// --usx-color-primary: #b00020,
|
|
23
|
+
// --usx-color-primary-hover: #8a0018,
|
|
24
|
+
// ),
|
|
25
|
+
// )
|
|
26
|
+
//
|
|
27
|
+
// Every emitted theme sets the union of tokens across all emitted themes
|
|
28
|
+
// (falling back to the :root default), so switching `data-theme` never leaks
|
|
29
|
+
// a value from the previous theme — even for nested per-section theming.
|
|
30
|
+
@use 'sass:list';
|
|
31
|
+
@use 'sass:map';
|
|
32
|
+
@use 'sass:meta';
|
|
33
|
+
@use '../dist/themes-registry' as registry;
|
|
34
|
+
|
|
35
|
+
$themes: () !default;
|
|
36
|
+
$default: null !default;
|
|
37
|
+
$prefersdark: null !default;
|
|
38
|
+
|
|
39
|
+
// Prebuilt theme names, for consumers that want to list/iterate them.
|
|
40
|
+
$available: map.keys(registry.$themes);
|
|
41
|
+
|
|
42
|
+
// Normalizes `$themes` (list of names, or name → overrides map) into
|
|
43
|
+
// name → full token map, merging user overrides over the prebuilt entry.
|
|
44
|
+
@function -resolve-themes($config) {
|
|
45
|
+
@if meta.type-of($config) != 'map' {
|
|
46
|
+
$as-map: ();
|
|
47
|
+
@each $name in $config {
|
|
48
|
+
$as-map: map.set($as-map, $name, ());
|
|
49
|
+
}
|
|
50
|
+
$config: $as-map;
|
|
51
|
+
}
|
|
52
|
+
@each $flagged in ($default, $prefersdark) {
|
|
53
|
+
@if $flagged and not map.has-key($config, $flagged) {
|
|
54
|
+
$config: map.set($config, $flagged, ());
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$out: ();
|
|
59
|
+
@each $name, $overrides in $config {
|
|
60
|
+
$prebuilt: map.get(registry.$themes, $name);
|
|
61
|
+
@if not $prebuilt and list.length(map.keys(map.remove($overrides, color-scheme))) == 0 {
|
|
62
|
+
@error "usx-theme: unknown theme '#{$name}'. Prebuilt themes: #{$available}. To add a custom theme, give it a token map.";
|
|
63
|
+
}
|
|
64
|
+
$out: map.set($out, $name, map.merge($prebuilt or (), $overrides));
|
|
65
|
+
}
|
|
66
|
+
@return $out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@function -token-union($themes) {
|
|
70
|
+
$keys: ();
|
|
71
|
+
@each $name, $tokens in $themes {
|
|
72
|
+
@each $key in map.keys(map.remove($tokens, color-scheme)) {
|
|
73
|
+
$keys: map.set($keys, $key, true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
@return map.keys($keys);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@mixin -declarations($tokens, $keys) {
|
|
80
|
+
color-scheme: map.get($tokens, color-scheme) or light;
|
|
81
|
+
@each $key in $keys {
|
|
82
|
+
$value: map.get($tokens, $key);
|
|
83
|
+
@if $value == null {
|
|
84
|
+
$value: map.get(registry.$defaults, $key);
|
|
85
|
+
}
|
|
86
|
+
@if $value == null {
|
|
87
|
+
@error "usx-theme: '#{$key}' is not a themeable token (see theme-manifest.js).";
|
|
88
|
+
}
|
|
89
|
+
#{$key}: #{meta.inspect($value)};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Emits one theme under an arbitrary selector — for ad-hoc use outside the
|
|
94
|
+
// `$themes` config (no union with other themes is applied).
|
|
95
|
+
@mixin theme($tokens, $selector: ':root') {
|
|
96
|
+
#{$selector} {
|
|
97
|
+
@include -declarations($tokens, map.keys(map.remove($tokens, color-scheme)));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
$-resolved: -resolve-themes($themes);
|
|
102
|
+
$-keys: -token-union($-resolved);
|
|
103
|
+
|
|
104
|
+
@if $default {
|
|
105
|
+
:root {
|
|
106
|
+
@include -declarations(map.get($-resolved, $default), $-keys);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
@if $prefersdark {
|
|
110
|
+
@media (prefers-color-scheme: dark) {
|
|
111
|
+
:root {
|
|
112
|
+
@include -declarations(map.get($-resolved, $prefersdark), $-keys);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
@each $name, $tokens in $-resolved {
|
|
117
|
+
[data-theme='#{$name}'] {
|
|
118
|
+
@include -declarations($tokens, $-keys);
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/_variables.scss
CHANGED
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
// ── Layout (compile-time only) ───────────────────────────────────────────────
|
|
47
47
|
$max-layout-width: 60rem !default;
|
|
48
48
|
$max-sidebar-width: 16rem !default;
|
|
49
|
-
$dynamic-content-width: calc(50% + 22rem) !default;
|
|
50
49
|
|
|
51
50
|
// ── Breakpoints (compile-time only) ─────────────────────────────────────────
|
|
52
51
|
$breakpoint-mobile: 640px !default;
|
package/src/derive.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// derive.js — pure JS color derivation + theme serialization.
|
|
2
|
+
//
|
|
3
|
+
// Shared by build.js (bakes the prebuilt themes in presets.js into
|
|
4
|
+
// dist/themes*) and the Storybook Theme Playground (live controls + export).
|
|
5
|
+
//
|
|
6
|
+
// Derivation model: every derived shade in the manifest records its base token
|
|
7
|
+
// (`derivedFrom`). When the user picks a new base color, each shade is
|
|
8
|
+
// re-derived by applying the HSL delta between the DEFAULT base and the
|
|
9
|
+
// DEFAULT shade to the NEW base value. With the default base, this reproduces
|
|
10
|
+
// the default shade exactly (round-trip rounding aside) — see the derivation
|
|
11
|
+
// parity test.
|
|
12
|
+
|
|
13
|
+
import { themeManifest } from './theme-manifest.js';
|
|
14
|
+
|
|
15
|
+
export { themeManifest };
|
|
16
|
+
|
|
17
|
+
// ── Color conversion ─────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export function hexToRgb(hex) {
|
|
20
|
+
let h = hex.replace('#', '');
|
|
21
|
+
if (h.length === 3) h = h.split('').map((ch) => ch + ch).join('');
|
|
22
|
+
const n = parseInt(h, 16);
|
|
23
|
+
return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function rgbToHex({ r, g, b }) {
|
|
27
|
+
const to2 = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, '0');
|
|
28
|
+
return `#${to2(r)}${to2(g)}${to2(b)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function rgbToHsl({ r, g, b }) {
|
|
32
|
+
const rn = r / 255;
|
|
33
|
+
const gn = g / 255;
|
|
34
|
+
const bn = b / 255;
|
|
35
|
+
const max = Math.max(rn, gn, bn);
|
|
36
|
+
const min = Math.min(rn, gn, bn);
|
|
37
|
+
const l = (max + min) / 2;
|
|
38
|
+
let h = 0;
|
|
39
|
+
let s = 0;
|
|
40
|
+
if (max !== min) {
|
|
41
|
+
const d = max - min;
|
|
42
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
43
|
+
if (max === rn) h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
|
|
44
|
+
else if (max === gn) h = ((bn - rn) / d + 2) / 6;
|
|
45
|
+
else h = ((rn - gn) / d + 4) / 6;
|
|
46
|
+
}
|
|
47
|
+
return { h: h * 360, s: s * 100, l: l * 100 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function hslToRgb({ h, s, l }) {
|
|
51
|
+
const hn = (((h % 360) + 360) % 360) / 360;
|
|
52
|
+
const sn = Math.max(0, Math.min(100, s)) / 100;
|
|
53
|
+
const ln = Math.max(0, Math.min(100, l)) / 100;
|
|
54
|
+
if (sn === 0) {
|
|
55
|
+
const v = ln * 255;
|
|
56
|
+
return { r: v, g: v, b: v };
|
|
57
|
+
}
|
|
58
|
+
const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
|
|
59
|
+
const p = 2 * ln - q;
|
|
60
|
+
const channel = (t) => {
|
|
61
|
+
let tn = ((t % 1) + 1) % 1;
|
|
62
|
+
if (tn < 1 / 6) return p + (q - p) * 6 * tn;
|
|
63
|
+
if (tn < 1 / 2) return q;
|
|
64
|
+
if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;
|
|
65
|
+
return p;
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
r: channel(hn + 1 / 3) * 255,
|
|
69
|
+
g: channel(hn) * 255,
|
|
70
|
+
b: channel(hn - 1 / 3) * 255
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const hexToHsl = (hex) => rgbToHsl(hexToRgb(hex));
|
|
75
|
+
export const hslToHex = (hsl) => rgbToHex(hslToRgb(hsl));
|
|
76
|
+
|
|
77
|
+
// ── Derivation ───────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
// Re-derive a shade for a new base color by applying the HSL delta between
|
|
80
|
+
// the default base and the default shade.
|
|
81
|
+
export function deriveShade(newBaseHex, defaultBaseHex, defaultShadeHex) {
|
|
82
|
+
const base = hexToHsl(defaultBaseHex);
|
|
83
|
+
const shade = hexToHsl(defaultShadeHex);
|
|
84
|
+
const next = hexToHsl(newBaseHex);
|
|
85
|
+
if (base.s < 5) {
|
|
86
|
+
// Achromatic default base (e.g. white surfaces, grays): its hue/sat delta
|
|
87
|
+
// to the shade is ill-defined, so shift lightness only. A chromatic new
|
|
88
|
+
// base keeps its own hue/sat; an achromatic one inherits the shade's.
|
|
89
|
+
const keepShadeTint = next.s < 5;
|
|
90
|
+
return hslToHex({
|
|
91
|
+
h: keepShadeTint ? shade.h : next.h,
|
|
92
|
+
s: keepShadeTint ? shade.s : next.s,
|
|
93
|
+
l: next.l + (shade.l - base.l)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return hslToHex({
|
|
97
|
+
h: next.h + (shade.h - base.h),
|
|
98
|
+
s: next.s + (shade.s - base.s),
|
|
99
|
+
l: next.l + (shade.l - base.l)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const byName = new Map(themeManifest.map((t) => [t.name, t]));
|
|
104
|
+
|
|
105
|
+
// build.js --watch re-imports the manifest with a cache-busting query, so it
|
|
106
|
+
// passes its own fresh copy in rather than relying on this module's import.
|
|
107
|
+
const indexCache = new WeakMap();
|
|
108
|
+
function indexOf(manifest) {
|
|
109
|
+
if (manifest === themeManifest) return byName;
|
|
110
|
+
let idx = indexCache.get(manifest);
|
|
111
|
+
if (!idx) {
|
|
112
|
+
idx = new Map(manifest.map((t) => [t.name, t]));
|
|
113
|
+
indexCache.set(manifest, idx);
|
|
114
|
+
}
|
|
115
|
+
return idx;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getToken(name) {
|
|
119
|
+
return byName.get(name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// All manifest entries whose `derivedFrom` is `baseName`, restricted to the
|
|
123
|
+
// same `group` as the base itself. `derivedFrom` is also used by "component"
|
|
124
|
+
// tokens purely to auto-recompute when their underlying color changes (e.g.
|
|
125
|
+
// usx-accordion-content-bg → color-light); those aren't true
|
|
126
|
+
// palette "shades" and must not be nested under the base's Colors-panel
|
|
127
|
+
// control (they already get their own row under Component colors).
|
|
128
|
+
export function shadesOf(baseName) {
|
|
129
|
+
const base = byName.get(baseName);
|
|
130
|
+
return themeManifest.filter((t) => t.derivedFrom === baseName && (!base || t.group === base.group));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Base (underived) color tokens — the primary playground controls.
|
|
134
|
+
export function baseColorTokens() {
|
|
135
|
+
return themeManifest.filter(
|
|
136
|
+
(t) => t.type === 'color' && !t.derivedFrom && t.group === 'color'
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Compute the effective value of every manifest token given user overrides.
|
|
141
|
+
//
|
|
142
|
+
// overrides: { [tokenName]: value } — explicit user-set values.
|
|
143
|
+
// autoDerive: { [baseName]: boolean } — when true (default), shades of a
|
|
144
|
+
// changed base are re-derived unless individually overridden.
|
|
145
|
+
//
|
|
146
|
+
// Returns { [tokenName]: effectiveValue }.
|
|
147
|
+
export function resolveTheme(overrides = {}, autoDerive = {}, manifest = themeManifest) {
|
|
148
|
+
const index = indexOf(manifest);
|
|
149
|
+
const out = {};
|
|
150
|
+
for (const t of manifest) {
|
|
151
|
+
if (overrides[t.name] !== undefined && overrides[t.name] !== '') {
|
|
152
|
+
out[t.name] = overrides[t.name];
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const base = t.derivedFrom && index.get(t.derivedFrom);
|
|
156
|
+
const baseOverride = base && overrides[base.name];
|
|
157
|
+
if (base && baseOverride && (autoDerive[base.name] ?? true)) {
|
|
158
|
+
out[t.name] = deriveShade(baseOverride, base.defaultValue, t.defaultValue);
|
|
159
|
+
} else {
|
|
160
|
+
out[t.name] = t.defaultValue;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Build a { '--usx-*': value } style object of CHANGED tokens only.
|
|
167
|
+
export function themeToCssVars(resolved) {
|
|
168
|
+
const vars = {};
|
|
169
|
+
for (const t of themeManifest) {
|
|
170
|
+
const value = resolved[t.name];
|
|
171
|
+
if (value !== undefined && value.toLowerCase?.() !== t.defaultValue.toLowerCase()) {
|
|
172
|
+
vars[t.cssVar] = value;
|
|
173
|
+
} else if (value !== undefined && value !== t.defaultValue) {
|
|
174
|
+
vars[t.cssVar] = value;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return vars;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Ordered [cssVar, value] pairs for a resolved theme (changed-only by default).
|
|
181
|
+
export function themeEntries(resolved, { changedOnly = true, manifest = themeManifest } = {}) {
|
|
182
|
+
const entries = [];
|
|
183
|
+
for (const t of manifest) {
|
|
184
|
+
const value = resolved[t.name] ?? t.defaultValue;
|
|
185
|
+
const changed = String(value).toLowerCase() !== t.defaultValue.toLowerCase();
|
|
186
|
+
if (!changedOnly || changed) entries.push([t.cssVar, value]);
|
|
187
|
+
}
|
|
188
|
+
return entries;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// `data-theme` attribute value for a display name: "GOV.UK" → "gov-uk".
|
|
192
|
+
export function themeSlug(name) {
|
|
193
|
+
return String(name).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// `color-scheme` hint for a resolved theme, judged by its page surface:
|
|
197
|
+
// tells the browser to render native UI (scrollbars, form controls) to match.
|
|
198
|
+
export function colorSchemeOf(resolved) {
|
|
199
|
+
const surface = resolved['surface-1'];
|
|
200
|
+
if (!/^#[0-9a-f]{3,8}$/i.test(surface || '')) return 'light';
|
|
201
|
+
return hexToHsl(surface).l < 50 ? 'dark' : 'light';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function themeSelector(name) {
|
|
205
|
+
return `[data-theme="${themeSlug(name)}"]`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Generate an exportable CSS block. `selector` defaults to `:root`; pass a
|
|
209
|
+
// theme name via `name` to target `[data-theme="<slug>"]` instead.
|
|
210
|
+
export function themeToCss(resolved, { changedOnly = true, name, selector, colorScheme, manifest = themeManifest } = {}) {
|
|
211
|
+
const lines = themeEntries(resolved, { changedOnly, manifest }).map(([cssVar, value]) => ` ${cssVar}: ${value};`);
|
|
212
|
+
if (!lines.length && !name) return '/* No changes from the default theme. */\n';
|
|
213
|
+
if (colorScheme) lines.unshift(` color-scheme: ${colorScheme};`);
|
|
214
|
+
return `${selector || (name ? themeSelector(name) : ':root')} {\n${lines.join('\n')}\n}\n`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// A CSS value written so Sass parses it back to the same text: comma lists
|
|
218
|
+
// (font stacks) must be parenthesized or Sass reads them as more map entries.
|
|
219
|
+
export function toSassValue(value) {
|
|
220
|
+
const v = String(value);
|
|
221
|
+
return v.includes(',') ? `(${v})` : v;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// A `<slug>: ( ... )` map entry for the `$themes` config of
|
|
225
|
+
// `pkg:@solexllc/usx-theme/themes` — the same shape the prebuilt registry
|
|
226
|
+
// uses, so a Playground export pastes in alongside the built-in themes.
|
|
227
|
+
export function themeToSass(resolved, { name = 'custom', changedOnly = true, colorScheme, manifest = themeManifest, indent = '' } = {}) {
|
|
228
|
+
const lines = [`${indent} color-scheme: ${colorScheme || colorSchemeOf(resolved)},`];
|
|
229
|
+
for (const [cssVar, value] of themeEntries(resolved, { changedOnly, manifest })) {
|
|
230
|
+
lines.push(`${indent} ${cssVar}: ${toSassValue(value)},`);
|
|
231
|
+
}
|
|
232
|
+
return `${indent}${themeSlug(name)}: (\n${lines.join('\n')}\n${indent}),\n`;
|
|
233
|
+
}
|