dsh-theme-studio 0.1.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 +11 -0
- package/README.md +40 -0
- package/cordis.patch.yml +9 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +14 -0
- package/lib/theme-studio.web.js +947 -0
- package/lib/themes.d.ts +19 -0
- package/lib/themes.js +126 -0
- package/lib/types.d.ts +32 -0
- package/lib/types.js +16 -0
- package/package.json +54 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Initial release.
|
|
6
|
+
- 6 pre-built theme presets (Ocean, Forest, Sunset, Monochrome, Nord, Dracula).
|
|
7
|
+
- Custom accent color picker.
|
|
8
|
+
- Density, border radius, and font family controls.
|
|
9
|
+
- Custom CSS variable override textarea.
|
|
10
|
+
- Live preview area.
|
|
11
|
+
- Preferences persisted in localStorage.
|
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# dsh-theme-studio
|
|
2
|
+
|
|
3
|
+
A [dsh](https://github.com/deepseek-ai/deepseek-harness) plugin that lets you customize the UI theme: pick pre-built color presets, set a custom accent color, adjust density, border radius, and font family, or write custom CSS variable overrides.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **6 pre-built presets**: Ocean, Forest, Sunset, Monochrome, Nord, Dracula
|
|
8
|
+
- **Custom accent color** picker with hex input
|
|
9
|
+
- **Density** control: Compact / Comfortable / Spacious
|
|
10
|
+
- **Border radius** control: Sharp / Rounded / Soft
|
|
11
|
+
- **Font family** selection: System / Monospace / Serif
|
|
12
|
+
- **Custom CSS** textarea for advanced `--property: value;` overrides
|
|
13
|
+
- **Live preview** with badges, cards, and buttons
|
|
14
|
+
- **Instant apply** — changes take effect immediately via CSS custom properties
|
|
15
|
+
- **Persistent** — preferences saved in `localStorage`, no server round-trips
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
dsh plugin add dsh-theme-studio
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or from source:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/hj01857655/dsh-theme-studio.git
|
|
27
|
+
cd dsh-theme-studio
|
|
28
|
+
npm install && npm run build
|
|
29
|
+
dsh plugin add link:.
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
The plugin registers a `settings.section` slot (order 47) that renders a Theme Studio page in the dsh Settings panel. All theme state is client-side: preferences persist in `localStorage` and are applied by setting CSS custom properties on `document.documentElement.style`.
|
|
35
|
+
|
|
36
|
+
The overrides target dsh's `--dsw-alias-*` semantic token layer, so they cascade through every component that reads the design tokens — no component patching required.
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
package/cordis.patch.yml
ADDED
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* A purely client-side plugin — the host half exists only to satisfy the Cordis
|
|
5
|
+
* bundle contract (every plugin needs a host entry point with a cordis.patch.yml).
|
|
6
|
+
* No routes, no storage, no services: all theme state lives in the browser's
|
|
7
|
+
* localStorage and is applied via CSS custom properties on document.documentElement.
|
|
8
|
+
*
|
|
9
|
+
* @module index
|
|
10
|
+
*/
|
|
11
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
12
|
+
export declare function apply(_ctx: Context): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host half of dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* A purely client-side plugin — the host half exists only to satisfy the Cordis
|
|
5
|
+
* bundle contract (every plugin needs a host entry point with a cordis.patch.yml).
|
|
6
|
+
* No routes, no storage, no services: all theme state lives in the browser's
|
|
7
|
+
* localStorage and is applied via CSS custom properties on document.documentElement.
|
|
8
|
+
*
|
|
9
|
+
* @module index
|
|
10
|
+
*/
|
|
11
|
+
export function apply(_ctx) {
|
|
12
|
+
// Intentionally empty. The client half (src/client/index.tsx) registers the
|
|
13
|
+
// settings section; the host half has no work to do.
|
|
14
|
+
}
|
|
@@ -0,0 +1,947 @@
|
|
|
1
|
+
// src/client/locales.ts
|
|
2
|
+
var NS = "theme-studio";
|
|
3
|
+
var zh = {
|
|
4
|
+
nav: "\u4E3B\u9898\u5DE5\u4F5C\u5BA4",
|
|
5
|
+
title: "\u4E3B\u9898\u5DE5\u4F5C\u5BA4",
|
|
6
|
+
subtitle: "\u81EA\u5B9A\u4E49 dsh \u754C\u9762\u5916\u89C2",
|
|
7
|
+
presetSection: "\u9884\u8BBE\u4E3B\u9898",
|
|
8
|
+
presetHint: "\u9009\u62E9\u4E00\u4E2A\u9884\u8BBE\u914D\u8272\u65B9\u6848",
|
|
9
|
+
noPreset: "\u65E0\uFF08\u4F7F\u7528\u9ED8\u8BA4\uFF09",
|
|
10
|
+
accentSection: "\u5F3A\u8C03\u8272",
|
|
11
|
+
accentHint: "\u81EA\u5B9A\u4E49\u754C\u9762\u5F3A\u8C03\u8272\uFF0C\u8986\u76D6\u9884\u8BBE\u4E2D\u7684\u5F3A\u8C03\u8272",
|
|
12
|
+
accentPlaceholder: "#4B8BBE",
|
|
13
|
+
densitySection: "\u5BC6\u5EA6",
|
|
14
|
+
densityCompact: "\u7D27\u51D1",
|
|
15
|
+
densityComfortable: "\u8212\u9002",
|
|
16
|
+
densitySpacious: "\u5BBD\u677E",
|
|
17
|
+
radiusSection: "\u5706\u89D2",
|
|
18
|
+
radiusSharp: "\u9510\u5229",
|
|
19
|
+
radiusRounded: "\u5706\u89D2",
|
|
20
|
+
radiusSoft: "\u67D4\u548C",
|
|
21
|
+
fontSection: "\u5B57\u4F53",
|
|
22
|
+
fontSystem: "\u7CFB\u7EDF",
|
|
23
|
+
fontMono: "\u7B49\u5BBD",
|
|
24
|
+
fontSerif: "\u886C\u7EBF",
|
|
25
|
+
customCssSection: "\u81EA\u5B9A\u4E49 CSS",
|
|
26
|
+
customCssHint: "\u76F4\u63A5\u8986\u76D6 CSS \u81EA\u5B9A\u4E49\u5C5E\u6027\uFF0C\u6BCF\u884C\u4E00\u4E2A `--property: value;`",
|
|
27
|
+
customCssPlaceholder: "--accent: #ff0000;\n--border: 1px solid red;",
|
|
28
|
+
preview: "\u9884\u89C8",
|
|
29
|
+
apply: "\u5E94\u7528",
|
|
30
|
+
reset: "\u91CD\u7F6E\u4E3A\u9ED8\u8BA4",
|
|
31
|
+
resetConfirm: "\u786E\u5B9A\u8981\u91CD\u7F6E\u6240\u6709\u4E3B\u9898\u8BBE\u7F6E\u5417\uFF1F",
|
|
32
|
+
applied: "\u4E3B\u9898\u5DF2\u5E94\u7528",
|
|
33
|
+
resetted: "\u5DF2\u91CD\u7F6E\u4E3A\u9ED8\u8BA4\u4E3B\u9898",
|
|
34
|
+
card: "\u5361\u7247",
|
|
35
|
+
button: "\u6309\u94AE",
|
|
36
|
+
badge: "\u6807\u7B7E",
|
|
37
|
+
text: "\u8FD9\u662F\u4E00\u6BB5\u793A\u4F8B\u6587\u672C\uFF0C\u7528\u4E8E\u9884\u89C8\u4E3B\u9898\u6548\u679C\u3002"
|
|
38
|
+
};
|
|
39
|
+
var en = {
|
|
40
|
+
nav: "Theme Studio",
|
|
41
|
+
title: "Theme Studio",
|
|
42
|
+
subtitle: "Customize the dsh UI appearance",
|
|
43
|
+
presetSection: "Preset Themes",
|
|
44
|
+
presetHint: "Choose a pre-built color scheme",
|
|
45
|
+
noPreset: "None (use default)",
|
|
46
|
+
accentSection: "Accent Color",
|
|
47
|
+
accentHint: "Custom accent color, overrides the preset",
|
|
48
|
+
accentPlaceholder: "#4B8BBE",
|
|
49
|
+
densitySection: "Density",
|
|
50
|
+
densityCompact: "Compact",
|
|
51
|
+
densityComfortable: "Comfortable",
|
|
52
|
+
densitySpacious: "Spacious",
|
|
53
|
+
radiusSection: "Border Radius",
|
|
54
|
+
radiusSharp: "Sharp",
|
|
55
|
+
radiusRounded: "Rounded",
|
|
56
|
+
radiusSoft: "Soft",
|
|
57
|
+
fontSection: "Font Family",
|
|
58
|
+
fontSystem: "System",
|
|
59
|
+
fontMono: "Monospace",
|
|
60
|
+
fontSerif: "Serif",
|
|
61
|
+
customCssSection: "Custom CSS",
|
|
62
|
+
customCssHint: "Override CSS custom properties directly, one `--property: value;` per line",
|
|
63
|
+
customCssPlaceholder: "--accent: #ff0000;\n--border: 1px solid red;",
|
|
64
|
+
preview: "Preview",
|
|
65
|
+
apply: "Apply",
|
|
66
|
+
reset: "Reset to Default",
|
|
67
|
+
resetConfirm: "Reset all theme settings to default?",
|
|
68
|
+
applied: "Theme applied",
|
|
69
|
+
resetted: "Reset to default theme",
|
|
70
|
+
card: "Card",
|
|
71
|
+
button: "Button",
|
|
72
|
+
badge: "Badge",
|
|
73
|
+
text: "This is sample text to preview the theme."
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/client/view.tsx
|
|
77
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
78
|
+
|
|
79
|
+
// src/types.ts
|
|
80
|
+
var DEFAULT_PREFERENCES = {
|
|
81
|
+
preset: null,
|
|
82
|
+
accentColor: null,
|
|
83
|
+
density: "comfortable",
|
|
84
|
+
radius: "rounded",
|
|
85
|
+
fontFamily: "system",
|
|
86
|
+
customCss: ""
|
|
87
|
+
};
|
|
88
|
+
var STORAGE_KEY = "dsh-theme-studio";
|
|
89
|
+
|
|
90
|
+
// src/themes.ts
|
|
91
|
+
var PRESETS = [
|
|
92
|
+
{
|
|
93
|
+
id: "ocean",
|
|
94
|
+
name: "Ocean",
|
|
95
|
+
description: "Deep blue accent with cool neutrals",
|
|
96
|
+
tokens: {
|
|
97
|
+
"--accent": "#2563eb",
|
|
98
|
+
"--accent-hover": "#1d4ed8",
|
|
99
|
+
"--dsw-alias-state-business-primary": "#2563eb",
|
|
100
|
+
"--dsw-alias-state-business-secondary": "#dbeafe",
|
|
101
|
+
"--dsw-alias-interactive-bg-hover": "rgba(37,99,235,0.08)"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "forest",
|
|
106
|
+
name: "Forest",
|
|
107
|
+
description: "Green accent with earthy neutrals",
|
|
108
|
+
tokens: {
|
|
109
|
+
"--accent": "#16a34a",
|
|
110
|
+
"--accent-hover": "#15803d",
|
|
111
|
+
"--dsw-alias-state-business-primary": "#16a34a",
|
|
112
|
+
"--dsw-alias-state-business-secondary": "#dcfce7",
|
|
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
|
+
}
|
|
164
|
+
];
|
|
165
|
+
var DENSITY_TOKENS = {
|
|
166
|
+
compact: {
|
|
167
|
+
"--dsh-content-font-size": "13px",
|
|
168
|
+
"--dsh-spacing-unit": "4px"
|
|
169
|
+
},
|
|
170
|
+
comfortable: {
|
|
171
|
+
"--dsh-content-font-size": "14px",
|
|
172
|
+
"--dsh-spacing-unit": "6px"
|
|
173
|
+
},
|
|
174
|
+
spacious: {
|
|
175
|
+
"--dsh-content-font-size": "15px",
|
|
176
|
+
"--dsh-spacing-unit": "8px"
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var RADIUS_TOKENS = {
|
|
180
|
+
sharp: {
|
|
181
|
+
"--dsh-radius-small": "2px",
|
|
182
|
+
"--dsh-radius-medium": "4px",
|
|
183
|
+
"--dsh-radius-large": "6px"
|
|
184
|
+
},
|
|
185
|
+
rounded: {
|
|
186
|
+
"--dsh-radius-small": "6px",
|
|
187
|
+
"--dsh-radius-medium": "10px",
|
|
188
|
+
"--dsh-radius-large": "14px"
|
|
189
|
+
},
|
|
190
|
+
soft: {
|
|
191
|
+
"--dsh-radius-small": "10px",
|
|
192
|
+
"--dsh-radius-medium": "16px",
|
|
193
|
+
"--dsh-radius-large": "22px"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var FONT_TOKENS = {
|
|
197
|
+
system: { "--dsh-font-family": "system-ui, -apple-system, sans-serif" },
|
|
198
|
+
mono: { "--dsh-font-family": "'JetBrains Mono', 'Fira Code', monospace" },
|
|
199
|
+
serif: { "--dsh-font-family": "'Georgia', 'Times New Roman', serif" }
|
|
200
|
+
};
|
|
201
|
+
function getPreset(id) {
|
|
202
|
+
return PRESETS.find((p) => p.id === id);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/client/ui.tsx
|
|
206
|
+
import {
|
|
207
|
+
createContext,
|
|
208
|
+
useCallback,
|
|
209
|
+
useContext,
|
|
210
|
+
useEffect,
|
|
211
|
+
useMemo,
|
|
212
|
+
useState
|
|
213
|
+
} from "react";
|
|
214
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
215
|
+
var T = {
|
|
216
|
+
accent: "var(--accent, #4B8BBE)",
|
|
217
|
+
accentHover: "var(--accent-hover, #3a6f9e)",
|
|
218
|
+
error: "var(--error, #e53935)",
|
|
219
|
+
success: "var(--success, #2e7d32)",
|
|
220
|
+
warning: "var(--warning, #ed6c02)",
|
|
221
|
+
bg: "var(--bg-secondary, rgba(128,128,128,0.04))",
|
|
222
|
+
bgHover: "var(--bg-tertiary, rgba(128,128,128,0.08))",
|
|
223
|
+
border: "var(--border, rgba(128,128,128,0.2))",
|
|
224
|
+
text: "var(--text-primary, inherit)",
|
|
225
|
+
muted: "var(--text-secondary, rgba(128,128,128,0.65))",
|
|
226
|
+
surface: "var(--bg-primary, #fff)"
|
|
227
|
+
};
|
|
228
|
+
var injected = false;
|
|
229
|
+
function ensureStyles() {
|
|
230
|
+
if (injected || typeof document === "undefined") return;
|
|
231
|
+
injected = true;
|
|
232
|
+
const el = document.createElement("style");
|
|
233
|
+
el.textContent = `
|
|
234
|
+
@keyframes dsh-fade-in{from{opacity:0}to{opacity:1}}
|
|
235
|
+
@keyframes dsh-slide-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
236
|
+
@keyframes dsh-spin{to{transform:rotate(360deg)}}
|
|
237
|
+
.dsh-modal-overlay{animation:dsh-fade-in .15s ease}
|
|
238
|
+
.dsh-modal-body{animation:dsh-slide-up .2s ease}
|
|
239
|
+
.dsh-toast{animation:dsh-slide-up .2s ease}
|
|
240
|
+
.dsh-spin{animation:dsh-spin .6s linear infinite}
|
|
241
|
+
`;
|
|
242
|
+
document.head.appendChild(el);
|
|
243
|
+
}
|
|
244
|
+
function Modal({
|
|
245
|
+
title,
|
|
246
|
+
onClose,
|
|
247
|
+
children,
|
|
248
|
+
footer,
|
|
249
|
+
width = 520
|
|
250
|
+
}) {
|
|
251
|
+
useEffect(() => {
|
|
252
|
+
ensureStyles();
|
|
253
|
+
const handler = (e) => {
|
|
254
|
+
if (e.key === "Escape") onClose();
|
|
255
|
+
};
|
|
256
|
+
document.addEventListener("keydown", handler);
|
|
257
|
+
return () => document.removeEventListener("keydown", handler);
|
|
258
|
+
}, [onClose]);
|
|
259
|
+
return /* @__PURE__ */ jsx(
|
|
260
|
+
"div",
|
|
261
|
+
{
|
|
262
|
+
className: "dsh-modal-overlay",
|
|
263
|
+
style: {
|
|
264
|
+
position: "fixed",
|
|
265
|
+
inset: 0,
|
|
266
|
+
zIndex: 1e3,
|
|
267
|
+
display: "flex",
|
|
268
|
+
alignItems: "center",
|
|
269
|
+
justifyContent: "center",
|
|
270
|
+
background: "rgba(0,0,0,0.45)",
|
|
271
|
+
backdropFilter: "blur(3px)",
|
|
272
|
+
WebkitBackdropFilter: "blur(3px)"
|
|
273
|
+
},
|
|
274
|
+
onClick: onClose,
|
|
275
|
+
children: /* @__PURE__ */ jsxs(
|
|
276
|
+
"div",
|
|
277
|
+
{
|
|
278
|
+
className: "dsh-modal-body",
|
|
279
|
+
style: {
|
|
280
|
+
background: T.surface,
|
|
281
|
+
borderRadius: 14,
|
|
282
|
+
boxShadow: "0 12px 40px rgba(0,0,0,0.25)",
|
|
283
|
+
width: `min(92vw, ${width}px)`,
|
|
284
|
+
maxHeight: "85vh",
|
|
285
|
+
display: "flex",
|
|
286
|
+
flexDirection: "column",
|
|
287
|
+
overflow: "hidden"
|
|
288
|
+
},
|
|
289
|
+
onClick: (e) => e.stopPropagation(),
|
|
290
|
+
children: [
|
|
291
|
+
/* @__PURE__ */ jsxs(
|
|
292
|
+
"div",
|
|
293
|
+
{
|
|
294
|
+
style: {
|
|
295
|
+
display: "flex",
|
|
296
|
+
alignItems: "center",
|
|
297
|
+
justifyContent: "space-between",
|
|
298
|
+
padding: "16px 22px",
|
|
299
|
+
borderBottom: `1px solid ${T.border}`
|
|
300
|
+
},
|
|
301
|
+
children: [
|
|
302
|
+
/* @__PURE__ */ jsx("h3", { style: { margin: 0, fontSize: 15, fontWeight: 600 }, children: title }),
|
|
303
|
+
/* @__PURE__ */ jsx(
|
|
304
|
+
"button",
|
|
305
|
+
{
|
|
306
|
+
type: "button",
|
|
307
|
+
onClick: onClose,
|
|
308
|
+
style: {
|
|
309
|
+
background: "none",
|
|
310
|
+
border: "none",
|
|
311
|
+
cursor: "pointer",
|
|
312
|
+
fontSize: 20,
|
|
313
|
+
color: T.muted,
|
|
314
|
+
padding: "0 4px",
|
|
315
|
+
lineHeight: 1,
|
|
316
|
+
borderRadius: 4
|
|
317
|
+
},
|
|
318
|
+
"aria-label": "Close",
|
|
319
|
+
children: "\xD7"
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
]
|
|
323
|
+
}
|
|
324
|
+
),
|
|
325
|
+
/* @__PURE__ */ jsx("div", { style: { padding: 22, overflow: "auto", flex: 1 }, children }),
|
|
326
|
+
footer !== void 0 && /* @__PURE__ */ jsx(
|
|
327
|
+
"div",
|
|
328
|
+
{
|
|
329
|
+
style: {
|
|
330
|
+
padding: "14px 22px",
|
|
331
|
+
borderTop: `1px solid ${T.border}`,
|
|
332
|
+
display: "flex",
|
|
333
|
+
justifyContent: "flex-end",
|
|
334
|
+
gap: 10,
|
|
335
|
+
background: T.bg
|
|
336
|
+
},
|
|
337
|
+
children: footer
|
|
338
|
+
}
|
|
339
|
+
)
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
function ConfirmDialog({
|
|
347
|
+
title,
|
|
348
|
+
message,
|
|
349
|
+
confirmLabel,
|
|
350
|
+
cancelLabel = "Cancel",
|
|
351
|
+
onConfirm,
|
|
352
|
+
onClose,
|
|
353
|
+
danger
|
|
354
|
+
}) {
|
|
355
|
+
return /* @__PURE__ */ jsx(
|
|
356
|
+
Modal,
|
|
357
|
+
{
|
|
358
|
+
title,
|
|
359
|
+
onClose,
|
|
360
|
+
width: 420,
|
|
361
|
+
footer: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
362
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: cancelLabel }),
|
|
363
|
+
/* @__PURE__ */ jsx(
|
|
364
|
+
Button,
|
|
365
|
+
{
|
|
366
|
+
variant: danger === true ? "danger" : "primary",
|
|
367
|
+
onClick: () => {
|
|
368
|
+
onConfirm();
|
|
369
|
+
onClose();
|
|
370
|
+
},
|
|
371
|
+
children: confirmLabel
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
] }),
|
|
375
|
+
children: /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 14, lineHeight: 1.6 }, children: message })
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
function Button({
|
|
380
|
+
variant = "secondary",
|
|
381
|
+
size = "md",
|
|
382
|
+
children,
|
|
383
|
+
style,
|
|
384
|
+
...rest
|
|
385
|
+
}) {
|
|
386
|
+
const base = {
|
|
387
|
+
cursor: "pointer",
|
|
388
|
+
borderRadius: 7,
|
|
389
|
+
fontWeight: 500,
|
|
390
|
+
transition: "all 0.15s ease",
|
|
391
|
+
border: "none",
|
|
392
|
+
display: "inline-flex",
|
|
393
|
+
alignItems: "center",
|
|
394
|
+
gap: 4,
|
|
395
|
+
fontSize: size === "sm" ? 12 : 13,
|
|
396
|
+
padding: size === "sm" ? "5px 12px" : "8px 18px",
|
|
397
|
+
fontFamily: "inherit"
|
|
398
|
+
};
|
|
399
|
+
const variants = {
|
|
400
|
+
primary: { background: T.accent, color: "#fff" },
|
|
401
|
+
secondary: {
|
|
402
|
+
background: T.bg,
|
|
403
|
+
color: T.text,
|
|
404
|
+
border: `1px solid ${T.border}`
|
|
405
|
+
},
|
|
406
|
+
danger: {
|
|
407
|
+
background: "transparent",
|
|
408
|
+
color: T.error,
|
|
409
|
+
border: `1px solid ${T.error}`
|
|
410
|
+
},
|
|
411
|
+
ghost: { background: "transparent", color: T.muted, border: "none" }
|
|
412
|
+
};
|
|
413
|
+
return /* @__PURE__ */ jsx("button", { type: "button", style: { ...base, ...variants[variant], ...style }, ...rest, children });
|
|
414
|
+
}
|
|
415
|
+
function Card({
|
|
416
|
+
title,
|
|
417
|
+
icon,
|
|
418
|
+
children,
|
|
419
|
+
actions,
|
|
420
|
+
style,
|
|
421
|
+
padding = 14
|
|
422
|
+
}) {
|
|
423
|
+
return /* @__PURE__ */ jsxs(
|
|
424
|
+
"div",
|
|
425
|
+
{
|
|
426
|
+
style: {
|
|
427
|
+
borderRadius: 10,
|
|
428
|
+
border: `1px solid ${T.border}`,
|
|
429
|
+
background: T.bg,
|
|
430
|
+
overflow: "hidden",
|
|
431
|
+
...style
|
|
432
|
+
},
|
|
433
|
+
children: [
|
|
434
|
+
(title !== void 0 || actions !== void 0) && /* @__PURE__ */ jsxs(
|
|
435
|
+
"div",
|
|
436
|
+
{
|
|
437
|
+
style: {
|
|
438
|
+
display: "flex",
|
|
439
|
+
alignItems: "center",
|
|
440
|
+
justifyContent: "space-between",
|
|
441
|
+
padding: "12px 16px",
|
|
442
|
+
borderBottom: title !== void 0 ? `1px solid ${T.border}` : "none"
|
|
443
|
+
},
|
|
444
|
+
children: [
|
|
445
|
+
/* @__PURE__ */ jsxs("span", { style: { fontSize: 13, fontWeight: 600, display: "flex", alignItems: "center", gap: 6 }, children: [
|
|
446
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", { children: icon }),
|
|
447
|
+
title
|
|
448
|
+
] }),
|
|
449
|
+
actions
|
|
450
|
+
]
|
|
451
|
+
}
|
|
452
|
+
),
|
|
453
|
+
/* @__PURE__ */ jsx("div", { style: { padding }, children })
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
function Badge({
|
|
459
|
+
children,
|
|
460
|
+
color = "default"
|
|
461
|
+
}) {
|
|
462
|
+
const colors = {
|
|
463
|
+
default: { bg: "rgba(128,128,128,0.12)", fg: T.muted },
|
|
464
|
+
success: { bg: "rgba(46,125,50,0.12)", fg: T.success },
|
|
465
|
+
error: { bg: "rgba(229,57,53,0.12)", fg: T.error },
|
|
466
|
+
warning: { bg: "rgba(245,124,0,0.12)", fg: T.warning },
|
|
467
|
+
info: { bg: "rgba(75,139,190,0.12)", fg: T.accent }
|
|
468
|
+
};
|
|
469
|
+
const c = colors[color];
|
|
470
|
+
return /* @__PURE__ */ jsx(
|
|
471
|
+
"span",
|
|
472
|
+
{
|
|
473
|
+
style: {
|
|
474
|
+
display: "inline-block",
|
|
475
|
+
padding: "2px 9px",
|
|
476
|
+
borderRadius: 10,
|
|
477
|
+
fontSize: 11,
|
|
478
|
+
fontWeight: 600,
|
|
479
|
+
background: c.bg,
|
|
480
|
+
color: c.fg
|
|
481
|
+
},
|
|
482
|
+
children
|
|
483
|
+
}
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
function Input({
|
|
487
|
+
style,
|
|
488
|
+
...rest
|
|
489
|
+
}) {
|
|
490
|
+
return /* @__PURE__ */ jsx(
|
|
491
|
+
"input",
|
|
492
|
+
{
|
|
493
|
+
...rest,
|
|
494
|
+
style: {
|
|
495
|
+
fontSize: 13,
|
|
496
|
+
padding: "8px 12px",
|
|
497
|
+
borderRadius: 7,
|
|
498
|
+
border: `1px solid ${T.border}`,
|
|
499
|
+
background: T.surface,
|
|
500
|
+
color: T.text,
|
|
501
|
+
width: "100%",
|
|
502
|
+
boxSizing: "border-box",
|
|
503
|
+
fontFamily: "inherit",
|
|
504
|
+
outline: "none",
|
|
505
|
+
transition: "border-color 0.15s",
|
|
506
|
+
...style
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
function Textarea({
|
|
512
|
+
style,
|
|
513
|
+
...rest
|
|
514
|
+
}) {
|
|
515
|
+
return /* @__PURE__ */ jsx(
|
|
516
|
+
"textarea",
|
|
517
|
+
{
|
|
518
|
+
...rest,
|
|
519
|
+
style: {
|
|
520
|
+
fontSize: 13,
|
|
521
|
+
padding: "8px 12px",
|
|
522
|
+
borderRadius: 7,
|
|
523
|
+
border: `1px solid ${T.border}`,
|
|
524
|
+
background: T.surface,
|
|
525
|
+
color: T.text,
|
|
526
|
+
width: "100%",
|
|
527
|
+
boxSizing: "border-box",
|
|
528
|
+
fontFamily: "inherit",
|
|
529
|
+
outline: "none",
|
|
530
|
+
resize: "vertical",
|
|
531
|
+
lineHeight: 1.5,
|
|
532
|
+
...style
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
var ToastContext = createContext(
|
|
538
|
+
() => {
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
function ToastProvider({ children }) {
|
|
542
|
+
const [toasts, setToasts] = useState([]);
|
|
543
|
+
const show = useCallback((type, message) => {
|
|
544
|
+
const id = Date.now() + Math.random();
|
|
545
|
+
setToasts((t) => [...t, { id, type, message }]);
|
|
546
|
+
setTimeout(() => setToasts((t) => t.filter((x) => x.id !== id)), 3500);
|
|
547
|
+
}, []);
|
|
548
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: show, children: [
|
|
549
|
+
children,
|
|
550
|
+
toasts.length > 0 && /* @__PURE__ */ jsx(
|
|
551
|
+
"div",
|
|
552
|
+
{
|
|
553
|
+
style: {
|
|
554
|
+
position: "fixed",
|
|
555
|
+
bottom: 24,
|
|
556
|
+
right: 24,
|
|
557
|
+
zIndex: 2e3,
|
|
558
|
+
display: "flex",
|
|
559
|
+
flexDirection: "column",
|
|
560
|
+
gap: 8
|
|
561
|
+
},
|
|
562
|
+
children: toasts.map((t) => /* @__PURE__ */ jsx(
|
|
563
|
+
"div",
|
|
564
|
+
{
|
|
565
|
+
className: "dsh-toast",
|
|
566
|
+
style: {
|
|
567
|
+
padding: "12px 18px",
|
|
568
|
+
borderRadius: 8,
|
|
569
|
+
fontSize: 13,
|
|
570
|
+
fontWeight: 500,
|
|
571
|
+
boxShadow: "0 6px 20px rgba(0,0,0,0.2)",
|
|
572
|
+
color: "#fff",
|
|
573
|
+
background: t.type === "success" ? T.success : t.type === "error" ? T.error : T.accent,
|
|
574
|
+
maxWidth: 360
|
|
575
|
+
},
|
|
576
|
+
children: t.message
|
|
577
|
+
},
|
|
578
|
+
t.id
|
|
579
|
+
))
|
|
580
|
+
}
|
|
581
|
+
)
|
|
582
|
+
] });
|
|
583
|
+
}
|
|
584
|
+
function useToast() {
|
|
585
|
+
return useContext(ToastContext);
|
|
586
|
+
}
|
|
587
|
+
function SectionTitle({
|
|
588
|
+
icon,
|
|
589
|
+
children,
|
|
590
|
+
actions
|
|
591
|
+
}) {
|
|
592
|
+
return /* @__PURE__ */ jsxs(
|
|
593
|
+
"div",
|
|
594
|
+
{
|
|
595
|
+
style: {
|
|
596
|
+
display: "flex",
|
|
597
|
+
alignItems: "center",
|
|
598
|
+
justifyContent: "space-between",
|
|
599
|
+
marginTop: 6,
|
|
600
|
+
marginBottom: 8
|
|
601
|
+
},
|
|
602
|
+
children: [
|
|
603
|
+
/* @__PURE__ */ jsxs(
|
|
604
|
+
"span",
|
|
605
|
+
{
|
|
606
|
+
style: {
|
|
607
|
+
fontSize: 14,
|
|
608
|
+
fontWeight: 600,
|
|
609
|
+
display: "flex",
|
|
610
|
+
alignItems: "center",
|
|
611
|
+
gap: 6
|
|
612
|
+
},
|
|
613
|
+
children: [
|
|
614
|
+
icon !== void 0 && /* @__PURE__ */ jsx("span", { children: icon }),
|
|
615
|
+
children
|
|
616
|
+
]
|
|
617
|
+
}
|
|
618
|
+
),
|
|
619
|
+
actions
|
|
620
|
+
]
|
|
621
|
+
}
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
var tableStyles = {
|
|
625
|
+
table: {
|
|
626
|
+
borderCollapse: "collapse",
|
|
627
|
+
width: "100%"
|
|
628
|
+
},
|
|
629
|
+
th: {
|
|
630
|
+
textAlign: "left",
|
|
631
|
+
padding: "8px 12px 8px 0",
|
|
632
|
+
fontWeight: 600,
|
|
633
|
+
fontSize: 12,
|
|
634
|
+
color: T.muted,
|
|
635
|
+
borderBottom: `1px solid ${T.border}`
|
|
636
|
+
},
|
|
637
|
+
td: {
|
|
638
|
+
padding: "8px 12px 8px 0",
|
|
639
|
+
fontSize: 13,
|
|
640
|
+
borderBottom: "1px solid rgba(128,128,128,0.08)"
|
|
641
|
+
},
|
|
642
|
+
clickRow: { cursor: "pointer" }
|
|
643
|
+
};
|
|
644
|
+
function Field({
|
|
645
|
+
label,
|
|
646
|
+
children,
|
|
647
|
+
hint
|
|
648
|
+
}) {
|
|
649
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 4 }, children: [
|
|
650
|
+
/* @__PURE__ */ jsx("label", { style: { fontSize: 12, fontWeight: 600, color: T.text }, children: label }),
|
|
651
|
+
children,
|
|
652
|
+
hint !== void 0 && /* @__PURE__ */ jsx("span", { style: { fontSize: 11, color: T.muted }, children: hint })
|
|
653
|
+
] });
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/client/view.tsx
|
|
657
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
658
|
+
function loadPreferences() {
|
|
659
|
+
if (typeof localStorage === "undefined") return { ...DEFAULT_PREFERENCES };
|
|
660
|
+
try {
|
|
661
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
662
|
+
if (raw === null) return { ...DEFAULT_PREFERENCES };
|
|
663
|
+
return { ...DEFAULT_PREFERENCES, ...JSON.parse(raw) };
|
|
664
|
+
} catch {
|
|
665
|
+
return { ...DEFAULT_PREFERENCES };
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function savePreferences(prefs) {
|
|
669
|
+
if (typeof localStorage === "undefined") return;
|
|
670
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs));
|
|
671
|
+
}
|
|
672
|
+
function applyTheme(prefs) {
|
|
673
|
+
if (typeof document === "undefined") return;
|
|
674
|
+
const root = document.documentElement;
|
|
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";
|
|
725
|
+
return /* @__PURE__ */ jsxs2(
|
|
726
|
+
"div",
|
|
727
|
+
{
|
|
728
|
+
onClick,
|
|
729
|
+
style: {
|
|
730
|
+
cursor: "pointer",
|
|
731
|
+
borderRadius: 10,
|
|
732
|
+
padding: 12,
|
|
733
|
+
border: selected ? "2px solid var(--accent, #4B8BBE)" : "1px solid var(--border, rgba(128,128,128,0.2))",
|
|
734
|
+
background: selected ? "rgba(75,139,190,0.06)" : "var(--bg-secondary, rgba(128,128,128,0.04))",
|
|
735
|
+
transition: "all 0.15s ease"
|
|
736
|
+
},
|
|
737
|
+
children: [
|
|
738
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }, children: [
|
|
739
|
+
/* @__PURE__ */ jsx2("div", { style: { width: 16, height: 16, borderRadius: "50%", background: accent, border: "1px solid rgba(128,128,128,0.2)" } }),
|
|
740
|
+
/* @__PURE__ */ jsx2("strong", { style: { fontSize: 13 }, children: preset.name }),
|
|
741
|
+
selected && /* @__PURE__ */ jsx2(Badge, { color: "info", children: "\u2713" })
|
|
742
|
+
] }),
|
|
743
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.6 }, children: preset.description })
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
function Segmented({ value, options, onChange }) {
|
|
749
|
+
return /* @__PURE__ */ jsx2("div", { style: { display: "inline-flex", borderRadius: 8, overflow: "hidden", border: "1px solid var(--border, rgba(128,128,128,0.2))" }, children: options.map((opt) => /* @__PURE__ */ jsx2(
|
|
750
|
+
"button",
|
|
751
|
+
{
|
|
752
|
+
type: "button",
|
|
753
|
+
onClick: () => onChange(opt.value),
|
|
754
|
+
style: {
|
|
755
|
+
cursor: "pointer",
|
|
756
|
+
border: "none",
|
|
757
|
+
padding: "7px 16px",
|
|
758
|
+
fontSize: 13,
|
|
759
|
+
fontWeight: 500,
|
|
760
|
+
fontFamily: "inherit",
|
|
761
|
+
background: value === opt.value ? "var(--accent, #4B8BBE)" : "transparent",
|
|
762
|
+
color: value === opt.value ? "#fff" : "var(--text-primary, inherit)",
|
|
763
|
+
transition: "all 0.15s ease"
|
|
764
|
+
},
|
|
765
|
+
children: opt.label
|
|
766
|
+
},
|
|
767
|
+
opt.value
|
|
768
|
+
)) });
|
|
769
|
+
}
|
|
770
|
+
function PreviewArea({ t }) {
|
|
771
|
+
return /* @__PURE__ */ jsx2(Card, { title: t("preview"), icon: "\u{1F441}", children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: [
|
|
772
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
|
|
773
|
+
/* @__PURE__ */ jsx2(Badge, { color: "info", children: "Info" }),
|
|
774
|
+
/* @__PURE__ */ jsx2(Badge, { color: "success", children: "Success" }),
|
|
775
|
+
/* @__PURE__ */ jsx2(Badge, { color: "warning", children: "Warning" }),
|
|
776
|
+
/* @__PURE__ */ jsx2(Badge, { color: "error", children: "Error" })
|
|
777
|
+
] }),
|
|
778
|
+
/* @__PURE__ */ jsxs2(Card, { children: [
|
|
779
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 13, marginBottom: 8 }, children: t("text") }),
|
|
780
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
|
|
781
|
+
/* @__PURE__ */ jsx2(Button, { variant: "primary", size: "sm", children: t("button") }),
|
|
782
|
+
/* @__PURE__ */ jsx2(Button, { variant: "secondary", size: "sm", children: "Secondary" }),
|
|
783
|
+
/* @__PURE__ */ jsx2(Button, { variant: "danger", size: "sm", children: "Danger" })
|
|
784
|
+
] })
|
|
785
|
+
] })
|
|
786
|
+
] }) });
|
|
787
|
+
}
|
|
788
|
+
function ThemePanelInner({ t }) {
|
|
789
|
+
const toast = useToast();
|
|
790
|
+
const [prefs, setPrefs] = useState2(loadPreferences);
|
|
791
|
+
const [confirmReset, setConfirmReset] = useState2(false);
|
|
792
|
+
useEffect2(() => {
|
|
793
|
+
applyTheme(prefs);
|
|
794
|
+
savePreferences(prefs);
|
|
795
|
+
}, [prefs]);
|
|
796
|
+
const update = useCallback2((key, value) => {
|
|
797
|
+
setPrefs((p) => ({ ...p, [key]: value }));
|
|
798
|
+
}, []);
|
|
799
|
+
const handleReset = useCallback2(() => {
|
|
800
|
+
clearTheme();
|
|
801
|
+
setPrefs({ ...DEFAULT_PREFERENCES });
|
|
802
|
+
toast("success", t("resetted"));
|
|
803
|
+
}, [t, toast]);
|
|
804
|
+
return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 820 }, children: [
|
|
805
|
+
/* @__PURE__ */ jsxs2("header", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
|
|
806
|
+
/* @__PURE__ */ jsxs2("strong", { style: { fontSize: 15 }, children: [
|
|
807
|
+
"\u{1F3A8} ",
|
|
808
|
+
t("title")
|
|
809
|
+
] }),
|
|
810
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: 12, opacity: 0.6 }, children: t("subtitle") }),
|
|
811
|
+
/* @__PURE__ */ jsx2("span", { style: { flex: 1 } }),
|
|
812
|
+
/* @__PURE__ */ jsxs2(Button, { variant: "danger", size: "sm", onClick: () => setConfirmReset(true), children: [
|
|
813
|
+
"\u21BA ",
|
|
814
|
+
t("reset")
|
|
815
|
+
] })
|
|
816
|
+
] }),
|
|
817
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F3AD}", children: t("presetSection") }),
|
|
818
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: 10 }, children: [
|
|
819
|
+
/* @__PURE__ */ jsx2(
|
|
820
|
+
PresetCard,
|
|
821
|
+
{
|
|
822
|
+
preset: { id: "none", name: t("noPreset"), description: "", tokens: {} },
|
|
823
|
+
selected: prefs.preset === null,
|
|
824
|
+
onClick: () => update("preset", null)
|
|
825
|
+
}
|
|
826
|
+
),
|
|
827
|
+
PRESETS.map((p) => /* @__PURE__ */ jsx2(
|
|
828
|
+
PresetCard,
|
|
829
|
+
{
|
|
830
|
+
preset: p,
|
|
831
|
+
selected: prefs.preset === p.id,
|
|
832
|
+
onClick: () => update("preset", p.id)
|
|
833
|
+
},
|
|
834
|
+
p.id
|
|
835
|
+
))
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F308}", children: t("accentSection") }),
|
|
838
|
+
/* @__PURE__ */ jsxs2(Card, { children: [
|
|
839
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: [
|
|
840
|
+
/* @__PURE__ */ jsx2(
|
|
841
|
+
"input",
|
|
842
|
+
{
|
|
843
|
+
type: "color",
|
|
844
|
+
value: prefs.accentColor ?? "#4B8BBE",
|
|
845
|
+
onChange: (e) => update("accentColor", e.target.value),
|
|
846
|
+
style: { width: 40, height: 40, cursor: "pointer", borderRadius: 8, border: "1px solid var(--border, rgba(128,128,128,0.2))" }
|
|
847
|
+
}
|
|
848
|
+
),
|
|
849
|
+
/* @__PURE__ */ jsx2(
|
|
850
|
+
Input,
|
|
851
|
+
{
|
|
852
|
+
value: prefs.accentColor ?? "",
|
|
853
|
+
onChange: (e) => update("accentColor", e.target.value || null),
|
|
854
|
+
placeholder: t("accentPlaceholder"),
|
|
855
|
+
style: { maxWidth: 200 }
|
|
856
|
+
}
|
|
857
|
+
),
|
|
858
|
+
prefs.accentColor !== null && /* @__PURE__ */ jsx2(Button, { variant: "ghost", size: "sm", onClick: () => update("accentColor", null), children: "\u2715" })
|
|
859
|
+
] }),
|
|
860
|
+
/* @__PURE__ */ jsx2("div", { style: { fontSize: 11, opacity: 0.5, marginTop: 6 }, children: t("accentHint") })
|
|
861
|
+
] }),
|
|
862
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F4D0}", children: t("densitySection") }),
|
|
863
|
+
/* @__PURE__ */ jsx2(
|
|
864
|
+
Segmented,
|
|
865
|
+
{
|
|
866
|
+
value: prefs.density,
|
|
867
|
+
onChange: (v) => update("density", v),
|
|
868
|
+
options: [
|
|
869
|
+
{ value: "compact", label: t("densityCompact") },
|
|
870
|
+
{ value: "comfortable", label: t("densityComfortable") },
|
|
871
|
+
{ value: "spacious", label: t("densitySpacious") }
|
|
872
|
+
]
|
|
873
|
+
}
|
|
874
|
+
),
|
|
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
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u{1F524}", children: t("fontSection") }),
|
|
889
|
+
/* @__PURE__ */ jsx2(
|
|
890
|
+
Segmented,
|
|
891
|
+
{
|
|
892
|
+
value: prefs.fontFamily,
|
|
893
|
+
onChange: (v) => update("fontFamily", v),
|
|
894
|
+
options: [
|
|
895
|
+
{ value: "system", label: t("fontSystem") },
|
|
896
|
+
{ value: "mono", label: t("fontMono") },
|
|
897
|
+
{ value: "serif", label: t("fontSerif") }
|
|
898
|
+
]
|
|
899
|
+
}
|
|
900
|
+
),
|
|
901
|
+
/* @__PURE__ */ jsx2(SectionTitle, { icon: "\u270F\uFE0F", children: t("customCssSection") }),
|
|
902
|
+
/* @__PURE__ */ jsx2(Card, { children: /* @__PURE__ */ jsx2(Field, { label: t("customCssSection"), hint: t("customCssHint"), children: /* @__PURE__ */ jsx2(
|
|
903
|
+
Textarea,
|
|
904
|
+
{
|
|
905
|
+
value: prefs.customCss,
|
|
906
|
+
onChange: (e) => update("customCss", e.target.value),
|
|
907
|
+
placeholder: t("customCssPlaceholder"),
|
|
908
|
+
rows: 4
|
|
909
|
+
}
|
|
910
|
+
) }) }),
|
|
911
|
+
/* @__PURE__ */ jsx2(PreviewArea, { t }),
|
|
912
|
+
confirmReset && /* @__PURE__ */ jsx2(
|
|
913
|
+
ConfirmDialog,
|
|
914
|
+
{
|
|
915
|
+
title: t("reset"),
|
|
916
|
+
message: t("resetConfirm"),
|
|
917
|
+
confirmLabel: t("reset"),
|
|
918
|
+
danger: true,
|
|
919
|
+
onConfirm: handleReset,
|
|
920
|
+
onClose: () => setConfirmReset(false)
|
|
921
|
+
}
|
|
922
|
+
)
|
|
923
|
+
] });
|
|
924
|
+
}
|
|
925
|
+
function ThemePanel({ t }) {
|
|
926
|
+
return /* @__PURE__ */ jsx2(ToastProvider, { children: /* @__PURE__ */ jsx2(ThemePanelInner, { t }) });
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/client/index.tsx
|
|
930
|
+
var inject = ["slots", "locale"];
|
|
931
|
+
function apply(ctx) {
|
|
932
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-theme-studio: dictionaries");
|
|
933
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register(
|
|
934
|
+
{
|
|
935
|
+
name: "settings.section",
|
|
936
|
+
id: "theme-studio",
|
|
937
|
+
order: 47,
|
|
938
|
+
label: () => ctx.locale.bind(NS)("nav"),
|
|
939
|
+
locale: NS
|
|
940
|
+
},
|
|
941
|
+
ThemePanel
|
|
942
|
+
));
|
|
943
|
+
}
|
|
944
|
+
export {
|
|
945
|
+
apply,
|
|
946
|
+
inject
|
|
947
|
+
};
|
package/lib/themes.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-built theme presets for dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* Each preset overrides a set of CSS custom properties on document.documentElement.
|
|
5
|
+
* The property names target dsh's `--dsw-alias-*` semantic layer so the overrides
|
|
6
|
+
* cascade through every component that reads the design tokens.
|
|
7
|
+
*
|
|
8
|
+
* @module themes
|
|
9
|
+
*/
|
|
10
|
+
import type { ThemePreset } from './types.js';
|
|
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
|
+
/** Look up a preset by id. */
|
|
19
|
+
export declare function getPreset(id: string): ThemePreset | undefined;
|
package/lib/themes.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-built theme presets for dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* Each preset overrides a set of CSS custom properties on document.documentElement.
|
|
5
|
+
* The property names target dsh's `--dsw-alias-*` semantic layer so the overrides
|
|
6
|
+
* cascade through every component that reads the design tokens.
|
|
7
|
+
*
|
|
8
|
+
* @module themes
|
|
9
|
+
*/
|
|
10
|
+
export const PRESETS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'ocean',
|
|
13
|
+
name: 'Ocean',
|
|
14
|
+
description: 'Deep blue accent with cool neutrals',
|
|
15
|
+
tokens: {
|
|
16
|
+
'--accent': '#2563eb',
|
|
17
|
+
'--accent-hover': '#1d4ed8',
|
|
18
|
+
'--dsw-alias-state-business-primary': '#2563eb',
|
|
19
|
+
'--dsw-alias-state-business-secondary': '#dbeafe',
|
|
20
|
+
'--dsw-alias-interactive-bg-hover': 'rgba(37,99,235,0.08)',
|
|
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
|
+
},
|
|
83
|
+
];
|
|
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
|
+
/** Look up a preset by id. */
|
|
124
|
+
export function getPreset(id) {
|
|
125
|
+
return PRESETS.find((p) => p.id === id);
|
|
126
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* @module types
|
|
5
|
+
*/
|
|
6
|
+
/** A named preset that overrides a set of CSS custom properties. */
|
|
7
|
+
export interface ThemePreset {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
/** CSS custom property → value, applied to document.documentElement.style */
|
|
12
|
+
tokens: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
/** User-selectable density preference. */
|
|
15
|
+
export type Density = 'compact' | 'comfortable' | 'spacious';
|
|
16
|
+
/** User-selectable border-radius preference. */
|
|
17
|
+
export type Radius = 'sharp' | 'rounded' | 'soft';
|
|
18
|
+
/** User-selectable font family preference. */
|
|
19
|
+
export type FontFamily = 'system' | 'mono' | 'serif';
|
|
20
|
+
/** Complete theme preferences persisted in localStorage. */
|
|
21
|
+
export interface ThemePreferences {
|
|
22
|
+
preset: string | null;
|
|
23
|
+
accentColor: string | null;
|
|
24
|
+
density: Density;
|
|
25
|
+
radius: Radius;
|
|
26
|
+
fontFamily: FontFamily;
|
|
27
|
+
customCss: string;
|
|
28
|
+
}
|
|
29
|
+
/** Default preferences — nothing overridden, dsh's built-in theme wins. */
|
|
30
|
+
export declare const DEFAULT_PREFERENCES: ThemePreferences;
|
|
31
|
+
/** localStorage key for persisting preferences. */
|
|
32
|
+
export declare const STORAGE_KEY = "dsh-theme-studio";
|
package/lib/types.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for dsh-theme-studio.
|
|
3
|
+
*
|
|
4
|
+
* @module types
|
|
5
|
+
*/
|
|
6
|
+
/** Default preferences — nothing overridden, dsh's built-in theme wins. */
|
|
7
|
+
export const DEFAULT_PREFERENCES = {
|
|
8
|
+
preset: null,
|
|
9
|
+
accentColor: null,
|
|
10
|
+
density: 'comfortable',
|
|
11
|
+
radius: 'rounded',
|
|
12
|
+
fontFamily: 'system',
|
|
13
|
+
customCss: '',
|
|
14
|
+
};
|
|
15
|
+
/** localStorage key for persisting preferences. */
|
|
16
|
+
export const STORAGE_KEY = 'dsh-theme-studio';
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-theme-studio",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Customize the dsh UI theme: pick presets, set accent colors, adjust density, border radius, and font family, or write custom CSS variable overrides.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./lib/client.d.ts",
|
|
15
|
+
"default": "./lib/theme-studio.web.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": ["lib", "cordis.patch.yml", "README.md", "CHANGELOG.md"],
|
|
20
|
+
"keywords": ["dsh", "dsh-plugin", "deepseek-harness", "theme", "appearance", "customization"],
|
|
21
|
+
"dsh": {
|
|
22
|
+
"bundle": { "patch": "./cordis.patch.yml" },
|
|
23
|
+
"client": {
|
|
24
|
+
"platform": "web",
|
|
25
|
+
"inject": [
|
|
26
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
27
|
+
"@deepseek-ai/dsh-client-locale",
|
|
28
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"author": "hj01857655",
|
|
34
|
+
"repository": { "type": "git", "url": "git+https://github.com/hj01857655/dsh-theme-studio.git" },
|
|
35
|
+
"bugs": { "url": "https://github.com/hj01857655/dsh-theme-studio/issues" },
|
|
36
|
+
"homepage": "https://github.com/hj01857655/dsh-theme-studio#readme",
|
|
37
|
+
"engines": { "node": ">=20" },
|
|
38
|
+
"peerDependencies": { "@deepseek-ai/cordis": "^4.0.1" },
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
41
|
+
"@types/node": "^22.20.3",
|
|
42
|
+
"@types/react": "^19.3.0",
|
|
43
|
+
"esbuild": "^0.28.2",
|
|
44
|
+
"react": "^19.3.0",
|
|
45
|
+
"react-dom": "^19.3.0",
|
|
46
|
+
"typescript": "^5.6.0"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.json && node scripts/bundle-client.mjs",
|
|
50
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json",
|
|
51
|
+
"test": "tsc -p tsconfig.json && node --test",
|
|
52
|
+
"prepublishOnly": "npm run build && npm test"
|
|
53
|
+
}
|
|
54
|
+
}
|