@zen-ui-capri/system 1.1.0 → 1.1.4
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/bin/capri.mjs +7 -2
- package/dist/lib/capri/capri-engine.d.ts +1 -1
- package/dist/lib/capri/capri-engine.d.ts.map +1 -1
- package/dist/lib/components/ZenIcon.d.ts.map +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +1118 -975
- package/dist/lib/theme/ZenThemeProvider.d.ts +2 -22
- package/dist/lib/theme/ZenThemeProvider.d.ts.map +1 -1
- package/dist/lib/theme/zen-theme-core.d.ts +23 -0
- package/dist/lib/theme/zen-theme-core.d.ts.map +1 -0
- package/dist/lib/universal/zen-universal.d.ts +1 -1
- package/dist/lib/universal/zen-universal.d.ts.map +1 -1
- package/dist/lib/universal/zen-universal.js +7 -7
- package/dist/lib/{zen-universal-C0PD8AI9.js → zen-universal-B_4UHCPT.js} +378 -465
- package/package.json +42 -12
- package/src/lib/audio/ZenAudioController.tsx +114 -114
- package/src/lib/capri/capri-engine.ts +1 -1
- package/src/lib/components/ZenIcon.tsx +24 -7
- package/src/lib/index.ts +1 -1
- package/src/lib/theme/ZenThemeProvider.tsx +36 -191
- package/src/lib/theme/zen-theme-core.ts +186 -0
- package/src/lib/universal/zen-universal.ts +95 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zen-ui-capri/system",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "zeN-UI // CAPRI — Universal Multi-Framework Design System (51 Primitives, 40 Vector Icons, 8 Dark/Light Themes & Capri 5-Subroutine AI Co-Pilot)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,27 +54,57 @@
|
|
|
54
54
|
"prepublishOnly": "npm run capri -- all && npm run build",
|
|
55
55
|
"publish:npm": "node bin/capri.mjs publish-npm"
|
|
56
56
|
},
|
|
57
|
-
"
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@react-three/fiber": ">=8.0.0",
|
|
59
|
+
"@react-three/postprocessing": ">=2.0.0",
|
|
60
|
+
"lucide-react": ">=0.300.0",
|
|
61
|
+
"postprocessing": ">=6.0.0",
|
|
62
|
+
"react": ">=18.0.0",
|
|
63
|
+
"react-dom": ">=18.0.0",
|
|
64
|
+
"three": ">=0.150.0"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"@react-three/fiber": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@react-three/postprocessing": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"lucide-react": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"postprocessing": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"react": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"react-dom": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"three": {
|
|
86
|
+
"optional": true
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
58
90
|
"@gsap/react": "^2.1.2",
|
|
91
|
+
"@playwright/test": "^1.51.0",
|
|
59
92
|
"@react-three/fiber": "^9.5.0",
|
|
60
93
|
"@react-three/postprocessing": "^3.2.2",
|
|
61
94
|
"@tailwindcss/vite": "^4.1.0",
|
|
95
|
+
"@types/node": "^22.10.0",
|
|
96
|
+
"@types/react": "^19.0.10",
|
|
97
|
+
"@types/react-dom": "^19.0.4",
|
|
98
|
+
"@types/three": "^0.182.0",
|
|
99
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
62
100
|
"gsap": "^3.13.0",
|
|
63
101
|
"lucide-react": "^0.562.0",
|
|
64
102
|
"postprocessing": "^6.39.6",
|
|
65
103
|
"react": "^19.2.3",
|
|
66
104
|
"react-dom": "^19.2.3",
|
|
67
105
|
"tailwindcss": "^4.1.0",
|
|
68
|
-
"three": "^0.182.0"
|
|
69
|
-
},
|
|
70
|
-
"devDependencies": {
|
|
71
|
-
"@playwright/test": "^1.51.0",
|
|
72
|
-
"@types/node": "^22.10.0",
|
|
73
|
-
"@types/react": "^19.0.10",
|
|
74
|
-
"@types/react-dom": "^19.0.4",
|
|
75
|
-
"@types/three": "^0.182.0",
|
|
76
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
106
|
+
"three": "^0.182.0",
|
|
77
107
|
"typescript": "~5.8.2",
|
|
78
108
|
"vite": "^6.2.0"
|
|
79
109
|
}
|
|
80
|
-
}
|
|
110
|
+
}
|
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import { Volume2, VolumeX, Disc3 } from 'lucide-react';
|
|
2
|
-
import type { ZenAudioSynthController, ZenSynthPresetId } from './useZenAudioSynth';
|
|
3
|
-
import { ZenButton } from '../components/ZenButton';
|
|
4
|
-
import { ZenSegmentedControl } from '../components/ZenSegmentedControl';
|
|
5
|
-
|
|
6
|
-
export interface ZenAudioControllerProps {
|
|
7
|
-
readonly synth: ZenAudioSynthController;
|
|
8
|
-
readonly mode?: 'pill' | 'deck';
|
|
9
|
-
readonly activeLabel?: string;
|
|
10
|
-
readonly idleLabel?: string;
|
|
11
|
-
readonly className?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function ZenAudioController({
|
|
15
|
-
synth,
|
|
16
|
-
mode = 'pill',
|
|
17
|
-
activeLabel = 'Lo-Fi Radio: ON',
|
|
18
|
-
idleLabel = 'Lo-Fi Background',
|
|
19
|
-
className = '',
|
|
20
|
-
}: ZenAudioControllerProps) {
|
|
21
|
-
if (mode === 'pill') {
|
|
22
|
-
return (
|
|
23
|
-
<ZenButton
|
|
24
|
-
variant="secondary"
|
|
25
|
-
size="sm"
|
|
26
|
-
mono
|
|
27
|
-
pressed={synth.active}
|
|
28
|
-
onClick={synth.toggle}
|
|
29
|
-
aria-label="Toggle procedural ambient synthesizer"
|
|
30
|
-
className={className}
|
|
31
|
-
iconLeft={
|
|
32
|
-
synth.active ? (
|
|
33
|
-
<Volume2 className="w-3.5 h-3.5 animate-pulse" />
|
|
34
|
-
) : (
|
|
35
|
-
<VolumeX className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
>
|
|
39
|
-
{synth.active ? activeLabel : idleLabel}
|
|
40
|
-
</ZenButton>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div
|
|
46
|
-
data-zen-occlude="true"
|
|
47
|
-
className={`p-5 rounded-3xl zen-surface-static flex flex-col gap-4 ${className}`.trim()}
|
|
48
|
-
>
|
|
49
|
-
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
50
|
-
<div className="flex items-center gap-3">
|
|
51
|
-
<Disc3
|
|
52
|
-
className={`w-6 h-6 text-[var(--zen-accent-primary)] ${
|
|
53
|
-
synth.active ? 'animate-spin' : ''
|
|
54
|
-
}`}
|
|
55
|
-
/>
|
|
56
|
-
<div>
|
|
57
|
-
<div className="zen-telemetry-label text-[var(--zen-accent-primary)]">
|
|
58
|
-
WEB AUDIO PROCEDURAL SYNTHESIZER
|
|
59
|
-
</div>
|
|
60
|
-
<div className="zen-font-display font-bold text-base text-[var(--zen-ink-primary)]">
|
|
61
|
-
Zero-Dependency Acoustic Engine
|
|
62
|
-
</div>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
|
|
66
|
-
<ZenButton
|
|
67
|
-
variant={synth.active ? 'primary' : 'secondary'}
|
|
68
|
-
size="sm"
|
|
69
|
-
mono
|
|
70
|
-
pressed={synth.active}
|
|
71
|
-
onClick={synth.toggle}
|
|
72
|
-
iconLeft={
|
|
73
|
-
synth.active ? (
|
|
74
|
-
<Volume2 className="w-3.5 h-3.5" />
|
|
75
|
-
) : (
|
|
76
|
-
<VolumeX className="w-3.5 h-3.5" />
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
>
|
|
80
|
-
{synth.active ? 'SYNTH ACTIVE' : 'START SYNTH'}
|
|
81
|
-
</ZenButton>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pt-2 border-t border-[var(--zen-border-subtle)]">
|
|
85
|
-
<ZenSegmentedControl<ZenSynthPresetId>
|
|
86
|
-
ariaLabel="Select synthesizer voicing preset"
|
|
87
|
-
size="sm"
|
|
88
|
-
value={synth.presetId}
|
|
89
|
-
onChange={synth.setPresetId}
|
|
90
|
-
items={synth.presets.map((p) => ({
|
|
91
|
-
value: p.id,
|
|
92
|
-
label: p.label.split(' (')[0],
|
|
93
|
-
}))}
|
|
94
|
-
/>
|
|
95
|
-
|
|
96
|
-
<div className="flex items-center gap-1.5" aria-label="8-step bar sequencer">
|
|
97
|
-
{Array.from({ length: 8 }).map((_, idx) => {
|
|
98
|
-
const isBeat = synth.active && synth.currentStep === idx;
|
|
99
|
-
return (
|
|
100
|
-
<span
|
|
101
|
-
key={idx}
|
|
102
|
-
className={`h-2.5 w-2.5 rounded-full transition-all ${
|
|
103
|
-
isBeat
|
|
104
|
-
? 'bg-[var(--zen-accent-primary)] scale-125'
|
|
105
|
-
: 'bg-[var(--zen-border-default)]'
|
|
106
|
-
}`}
|
|
107
|
-
/>
|
|
108
|
-
);
|
|
109
|
-
})}
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
</div>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
1
|
+
import { Volume2, VolumeX, Disc3 } from 'lucide-react';
|
|
2
|
+
import type { ZenAudioSynthController, ZenSynthPresetId } from './useZenAudioSynth';
|
|
3
|
+
import { ZenButton } from '../components/ZenButton';
|
|
4
|
+
import { ZenSegmentedControl } from '../components/ZenSegmentedControl';
|
|
5
|
+
|
|
6
|
+
export interface ZenAudioControllerProps {
|
|
7
|
+
readonly synth: ZenAudioSynthController;
|
|
8
|
+
readonly mode?: 'pill' | 'deck';
|
|
9
|
+
readonly activeLabel?: string;
|
|
10
|
+
readonly idleLabel?: string;
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ZenAudioController({
|
|
15
|
+
synth,
|
|
16
|
+
mode = 'pill',
|
|
17
|
+
activeLabel = 'Lo-Fi Radio: ON',
|
|
18
|
+
idleLabel = 'Lo-Fi Background',
|
|
19
|
+
className = '',
|
|
20
|
+
}: ZenAudioControllerProps) {
|
|
21
|
+
if (mode === 'pill') {
|
|
22
|
+
return (
|
|
23
|
+
<ZenButton
|
|
24
|
+
variant="secondary"
|
|
25
|
+
size="sm"
|
|
26
|
+
mono
|
|
27
|
+
pressed={synth.active}
|
|
28
|
+
onClick={synth.toggle}
|
|
29
|
+
aria-label="Toggle procedural ambient synthesizer"
|
|
30
|
+
className={className}
|
|
31
|
+
iconLeft={
|
|
32
|
+
synth.active ? (
|
|
33
|
+
<Volume2 className="w-3.5 h-3.5 animate-pulse" />
|
|
34
|
+
) : (
|
|
35
|
+
<VolumeX className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
{synth.active ? activeLabel : idleLabel}
|
|
40
|
+
</ZenButton>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
data-zen-occlude="true"
|
|
47
|
+
className={`p-5 rounded-3xl zen-surface-static flex flex-col gap-4 ${className}`.trim()}
|
|
48
|
+
>
|
|
49
|
+
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
50
|
+
<div className="flex items-center gap-3">
|
|
51
|
+
<Disc3
|
|
52
|
+
className={`w-6 h-6 text-[var(--zen-accent-primary)] ${
|
|
53
|
+
synth.active ? 'animate-spin' : ''
|
|
54
|
+
}`}
|
|
55
|
+
/>
|
|
56
|
+
<div>
|
|
57
|
+
<div className="zen-telemetry-label text-[var(--zen-accent-primary)]">
|
|
58
|
+
WEB AUDIO PROCEDURAL SYNTHESIZER
|
|
59
|
+
</div>
|
|
60
|
+
<div className="zen-font-display font-bold text-base text-[var(--zen-ink-primary)]">
|
|
61
|
+
Zero-Dependency Acoustic Engine
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<ZenButton
|
|
67
|
+
variant={synth.active ? 'primary' : 'secondary'}
|
|
68
|
+
size="sm"
|
|
69
|
+
mono
|
|
70
|
+
pressed={synth.active}
|
|
71
|
+
onClick={synth.toggle}
|
|
72
|
+
iconLeft={
|
|
73
|
+
synth.active ? (
|
|
74
|
+
<Volume2 className="w-3.5 h-3.5" />
|
|
75
|
+
) : (
|
|
76
|
+
<VolumeX className="w-3.5 h-3.5" />
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
>
|
|
80
|
+
{synth.active ? 'SYNTH ACTIVE' : 'START SYNTH'}
|
|
81
|
+
</ZenButton>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pt-2 border-t border-[var(--zen-border-subtle)]">
|
|
85
|
+
<ZenSegmentedControl<ZenSynthPresetId>
|
|
86
|
+
ariaLabel="Select synthesizer voicing preset"
|
|
87
|
+
size="sm"
|
|
88
|
+
value={synth.presetId}
|
|
89
|
+
onChange={synth.setPresetId}
|
|
90
|
+
items={synth.presets.map((p) => ({
|
|
91
|
+
value: p.id,
|
|
92
|
+
label: p.label.split(' (')[0],
|
|
93
|
+
}))}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<div className="flex items-center gap-1.5" aria-label="8-step bar sequencer">
|
|
97
|
+
{Array.from({ length: 8 }).map((_, idx) => {
|
|
98
|
+
const isBeat = synth.active && synth.currentStep === idx;
|
|
99
|
+
return (
|
|
100
|
+
<span
|
|
101
|
+
key={idx}
|
|
102
|
+
className={`h-2.5 w-2.5 rounded-full transition-all ${
|
|
103
|
+
isBeat
|
|
104
|
+
? 'bg-[var(--zen-accent-primary)] scale-125'
|
|
105
|
+
: 'bg-[var(--zen-border-default)]'
|
|
106
|
+
}`}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
})}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -167,10 +167,21 @@ export function renderZenIconSvgMarkup(options: {
|
|
|
167
167
|
readonly strokeWidth?: number;
|
|
168
168
|
readonly spin?: boolean;
|
|
169
169
|
}): string {
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
const sizeKey = options.size ?? 'md';
|
|
171
|
+
const px = Object.prototype.hasOwnProperty.call(ZEN_ICON_SIZE_PX, sizeKey)
|
|
172
|
+
? ZEN_ICON_SIZE_PX[sizeKey]
|
|
173
|
+
: ZEN_ICON_SIZE_PX.md;
|
|
174
|
+
const toneKey = options.tone ?? 'inherit';
|
|
175
|
+
const color = Object.prototype.hasOwnProperty.call(TONE_COLOR_MAP, toneKey)
|
|
176
|
+
? TONE_COLOR_MAP[toneKey]
|
|
177
|
+
: TONE_COLOR_MAP.inherit;
|
|
178
|
+
const sw =
|
|
179
|
+
typeof options.strokeWidth === 'number' && Number.isFinite(options.strokeWidth)
|
|
180
|
+
? Math.min(Math.max(options.strokeWidth, 0.5), 4)
|
|
181
|
+
: 2;
|
|
182
|
+
const inner = Object.prototype.hasOwnProperty.call(ZEN_ICON_SVG_INNER, options.name)
|
|
183
|
+
? ZEN_ICON_SVG_INNER[options.name]
|
|
184
|
+
: ZEN_ICON_SVG_INNER.sparkles;
|
|
174
185
|
const spinStyle = options.spin ? 'animation: spin 1s linear infinite;' : '';
|
|
175
186
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:middle;flex-shrink:0;${spinStyle}">${inner}</svg>`;
|
|
176
187
|
}
|
|
@@ -194,9 +205,15 @@ export function ZenIcon({
|
|
|
194
205
|
className = '',
|
|
195
206
|
...rest
|
|
196
207
|
}: ZenIconProps) {
|
|
197
|
-
const px = ZEN_ICON_SIZE_PX
|
|
198
|
-
|
|
199
|
-
|
|
208
|
+
const px = Object.prototype.hasOwnProperty.call(ZEN_ICON_SIZE_PX, size)
|
|
209
|
+
? ZEN_ICON_SIZE_PX[size]
|
|
210
|
+
: ZEN_ICON_SIZE_PX.md;
|
|
211
|
+
const strokeColor = Object.prototype.hasOwnProperty.call(TONE_COLOR_MAP, tone)
|
|
212
|
+
? TONE_COLOR_MAP[tone]
|
|
213
|
+
: TONE_COLOR_MAP.inherit;
|
|
214
|
+
const innerMarkup = Object.prototype.hasOwnProperty.call(ZEN_ICON_SVG_INNER, name)
|
|
215
|
+
? ZEN_ICON_SVG_INNER[name]
|
|
216
|
+
: ZEN_ICON_SVG_INNER.sparkles;
|
|
200
217
|
|
|
201
218
|
return (
|
|
202
219
|
<svg
|
package/src/lib/index.ts
CHANGED
|
@@ -6,121 +6,27 @@ import {
|
|
|
6
6
|
useState,
|
|
7
7
|
type ReactNode,
|
|
8
8
|
} from 'react';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
readonly accentHex: string;
|
|
31
|
-
readonly secondaryHex: string;
|
|
32
|
-
readonly inkHex: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const ZEN_THEMES: readonly ZenThemeMetadata[] = [
|
|
36
|
-
{
|
|
37
|
-
id: 'obsidian-gold',
|
|
38
|
-
name: 'Obsidian Gold',
|
|
39
|
-
polarity: 'dark',
|
|
40
|
-
tagline: 'Warm alabaster & solar gold on deep mineral void',
|
|
41
|
-
canvasHex: '#070709',
|
|
42
|
-
accentHex: '#F5B841',
|
|
43
|
-
secondaryHex: '#FB923C',
|
|
44
|
-
inkHex: '#F6F2E9',
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: 'blackwall-crimson',
|
|
48
|
-
name: 'Capri // Capricorn',
|
|
49
|
-
polarity: 'dark',
|
|
50
|
-
tagline: 'Edgerunners S2 mixed-construct hot pink & acid neon green on void',
|
|
51
|
-
canvasHex: '#050508',
|
|
52
|
-
accentHex: '#FF2E93',
|
|
53
|
-
secondaryHex: '#00FF66',
|
|
54
|
-
inkHex: '#F4F7FA',
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
id: 'neon-emerald',
|
|
58
|
-
name: 'Bio-Emerald Terminal',
|
|
59
|
-
polarity: 'dark',
|
|
60
|
-
tagline: 'High-frequency financial & telemetry phosphor emerald',
|
|
61
|
-
canvasHex: '#040807',
|
|
62
|
-
accentHex: '#10B981',
|
|
63
|
-
secondaryHex: '#2DD4BF',
|
|
64
|
-
inkHex: '#ECFDF5',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: 'midnight-cobalt',
|
|
68
|
-
name: 'Midnight Cobalt',
|
|
69
|
-
polarity: 'dark',
|
|
70
|
-
tagline: 'Cloud infrastructure & distributed systems deep slate',
|
|
71
|
-
canvasHex: '#060911',
|
|
72
|
-
accentHex: '#38BDF8',
|
|
73
|
-
secondaryHex: '#818CF8',
|
|
74
|
-
inkHex: '#F1F5F9',
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
id: 'atelier-paper',
|
|
78
|
-
name: 'Atelier Paper',
|
|
79
|
-
polarity: 'light',
|
|
80
|
-
tagline: 'Swiss archival cotton stock with burnished bronze ink',
|
|
81
|
-
canvasHex: '#F6F2E9',
|
|
82
|
-
accentHex: '#B45309',
|
|
83
|
-
secondaryHex: '#C2410C',
|
|
84
|
-
inkHex: '#121216',
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: 'nordic-snow',
|
|
88
|
-
name: 'Nordic Snow',
|
|
89
|
-
polarity: 'light',
|
|
90
|
-
tagline: 'Crisp daylight SaaS alabaster with royal cobalt precision',
|
|
91
|
-
canvasHex: '#F8FAFC',
|
|
92
|
-
accentHex: '#2563EB',
|
|
93
|
-
secondaryHex: '#0891B2',
|
|
94
|
-
inkHex: '#0F172A',
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: 'kyoto-matcha',
|
|
98
|
-
name: 'Kyoto Matcha',
|
|
99
|
-
polarity: 'light',
|
|
100
|
-
tagline: 'Raw botanical stone & forest ink for calm workspaces',
|
|
101
|
-
canvasHex: '#F3F5F0',
|
|
102
|
-
accentHex: '#15803D',
|
|
103
|
-
secondaryHex: '#0F766E',
|
|
104
|
-
inkHex: '#142018',
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
id: 'cyber-daylight',
|
|
108
|
-
name: 'Arasaka Daylight',
|
|
109
|
-
polarity: 'light',
|
|
110
|
-
tagline: 'High-contrast titanium white with razor crimson signal',
|
|
111
|
-
canvasHex: '#F2F2F5',
|
|
112
|
-
accentHex: '#E11D48',
|
|
113
|
-
secondaryHex: '#0284C7',
|
|
114
|
-
inkHex: '#09090D',
|
|
115
|
-
},
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
export interface ZenThemeTokenOverrides {
|
|
119
|
-
readonly accentPrimary?: string;
|
|
120
|
-
readonly bgCanvas?: string;
|
|
121
|
-
readonly surface1?: string;
|
|
122
|
-
readonly inkPrimary?: string;
|
|
123
|
-
}
|
|
9
|
+
import {
|
|
10
|
+
ZEN_THEMES,
|
|
11
|
+
syncZenScrollbarStyles,
|
|
12
|
+
type ZenDensityMode,
|
|
13
|
+
type ZenRadiusMode,
|
|
14
|
+
type ZenThemeId,
|
|
15
|
+
type ZenThemeMetadata,
|
|
16
|
+
type ZenThemeMode,
|
|
17
|
+
type ZenThemeTokenOverrides,
|
|
18
|
+
} from './zen-theme-core';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
ZEN_THEMES,
|
|
22
|
+
syncZenScrollbarStyles,
|
|
23
|
+
type ZenDensityMode,
|
|
24
|
+
type ZenRadiusMode,
|
|
25
|
+
type ZenThemeId,
|
|
26
|
+
type ZenThemeMetadata,
|
|
27
|
+
type ZenThemeMode,
|
|
28
|
+
type ZenThemeTokenOverrides,
|
|
29
|
+
};
|
|
124
30
|
|
|
125
31
|
export interface ZenThemeContextValue {
|
|
126
32
|
readonly theme: ZenThemeId;
|
|
@@ -136,7 +42,16 @@ export interface ZenThemeContextValue {
|
|
|
136
42
|
readonly setRadius: (next: ZenRadiusMode) => void;
|
|
137
43
|
}
|
|
138
44
|
|
|
139
|
-
|
|
45
|
+
let zenThemeContextInstance: ReturnType<
|
|
46
|
+
typeof createContext<ZenThemeContextValue | null>
|
|
47
|
+
> | null = null;
|
|
48
|
+
|
|
49
|
+
function getZenThemeContext() {
|
|
50
|
+
if (!zenThemeContextInstance) {
|
|
51
|
+
zenThemeContextInstance = createContext<ZenThemeContextValue | null>(null);
|
|
52
|
+
}
|
|
53
|
+
return zenThemeContextInstance;
|
|
54
|
+
}
|
|
140
55
|
|
|
141
56
|
export interface ZenThemeProviderProps {
|
|
142
57
|
readonly defaultTheme?: ZenThemeId;
|
|
@@ -147,78 +62,6 @@ export interface ZenThemeProviderProps {
|
|
|
147
62
|
readonly children: ReactNode;
|
|
148
63
|
}
|
|
149
64
|
|
|
150
|
-
export function syncZenScrollbarStyles(
|
|
151
|
-
themeMeta: ZenThemeMetadata,
|
|
152
|
-
overrides?: ZenThemeTokenOverrides
|
|
153
|
-
): void {
|
|
154
|
-
if (typeof document === 'undefined') return;
|
|
155
|
-
const accent = overrides?.accentPrimary ?? themeMeta.accentHex;
|
|
156
|
-
const track = overrides?.bgCanvas ?? themeMeta.canvasHex;
|
|
157
|
-
const borderSubtle =
|
|
158
|
-
themeMeta.polarity === 'dark'
|
|
159
|
-
? 'rgba(255, 255, 255, 0.12)'
|
|
160
|
-
: 'rgba(15, 23, 42, 0.14)';
|
|
161
|
-
|
|
162
|
-
const styleId = 'zen-theme-scrollbar';
|
|
163
|
-
let styleEl = document.getElementById(styleId) as HTMLStyleElement | null;
|
|
164
|
-
if (!styleEl) {
|
|
165
|
-
styleEl = document.createElement('style');
|
|
166
|
-
styleEl.id = styleId;
|
|
167
|
-
document.head.appendChild(styleEl);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
styleEl.textContent = `
|
|
171
|
-
@supports (-moz-appearance: none) {
|
|
172
|
-
html, body, * {
|
|
173
|
-
scrollbar-width: thin;
|
|
174
|
-
scrollbar-color: ${accent} ${track};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
::-webkit-scrollbar,
|
|
178
|
-
html::-webkit-scrollbar,
|
|
179
|
-
body::-webkit-scrollbar,
|
|
180
|
-
*::-webkit-scrollbar {
|
|
181
|
-
width: 10px;
|
|
182
|
-
height: 10px;
|
|
183
|
-
}
|
|
184
|
-
::-webkit-scrollbar-track,
|
|
185
|
-
html::-webkit-scrollbar-track,
|
|
186
|
-
body::-webkit-scrollbar-track,
|
|
187
|
-
*::-webkit-scrollbar-track {
|
|
188
|
-
background-color: ${track};
|
|
189
|
-
border-left: 1px solid ${borderSubtle};
|
|
190
|
-
}
|
|
191
|
-
::-webkit-scrollbar-thumb,
|
|
192
|
-
html::-webkit-scrollbar-thumb,
|
|
193
|
-
body::-webkit-scrollbar-thumb,
|
|
194
|
-
*::-webkit-scrollbar-thumb {
|
|
195
|
-
background-color: color-mix(in srgb, ${accent} 68%, ${track});
|
|
196
|
-
border: 2px solid ${track};
|
|
197
|
-
border-radius: 9999px;
|
|
198
|
-
}
|
|
199
|
-
::-webkit-scrollbar-thumb:hover,
|
|
200
|
-
html::-webkit-scrollbar-thumb:hover,
|
|
201
|
-
body::-webkit-scrollbar-thumb:hover,
|
|
202
|
-
*::-webkit-scrollbar-thumb:hover {
|
|
203
|
-
background-color: ${accent};
|
|
204
|
-
border: 2px solid ${track};
|
|
205
|
-
}
|
|
206
|
-
::-webkit-scrollbar-thumb:active,
|
|
207
|
-
html::-webkit-scrollbar-thumb:active,
|
|
208
|
-
body::-webkit-scrollbar-thumb:active,
|
|
209
|
-
*::-webkit-scrollbar-thumb:active {
|
|
210
|
-
background-color: ${themeMeta.secondaryHex};
|
|
211
|
-
border: 2px solid ${track};
|
|
212
|
-
}
|
|
213
|
-
::-webkit-scrollbar-corner,
|
|
214
|
-
html::-webkit-scrollbar-corner,
|
|
215
|
-
body::-webkit-scrollbar-corner,
|
|
216
|
-
*::-webkit-scrollbar-corner {
|
|
217
|
-
background-color: ${track};
|
|
218
|
-
}
|
|
219
|
-
`.trim();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
65
|
export function ZenThemeProvider({
|
|
223
66
|
defaultTheme = 'blackwall-crimson',
|
|
224
67
|
defaultDensity = 'comfortable',
|
|
@@ -312,15 +155,17 @@ export function ZenThemeProvider({
|
|
|
312
155
|
[theme, themeMeta, density, radius, reducedMotion]
|
|
313
156
|
);
|
|
314
157
|
|
|
158
|
+
const ThemeCtx = getZenThemeContext();
|
|
159
|
+
|
|
315
160
|
return (
|
|
316
|
-
<
|
|
161
|
+
<ThemeCtx.Provider value={value}>
|
|
317
162
|
{children}
|
|
318
|
-
</
|
|
163
|
+
</ThemeCtx.Provider>
|
|
319
164
|
);
|
|
320
165
|
}
|
|
321
166
|
|
|
322
167
|
export function useZenTheme(): ZenThemeContextValue {
|
|
323
|
-
const ctx = useContext(
|
|
168
|
+
const ctx = useContext(getZenThemeContext());
|
|
324
169
|
if (!ctx) {
|
|
325
170
|
throw new Error('useZenTheme must be used within a ZenThemeProvider');
|
|
326
171
|
}
|