@rokkit/cli 1.1.14 → 1.1.15
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/dist/src/init.d.ts +16 -19
- package/package.json +2 -1
- package/skills/skin-system-rokkit/SKILL.md +224 -0
- package/src/doctor.js +10 -3
- package/src/init.js +39 -59
package/dist/src/init.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derive a safe localStorage key from an npm package name.
|
|
3
|
+
* Strips the scope, keeps case, maps unsafe chars to '-'. Returns '' when there
|
|
4
|
+
* is no usable name — the caller then omits `storageKey` rather than baking in a
|
|
5
|
+
* hardcoded default (the consumer config / library fallback decides instead).
|
|
6
|
+
* @param {string} [name]
|
|
7
|
+
* @returns {string}
|
|
8
|
+
*/
|
|
9
|
+
export function storageKeyFromName(name?: string): string;
|
|
1
10
|
/**
|
|
2
11
|
* Serialize a rokkit config object to a JS module string with a header comment.
|
|
3
12
|
* @param {Record<string, unknown>} config
|
|
@@ -31,7 +40,8 @@ export function generateChartConfig({ chartColors, chartShades }: {
|
|
|
31
40
|
/**
|
|
32
41
|
* Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
|
|
33
42
|
* @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
|
|
34
|
-
* includeChart?: boolean, chartColors?: string, chartShades?: string
|
|
43
|
+
* includeChart?: boolean, chartColors?: string, chartShades?: string,
|
|
44
|
+
* storageKey?: string }} [opts]
|
|
35
45
|
* @returns {Record<string, unknown>}
|
|
36
46
|
*/
|
|
37
47
|
export function generateZenSumiConfig(opts?: {
|
|
@@ -41,6 +51,7 @@ export function generateZenSumiConfig(opts?: {
|
|
|
41
51
|
includeChart?: boolean;
|
|
42
52
|
chartColors?: string;
|
|
43
53
|
chartShades?: string;
|
|
54
|
+
storageKey?: string;
|
|
44
55
|
}): Record<string, unknown>;
|
|
45
56
|
/**
|
|
46
57
|
* Build a Rokkit config object from the user's init choices.
|
|
@@ -48,10 +59,11 @@ export function generateZenSumiConfig(opts?: {
|
|
|
48
59
|
* optional `icons` and `chart` sections.
|
|
49
60
|
* @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
|
|
50
61
|
* iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
|
|
51
|
-
* switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string
|
|
62
|
+
* switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string,
|
|
63
|
+
* storageKey?: string }} opts
|
|
52
64
|
* @returns {Record<string, unknown>}
|
|
53
65
|
*/
|
|
54
|
-
export function generateConfig({ palette, customColors, icons, iconPath, iconStyle, themes, defaultTheme, switcher, includeChart, chartColors, chartShades }: {
|
|
66
|
+
export function generateConfig({ palette, customColors, icons, iconPath, iconStyle, themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey }: {
|
|
55
67
|
palette: string;
|
|
56
68
|
customColors?: Record<string, string>;
|
|
57
69
|
icons?: string;
|
|
@@ -63,6 +75,7 @@ export function generateConfig({ palette, customColors, icons, iconPath, iconSty
|
|
|
63
75
|
includeChart?: boolean;
|
|
64
76
|
chartColors?: string;
|
|
65
77
|
chartShades?: string;
|
|
78
|
+
storageKey?: string;
|
|
66
79
|
}): Record<string, unknown>;
|
|
67
80
|
/**
|
|
68
81
|
* Generate the content for uno.config.js.
|
|
@@ -75,22 +88,6 @@ export function generateUnoConfig(): string;
|
|
|
75
88
|
* @returns {string[]}
|
|
76
89
|
*/
|
|
77
90
|
export function generateAppCssImports(themes: string[]): string[];
|
|
78
|
-
/**
|
|
79
|
-
* Generate the flash-prevention script for app.html.
|
|
80
|
-
*
|
|
81
|
-
* Themes flip on `[data-mode="dark"]` only — there is no `@media
|
|
82
|
-
* (prefers-color-scheme)` fallback — so every switcher needs a script that
|
|
83
|
-
* resolves the mode pre-paint (mirrors `@rokkit/unocss`'s `themeHook`):
|
|
84
|
-
* - `system` tracks the OS preference directly (no persisted toggle).
|
|
85
|
-
* - `manual` / `full` read the persisted mode and resolve a stored-or-default
|
|
86
|
-
* `'system'`/`'auto'` against the OS preference (default: follow the OS).
|
|
87
|
-
*
|
|
88
|
-
* @param {string} switcher — 'system' | 'manual' | 'full'
|
|
89
|
-
* @param {string} [storageKey='rokkit-theme']
|
|
90
|
-
* @param {string} [defaultStyle='rokkit']
|
|
91
|
-
* @returns {string} the `<script>` tag to inject after `<body>`
|
|
92
|
-
*/
|
|
93
|
-
export function generateInitScript(switcher: string, storageKey?: string, defaultStyle?: string): string;
|
|
94
91
|
/**
|
|
95
92
|
* Interactive init command — prompts the user, writes config files.
|
|
96
93
|
* @param {Record<string, unknown>} [_opts]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "Command line utilities for icon bundling and conversion.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@mdi/svg": "^7.4.47",
|
|
30
|
+
"@rokkit/unocss": "1.1.15",
|
|
30
31
|
"sade": "^1.8.1",
|
|
31
32
|
"@iconify/tools": "5.0.3",
|
|
32
33
|
"@iconify/types": "^2.0.0",
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skin-system-rokkit
|
|
3
|
+
description: Use when switching color skins (which palette backs paper/ink/primary/accent) in a Rokkit (Svelte 5) app — the vibe.skin state, the data-skin mechanism, themable sync, defining skins in rokkit.config.js (skins:), built-in skins, SkinSwitcherToggle, and the skinnable action for dynamic/runtime skins.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rokkit Skin System
|
|
7
|
+
|
|
8
|
+
A **skin** is a named colormap — it selects which palette fills each semantic role
|
|
9
|
+
(`surface`, `ink`, `primary`, `accent`, …). Swapping the skin changes the entire palette
|
|
10
|
+
backing `--paper`, `--ink`, `--primary`, `--accent`, and all their derivatives. Named tokens
|
|
11
|
+
(`bg-paper`, `text-ink`, `bg-primary`) keep working unchanged; only the palette under them
|
|
12
|
+
shifts.
|
|
13
|
+
|
|
14
|
+
**Skin vs style vs mode:**
|
|
15
|
+
|
|
16
|
+
| Dimension | Controls | Example values |
|
|
17
|
+
|-----------|----------|----------------|
|
|
18
|
+
| **skin** | which palette backs the roles | `default` · `ocean` · `violet` |
|
|
19
|
+
| **style** | which visual theme (border radius, shadow, surface recipe) | `rokkit` · `minimal` · `zen-sumi` |
|
|
20
|
+
| **mode** | light or dark rendering | `light` · `dark` |
|
|
21
|
+
|
|
22
|
+
These three are orthogonal — a skin switch does not change the style or mode.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Defining skins
|
|
27
|
+
|
|
28
|
+
Declare named skins in `rokkit.config.js` under `skins:`. Each key is a skin name; each
|
|
29
|
+
value maps role names to palette names. **Use `skins:` OR `skin:`, not both** — `skins:`
|
|
30
|
+
wins when both are present.
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
// rokkit.config.js
|
|
34
|
+
export default {
|
|
35
|
+
skins: {
|
|
36
|
+
default: {
|
|
37
|
+
surface: { light: 'kami', dark: 'sumi' }, // dual-palette → emits dark block
|
|
38
|
+
ink: { light: 'kami', dark: 'sumi' },
|
|
39
|
+
primary: 'shu',
|
|
40
|
+
accent: 'shu',
|
|
41
|
+
success: 'hisui', warning: 'kohaku', danger: 'shu', error: 'shu', info: 'kohaku',
|
|
42
|
+
},
|
|
43
|
+
ocean: { surface: 'slate', ink: 'slate', primary: 'sky', accent: 'teal' },
|
|
44
|
+
violet: { surface: 'zinc', ink: 'zinc', primary: 'violet', accent: 'indigo' },
|
|
45
|
+
rose: { surface: 'stone', ink: 'stone', primary: 'rose', accent: 'orange' },
|
|
46
|
+
emerald: { surface: 'slate', ink: 'slate', primary: 'emerald', accent: 'cyan' },
|
|
47
|
+
},
|
|
48
|
+
// ... palettes, colorSpace, tokens, etc.
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Dual-palette roles** (`{ light, dark }`) tell the preset to emit both a light block and a
|
|
53
|
+
`[data-mode='dark']` override block. Single-palette roles (e.g. `primary: 'shu'`) stay
|
|
54
|
+
constant across modes — correct for brand accents.
|
|
55
|
+
|
|
56
|
+
### Built-in skins (available out of the box)
|
|
57
|
+
|
|
58
|
+
The preset ships five built-in skins in `@rokkit/unocss`. Their `[data-skin='name']` CSS
|
|
59
|
+
blocks are emitted automatically — no config needed to use them:
|
|
60
|
+
|
|
61
|
+
| Skin | Surface | Ink | Primary | Accent |
|
|
62
|
+
|------|---------|-----|---------|--------|
|
|
63
|
+
| `default` | (DEFAULT_THEME_MAPPING) | `slate` | — | — |
|
|
64
|
+
| `ocean` | `slate` | `slate` | `sky` | `teal` |
|
|
65
|
+
| `violet` | `zinc` | `zinc` | `violet` | `indigo` |
|
|
66
|
+
| `rose` | `stone` | `stone` | `rose` | `orange` |
|
|
67
|
+
| `emerald` | `slate` | `slate` | `emerald` | `cyan` |
|
|
68
|
+
|
|
69
|
+
Consumer `skins:` in `rokkit.config.js` merge on top and can override or extend these.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Switching at runtime
|
|
74
|
+
|
|
75
|
+
Three moving parts: **`vibe.skin`** (reactive state), **`themable`** (writes `data-skin` to
|
|
76
|
+
DOM), **`SkinSwitcherToggle`** (ready-made control).
|
|
77
|
+
|
|
78
|
+
### 1. Wire once in the root layout
|
|
79
|
+
|
|
80
|
+
Set `vibe.allowedSkins` **before** `themable` runs — persisted skin can only restore if the
|
|
81
|
+
value is already in the allowed list when `themable` calls `theme.load(storageKey)`.
|
|
82
|
+
|
|
83
|
+
```svelte
|
|
84
|
+
<!-- src/routes/+layout.svelte -->
|
|
85
|
+
<script>
|
|
86
|
+
import { vibe } from '@rokkit/states'
|
|
87
|
+
import { themable } from '@rokkit/actions'
|
|
88
|
+
|
|
89
|
+
// MUST be set before themable so a persisted skin restores correctly.
|
|
90
|
+
vibe.allowedSkins = ['default', 'ocean', 'violet', 'rose', 'emerald']
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
<svelte:body use:themable={{ theme: vibe, storageKey: 'app-theme' }} />
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`themable` writes `data-skin={vibe.skin}` on `<body>` (and mirrors to `<html>`) reactively.
|
|
97
|
+
When `storageKey` is provided it also loads/saves the persisted skin.
|
|
98
|
+
|
|
99
|
+
### 2. Drop in the toggle
|
|
100
|
+
|
|
101
|
+
```svelte
|
|
102
|
+
<script>
|
|
103
|
+
import { SkinSwitcherToggle } from '@rokkit/app'
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<!-- Reads vibe.allowedSkins by default -->
|
|
107
|
+
<SkinSwitcherToggle />
|
|
108
|
+
|
|
109
|
+
<!-- Or pass an explicit list (same names as skins: in config) -->
|
|
110
|
+
<SkinSwitcherToggle skins={['default', 'ocean', 'violet', 'rose', 'emerald']} />
|
|
111
|
+
|
|
112
|
+
<!-- With labels -->
|
|
113
|
+
<SkinSwitcherToggle
|
|
114
|
+
skins={[
|
|
115
|
+
{ name: 'default', label: 'Zen Sumi' },
|
|
116
|
+
{ name: 'ocean', label: 'Ocean' },
|
|
117
|
+
{ name: 'violet', label: 'Violet' },
|
|
118
|
+
]}
|
|
119
|
+
showLabels
|
|
120
|
+
size="md"
|
|
121
|
+
/>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**`SkinSwitcherToggle` props:**
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Default | Purpose |
|
|
127
|
+
|------|------|---------|---------|
|
|
128
|
+
| `skins` | `Array<string \| { name, label?, icon? }>` | `vibe.allowedSkins` | skin options |
|
|
129
|
+
| `labels` | `Record<string, string>` | `{}` | display labels keyed by name |
|
|
130
|
+
| `showLabels` | `boolean` | `false` | show label text in the toggle |
|
|
131
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | control size |
|
|
132
|
+
| `disabled` | `boolean` | `false` | disable the control |
|
|
133
|
+
| `onchange` | `(skin: string) => void` | — | callback after switching |
|
|
134
|
+
|
|
135
|
+
The component sets `vibe.skin` directly — no extra wiring needed.
|
|
136
|
+
|
|
137
|
+
### 3. Switch programmatically
|
|
138
|
+
|
|
139
|
+
```svelte
|
|
140
|
+
<button onclick={() => (vibe.skin = 'ocean')}>Ocean</button>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`vibe.skin`'s setter validates against `vibe.allowedSkins` — the assignment is a no-op if
|
|
144
|
+
the skin name is not in the allowed list.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## How the CSS works
|
|
149
|
+
|
|
150
|
+
The UnoCSS preset emits one CSS block per skin:
|
|
151
|
+
|
|
152
|
+
```css
|
|
153
|
+
/* default skin on :root — always active as fallback */
|
|
154
|
+
:root {
|
|
155
|
+
--paper: oklch(0.985 0.005 85);
|
|
156
|
+
--ink: oklch(0.220 0.012 50);
|
|
157
|
+
--primary: oklch(0.580 0.150 35);
|
|
158
|
+
/* ... all named tokens ... */
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* named skin blocks — activated by data-skin attribute */
|
|
162
|
+
[data-skin='ocean'] {
|
|
163
|
+
--paper: /* slate.50 */;
|
|
164
|
+
--ink: /* slate.900 */;
|
|
165
|
+
--primary: /* sky.500 */;
|
|
166
|
+
/* ... */
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* dark mode override for dual-palette skins (single compound selector) */
|
|
170
|
+
[data-mode='dark'][data-skin='ocean'] {
|
|
171
|
+
--paper: /* slate.950 (inverted) */;
|
|
172
|
+
--ink: /* slate.50 (inverted) */;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`themable` keeps `data-skin` on `<body>` (and `<html>`) in sync with `vibe.skin`. CSS
|
|
177
|
+
specificity does the rest — no JavaScript skin loops, no `classList` juggling.
|
|
178
|
+
|
|
179
|
+
**The old `skin-{name}` class is removed.** Do not use `class="skin-ocean"` — it no longer
|
|
180
|
+
exists. `data-skin='ocean'` is the only mechanism.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Dynamic / custom skins (skinnable)
|
|
185
|
+
|
|
186
|
+
For skins that are not pre-baked in config (server-configured palettes, user-customized
|
|
187
|
+
color pickers, multi-tenant theming), use the `skinnable` action from `@rokkit/actions`.
|
|
188
|
+
It applies CSS custom properties inline on a specific element, scoped to that element's
|
|
189
|
+
subtree.
|
|
190
|
+
|
|
191
|
+
```svelte
|
|
192
|
+
<script>
|
|
193
|
+
import { skinnable } from '@rokkit/actions'
|
|
194
|
+
|
|
195
|
+
// varMap: CSS var name → complete color value
|
|
196
|
+
const brandVars = {
|
|
197
|
+
'--primary': 'oklch(0.60 0.18 260)',
|
|
198
|
+
'--accent': 'oklch(0.55 0.15 200)',
|
|
199
|
+
'--paper': 'oklch(0.97 0.005 260)',
|
|
200
|
+
'--ink': 'oklch(0.22 0.010 260)',
|
|
201
|
+
}
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<div use:skinnable={brandVars}>
|
|
205
|
+
<!-- descendants resolve CSS vars from inline styles -->
|
|
206
|
+
</div>
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`skinnable` is complementary to the `data-skin` cascade, not a replacement. Use
|
|
210
|
+
`vibe.skin` + `themable` for app-wide named skins; use `skinnable` for scoped or
|
|
211
|
+
runtime-only color overrides.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Common mistakes
|
|
216
|
+
|
|
217
|
+
| Mistake | Why it fails | Fix |
|
|
218
|
+
|---------|-------------|-----|
|
|
219
|
+
| `vibe.allowedSkins` set after `themable` runs | persisted skin can't restore; `vibe.skin = storedValue` is a no-op because the list defaults to `['default']` only | set `vibe.allowedSkins` before `<svelte:body use:themable ...>` in the root layout |
|
|
220
|
+
| `class="skin-ocean"` on an element | `skin-{name}` class was removed | use `data-skin='ocean'` attribute, or let `themable` write it via `vibe.skin` |
|
|
221
|
+
| Hand-rolling `<style>` injection for a skin | brittle, order-dependent, bypasses named-token aliases | define in `rokkit.config.js` `skins:` + let the preset emit `[data-skin='name']` blocks |
|
|
222
|
+
| `vibe.skin = 'ocean'` with no `allowedSkins` set | setter rejects unknown values | set `vibe.allowedSkins = ['default', 'ocean', ...]` first |
|
|
223
|
+
| `skins:` and `skin:` both in config | `skins:` silently wins; `skin:` is ignored | pick one |
|
|
224
|
+
| Single-palette `surface` in a skin (`surface: 'slate'`) expecting dark-mode flip | no `[data-mode='dark']` block is emitted for single-palette roles | use dual-palette `surface: { light: 'kami', dark: 'sumi' }` for dark support |
|
package/src/doctor.js
CHANGED
|
@@ -5,11 +5,11 @@ import { loadConfig as loadParsedConfig } from './config.js'
|
|
|
5
5
|
import {
|
|
6
6
|
generateUnoConfig,
|
|
7
7
|
generateAppCssImports,
|
|
8
|
-
generateInitScript,
|
|
9
8
|
generateChartConfig,
|
|
10
9
|
generateConfig,
|
|
11
10
|
serializeRokkitConfig
|
|
12
11
|
} from './init.js'
|
|
12
|
+
import { themeInitScript } from '@rokkit/unocss/hooks'
|
|
13
13
|
|
|
14
14
|
const KNOWN_THEMES = [
|
|
15
15
|
'rokkit',
|
|
@@ -220,8 +220,15 @@ function applyPatchHtml(cwd, label) {
|
|
|
220
220
|
const htmlPath = resolve(cwd, 'src/app.html')
|
|
221
221
|
if (!existsSync(htmlPath)) return false
|
|
222
222
|
const html = readFileSync(htmlPath, 'utf-8')
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
// Use the project's configured storageKey so the injected script reads the
|
|
224
|
+
// same localStorage slot the app persists to — don't hardcode a key.
|
|
225
|
+
const configPath = resolve(cwd, 'rokkit.config.js')
|
|
226
|
+
const storageKey = existsSync(configPath)
|
|
227
|
+
? readFileSync(configPath, 'utf-8').match(/storageKey:\s*['"]([^'"]+)['"]/)?.[1]
|
|
228
|
+
: undefined
|
|
229
|
+
const marker = storageKey || 'rokkit-theme'
|
|
230
|
+
if (html.includes(marker)) return false
|
|
231
|
+
const script = themeInitScript({ storageKey })
|
|
225
232
|
const patched = html.replace(/(<body[^>]*>)/, `$1\n${script}`)
|
|
226
233
|
writeFileSync(htmlPath, patched)
|
|
227
234
|
console.info(` Fixed: ${label}`)
|
package/src/init.js
CHANGED
|
@@ -4,6 +4,7 @@ import { writeFileSync, readFileSync, existsSync } from 'fs'
|
|
|
4
4
|
import { resolve } from 'path'
|
|
5
5
|
import { execFileSync } from 'child_process'
|
|
6
6
|
import { detectPackageManager, buildInstallCommand } from './upgrade.js'
|
|
7
|
+
import { themeInitScript } from '@rokkit/unocss/hooks'
|
|
7
8
|
|
|
8
9
|
const ROKKIT_PACKAGES = ['@rokkit/ui', '@rokkit/unocss', '@rokkit/themes', '@rokkit/icons']
|
|
9
10
|
const LOCKFILES = ['bun.lock', 'bun.lockb', 'pnpm-lock.yaml', 'yarn.lock', 'package-lock.json']
|
|
@@ -25,6 +26,9 @@ const NAMED_TOKEN_HEADER = `/**
|
|
|
25
26
|
*
|
|
26
27
|
* tokens: 'core' emits the named vocabulary; 'extended' also emits the full
|
|
27
28
|
* 11-shade palette ladder per role (for charts / data-viz).
|
|
29
|
+
*
|
|
30
|
+
* Also: \`skins\` (named alternates → \`[data-skin]\` blocks), \`switcher\`
|
|
31
|
+
* ('system'|'manual'|'full'), \`storageKey\`, \`shape\`, \`typography\`.
|
|
28
32
|
*/
|
|
29
33
|
`
|
|
30
34
|
|
|
@@ -34,6 +38,21 @@ const OKLCH_PALETTES_NOTE = `/**
|
|
|
34
38
|
*/
|
|
35
39
|
`
|
|
36
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Derive a safe localStorage key from an npm package name.
|
|
43
|
+
* Strips the scope, keeps case, maps unsafe chars to '-'. Returns '' when there
|
|
44
|
+
* is no usable name — the caller then omits `storageKey` rather than baking in a
|
|
45
|
+
* hardcoded default (the consumer config / library fallback decides instead).
|
|
46
|
+
* @param {string} [name]
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
export function storageKeyFromName(name) {
|
|
50
|
+
return String(name ?? '')
|
|
51
|
+
.replace(/^@/, '')
|
|
52
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
53
|
+
.replace(/^-+|-+$/g, '')
|
|
54
|
+
}
|
|
55
|
+
|
|
37
56
|
/**
|
|
38
57
|
* Serialize a rokkit config object to a JS module string with a header comment.
|
|
39
58
|
* @param {Record<string, unknown>} config
|
|
@@ -201,11 +220,12 @@ export function generateChartConfig({ chartColors, chartShades }) {
|
|
|
201
220
|
/**
|
|
202
221
|
* Build the Zen-Sumi OKLCH starter (ink-on-paper, dual-palette dark mode).
|
|
203
222
|
* @param {{ themes?: string[], defaultTheme?: string, switcher?: string,
|
|
204
|
-
* includeChart?: boolean, chartColors?: string, chartShades?: string
|
|
223
|
+
* includeChart?: boolean, chartColors?: string, chartShades?: string,
|
|
224
|
+
* storageKey?: string }} [opts]
|
|
205
225
|
* @returns {Record<string, unknown>}
|
|
206
226
|
*/
|
|
207
227
|
export function generateZenSumiConfig(opts = {}) {
|
|
208
|
-
const { themes, defaultTheme, switcher, includeChart, chartColors, chartShades } = opts
|
|
228
|
+
const { themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey } = opts
|
|
209
229
|
const config = {
|
|
210
230
|
palettes: ZEN_SUMI_PALETTES,
|
|
211
231
|
colorSpace: 'oklch',
|
|
@@ -230,7 +250,7 @@ export function generateZenSumiConfig(opts = {}) {
|
|
|
230
250
|
themes: themes && themes.length ? themes : ['rokkit', 'zen-sumi'],
|
|
231
251
|
defaultTheme: defaultTheme || 'zen-sumi',
|
|
232
252
|
switcher: switcher || 'full',
|
|
233
|
-
storageKey:
|
|
253
|
+
...(storageKey ? { storageKey } : {})
|
|
234
254
|
}
|
|
235
255
|
if (includeChart) config.chart = generateChartConfig({ chartColors, chartShades })
|
|
236
256
|
return config
|
|
@@ -242,7 +262,8 @@ export function generateZenSumiConfig(opts = {}) {
|
|
|
242
262
|
* optional `icons` and `chart` sections.
|
|
243
263
|
* @param {{ palette: string, customColors?: Record<string, string>, icons?: string,
|
|
244
264
|
* iconPath?: string, iconStyle?: string, themes: string[], defaultTheme?: string,
|
|
245
|
-
* switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string
|
|
265
|
+
* switcher: string, includeChart?: boolean, chartColors?: string, chartShades?: string,
|
|
266
|
+
* storageKey?: string }} opts
|
|
246
267
|
* @returns {Record<string, unknown>}
|
|
247
268
|
*/
|
|
248
269
|
export function generateConfig({
|
|
@@ -256,20 +277,22 @@ export function generateConfig({
|
|
|
256
277
|
switcher,
|
|
257
278
|
includeChart,
|
|
258
279
|
chartColors,
|
|
259
|
-
chartShades
|
|
280
|
+
chartShades,
|
|
281
|
+
storageKey
|
|
260
282
|
}) {
|
|
261
283
|
if (palette === 'zen-sumi') {
|
|
262
|
-
return generateZenSumiConfig({ themes, defaultTheme, switcher, includeChart, chartColors, chartShades })
|
|
284
|
+
return generateZenSumiConfig({ themes, defaultTheme, switcher, includeChart, chartColors, chartShades, storageKey })
|
|
263
285
|
}
|
|
264
286
|
|
|
265
287
|
const config = {
|
|
266
288
|
skin: resolveSkin(palette, customColors),
|
|
267
289
|
colorSpace: 'rgb',
|
|
268
290
|
tokens: 'core',
|
|
291
|
+
shape: { radius: 'soft' },
|
|
269
292
|
themes,
|
|
270
293
|
defaultTheme: defaultTheme || themes[0],
|
|
271
294
|
switcher,
|
|
272
|
-
storageKey:
|
|
295
|
+
...(storageKey ? { storageKey } : {})
|
|
273
296
|
}
|
|
274
297
|
|
|
275
298
|
const iconConfig = {}
|
|
@@ -312,50 +335,6 @@ export function generateAppCssImports(themes) {
|
|
|
312
335
|
return lines
|
|
313
336
|
}
|
|
314
337
|
|
|
315
|
-
/**
|
|
316
|
-
* Generate the flash-prevention script for app.html.
|
|
317
|
-
*
|
|
318
|
-
* Themes flip on `[data-mode="dark"]` only — there is no `@media
|
|
319
|
-
* (prefers-color-scheme)` fallback — so every switcher needs a script that
|
|
320
|
-
* resolves the mode pre-paint (mirrors `@rokkit/unocss`'s `themeHook`):
|
|
321
|
-
* - `system` tracks the OS preference directly (no persisted toggle).
|
|
322
|
-
* - `manual` / `full` read the persisted mode and resolve a stored-or-default
|
|
323
|
-
* `'system'`/`'auto'` against the OS preference (default: follow the OS).
|
|
324
|
-
*
|
|
325
|
-
* @param {string} switcher — 'system' | 'manual' | 'full'
|
|
326
|
-
* @param {string} [storageKey='rokkit-theme']
|
|
327
|
-
* @param {string} [defaultStyle='rokkit']
|
|
328
|
-
* @returns {string} the `<script>` tag to inject after `<body>`
|
|
329
|
-
*/
|
|
330
|
-
export function generateInitScript(switcher, storageKey = 'rokkit-theme', defaultStyle = 'rokkit') {
|
|
331
|
-
if (switcher === 'system') {
|
|
332
|
-
return ` <script>
|
|
333
|
-
(function () {
|
|
334
|
-
try {
|
|
335
|
-
document.body.dataset.mode =
|
|
336
|
-
matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
337
|
-
} catch (e) {}
|
|
338
|
-
})()
|
|
339
|
-
</script>`
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const setStyle =
|
|
343
|
-
switcher === 'full' ? `b.dataset.style = t.style || '${defaultStyle}'\n ` : ''
|
|
344
|
-
|
|
345
|
-
return ` <script>
|
|
346
|
-
(function () {
|
|
347
|
-
try {
|
|
348
|
-
var t = JSON.parse(localStorage.getItem('${storageKey}') || '{}')
|
|
349
|
-
var b = document.body
|
|
350
|
-
${setStyle}var m = t.mode || 'system'
|
|
351
|
-
if (m === 'system' || m === 'auto')
|
|
352
|
-
m = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
|
353
|
-
b.dataset.mode = m
|
|
354
|
-
} catch (e) {}
|
|
355
|
-
})()
|
|
356
|
-
</script>`
|
|
357
|
-
}
|
|
358
|
-
|
|
359
338
|
const PROMPTS_CONFIG = [
|
|
360
339
|
{
|
|
361
340
|
type: 'select',
|
|
@@ -375,12 +354,6 @@ const PROMPTS_CONFIG = [
|
|
|
375
354
|
message: 'Primary color (Tailwind palette name)',
|
|
376
355
|
initial: 'orange'
|
|
377
356
|
},
|
|
378
|
-
{
|
|
379
|
-
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
380
|
-
name: 'secondary',
|
|
381
|
-
message: 'Secondary color',
|
|
382
|
-
initial: 'pink'
|
|
383
|
-
},
|
|
384
357
|
{
|
|
385
358
|
type: (_, values) => (values.palette === 'custom' ? 'text' : null),
|
|
386
359
|
name: 'accent',
|
|
@@ -578,19 +551,26 @@ export async function init(_opts = {}, adapters = {}) {
|
|
|
578
551
|
if (response.palette === 'custom') {
|
|
579
552
|
response.customColors = {
|
|
580
553
|
primary: response.primary,
|
|
581
|
-
secondary: response.secondary,
|
|
582
554
|
accent: response.accent,
|
|
583
555
|
surface: response.surface
|
|
584
556
|
}
|
|
585
557
|
}
|
|
586
558
|
|
|
559
|
+
let appName
|
|
560
|
+
try {
|
|
561
|
+
appName = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8')).name
|
|
562
|
+
} catch {
|
|
563
|
+
appName = undefined
|
|
564
|
+
}
|
|
565
|
+
response.storageKey = storageKeyFromName(appName)
|
|
566
|
+
|
|
587
567
|
const config = generateConfig(response)
|
|
588
568
|
|
|
589
569
|
writeRokkitConfig(cwd, config)
|
|
590
570
|
writeUnoConfig(cwd)
|
|
591
571
|
writeAppCss(cwd, generateAppCssImports(config.themes))
|
|
592
572
|
|
|
593
|
-
const initScript =
|
|
573
|
+
const initScript = themeInitScript({ storageKey: config.storageKey, defaultStyle: config.defaultTheme })
|
|
594
574
|
if (initScript) writeAppHtml(cwd, initScript, config.storageKey)
|
|
595
575
|
|
|
596
576
|
console.info('\nDone! Run `rokkit doctor` to verify your setup.')
|