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/theme-studio.web.js
CHANGED
|
@@ -8,33 +8,44 @@ var zh = {
|
|
|
8
8
|
presetHint: "\u9009\u62E9\u4E00\u4E2A\u9884\u8BBE\u914D\u8272\u65B9\u6848",
|
|
9
9
|
noPreset: "\u65E0\uFF08\u4F7F\u7528\u9ED8\u8BA4\uFF09",
|
|
10
10
|
accentSection: "\u5F3A\u8C03\u8272",
|
|
11
|
-
accentHint: "\
|
|
12
|
-
accentPlaceholder: "#
|
|
11
|
+
accentHint: "\u540C\u65F6\u4F5C\u7528\u4E8E\u4E3B\u6309\u94AE\u3001\u94FE\u63A5\u3001\u5BFC\u822A\u9009\u4E2D\u6001\u4E0E\u60AC\u505C\u9AD8\u4EAE",
|
|
12
|
+
accentPlaceholder: "#4d6bfe",
|
|
13
|
+
accentApplying: "\u5F53\u524D\u751F\u6548\uFF1A",
|
|
14
|
+
darkAccentSection: "\u6697\u8272\u6A21\u5F0F\u5F3A\u8C03\u8272",
|
|
15
|
+
darkAccentHint: "\u6697\u8272\u6A21\u5F0F\u4E0B\u4F7F\u7528\u7684\u5F3A\u8C03\u8272\uFF0C\u7559\u7A7A\u5219\u6CBF\u7528\u4E0A\u9762\u7684\u989C\u8272",
|
|
16
|
+
preserveContrast: "\u5DF2\u81EA\u52A8\u63D0\u4EAE\u4EE5\u4FDD\u8BC1\u6697\u8272\u4E0B\u53EF\u8BFB",
|
|
17
|
+
tokenNote: "\u672C\u9762\u677F\u53EA\u5199 dsh \u771F\u5B9E\u5B58\u5728\u7684\u8BBE\u8BA1\u4EE4\u724C\uFF08{count} \u4E2A\uFF0C\u5747\u4ECE dsh \u6837\u5F0F\u8868\u6838\u5B9E\uFF09\u3002dsh \u6309\u7EC4\u4EF6\u786C\u7F16\u7801\u4E86\u5706\u89D2\u4E14\u672A\u63D0\u4F9B\u52A8\u753B\u4EE4\u724C\uFF0C\u56E0\u6B64\u4E0D\u63D0\u4F9B\u5706\u89D2\u8C03\u8282\uFF0C\u52A8\u753B\u5F00\u5173\u6539\u7528\u6837\u5F0F\u8868\u5B9E\u73B0\u3002",
|
|
13
18
|
densitySection: "\u5BC6\u5EA6",
|
|
14
19
|
densityCompact: "\u7D27\u51D1",
|
|
15
20
|
densityComfortable: "\u8212\u9002",
|
|
16
21
|
densitySpacious: "\u5BBD\u677E",
|
|
17
22
|
radiusSection: "\u5706\u89D2",
|
|
18
|
-
radiusSharp: "\u9510\u5229",
|
|
19
|
-
radiusRounded: "\u5706\u89D2",
|
|
20
|
-
radiusSoft: "\u67D4\u548C",
|
|
21
23
|
fontSection: "\u5B57\u4F53",
|
|
22
24
|
fontSystem: "\u7CFB\u7EDF",
|
|
23
25
|
fontMono: "\u7B49\u5BBD",
|
|
24
26
|
fontSerif: "\u886C\u7EBF",
|
|
25
27
|
customCssSection: "\u81EA\u5B9A\u4E49 CSS",
|
|
26
|
-
customCssHint: "\
|
|
27
|
-
customCssPlaceholder: "--
|
|
28
|
+
customCssHint: "\u8986\u76D6 dsh \u4EE4\u724C\uFF0C\u6BCF\u884C\u4E00\u4E2A `--dsw-* \u6216 --dsh-*: value;`",
|
|
29
|
+
customCssPlaceholder: "--dsw-alias-brand-primary: #ff0000;\n--dsw-alias-label-primary: #111111;",
|
|
30
|
+
behaviorSection: "\u884C\u4E3A",
|
|
31
|
+
animations: "\u754C\u9762\u52A8\u753B",
|
|
32
|
+
animationsHint: "\u5173\u95ED\u53EF\u51CF\u5C11\u52A8\u753B\u5E72\u6270\uFF08\u7528\u6837\u5F0F\u8868\u628A\u8FC7\u6E21\u65F6\u957F\u538B\u5230\u8FD1\u96F6\uFF0C\u4E8B\u4EF6\u4ECD\u4F1A\u89E6\u53D1\uFF09",
|
|
33
|
+
ioSection: "\u5BFC\u5165 / \u5BFC\u51FA",
|
|
34
|
+
export: "\u5BFC\u51FA\u4E3B\u9898",
|
|
35
|
+
import: "\u5BFC\u5165\u4E3B\u9898",
|
|
36
|
+
importHint: "\u7C98\u8D34\u5BFC\u51FA\u7684 JSON\uFF0C\u6216\u4E0A\u4F20 .json \u6587\u4EF6",
|
|
37
|
+
importPlaceholder: '{"preset":"nord","accentColor":"#5e81ac",...}',
|
|
38
|
+
importFile: "\u9009\u62E9\u6587\u4EF6",
|
|
39
|
+
importSuccess: "\u4E3B\u9898\u5DF2\u5BFC\u5165",
|
|
40
|
+
importFailed: "\u5BFC\u5165\u5931\u8D25\uFF1A\u4E0D\u662F\u6709\u6548\u7684\u4E3B\u9898 JSON",
|
|
41
|
+
exportSuccess: "\u4E3B\u9898\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
|
|
42
|
+
exportFailed: "\u590D\u5236\u5931\u8D25\uFF0C\u5DF2\u586B\u5165\u4E0B\u65B9\u6587\u672C\u6846",
|
|
28
43
|
preview: "\u9884\u89C8",
|
|
29
|
-
apply: "\u5E94\u7528",
|
|
30
44
|
reset: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4",
|
|
31
45
|
resetConfirm: "\u786E\u5B9A\u8981\u91CD\u7F6E\u6240\u6709\u4E3B\u9898\u8BBE\u7F6E\u5417\uFF1F",
|
|
32
|
-
applied: "\u4E3B\u9898\u5DF2\u5E94\u7528",
|
|
33
46
|
resetted: "\u5DF2\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u4E3B\u9898",
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
badge: "\u6807\u7B7E",
|
|
37
|
-
text: "\u8FD9\u662F\u4E00\u6BB5\u793A\u4F8B\u6587\u672C\uFF0C\u7528\u4E8E\u9884\u89C8\u4E3B\u9898\u6548\u679C\u3002"
|
|
47
|
+
link: "\u94FE\u63A5",
|
|
48
|
+
text: "\u8FD9\u662F\u4E00\u6BB5\u793A\u4F8B\u6587\u672C\uFF0C\u7528\u4E8E\u9884\u89C8\u5B57\u53F7\u4E0E\u5B57\u4F53\u3002"
|
|
38
49
|
};
|
|
39
50
|
var en = {
|
|
40
51
|
nav: "Theme Studio",
|
|
@@ -44,164 +55,367 @@ var en = {
|
|
|
44
55
|
presetHint: "Choose a pre-built color scheme",
|
|
45
56
|
noPreset: "None (use default)",
|
|
46
57
|
accentSection: "Accent Color",
|
|
47
|
-
accentHint: "Custom accent color,
|
|
48
|
-
accentPlaceholder: "#
|
|
58
|
+
accentHint: "Custom accent color \u2014 drives buttons, links, the active nav item and hover tints",
|
|
59
|
+
accentPlaceholder: "#4d6bfe",
|
|
60
|
+
accentApplying: "Currently applied:",
|
|
61
|
+
darkAccentSection: "Dark Mode Accent",
|
|
62
|
+
darkAccentHint: "Accent used in dark mode; leave empty to reuse the color above",
|
|
63
|
+
preserveContrast: "Lightened automatically for dark-mode readability",
|
|
64
|
+
tokenNote: "This panel only writes design tokens that actually exist in dsh ({count} verified against its stylesheets). dsh hardcodes per-component corner radii and ships no motion tokens, so there is no radius control and the animation toggle is implemented as a stylesheet.",
|
|
49
65
|
densitySection: "Density",
|
|
50
66
|
densityCompact: "Compact",
|
|
51
67
|
densityComfortable: "Comfortable",
|
|
52
68
|
densitySpacious: "Spacious",
|
|
53
69
|
radiusSection: "Border Radius",
|
|
54
|
-
|
|
55
|
-
radiusRounded: "Rounded",
|
|
56
|
-
radiusSoft: "Soft",
|
|
70
|
+
radiusUnavailable: "dsh ships no radius token, so this cannot be adjusted",
|
|
57
71
|
fontSection: "Font Family",
|
|
58
72
|
fontSystem: "System",
|
|
59
73
|
fontMono: "Monospace",
|
|
60
74
|
fontSerif: "Serif",
|
|
61
75
|
customCssSection: "Custom CSS",
|
|
62
|
-
customCssHint: "Override
|
|
63
|
-
customCssPlaceholder: "--
|
|
76
|
+
customCssHint: "Override dsh tokens, one `--dsw-* or --dsh-*: value;` per line",
|
|
77
|
+
customCssPlaceholder: "--dsw-alias-brand-primary: #ff0000;\n--dsw-alias-label-primary: #111111;",
|
|
78
|
+
behaviorSection: "Behavior",
|
|
79
|
+
animations: "UI Animations",
|
|
80
|
+
animationsHint: "Turn off to reduce motion (a stylesheet compresses transition durations to near-zero so events still fire)",
|
|
81
|
+
ioSection: "Import / Export",
|
|
82
|
+
export: "Export Theme",
|
|
83
|
+
import: "Import Theme",
|
|
84
|
+
importHint: "Paste exported JSON, or upload a .json file",
|
|
85
|
+
importPlaceholder: '{"preset":"nord","accentColor":"#5e81ac",...}',
|
|
86
|
+
importFile: "Choose File",
|
|
87
|
+
importSuccess: "Theme imported",
|
|
88
|
+
importFailed: "Import failed: not a valid theme JSON",
|
|
89
|
+
exportSuccess: "Theme copied to clipboard",
|
|
90
|
+
exportFailed: "Copy failed \u2014 filled into the box below",
|
|
64
91
|
preview: "Preview",
|
|
65
|
-
apply: "Apply",
|
|
66
92
|
reset: "Reset to Default",
|
|
67
93
|
resetConfirm: "Reset all theme settings to default?",
|
|
68
|
-
applied: "Theme applied",
|
|
69
94
|
resetted: "Reset to default theme",
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
badge: "Badge",
|
|
73
|
-
text: "This is sample text to preview the theme."
|
|
95
|
+
link: "Link",
|
|
96
|
+
text: "Sample text for previewing size and font family."
|
|
74
97
|
};
|
|
75
98
|
|
|
76
99
|
// src/client/view.tsx
|
|
77
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
100
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useRef, useState as useState2 } from "react";
|
|
78
101
|
|
|
79
102
|
// src/types.ts
|
|
80
103
|
var DEFAULT_PREFERENCES = {
|
|
81
104
|
preset: null,
|
|
82
105
|
accentColor: null,
|
|
106
|
+
darkAccentColor: null,
|
|
83
107
|
density: "comfortable",
|
|
84
|
-
radius: "rounded",
|
|
85
108
|
fontFamily: "system",
|
|
109
|
+
animations: true,
|
|
86
110
|
customCss: ""
|
|
87
111
|
};
|
|
88
112
|
var STORAGE_KEY = "dsh-theme-studio";
|
|
113
|
+
var STYLE_ELEMENT_ID = "dsh-theme-studio-overrides";
|
|
89
114
|
|
|
90
|
-
// src/
|
|
91
|
-
var
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"--dsw-alias-interactive-bg-hover": "rgba(22,163,74,0.08)"
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
id: "sunset",
|
|
118
|
-
name: "Sunset",
|
|
119
|
-
description: "Warm orange accent with amber highlights",
|
|
120
|
-
tokens: {
|
|
121
|
-
"--accent": "#ea580c",
|
|
122
|
-
"--accent-hover": "#c2410c",
|
|
123
|
-
"--dsw-alias-state-business-primary": "#ea580c",
|
|
124
|
-
"--dsw-alias-state-business-secondary": "#fed7aa",
|
|
125
|
-
"--dsw-alias-interactive-bg-hover": "rgba(234,88,12,0.08)"
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
id: "monochrome",
|
|
130
|
-
name: "Monochrome",
|
|
131
|
-
description: "Grayscale only \u2014 no color accent",
|
|
132
|
-
tokens: {
|
|
133
|
-
"--accent": "#404040",
|
|
134
|
-
"--accent-hover": "#262626",
|
|
135
|
-
"--dsw-alias-state-business-primary": "#404040",
|
|
136
|
-
"--dsw-alias-state-business-secondary": "#e5e5e5",
|
|
137
|
-
"--dsw-alias-interactive-bg-hover": "rgba(64,64,64,0.08)"
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
id: "nord",
|
|
142
|
-
name: "Nord",
|
|
143
|
-
description: "Frost blue inspired by the Nord palette",
|
|
144
|
-
tokens: {
|
|
145
|
-
"--accent": "#5e81ac",
|
|
146
|
-
"--accent-hover": "#4c6f96",
|
|
147
|
-
"--dsw-alias-state-business-primary": "#5e81ac",
|
|
148
|
-
"--dsw-alias-state-business-secondary": "#e5ebf1",
|
|
149
|
-
"--dsw-alias-interactive-bg-hover": "rgba(94,129,172,0.08)"
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
id: "dracula",
|
|
154
|
-
name: "Dracula",
|
|
155
|
-
description: "Purple accent with dark mode flair",
|
|
156
|
-
tokens: {
|
|
157
|
-
"--accent": "#bd93f9",
|
|
158
|
-
"--accent-hover": "#a678e8",
|
|
159
|
-
"--dsw-alias-state-business-primary": "#bd93f9",
|
|
160
|
-
"--dsw-alias-state-business-secondary": "#3a3a5c",
|
|
161
|
-
"--dsw-alias-interactive-bg-hover": "rgba(189,147,249,0.12)"
|
|
162
|
-
}
|
|
163
|
-
}
|
|
115
|
+
// src/tokens.ts
|
|
116
|
+
var VERIFIED_TOKENS = [
|
|
117
|
+
// Accent / business
|
|
118
|
+
"--dsw-alias-state-business-primary",
|
|
119
|
+
"--dsw-alias-state-business-tertiary",
|
|
120
|
+
"--dsw-alias-brand-primary",
|
|
121
|
+
"--dsw-alias-brand-text",
|
|
122
|
+
"--dsw-alias-link",
|
|
123
|
+
"--dsw-alias-interactive-bg-hover",
|
|
124
|
+
"--dsw-alias-interactive-bg-hover-accent",
|
|
125
|
+
"--dsw-alias-button-primary-fill",
|
|
126
|
+
"--dsw-alias-button-primary-hover",
|
|
127
|
+
// Typography
|
|
128
|
+
"--dsw-font-family",
|
|
129
|
+
"--dsw-font-mono",
|
|
130
|
+
"--dsw-font-xs-13",
|
|
131
|
+
"--dsw-font-xs-strong-13",
|
|
132
|
+
"--dsw-font-xxs-12",
|
|
133
|
+
"--dsw-font-xxxs-11",
|
|
134
|
+
"--dsw-font-s-14",
|
|
135
|
+
"--dsw-font-l-20",
|
|
136
|
+
"--dsh-content-font-size",
|
|
137
|
+
"--dsh-content-font-delta"
|
|
164
138
|
];
|
|
165
|
-
var
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
139
|
+
var VERIFIED = new Set(VERIFIED_TOKENS);
|
|
140
|
+
function tint(hex, alpha) {
|
|
141
|
+
const value = hex.trim().replace(/^#/, "");
|
|
142
|
+
let r;
|
|
143
|
+
let g;
|
|
144
|
+
let b;
|
|
145
|
+
if (value.length === 3) {
|
|
146
|
+
r = parseInt(value[0] + value[0], 16);
|
|
147
|
+
g = parseInt(value[1] + value[1], 16);
|
|
148
|
+
b = parseInt(value[2] + value[2], 16);
|
|
149
|
+
} else if (value.length === 6) {
|
|
150
|
+
r = parseInt(value.slice(0, 2), 16);
|
|
151
|
+
g = parseInt(value.slice(2, 4), 16);
|
|
152
|
+
b = parseInt(value.slice(4, 6), 16);
|
|
153
|
+
} else {
|
|
154
|
+
return hex;
|
|
177
155
|
}
|
|
156
|
+
if ([r, g, b].some((n) => Number.isNaN(n))) return hex;
|
|
157
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
158
|
+
}
|
|
159
|
+
function accentTokens(hex) {
|
|
160
|
+
return {
|
|
161
|
+
"--dsw-alias-state-business-primary": hex,
|
|
162
|
+
"--dsw-alias-state-business-tertiary": tint(hex, 0.12),
|
|
163
|
+
"--dsw-alias-brand-primary": hex,
|
|
164
|
+
"--dsw-alias-brand-text": hex,
|
|
165
|
+
"--dsw-alias-link": hex,
|
|
166
|
+
"--dsw-alias-interactive-bg-hover-accent": tint(hex, 0.1),
|
|
167
|
+
"--dsw-alias-button-primary-fill": hex,
|
|
168
|
+
"--dsw-alias-button-primary-hover": hex
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
var DENSITY_TOKENS = {
|
|
172
|
+
compact: { "--dsh-content-font-size": "13px" },
|
|
173
|
+
comfortable: { "--dsh-content-font-size": "14px" },
|
|
174
|
+
spacious: { "--dsh-content-font-size": "15px" }
|
|
178
175
|
};
|
|
179
|
-
var
|
|
180
|
-
|
|
181
|
-
"--
|
|
182
|
-
"--
|
|
183
|
-
"--dsh-radius-large": "6px"
|
|
176
|
+
var FONT_TOKENS = {
|
|
177
|
+
system: {
|
|
178
|
+
"--dsw-font-family": "system-ui, -apple-system, 'Segoe UI', sans-serif",
|
|
179
|
+
"--dsw-font-mono": "'Cascadia Code', 'JetBrains Mono', Consolas, monospace"
|
|
184
180
|
},
|
|
185
|
-
|
|
186
|
-
"--
|
|
187
|
-
"--
|
|
188
|
-
"--dsh-radius-large": "14px"
|
|
181
|
+
mono: {
|
|
182
|
+
"--dsw-font-family": "'Cascadia Code', 'JetBrains Mono', Consolas, monospace",
|
|
183
|
+
"--dsw-font-mono": "'Cascadia Code', 'JetBrains Mono', Consolas, monospace"
|
|
189
184
|
},
|
|
190
|
-
|
|
191
|
-
"--
|
|
192
|
-
"--
|
|
193
|
-
"--dsh-radius-large": "22px"
|
|
185
|
+
serif: {
|
|
186
|
+
"--dsw-font-family": "Georgia, 'Times New Roman', serif",
|
|
187
|
+
"--dsw-font-mono": "'Cascadia Code', Consolas, monospace"
|
|
194
188
|
}
|
|
195
189
|
};
|
|
196
|
-
var
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
190
|
+
var MOTION_OFF_CSS = `
|
|
191
|
+
*, *::before, *::after {
|
|
192
|
+
transition-duration: 0.001ms !important;
|
|
193
|
+
transition-delay: 0ms !important;
|
|
194
|
+
animation-duration: 0.001ms !important;
|
|
195
|
+
animation-delay: 0ms !important;
|
|
196
|
+
animation-iteration-count: 1 !important;
|
|
197
|
+
scroll-behavior: auto !important;
|
|
198
|
+
}
|
|
199
|
+
`.trim();
|
|
200
|
+
|
|
201
|
+
// src/themes.ts
|
|
202
|
+
function preset(id, name, description, lightAccent, darkAccent) {
|
|
203
|
+
const base = { id, name, description, tokens: accentTokens(lightAccent) };
|
|
204
|
+
if (darkAccent !== void 0 && darkAccent.toLowerCase() !== lightAccent.toLowerCase()) {
|
|
205
|
+
base.darkTokens = accentTokens(darkAccent);
|
|
206
|
+
}
|
|
207
|
+
return base;
|
|
208
|
+
}
|
|
209
|
+
var PRESETS = [
|
|
210
|
+
preset("ocean", "Ocean", "Deep blue, close to dsh default", "#4d6bfe"),
|
|
211
|
+
preset("forest", "Forest", "Calm green", "#16a34a", "#22c55e"),
|
|
212
|
+
preset("sunset", "Sunset", "Warm orange", "#ea580c", "#fb923c"),
|
|
213
|
+
preset("monochrome", "Monochrome", "No color accent at all", "#5c5c5c", "#9e9e9e"),
|
|
214
|
+
preset("nord", "Nord", "Frost blue", "#5e81ac", "#88c0d0"),
|
|
215
|
+
preset("dracula", "Dracula", "Purple, brighter on dark", "#7c5cd6", "#bd93f9"),
|
|
216
|
+
preset("gruvbox", "Gruvbox", "Retro warm earth tones", "#b57614", "#fe8019"),
|
|
217
|
+
preset("solarized", "Solarized", "Schoonover precision palette", "#268bd2", "#5fa8d3"),
|
|
218
|
+
preset("tokyo-night", "Tokyo Night", "City lights blue", "#3d59a1", "#7aa2f7"),
|
|
219
|
+
preset("catppuccin", "Catppuccin", "Soft pastel", "#7287fd", "#cba6f7"),
|
|
220
|
+
preset("rose", "Ros\xE9", "Muted pink", "#b3276b", "#f472b6"),
|
|
221
|
+
preset("ember", "Ember", "Deep red", "#b91c1c", "#ef4444")
|
|
222
|
+
];
|
|
201
223
|
function getPreset(id) {
|
|
202
224
|
return PRESETS.find((p) => p.id === id);
|
|
203
225
|
}
|
|
204
226
|
|
|
227
|
+
// src/io.ts
|
|
228
|
+
function exportTheme(prefs, name) {
|
|
229
|
+
const payload = {
|
|
230
|
+
$schema: "dsh-theme-studio/v1",
|
|
231
|
+
preferences: prefs,
|
|
232
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
233
|
+
};
|
|
234
|
+
if (name !== void 0) payload.name = name;
|
|
235
|
+
return JSON.stringify(payload, null, 2);
|
|
236
|
+
}
|
|
237
|
+
var DENSITIES = /* @__PURE__ */ new Set(["compact", "comfortable", "spacious"]);
|
|
238
|
+
var FONTS = /* @__PURE__ */ new Set(["system", "mono", "serif"]);
|
|
239
|
+
function asColor(value) {
|
|
240
|
+
if (typeof value !== "string") return null;
|
|
241
|
+
const trimmed = value.trim();
|
|
242
|
+
if (trimmed === "") return null;
|
|
243
|
+
return /^#[0-9a-fA-F]{3,8}$/.test(trimmed) ? trimmed : null;
|
|
244
|
+
}
|
|
245
|
+
function parseTheme(raw) {
|
|
246
|
+
let parsed;
|
|
247
|
+
try {
|
|
248
|
+
parsed = JSON.parse(raw);
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
253
|
+
const doc = parsed;
|
|
254
|
+
const source = doc["preferences"] !== null && typeof doc["preferences"] === "object" && !Array.isArray(doc["preferences"]) ? doc["preferences"] : doc;
|
|
255
|
+
const result = { ...DEFAULT_PREFERENCES };
|
|
256
|
+
if (typeof source["preset"] === "string" || source["preset"] === null) {
|
|
257
|
+
result.preset = source["preset"];
|
|
258
|
+
}
|
|
259
|
+
result.accentColor = asColor(source["accentColor"]);
|
|
260
|
+
result.darkAccentColor = asColor(source["darkAccentColor"]);
|
|
261
|
+
if (typeof source["density"] === "string" && DENSITIES.has(source["density"])) {
|
|
262
|
+
result.density = source["density"];
|
|
263
|
+
}
|
|
264
|
+
if (typeof source["fontFamily"] === "string" && FONTS.has(source["fontFamily"])) {
|
|
265
|
+
result.fontFamily = source["fontFamily"];
|
|
266
|
+
}
|
|
267
|
+
if (typeof source["animations"] === "boolean") result.animations = source["animations"];
|
|
268
|
+
if (typeof source["customCss"] === "string") result.customCss = source["customCss"];
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
function hexToRgb(hex) {
|
|
272
|
+
const value = hex.trim().replace(/^#/, "");
|
|
273
|
+
if (value.length === 3) {
|
|
274
|
+
const [r, g, b] = value.split("");
|
|
275
|
+
return [parseInt(r + r, 16), parseInt(g + g, 16), parseInt(b + b, 16)];
|
|
276
|
+
}
|
|
277
|
+
if (value.length === 6) {
|
|
278
|
+
return [
|
|
279
|
+
parseInt(value.slice(0, 2), 16),
|
|
280
|
+
parseInt(value.slice(2, 4), 16),
|
|
281
|
+
parseInt(value.slice(4, 6), 16)
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
function luminance(hex) {
|
|
287
|
+
const rgb = hexToRgb(hex);
|
|
288
|
+
if (rgb === null) return 0;
|
|
289
|
+
const [r, g, b] = rgb.map((c) => {
|
|
290
|
+
const s = c / 255;
|
|
291
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
292
|
+
});
|
|
293
|
+
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
294
|
+
}
|
|
295
|
+
function rgbToHex(r, g, b) {
|
|
296
|
+
const clamp = (n) => Math.max(0, Math.min(255, Math.round(n)));
|
|
297
|
+
return "#" + [r, g, b].map((n) => clamp(n).toString(16).padStart(2, "0")).join("");
|
|
298
|
+
}
|
|
299
|
+
function lighten(hex, amount) {
|
|
300
|
+
const rgb = hexToRgb(hex);
|
|
301
|
+
if (rgb === null) return hex;
|
|
302
|
+
const [r, g, b] = rgb;
|
|
303
|
+
return rgbToHex(
|
|
304
|
+
r + (255 - r) * amount,
|
|
305
|
+
g + (255 - g) * amount,
|
|
306
|
+
b + (255 - b) * amount
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
function ensureDarkContrast(hex) {
|
|
310
|
+
const MIN_LUMINANCE = 0.18;
|
|
311
|
+
if (hexToRgb(hex) === null) return { color: hex, adjusted: false };
|
|
312
|
+
if (luminance(hex) >= MIN_LUMINANCE) return { color: hex, adjusted: false };
|
|
313
|
+
let current = hex;
|
|
314
|
+
for (let i = 0; i < 10; i += 1) {
|
|
315
|
+
current = lighten(current, 0.12);
|
|
316
|
+
if (luminance(current) >= MIN_LUMINANCE) return { color: current, adjusted: true };
|
|
317
|
+
}
|
|
318
|
+
return { color: current, adjusted: true };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/apply.ts
|
|
322
|
+
function themeTarget() {
|
|
323
|
+
if (typeof document === "undefined") return null;
|
|
324
|
+
return document.body ?? null;
|
|
325
|
+
}
|
|
326
|
+
function isDshToken(name) {
|
|
327
|
+
return /^--dsh-\S+$/.test(name) || /^--dsw-\S+$/.test(name);
|
|
328
|
+
}
|
|
329
|
+
function isDarkMode() {
|
|
330
|
+
if (typeof document === "undefined") return false;
|
|
331
|
+
return document.body.hasAttribute("data-ds-dark-theme");
|
|
332
|
+
}
|
|
333
|
+
function observeDarkMode(onChange) {
|
|
334
|
+
if (typeof document === "undefined" || typeof MutationObserver === "undefined") {
|
|
335
|
+
return () => {
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const observer = new MutationObserver(() => onChange(isDarkMode()));
|
|
339
|
+
observer.observe(document.body, {
|
|
340
|
+
attributes: true,
|
|
341
|
+
attributeFilter: ["data-ds-dark-theme"]
|
|
342
|
+
});
|
|
343
|
+
return () => observer.disconnect();
|
|
344
|
+
}
|
|
345
|
+
function resolveAccent(prefs, dark) {
|
|
346
|
+
const chosen = dark ? prefs.darkAccentColor ?? prefs.accentColor : prefs.accentColor;
|
|
347
|
+
if (chosen === null || chosen.trim() === "") return { accent: null, adjusted: false };
|
|
348
|
+
if (!dark) return { accent: chosen, adjusted: false };
|
|
349
|
+
const guarded = ensureDarkContrast(chosen);
|
|
350
|
+
return { accent: guarded.color, adjusted: guarded.adjusted };
|
|
351
|
+
}
|
|
352
|
+
function parseCustomCss(css, allow) {
|
|
353
|
+
const out = {};
|
|
354
|
+
for (const line of css.split("\n")) {
|
|
355
|
+
const match = line.match(/^\s*(--[\w-]+)\s*:\s*([^;{}/*]+?)\s*;?\s*$/);
|
|
356
|
+
if (match === null) continue;
|
|
357
|
+
if (!allow(match[1])) continue;
|
|
358
|
+
out[match[1]] = match[2];
|
|
359
|
+
}
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
function clearManagedProperties(fullCustomCss = "") {
|
|
363
|
+
const target = themeTarget();
|
|
364
|
+
if (target === null) return;
|
|
365
|
+
const names = /* @__PURE__ */ new Set([
|
|
366
|
+
...Object.keys(accentTokens("#000000")),
|
|
367
|
+
...Object.values(DENSITY_TOKENS).flatMap((t) => Object.keys(t)),
|
|
368
|
+
...Object.values(FONT_TOKENS).flatMap((t) => Object.keys(t))
|
|
369
|
+
]);
|
|
370
|
+
for (const line of fullCustomCss.split("\n")) {
|
|
371
|
+
const match = line.match(/^\s*(--[\w-]+)\s*:/);
|
|
372
|
+
if (match !== null) names.add(match[1]);
|
|
373
|
+
}
|
|
374
|
+
for (const name of names) target.style.removeProperty(name);
|
|
375
|
+
}
|
|
376
|
+
function motionStylesheet() {
|
|
377
|
+
if (typeof document === "undefined") return null;
|
|
378
|
+
let el = document.getElementById(STYLE_ELEMENT_ID);
|
|
379
|
+
if (el === null) {
|
|
380
|
+
el = document.createElement("style");
|
|
381
|
+
el.id = STYLE_ELEMENT_ID;
|
|
382
|
+
document.head.appendChild(el);
|
|
383
|
+
}
|
|
384
|
+
return el;
|
|
385
|
+
}
|
|
386
|
+
function applyTheme(prefs, dark = isDarkMode()) {
|
|
387
|
+
const target = themeTarget();
|
|
388
|
+
if (target === null) return false;
|
|
389
|
+
clearManagedProperties(prefs.customCss);
|
|
390
|
+
const tokens = {};
|
|
391
|
+
if (prefs.preset !== null) {
|
|
392
|
+
const found = getPreset(prefs.preset);
|
|
393
|
+
if (found !== void 0) {
|
|
394
|
+
Object.assign(tokens, found.tokens);
|
|
395
|
+
if (dark && found.darkTokens !== void 0) Object.assign(tokens, found.darkTokens);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const { accent, adjusted } = resolveAccent(prefs, dark);
|
|
399
|
+
if (accent !== null) Object.assign(tokens, accentTokens(accent));
|
|
400
|
+
Object.assign(tokens, DENSITY_TOKENS[prefs.density] ?? {});
|
|
401
|
+
Object.assign(tokens, FONT_TOKENS[prefs.fontFamily] ?? {});
|
|
402
|
+
for (const [name, value] of Object.entries(tokens)) {
|
|
403
|
+
target.style.setProperty(name, value);
|
|
404
|
+
}
|
|
405
|
+
const custom = parseCustomCss(prefs.customCss, isDshToken);
|
|
406
|
+
for (const [name, value] of Object.entries(custom)) {
|
|
407
|
+
target.style.setProperty(name, value);
|
|
408
|
+
}
|
|
409
|
+
const sheet = motionStylesheet();
|
|
410
|
+
if (sheet !== null) sheet.textContent = prefs.animations ? "" : MOTION_OFF_CSS;
|
|
411
|
+
return adjusted;
|
|
412
|
+
}
|
|
413
|
+
function resetTheme() {
|
|
414
|
+
clearManagedProperties();
|
|
415
|
+
const sheet = typeof document !== "undefined" ? document.getElementById(STYLE_ELEMENT_ID) : null;
|
|
416
|
+
if (sheet !== null) sheet.textContent = "";
|
|
417
|
+
}
|
|
418
|
+
|
|
205
419
|
// src/client/ui.tsx
|
|
206
420
|
import {
|
|
207
421
|
createContext,
|
|
@@ -570,7 +784,7 @@ function ToastProvider({ children }) {
|
|
|
570
784
|
fontWeight: 500,
|
|
571
785
|
boxShadow: "0 6px 20px rgba(0,0,0,0.2)",
|
|
572
786
|
color: "#fff",
|
|
573
|
-
background: t.type === "success" ? T.success : t.type === "error" ? T.error : T.accent,
|
|
787
|
+
background: t.type === "success" ? T.success : t.type === "error" ? T.error : t.type === "warning" ? T.warning : T.accent,
|
|
574
788
|
maxWidth: 360
|
|
575
789
|
},
|
|
576
790
|
children: t.message
|
|
@@ -669,59 +883,9 @@ function savePreferences(prefs) {
|
|
|
669
883
|
if (typeof localStorage === "undefined") return;
|
|
670
884
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
671
885
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
const
|
|
675
|
-
const allTokens = {};
|
|
676
|
-
if (prefs.preset !== null) {
|
|
677
|
-
const preset = getPreset(prefs.preset);
|
|
678
|
-
if (preset !== void 0) Object.assign(allTokens, preset.tokens);
|
|
679
|
-
}
|
|
680
|
-
if (prefs.accentColor !== null && prefs.accentColor.trim() !== "") {
|
|
681
|
-
allTokens["--accent"] = prefs.accentColor;
|
|
682
|
-
}
|
|
683
|
-
Object.assign(allTokens, DENSITY_TOKENS[prefs.density] ?? {});
|
|
684
|
-
Object.assign(allTokens, RADIUS_TOKENS[prefs.radius] ?? {});
|
|
685
|
-
Object.assign(allTokens, FONT_TOKENS[prefs.fontFamily] ?? {});
|
|
686
|
-
for (const [prop, value] of Object.entries(allTokens)) {
|
|
687
|
-
root.style.setProperty(prop, value);
|
|
688
|
-
}
|
|
689
|
-
if (prefs.customCss.trim() !== "") {
|
|
690
|
-
for (const line of prefs.customCss.split("\n")) {
|
|
691
|
-
const match = line.match(/^\s*(--[\w-]+)\s*:\s*(.+?)\s*;?\s*$/);
|
|
692
|
-
if (match !== null) {
|
|
693
|
-
root.style.setProperty(match[1], match[2]);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
function clearTheme() {
|
|
699
|
-
if (typeof document === "undefined") return;
|
|
700
|
-
const root = document.documentElement;
|
|
701
|
-
const known = /* @__PURE__ */ new Set([
|
|
702
|
-
"--accent",
|
|
703
|
-
"--accent-hover",
|
|
704
|
-
"--dsh-content-font-size",
|
|
705
|
-
"--dsh-spacing-unit",
|
|
706
|
-
"--dsh-radius-small",
|
|
707
|
-
"--dsh-radius-medium",
|
|
708
|
-
"--dsh-radius-large",
|
|
709
|
-
"--dsh-font-family",
|
|
710
|
-
"--dsw-alias-state-business-primary",
|
|
711
|
-
"--dsw-alias-state-business-secondary",
|
|
712
|
-
"--dsw-alias-interactive-bg-hover"
|
|
713
|
-
]);
|
|
714
|
-
for (const prop of known) root.style.removeProperty(prop);
|
|
715
|
-
const prefs = loadPreferences();
|
|
716
|
-
if (prefs.customCss.trim() !== "") {
|
|
717
|
-
for (const line of prefs.customCss.split("\n")) {
|
|
718
|
-
const match = line.match(/^\s*(--[\w-]+)\s*:/);
|
|
719
|
-
if (match !== null) root.style.removeProperty(match[1]);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
function PresetCard({ preset, selected, onClick }) {
|
|
724
|
-
const accent = preset.tokens["--accent"] ?? "#4B8BBE";
|
|
886
|
+
function PresetCard({ preset: preset2, selected, onClick }) {
|
|
887
|
+
const accent = preset2.tokens["--dsw-alias-state-business-primary"] ?? "#4d6bfe";
|
|
888
|
+
const darkAccent = preset2.darkTokens?.["--dsw-alias-state-business-primary"];
|
|
725
889
|
return /* @__PURE__ */ jsxs2(
|
|
726
890
|
"div",
|
|
727
891
|
{
|
|
@@ -730,23 +894,31 @@ function PresetCard({ preset, selected, onClick }) {
|
|
|
730
894
|
cursor: "pointer",
|
|
731
895
|
borderRadius: 10,
|
|
732
896
|
padding: 12,
|
|
733
|
-
border: selected ?
|
|
734
|
-
background: selected ? "
|
|
897
|
+
border: selected ? `2px solid ${accent}` : "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
|
|
898
|
+
background: selected ? "var(--dsw-alias-interactive-bg-hover-accent, rgba(77,107,254,0.08))" : "transparent",
|
|
735
899
|
transition: "all 0.15s ease"
|
|
736
900
|
},
|
|
737
901
|
children: [
|
|
738
|
-
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom:
|
|
739
|
-
/* @__PURE__ */
|
|
740
|
-
|
|
741
|
-
|
|
902
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }, children: [
|
|
903
|
+
/* @__PURE__ */ jsxs2("span", { style: { display: "flex", gap: 3 }, children: [
|
|
904
|
+
/* @__PURE__ */ jsx2("i", { style: { width: 14, height: 14, borderRadius: "50%", background: accent, display: "block" } }),
|
|
905
|
+
darkAccent !== void 0 && /* @__PURE__ */ jsx2("i", { style: { width: 14, height: 14, borderRadius: "50%", background: darkAccent, display: "block" } })
|
|
906
|
+
] }),
|
|
907
|
+
/* @__PURE__ */ jsx2("strong", { style: { fontSize: 13 }, children: preset2.name }),
|
|
908
|
+
selected && /* @__PURE__ */ jsx2(Badge, { color: "success", children: "\u2713" })
|
|
742
909
|
] }),
|
|
743
|
-
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.
|
|
910
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.65, lineHeight: 1.4 }, children: preset2.description })
|
|
744
911
|
]
|
|
745
912
|
}
|
|
746
913
|
);
|
|
747
914
|
}
|
|
748
915
|
function Segmented({ value, options, onChange }) {
|
|
749
|
-
return /* @__PURE__ */ jsx2("div", { style: {
|
|
916
|
+
return /* @__PURE__ */ jsx2("div", { style: {
|
|
917
|
+
display: "inline-flex",
|
|
918
|
+
borderRadius: 8,
|
|
919
|
+
overflow: "hidden",
|
|
920
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))"
|
|
921
|
+
}, children: options.map((opt) => /* @__PURE__ */ jsx2(
|
|
750
922
|
"button",
|
|
751
923
|
{
|
|
752
924
|
type: "button",
|
|
@@ -758,8 +930,8 @@ function Segmented({ value, options, onChange }) {
|
|
|
758
930
|
fontSize: 13,
|
|
759
931
|
fontWeight: 500,
|
|
760
932
|
fontFamily: "inherit",
|
|
761
|
-
background: value === opt.value ? "var(--
|
|
762
|
-
color: value === opt.value ? "#fff" : "
|
|
933
|
+
background: value === opt.value ? "var(--dsw-alias-button-primary-fill, #4d6bfe)" : "transparent",
|
|
934
|
+
color: value === opt.value ? "#fff" : "inherit",
|
|
763
935
|
transition: "all 0.15s ease"
|
|
764
936
|
},
|
|
765
937
|
children: opt.label
|
|
@@ -767,55 +939,265 @@ function Segmented({ value, options, onChange }) {
|
|
|
767
939
|
opt.value
|
|
768
940
|
)) });
|
|
769
941
|
}
|
|
770
|
-
function
|
|
771
|
-
return /* @__PURE__ */
|
|
772
|
-
/* @__PURE__ */
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
942
|
+
function ColorRow({ value, onChange, placeholder }) {
|
|
943
|
+
return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
|
|
944
|
+
/* @__PURE__ */ jsx2(
|
|
945
|
+
"input",
|
|
946
|
+
{
|
|
947
|
+
type: "color",
|
|
948
|
+
value: value ?? "#4d6bfe",
|
|
949
|
+
onChange: (e) => onChange(e.target.value),
|
|
950
|
+
style: {
|
|
951
|
+
width: 40,
|
|
952
|
+
height: 40,
|
|
953
|
+
cursor: "pointer",
|
|
954
|
+
borderRadius: 8,
|
|
955
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))"
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
),
|
|
959
|
+
/* @__PURE__ */ jsx2(
|
|
960
|
+
Input,
|
|
961
|
+
{
|
|
962
|
+
value: value ?? "",
|
|
963
|
+
onChange: (e) => onChange(e.target.value.trim() === "" ? null : e.target.value),
|
|
964
|
+
placeholder,
|
|
965
|
+
style: { maxWidth: 200 }
|
|
966
|
+
}
|
|
967
|
+
),
|
|
968
|
+
value !== null && /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: () => onChange(null), children: "\u2715" })
|
|
969
|
+
] });
|
|
970
|
+
}
|
|
971
|
+
function PreviewArea({ t, dark, accentApplied }) {
|
|
972
|
+
return /* @__PURE__ */ jsx2(
|
|
973
|
+
Card,
|
|
974
|
+
{
|
|
975
|
+
title: t("preview"),
|
|
976
|
+
icon: "\u{1F441}",
|
|
977
|
+
actions: /* @__PURE__ */ jsx2(Badge, { color: dark ? "info" : "warning", children: dark ? "\u{1F319} dark" : "\u2600 light" }),
|
|
978
|
+
children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: [
|
|
979
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
980
|
+
/* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12 }, children: [
|
|
981
|
+
/* @__PURE__ */ jsx2("i", { style: {
|
|
982
|
+
width: 18,
|
|
983
|
+
height: 18,
|
|
984
|
+
borderRadius: 4,
|
|
985
|
+
display: "block",
|
|
986
|
+
background: "var(--dsw-alias-state-business-primary, #4d6bfe)"
|
|
987
|
+
} }),
|
|
988
|
+
"accent"
|
|
989
|
+
] }),
|
|
990
|
+
/* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12 }, children: [
|
|
991
|
+
/* @__PURE__ */ jsx2("i", { style: {
|
|
992
|
+
width: 18,
|
|
993
|
+
height: 18,
|
|
994
|
+
borderRadius: 4,
|
|
995
|
+
display: "block",
|
|
996
|
+
background: "var(--dsw-alias-button-primary-fill, #4d6bfe)"
|
|
997
|
+
} }),
|
|
998
|
+
"button"
|
|
999
|
+
] }),
|
|
1000
|
+
/* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12 }, children: [
|
|
1001
|
+
/* @__PURE__ */ jsx2("i", { style: {
|
|
1002
|
+
width: 18,
|
|
1003
|
+
height: 18,
|
|
1004
|
+
borderRadius: 4,
|
|
1005
|
+
display: "block",
|
|
1006
|
+
background: "var(--dsw-alias-label-primary, currentColor)"
|
|
1007
|
+
} }),
|
|
1008
|
+
"label"
|
|
1009
|
+
] }),
|
|
1010
|
+
/* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 6, fontSize: 12 }, children: [
|
|
1011
|
+
/* @__PURE__ */ jsx2("i", { style: {
|
|
1012
|
+
width: 18,
|
|
1013
|
+
height: 18,
|
|
1014
|
+
borderRadius: 4,
|
|
1015
|
+
display: "block",
|
|
1016
|
+
background: "var(--dsw-alias-bg-layer-1, rgba(128,128,128,0.1))",
|
|
1017
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))"
|
|
1018
|
+
} }),
|
|
1019
|
+
"surface"
|
|
1020
|
+
] })
|
|
1021
|
+
] }),
|
|
1022
|
+
/* @__PURE__ */ jsxs2("div", { style: {
|
|
1023
|
+
padding: 12,
|
|
1024
|
+
borderRadius: 10,
|
|
1025
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
|
|
1026
|
+
background: "var(--dsw-alias-bg-layer-1, transparent)"
|
|
1027
|
+
}, children: [
|
|
1028
|
+
/* @__PURE__ */ jsx2(
|
|
1029
|
+
"div",
|
|
1030
|
+
{
|
|
1031
|
+
style: {
|
|
1032
|
+
fontSize: "var(--dsh-content-font-size, 14px)",
|
|
1033
|
+
fontFamily: "var(--dsw-font-family, inherit)",
|
|
1034
|
+
marginBottom: 10
|
|
1035
|
+
},
|
|
1036
|
+
children: t("text")
|
|
1037
|
+
}
|
|
1038
|
+
),
|
|
1039
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }, children: [
|
|
1040
|
+
/* @__PURE__ */ jsx2("span", { style: {
|
|
1041
|
+
display: "inline-block",
|
|
1042
|
+
padding: "7px 16px",
|
|
1043
|
+
borderRadius: 7,
|
|
1044
|
+
background: "var(--dsw-alias-button-primary-fill, #4d6bfe)",
|
|
1045
|
+
color: "var(--dsw-alias-label-primary-inverted, #fff)",
|
|
1046
|
+
fontSize: 13
|
|
1047
|
+
}, children: t("button") }),
|
|
1048
|
+
/* @__PURE__ */ jsx2("span", { style: {
|
|
1049
|
+
display: "inline-block",
|
|
1050
|
+
padding: "7px 16px",
|
|
1051
|
+
borderRadius: 7,
|
|
1052
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(128,128,128,0.25))",
|
|
1053
|
+
fontSize: 13
|
|
1054
|
+
}, children: t("link") }),
|
|
1055
|
+
/* @__PURE__ */ jsx2(
|
|
1056
|
+
"a",
|
|
1057
|
+
{
|
|
1058
|
+
href: "#preview",
|
|
1059
|
+
onClick: (e) => e.preventDefault(),
|
|
1060
|
+
style: { color: "var(--dsw-alias-link, #4d6bfe)", fontSize: 13 },
|
|
1061
|
+
children: t("link")
|
|
1062
|
+
}
|
|
1063
|
+
)
|
|
1064
|
+
] })
|
|
1065
|
+
] }),
|
|
1066
|
+
accentApplied !== null && /* @__PURE__ */ jsxs2("div", { style: { fontSize: 11, opacity: 0.6, fontFamily: "var(--dsw-font-mono, monospace)" }, children: [
|
|
1067
|
+
t("accentApplying"),
|
|
1068
|
+
" ",
|
|
1069
|
+
accentApplied
|
|
1070
|
+
] })
|
|
784
1071
|
] })
|
|
785
|
-
|
|
786
|
-
|
|
1072
|
+
}
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
function TokenCoverage({ t }) {
|
|
1076
|
+
return /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.55, lineHeight: 1.5 }, children: t("tokenNote", { count: VERIFIED_TOKENS.length }) });
|
|
787
1077
|
}
|
|
788
1078
|
function ThemePanelInner({ t }) {
|
|
789
1079
|
const toast = useToast();
|
|
790
1080
|
const [prefs, setPrefs] = useState2(loadPreferences);
|
|
1081
|
+
const [dark, setDark] = useState2(isDarkMode);
|
|
1082
|
+
const [contrastAdjusted, setContrastAdjusted] = useState2(false);
|
|
791
1083
|
const [confirmReset, setConfirmReset] = useState2(false);
|
|
1084
|
+
const [importText, setImportText] = useState2("");
|
|
1085
|
+
const [importOpen, setImportOpen] = useState2(false);
|
|
1086
|
+
const fileInput = useRef(null);
|
|
792
1087
|
useEffect2(() => {
|
|
793
|
-
applyTheme(prefs);
|
|
1088
|
+
setContrastAdjusted(applyTheme(prefs, dark));
|
|
794
1089
|
savePreferences(prefs);
|
|
795
|
-
}, [prefs]);
|
|
1090
|
+
}, [prefs, dark]);
|
|
1091
|
+
useEffect2(() => observeDarkMode(setDark), []);
|
|
1092
|
+
useEffect2(() => () => {
|
|
1093
|
+
applyTheme(loadPreferences(), isDarkMode());
|
|
1094
|
+
}, []);
|
|
796
1095
|
const update = useCallback2((key, value) => {
|
|
797
1096
|
setPrefs((p) => ({ ...p, [key]: value }));
|
|
798
1097
|
}, []);
|
|
799
1098
|
const handleReset = useCallback2(() => {
|
|
800
|
-
|
|
1099
|
+
resetTheme();
|
|
801
1100
|
setPrefs({ ...DEFAULT_PREFERENCES });
|
|
1101
|
+
setConfirmReset(false);
|
|
802
1102
|
toast("success", t("resetted"));
|
|
803
1103
|
}, [t, toast]);
|
|
804
|
-
|
|
805
|
-
|
|
1104
|
+
const handleExport = useCallback2(() => {
|
|
1105
|
+
const json = exportTheme(prefs);
|
|
1106
|
+
const clipboard = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
1107
|
+
if (clipboard === void 0) {
|
|
1108
|
+
setImportText(json);
|
|
1109
|
+
setImportOpen(true);
|
|
1110
|
+
toast("warning", t("exportFailed"));
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
clipboard.writeText(json).then(
|
|
1114
|
+
() => toast("success", t("exportSuccess")),
|
|
1115
|
+
() => {
|
|
1116
|
+
setImportText(json);
|
|
1117
|
+
setImportOpen(true);
|
|
1118
|
+
toast("warning", t("exportFailed"));
|
|
1119
|
+
}
|
|
1120
|
+
);
|
|
1121
|
+
}, [prefs, t, toast]);
|
|
1122
|
+
const runImport = useCallback2((raw) => {
|
|
1123
|
+
const parsed = parseTheme(raw);
|
|
1124
|
+
if (parsed === null) {
|
|
1125
|
+
toast("error", t("importFailed"));
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
setPrefs(parsed);
|
|
1129
|
+
setImportOpen(false);
|
|
1130
|
+
setImportText("");
|
|
1131
|
+
toast("success", t("importSuccess"));
|
|
1132
|
+
}, [t, toast]);
|
|
1133
|
+
const handleFile = useCallback2((file) => {
|
|
1134
|
+
if (file === void 0) return;
|
|
1135
|
+
file.text().then(runImport, () => toast("error", t("importFailed")));
|
|
1136
|
+
}, [runImport, t, toast]);
|
|
1137
|
+
const { accent: accentApplied } = resolveAccent(prefs, dark);
|
|
1138
|
+
return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 860 }, children: [
|
|
1139
|
+
/* @__PURE__ */ jsxs2("header", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
|
|
806
1140
|
/* @__PURE__ */ jsxs2("strong", { style: { fontSize: 15 }, children: [
|
|
807
1141
|
"\u{1F3A8} ",
|
|
808
1142
|
t("title")
|
|
809
1143
|
] }),
|
|
810
1144
|
/* @__PURE__ */ jsx2("span", { style: { fontSize: 12, opacity: 0.6 }, children: t("subtitle") }),
|
|
811
1145
|
/* @__PURE__ */ jsx2("span", { style: { flex: 1 } }),
|
|
1146
|
+
/* @__PURE__ */ jsxs2(Button, { variant: "secondary", size: "sm", onClick: handleExport, children: [
|
|
1147
|
+
"\u2934 ",
|
|
1148
|
+
t("export")
|
|
1149
|
+
] }),
|
|
1150
|
+
/* @__PURE__ */ jsxs2(Button, { variant: "secondary", size: "sm", onClick: () => setImportOpen((v) => !v), children: [
|
|
1151
|
+
"\u2935 ",
|
|
1152
|
+
t("import")
|
|
1153
|
+
] }),
|
|
812
1154
|
/* @__PURE__ */ jsxs2(Button, { variant: "danger", size: "sm", onClick: () => setConfirmReset(true), children: [
|
|
813
1155
|
"\u21BA ",
|
|
814
1156
|
t("reset")
|
|
815
1157
|
] })
|
|
816
1158
|
] }),
|
|
1159
|
+
importOpen && /* @__PURE__ */ jsxs2(Card, { title: t("import"), icon: "\u2935", children: [
|
|
1160
|
+
/* @__PURE__ */ jsx2(Field, { label: t("import"), hint: t("importHint"), children: /* @__PURE__ */ jsx2(
|
|
1161
|
+
Textarea,
|
|
1162
|
+
{
|
|
1163
|
+
value: importText,
|
|
1164
|
+
onChange: (e) => setImportText(e.target.value),
|
|
1165
|
+
placeholder: t("importPlaceholder"),
|
|
1166
|
+
rows: 5
|
|
1167
|
+
}
|
|
1168
|
+
) }),
|
|
1169
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, marginTop: 8 }, children: [
|
|
1170
|
+
/* @__PURE__ */ jsx2(
|
|
1171
|
+
Button,
|
|
1172
|
+
{
|
|
1173
|
+
variant: "primary",
|
|
1174
|
+
size: "sm",
|
|
1175
|
+
onClick: () => runImport(importText),
|
|
1176
|
+
disabled: importText.trim() === "",
|
|
1177
|
+
children: t("import")
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
/* @__PURE__ */ jsxs2(Button, { variant: "secondary", size: "sm", onClick: () => fileInput.current?.click(), children: [
|
|
1181
|
+
"\u{1F4C4} ",
|
|
1182
|
+
t("importFile")
|
|
1183
|
+
] }),
|
|
1184
|
+
/* @__PURE__ */ jsx2(
|
|
1185
|
+
"input",
|
|
1186
|
+
{
|
|
1187
|
+
ref: fileInput,
|
|
1188
|
+
type: "file",
|
|
1189
|
+
accept: "application/json,.json",
|
|
1190
|
+
style: { display: "none" },
|
|
1191
|
+
onChange: (e) => {
|
|
1192
|
+
handleFile(e.target.files?.[0]);
|
|
1193
|
+
e.target.value = "";
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
)
|
|
1197
|
+
] })
|
|
1198
|
+
] }),
|
|
817
1199
|
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F3AD}", children: t("presetSection") }),
|
|
818
|
-
/* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(
|
|
1200
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))", gap: 10 }, children: [
|
|
819
1201
|
/* @__PURE__ */ jsx2(
|
|
820
1202
|
PresetCard,
|
|
821
1203
|
{
|
|
@@ -836,28 +1218,28 @@ function ThemePanelInner({ t }) {
|
|
|
836
1218
|
] }),
|
|
837
1219
|
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F308}", children: t("accentSection") }),
|
|
838
1220
|
/* @__PURE__ */ jsxs2(Card, { children: [
|
|
839
|
-
/* @__PURE__ */
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1221
|
+
/* @__PURE__ */ jsx2(
|
|
1222
|
+
ColorRow,
|
|
1223
|
+
{
|
|
1224
|
+
value: prefs.accentColor,
|
|
1225
|
+
onChange: (v) => update("accentColor", v),
|
|
1226
|
+
placeholder: t("accentPlaceholder")
|
|
1227
|
+
}
|
|
1228
|
+
),
|
|
1229
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.55, marginTop: 6 }, children: t("accentHint") }),
|
|
1230
|
+
/* @__PURE__ */ jsx2("div", { style: { height: 14 } }),
|
|
1231
|
+
/* @__PURE__ */ jsx2(Field, { label: t("darkAccentSection"), hint: t("darkAccentHint"), children: /* @__PURE__ */ jsx2(
|
|
1232
|
+
ColorRow,
|
|
1233
|
+
{
|
|
1234
|
+
value: prefs.darkAccentColor,
|
|
1235
|
+
onChange: (v) => update("darkAccentColor", v),
|
|
1236
|
+
placeholder: t("accentPlaceholder")
|
|
1237
|
+
}
|
|
1238
|
+
) }),
|
|
1239
|
+
contrastAdjusted && /* @__PURE__ */ jsx2("div", { style: { marginTop: 8 }, children: /* @__PURE__ */ jsxs2(Badge, { color: "warning", children: [
|
|
1240
|
+
"\u26A0 ",
|
|
1241
|
+
t("preserveContrast")
|
|
1242
|
+
] }) })
|
|
861
1243
|
] }),
|
|
862
1244
|
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F4D0}", children: t("densitySection") }),
|
|
863
1245
|
/* @__PURE__ */ jsx2(
|
|
@@ -872,19 +1254,6 @@ function ThemePanelInner({ t }) {
|
|
|
872
1254
|
]
|
|
873
1255
|
}
|
|
874
1256
|
),
|
|
875
|
-
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u2B1C", children: t("radiusSection") }),
|
|
876
|
-
/* @__PURE__ */ jsx2(
|
|
877
|
-
Segmented,
|
|
878
|
-
{
|
|
879
|
-
value: prefs.radius,
|
|
880
|
-
onChange: (v) => update("radius", v),
|
|
881
|
-
options: [
|
|
882
|
-
{ value: "sharp", label: t("radiusSharp") },
|
|
883
|
-
{ value: "rounded", label: t("radiusRounded") },
|
|
884
|
-
{ value: "soft", label: t("radiusSoft") }
|
|
885
|
-
]
|
|
886
|
-
}
|
|
887
|
-
),
|
|
888
1257
|
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F524}", children: t("fontSection") }),
|
|
889
1258
|
/* @__PURE__ */ jsx2(
|
|
890
1259
|
Segmented,
|
|
@@ -898,6 +1267,23 @@ function ThemePanelInner({ t }) {
|
|
|
898
1267
|
]
|
|
899
1268
|
}
|
|
900
1269
|
),
|
|
1270
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u2699", children: t("behaviorSection") }),
|
|
1271
|
+
/* @__PURE__ */ jsxs2(Card, { children: [
|
|
1272
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
|
|
1273
|
+
/* @__PURE__ */ jsx2(
|
|
1274
|
+
"input",
|
|
1275
|
+
{
|
|
1276
|
+
id: "theme-animations",
|
|
1277
|
+
type: "checkbox",
|
|
1278
|
+
checked: prefs.animations,
|
|
1279
|
+
onChange: (e) => update("animations", e.target.checked),
|
|
1280
|
+
style: { width: 16, height: 16, cursor: "pointer" }
|
|
1281
|
+
}
|
|
1282
|
+
),
|
|
1283
|
+
/* @__PURE__ */ jsx2("label", { htmlFor: "theme-animations", style: { fontSize: 13, cursor: "pointer" }, children: t("animations") })
|
|
1284
|
+
] }),
|
|
1285
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.55, marginTop: 6, marginLeft: 26 }, children: t("animationsHint") })
|
|
1286
|
+
] }),
|
|
901
1287
|
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u270F\uFE0F", children: t("customCssSection") }),
|
|
902
1288
|
/* @__PURE__ */ jsx2(Card, { children: /* @__PURE__ */ jsx2(Field, { label: t("customCssSection"), hint: t("customCssHint"), children: /* @__PURE__ */ jsx2(
|
|
903
1289
|
Textarea,
|
|
@@ -908,7 +1294,8 @@ function ThemePanelInner({ t }) {
|
|
|
908
1294
|
rows: 4
|
|
909
1295
|
}
|
|
910
1296
|
) }) }),
|
|
911
|
-
/* @__PURE__ */ jsx2(PreviewArea, { t }),
|
|
1297
|
+
/* @__PURE__ */ jsx2(PreviewArea, { t, dark, accentApplied }),
|
|
1298
|
+
/* @__PURE__ */ jsx2(TokenCoverage, { t }),
|
|
912
1299
|
confirmReset && /* @__PURE__ */ jsx2(
|
|
913
1300
|
ConfirmDialog,
|
|
914
1301
|
{
|