dsh-theme-studio 0.1.0 → 0.3.0
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/CHANGELOG.md +49 -0
- package/README.md +26 -13
- package/lib/apply.d.ts +60 -0
- package/lib/apply.js +172 -0
- package/lib/io.d.ts +48 -0
- package/lib/io.js +142 -0
- package/lib/theme-studio.web.js +641 -254
- package/lib/themes.d.ts +4 -10
- package/lib/themes.js +27 -115
- package/lib/tokens.d.ts +67 -0
- package/lib/tokens.js +151 -0
- package/lib/types.d.ts +8 -4
- package/lib/types.js +4 -1
- package/package.json +2 -2
package/lib/themes.d.ts
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Theme presets for dsh-theme-studio.
|
|
3
3
|
*
|
|
4
|
-
* Each preset
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each preset only sets tokens listed in `VERIFIED_TOKENS` — real dsh design
|
|
5
|
+
* tokens, read out of the shipped stylesheets. The accent bundle comes from
|
|
6
|
+
* `accentTokens()` so a preset and a hand-picked color produce the same shape.
|
|
7
7
|
*
|
|
8
8
|
* @module themes
|
|
9
9
|
*/
|
|
10
10
|
import type { ThemePreset } from './types.js';
|
|
11
11
|
export declare const PRESETS: readonly ThemePreset[];
|
|
12
|
-
/** Density → CSS custom property overrides */
|
|
13
|
-
export declare const DENSITY_TOKENS: Record<string, Record<string, string>>;
|
|
14
|
-
/** Radius → CSS custom property overrides */
|
|
15
|
-
export declare const RADIUS_TOKENS: Record<string, Record<string, string>>;
|
|
16
|
-
/** Font family → CSS custom property overrides */
|
|
17
|
-
export declare const FONT_TOKENS: Record<string, Record<string, string>>;
|
|
18
12
|
/** Look up a preset by id. */
|
|
19
13
|
export declare function getPreset(id: string): ThemePreset | undefined;
|
package/lib/themes.js
CHANGED
|
@@ -1,125 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Theme presets for dsh-theme-studio.
|
|
3
3
|
*
|
|
4
|
-
* Each preset
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Each preset only sets tokens listed in `VERIFIED_TOKENS` — real dsh design
|
|
5
|
+
* tokens, read out of the shipped stylesheets. The accent bundle comes from
|
|
6
|
+
* `accentTokens()` so a preset and a hand-picked color produce the same shape.
|
|
7
7
|
*
|
|
8
8
|
* @module themes
|
|
9
9
|
*/
|
|
10
|
+
import { accentTokens } from './tokens.js';
|
|
11
|
+
/** Every preset is built from one accent color per mode. */
|
|
12
|
+
function preset(id, name, description, lightAccent, darkAccent) {
|
|
13
|
+
const base = { id, name, description, tokens: accentTokens(lightAccent) };
|
|
14
|
+
// A dark entry equal to the light one would be a no-op that still shows the
|
|
15
|
+
// "adapts to dark" badge, so it is only kept when the color actually changes.
|
|
16
|
+
if (darkAccent !== undefined && darkAccent.toLowerCase() !== lightAccent.toLowerCase()) {
|
|
17
|
+
base.darkTokens = accentTokens(darkAccent);
|
|
18
|
+
}
|
|
19
|
+
return base;
|
|
20
|
+
}
|
|
10
21
|
export const PRESETS = [
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
{
|
|
24
|
-
id: 'forest',
|
|
25
|
-
name: 'Forest',
|
|
26
|
-
description: 'Green accent with earthy neutrals',
|
|
27
|
-
tokens: {
|
|
28
|
-
'--accent': '#16a34a',
|
|
29
|
-
'--accent-hover': '#15803d',
|
|
30
|
-
'--dsw-alias-state-business-primary': '#16a34a',
|
|
31
|
-
'--dsw-alias-state-business-secondary': '#dcfce7',
|
|
32
|
-
'--dsw-alias-interactive-bg-hover': 'rgba(22,163,74,0.08)',
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: 'sunset',
|
|
37
|
-
name: 'Sunset',
|
|
38
|
-
description: 'Warm orange accent with amber highlights',
|
|
39
|
-
tokens: {
|
|
40
|
-
'--accent': '#ea580c',
|
|
41
|
-
'--accent-hover': '#c2410c',
|
|
42
|
-
'--dsw-alias-state-business-primary': '#ea580c',
|
|
43
|
-
'--dsw-alias-state-business-secondary': '#fed7aa',
|
|
44
|
-
'--dsw-alias-interactive-bg-hover': 'rgba(234,88,12,0.08)',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
id: 'monochrome',
|
|
49
|
-
name: 'Monochrome',
|
|
50
|
-
description: 'Grayscale only — no color accent',
|
|
51
|
-
tokens: {
|
|
52
|
-
'--accent': '#404040',
|
|
53
|
-
'--accent-hover': '#262626',
|
|
54
|
-
'--dsw-alias-state-business-primary': '#404040',
|
|
55
|
-
'--dsw-alias-state-business-secondary': '#e5e5e5',
|
|
56
|
-
'--dsw-alias-interactive-bg-hover': 'rgba(64,64,64,0.08)',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
id: 'nord',
|
|
61
|
-
name: 'Nord',
|
|
62
|
-
description: 'Frost blue inspired by the Nord palette',
|
|
63
|
-
tokens: {
|
|
64
|
-
'--accent': '#5e81ac',
|
|
65
|
-
'--accent-hover': '#4c6f96',
|
|
66
|
-
'--dsw-alias-state-business-primary': '#5e81ac',
|
|
67
|
-
'--dsw-alias-state-business-secondary': '#e5ebf1',
|
|
68
|
-
'--dsw-alias-interactive-bg-hover': 'rgba(94,129,172,0.08)',
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
id: 'dracula',
|
|
73
|
-
name: 'Dracula',
|
|
74
|
-
description: 'Purple accent with dark mode flair',
|
|
75
|
-
tokens: {
|
|
76
|
-
'--accent': '#bd93f9',
|
|
77
|
-
'--accent-hover': '#a678e8',
|
|
78
|
-
'--dsw-alias-state-business-primary': '#bd93f9',
|
|
79
|
-
'--dsw-alias-state-business-secondary': '#3a3a5c',
|
|
80
|
-
'--dsw-alias-interactive-bg-hover': 'rgba(189,147,249,0.12)',
|
|
81
|
-
},
|
|
82
|
-
},
|
|
22
|
+
preset('ocean', 'Ocean', 'Deep blue, close to dsh default', '#4d6bfe'),
|
|
23
|
+
preset('forest', 'Forest', 'Calm green', '#16a34a', '#22c55e'),
|
|
24
|
+
preset('sunset', 'Sunset', 'Warm orange', '#ea580c', '#fb923c'),
|
|
25
|
+
preset('monochrome', 'Monochrome', 'No color accent at all', '#5c5c5c', '#9e9e9e'),
|
|
26
|
+
preset('nord', 'Nord', 'Frost blue', '#5e81ac', '#88c0d0'),
|
|
27
|
+
preset('dracula', 'Dracula', 'Purple, brighter on dark', '#7c5cd6', '#bd93f9'),
|
|
28
|
+
preset('gruvbox', 'Gruvbox', 'Retro warm earth tones', '#b57614', '#fe8019'),
|
|
29
|
+
preset('solarized', 'Solarized', 'Schoonover precision palette', '#268bd2', '#5fa8d3'),
|
|
30
|
+
preset('tokyo-night', 'Tokyo Night', 'City lights blue', '#3d59a1', '#7aa2f7'),
|
|
31
|
+
preset('catppuccin', 'Catppuccin', 'Soft pastel', '#7287fd', '#cba6f7'),
|
|
32
|
+
preset('rose', 'Rosé', 'Muted pink', '#b3276b', '#f472b6'),
|
|
33
|
+
preset('ember', 'Ember', 'Deep red', '#b91c1c', '#ef4444'),
|
|
83
34
|
];
|
|
84
|
-
/** Density → CSS custom property overrides */
|
|
85
|
-
export const DENSITY_TOKENS = {
|
|
86
|
-
compact: {
|
|
87
|
-
'--dsh-content-font-size': '13px',
|
|
88
|
-
'--dsh-spacing-unit': '4px',
|
|
89
|
-
},
|
|
90
|
-
comfortable: {
|
|
91
|
-
'--dsh-content-font-size': '14px',
|
|
92
|
-
'--dsh-spacing-unit': '6px',
|
|
93
|
-
},
|
|
94
|
-
spacious: {
|
|
95
|
-
'--dsh-content-font-size': '15px',
|
|
96
|
-
'--dsh-spacing-unit': '8px',
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
/** Radius → CSS custom property overrides */
|
|
100
|
-
export const RADIUS_TOKENS = {
|
|
101
|
-
sharp: {
|
|
102
|
-
'--dsh-radius-small': '2px',
|
|
103
|
-
'--dsh-radius-medium': '4px',
|
|
104
|
-
'--dsh-radius-large': '6px',
|
|
105
|
-
},
|
|
106
|
-
rounded: {
|
|
107
|
-
'--dsh-radius-small': '6px',
|
|
108
|
-
'--dsh-radius-medium': '10px',
|
|
109
|
-
'--dsh-radius-large': '14px',
|
|
110
|
-
},
|
|
111
|
-
soft: {
|
|
112
|
-
'--dsh-radius-small': '10px',
|
|
113
|
-
'--dsh-radius-medium': '16px',
|
|
114
|
-
'--dsh-radius-large': '22px',
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
/** Font family → CSS custom property overrides */
|
|
118
|
-
export const FONT_TOKENS = {
|
|
119
|
-
system: { '--dsh-font-family': "system-ui, -apple-system, sans-serif" },
|
|
120
|
-
mono: { '--dsh-font-family': "'JetBrains Mono', 'Fira Code', monospace" },
|
|
121
|
-
serif: { '--dsh-font-family': "'Georgia', 'Times New Roman', serif" },
|
|
122
|
-
};
|
|
123
35
|
/** Look up a preset by id. */
|
|
124
36
|
export function getPreset(id) {
|
|
125
37
|
return PRESETS.find((p) => p.id === id);
|
package/lib/tokens.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verified dsh design-token surface this plugin is allowed to write.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS
|
|
5
|
+
* --------------------
|
|
6
|
+
* An earlier version of this plugin invented its own variable names
|
|
7
|
+
* (`--accent`, `--border`, `--dsh-radius-medium`, `--dsh-transition-fast`).
|
|
8
|
+
* None of them exist in dsh, so nothing the panel did had any effect on the
|
|
9
|
+
* real UI — while the panel's own preview, which read the same invented
|
|
10
|
+
* fallbacks, changed color and made it look like it worked.
|
|
11
|
+
*
|
|
12
|
+
* Every name below was read out of the shipped dsh stylesheets
|
|
13
|
+
* (`@deepseek-ai/dsh-client-ui-theme/lib/client.js`), which define the palette
|
|
14
|
+
* on `body` and `body[data-ds-dark-theme]`. Anything not on this list must not
|
|
15
|
+
* be written by this plugin, and `tests/tokens.test.mjs` enforces that.
|
|
16
|
+
*
|
|
17
|
+
* Two consequences that are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* 1. The palette lives on `body`, not `:root`/`html`. A custom property set on
|
|
20
|
+
* `documentElement` is inherited INTO body, but body's own declaration wins
|
|
21
|
+
* — so this plugin must write to `document.body`, which `applyTheme` does.
|
|
22
|
+
* 2. dsh hardcodes `border-radius` per component (including many `50%` circles
|
|
23
|
+
* and `corner-shape: round`), and ships no motion tokens at all. Radius
|
|
24
|
+
* therefore has no faithful token target and is deliberately not offered;
|
|
25
|
+
* animation-reduction is done with a stylesheet instead (see `motionCss`).
|
|
26
|
+
*
|
|
27
|
+
* @module tokens
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Token names confirmed present in dsh, kept here as data so the guard test can
|
|
31
|
+
* check every map this plugin builds against it.
|
|
32
|
+
*/
|
|
33
|
+
export declare const VERIFIED_TOKENS: readonly string[];
|
|
34
|
+
/** True when `name` is a token this plugin has verified against dsh. */
|
|
35
|
+
export declare function isVerifiedToken(name: string): boolean;
|
|
36
|
+
/** Turn `#rrggbb` into an `rgba()` tint at the given alpha. */
|
|
37
|
+
export declare function tint(hex: string, alpha: number): string;
|
|
38
|
+
/**
|
|
39
|
+
* The accent bundle: every token that should follow one accent color.
|
|
40
|
+
*
|
|
41
|
+
* Applying one color to all of these keeps the primary button, links, active
|
|
42
|
+
* nav item, focus accents and hover tint in step. Deriving the hover tint from
|
|
43
|
+
* the accent rather than using a fixed value is what makes a custom color look
|
|
44
|
+
* deliberate instead of half-applied.
|
|
45
|
+
*/
|
|
46
|
+
export declare function accentTokens(hex: string): Record<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* Density → content font size.
|
|
49
|
+
*
|
|
50
|
+
* `--dsh-content-font-size` is the verified knob: dsh's own theme bootstrap
|
|
51
|
+
* writes it on `body`, and 22 shipped stylesheets read it, so changing it moves
|
|
52
|
+
* the whole content column.
|
|
53
|
+
*/
|
|
54
|
+
export declare const DENSITY_TOKENS: Record<string, Record<string, string>>;
|
|
55
|
+
/** Font family → the two verified family tokens. */
|
|
56
|
+
export declare const FONT_TOKENS: Record<string, Record<string, string>>;
|
|
57
|
+
/**
|
|
58
|
+
* CSS that suppresses motion, injected as a stylesheet rather than set as
|
|
59
|
+
* tokens (dsh ships no motion tokens).
|
|
60
|
+
*
|
|
61
|
+
* Durations are `0.001ms` rather than `0s` on purpose: a zero duration can stop
|
|
62
|
+
* `transitionend` / `animationend` from firing, and components that wait on
|
|
63
|
+
* those events would hang. A near-zero duration keeps every event firing while
|
|
64
|
+
* being visually instant — the same approach the `prefers-reduced-motion`
|
|
65
|
+
* convention uses.
|
|
66
|
+
*/
|
|
67
|
+
export declare const MOTION_OFF_CSS: string;
|
package/lib/tokens.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verified dsh design-token surface this plugin is allowed to write.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS FILE EXISTS
|
|
5
|
+
* --------------------
|
|
6
|
+
* An earlier version of this plugin invented its own variable names
|
|
7
|
+
* (`--accent`, `--border`, `--dsh-radius-medium`, `--dsh-transition-fast`).
|
|
8
|
+
* None of them exist in dsh, so nothing the panel did had any effect on the
|
|
9
|
+
* real UI — while the panel's own preview, which read the same invented
|
|
10
|
+
* fallbacks, changed color and made it look like it worked.
|
|
11
|
+
*
|
|
12
|
+
* Every name below was read out of the shipped dsh stylesheets
|
|
13
|
+
* (`@deepseek-ai/dsh-client-ui-theme/lib/client.js`), which define the palette
|
|
14
|
+
* on `body` and `body[data-ds-dark-theme]`. Anything not on this list must not
|
|
15
|
+
* be written by this plugin, and `tests/tokens.test.mjs` enforces that.
|
|
16
|
+
*
|
|
17
|
+
* Two consequences that are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* 1. The palette lives on `body`, not `:root`/`html`. A custom property set on
|
|
20
|
+
* `documentElement` is inherited INTO body, but body's own declaration wins
|
|
21
|
+
* — so this plugin must write to `document.body`, which `applyTheme` does.
|
|
22
|
+
* 2. dsh hardcodes `border-radius` per component (including many `50%` circles
|
|
23
|
+
* and `corner-shape: round`), and ships no motion tokens at all. Radius
|
|
24
|
+
* therefore has no faithful token target and is deliberately not offered;
|
|
25
|
+
* animation-reduction is done with a stylesheet instead (see `motionCss`).
|
|
26
|
+
*
|
|
27
|
+
* @module tokens
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Token names confirmed present in dsh, kept here as data so the guard test can
|
|
31
|
+
* check every map this plugin builds against it.
|
|
32
|
+
*/
|
|
33
|
+
export const VERIFIED_TOKENS = [
|
|
34
|
+
// Accent / business
|
|
35
|
+
'--dsw-alias-state-business-primary',
|
|
36
|
+
'--dsw-alias-state-business-tertiary',
|
|
37
|
+
'--dsw-alias-brand-primary',
|
|
38
|
+
'--dsw-alias-brand-text',
|
|
39
|
+
'--dsw-alias-link',
|
|
40
|
+
'--dsw-alias-interactive-bg-hover',
|
|
41
|
+
'--dsw-alias-interactive-bg-hover-accent',
|
|
42
|
+
'--dsw-alias-button-primary-fill',
|
|
43
|
+
'--dsw-alias-button-primary-hover',
|
|
44
|
+
// Typography
|
|
45
|
+
'--dsw-font-family',
|
|
46
|
+
'--dsw-font-mono',
|
|
47
|
+
'--dsw-font-xs-13',
|
|
48
|
+
'--dsw-font-xs-strong-13',
|
|
49
|
+
'--dsw-font-xxs-12',
|
|
50
|
+
'--dsw-font-xxxs-11',
|
|
51
|
+
'--dsw-font-s-14',
|
|
52
|
+
'--dsw-font-l-20',
|
|
53
|
+
'--dsh-content-font-size',
|
|
54
|
+
'--dsh-content-font-delta',
|
|
55
|
+
];
|
|
56
|
+
/** Fast membership test used by the presets and the guard test. */
|
|
57
|
+
const VERIFIED = new Set(VERIFIED_TOKENS);
|
|
58
|
+
/** True when `name` is a token this plugin has verified against dsh. */
|
|
59
|
+
export function isVerifiedToken(name) {
|
|
60
|
+
return VERIFIED.has(name);
|
|
61
|
+
}
|
|
62
|
+
/** Turn `#rrggbb` into an `rgba()` tint at the given alpha. */
|
|
63
|
+
export function tint(hex, alpha) {
|
|
64
|
+
const value = hex.trim().replace(/^#/, '');
|
|
65
|
+
let r;
|
|
66
|
+
let g;
|
|
67
|
+
let b;
|
|
68
|
+
if (value.length === 3) {
|
|
69
|
+
r = parseInt(value[0] + value[0], 16);
|
|
70
|
+
g = parseInt(value[1] + value[1], 16);
|
|
71
|
+
b = parseInt(value[2] + value[2], 16);
|
|
72
|
+
}
|
|
73
|
+
else if (value.length === 6) {
|
|
74
|
+
r = parseInt(value.slice(0, 2), 16);
|
|
75
|
+
g = parseInt(value.slice(2, 4), 16);
|
|
76
|
+
b = parseInt(value.slice(4, 6), 16);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
return hex;
|
|
80
|
+
}
|
|
81
|
+
if ([r, g, b].some((n) => Number.isNaN(n)))
|
|
82
|
+
return hex;
|
|
83
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The accent bundle: every token that should follow one accent color.
|
|
87
|
+
*
|
|
88
|
+
* Applying one color to all of these keeps the primary button, links, active
|
|
89
|
+
* nav item, focus accents and hover tint in step. Deriving the hover tint from
|
|
90
|
+
* the accent rather than using a fixed value is what makes a custom color look
|
|
91
|
+
* deliberate instead of half-applied.
|
|
92
|
+
*/
|
|
93
|
+
export function accentTokens(hex) {
|
|
94
|
+
return {
|
|
95
|
+
'--dsw-alias-state-business-primary': hex,
|
|
96
|
+
'--dsw-alias-state-business-tertiary': tint(hex, 0.12),
|
|
97
|
+
'--dsw-alias-brand-primary': hex,
|
|
98
|
+
'--dsw-alias-brand-text': hex,
|
|
99
|
+
'--dsw-alias-link': hex,
|
|
100
|
+
'--dsw-alias-interactive-bg-hover-accent': tint(hex, 0.10),
|
|
101
|
+
'--dsw-alias-button-primary-fill': hex,
|
|
102
|
+
'--dsw-alias-button-primary-hover': hex,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Density → content font size.
|
|
107
|
+
*
|
|
108
|
+
* `--dsh-content-font-size` is the verified knob: dsh's own theme bootstrap
|
|
109
|
+
* writes it on `body`, and 22 shipped stylesheets read it, so changing it moves
|
|
110
|
+
* the whole content column.
|
|
111
|
+
*/
|
|
112
|
+
export const DENSITY_TOKENS = {
|
|
113
|
+
compact: { '--dsh-content-font-size': '13px' },
|
|
114
|
+
comfortable: { '--dsh-content-font-size': '14px' },
|
|
115
|
+
spacious: { '--dsh-content-font-size': '15px' },
|
|
116
|
+
};
|
|
117
|
+
/** Font family → the two verified family tokens. */
|
|
118
|
+
export const FONT_TOKENS = {
|
|
119
|
+
system: {
|
|
120
|
+
'--dsw-font-family': "system-ui, -apple-system, 'Segoe UI', sans-serif",
|
|
121
|
+
'--dsw-font-mono': "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
|
|
122
|
+
},
|
|
123
|
+
mono: {
|
|
124
|
+
'--dsw-font-family': "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
|
|
125
|
+
'--dsw-font-mono': "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
|
|
126
|
+
},
|
|
127
|
+
serif: {
|
|
128
|
+
'--dsw-font-family': "Georgia, 'Times New Roman', serif",
|
|
129
|
+
'--dsw-font-mono': "'Cascadia Code', Consolas, monospace",
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
/**
|
|
133
|
+
* CSS that suppresses motion, injected as a stylesheet rather than set as
|
|
134
|
+
* tokens (dsh ships no motion tokens).
|
|
135
|
+
*
|
|
136
|
+
* Durations are `0.001ms` rather than `0s` on purpose: a zero duration can stop
|
|
137
|
+
* `transitionend` / `animationend` from firing, and components that wait on
|
|
138
|
+
* those events would hang. A near-zero duration keeps every event firing while
|
|
139
|
+
* being visually instant — the same approach the `prefers-reduced-motion`
|
|
140
|
+
* convention uses.
|
|
141
|
+
*/
|
|
142
|
+
export const MOTION_OFF_CSS = `
|
|
143
|
+
*, *::before, *::after {
|
|
144
|
+
transition-duration: 0.001ms !important;
|
|
145
|
+
transition-delay: 0ms !important;
|
|
146
|
+
animation-duration: 0.001ms !important;
|
|
147
|
+
animation-delay: 0ms !important;
|
|
148
|
+
animation-iteration-count: 1 !important;
|
|
149
|
+
scroll-behavior: auto !important;
|
|
150
|
+
}
|
|
151
|
+
`.trim();
|
package/lib/types.d.ts
CHANGED
|
@@ -8,25 +8,29 @@ export interface ThemePreset {
|
|
|
8
8
|
id: string;
|
|
9
9
|
name: string;
|
|
10
10
|
description: string;
|
|
11
|
-
/**
|
|
11
|
+
/** Accent tokens for light mode. */
|
|
12
12
|
tokens: Record<string, string>;
|
|
13
|
+
/** Accent tokens for dark mode, applied when dsh is dark. */
|
|
14
|
+
darkTokens?: Record<string, string>;
|
|
13
15
|
}
|
|
14
16
|
/** User-selectable density preference. */
|
|
15
17
|
export type Density = 'compact' | 'comfortable' | 'spacious';
|
|
16
|
-
/** User-selectable border-radius preference. */
|
|
17
|
-
export type Radius = 'sharp' | 'rounded' | 'soft';
|
|
18
18
|
/** User-selectable font family preference. */
|
|
19
19
|
export type FontFamily = 'system' | 'mono' | 'serif';
|
|
20
20
|
/** Complete theme preferences persisted in localStorage. */
|
|
21
21
|
export interface ThemePreferences {
|
|
22
22
|
preset: string | null;
|
|
23
23
|
accentColor: string | null;
|
|
24
|
+
/** Separate accent color for dark mode; null = use accentColor */
|
|
25
|
+
darkAccentColor: string | null;
|
|
24
26
|
density: Density;
|
|
25
|
-
radius: Radius;
|
|
26
27
|
fontFamily: FontFamily;
|
|
28
|
+
animations: boolean;
|
|
27
29
|
customCss: string;
|
|
28
30
|
}
|
|
29
31
|
/** Default preferences — nothing overridden, dsh's built-in theme wins. */
|
|
30
32
|
export declare const DEFAULT_PREFERENCES: ThemePreferences;
|
|
31
33
|
/** localStorage key for persisting preferences. */
|
|
32
34
|
export declare const STORAGE_KEY = "dsh-theme-studio";
|
|
35
|
+
/** Element carrying the plugin's injected stylesheet id. */
|
|
36
|
+
export declare const STYLE_ELEMENT_ID = "dsh-theme-studio-overrides";
|
package/lib/types.js
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
export const DEFAULT_PREFERENCES = {
|
|
8
8
|
preset: null,
|
|
9
9
|
accentColor: null,
|
|
10
|
+
darkAccentColor: null,
|
|
10
11
|
density: 'comfortable',
|
|
11
|
-
radius: 'rounded',
|
|
12
12
|
fontFamily: 'system',
|
|
13
|
+
animations: true,
|
|
13
14
|
customCss: '',
|
|
14
15
|
};
|
|
15
16
|
/** localStorage key for persisting preferences. */
|
|
16
17
|
export const STORAGE_KEY = 'dsh-theme-studio';
|
|
18
|
+
/** Element carrying the plugin's injected stylesheet id. */
|
|
19
|
+
export const STYLE_ELEMENT_ID = 'dsh-theme-studio-overrides';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-theme-studio",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Customize the dsh UI theme:
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Customize the dsh UI theme: 12 accent presets, dark-mode aware accents with a contrast guard, density, font family, animation toggle, and raw design-token overrides.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|