@zen-ui-capri/system 0.0.0-stage → 1.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/.agents/skills/capri-zen-ui/SKILL.md +59 -0
- package/AGENTS.md +57 -0
- package/CLAUDE.md +7 -0
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/bin/capri.mjs +1187 -0
- package/dist/lib/audio/ZenAudioController.d.ts +10 -0
- package/dist/lib/audio/ZenAudioController.d.ts.map +1 -0
- package/dist/lib/audio/useZenAudioSynth.d.ts +26 -0
- package/dist/lib/audio/useZenAudioSynth.d.ts.map +1 -0
- package/dist/lib/capri/capri-engine.d.ts +127 -0
- package/dist/lib/capri/capri-engine.d.ts.map +1 -0
- package/dist/lib/components/ZenAIConstruct.d.ts +24 -0
- package/dist/lib/components/ZenAIConstruct.d.ts.map +1 -0
- package/dist/lib/components/ZenAmbientBackground.d.ts +8 -0
- package/dist/lib/components/ZenAmbientBackground.d.ts.map +1 -0
- package/dist/lib/components/ZenBadge.d.ts +21 -0
- package/dist/lib/components/ZenBadge.d.ts.map +1 -0
- package/dist/lib/components/ZenButton.d.ts +24 -0
- package/dist/lib/components/ZenButton.d.ts.map +1 -0
- package/dist/lib/components/ZenCard.d.ts +36 -0
- package/dist/lib/components/ZenCard.d.ts.map +1 -0
- package/dist/lib/components/ZenDataDisplay.d.ts +50 -0
- package/dist/lib/components/ZenDataDisplay.d.ts.map +1 -0
- package/dist/lib/components/ZenDataTable.d.ts +20 -0
- package/dist/lib/components/ZenDataTable.d.ts.map +1 -0
- package/dist/lib/components/ZenDossierPanel.d.ts +33 -0
- package/dist/lib/components/ZenDossierPanel.d.ts.map +1 -0
- package/dist/lib/components/ZenDropdown.d.ts +19 -0
- package/dist/lib/components/ZenDropdown.d.ts.map +1 -0
- package/dist/lib/components/ZenFeedback.d.ts +24 -0
- package/dist/lib/components/ZenFeedback.d.ts.map +1 -0
- package/dist/lib/components/ZenForms.d.ts +84 -0
- package/dist/lib/components/ZenForms.d.ts.map +1 -0
- package/dist/lib/components/ZenHeading.d.ts +15 -0
- package/dist/lib/components/ZenHeading.d.ts.map +1 -0
- package/dist/lib/components/ZenIcon.d.ts +24 -0
- package/dist/lib/components/ZenIcon.d.ts.map +1 -0
- package/dist/lib/components/ZenInput.d.ts +12 -0
- package/dist/lib/components/ZenInput.d.ts.map +1 -0
- package/dist/lib/components/ZenModal.d.ts +12 -0
- package/dist/lib/components/ZenModal.d.ts.map +1 -0
- package/dist/lib/components/ZenNavbar.d.ts +11 -0
- package/dist/lib/components/ZenNavbar.d.ts.map +1 -0
- package/dist/lib/components/ZenNavigation.d.ts +52 -0
- package/dist/lib/components/ZenNavigation.d.ts.map +1 -0
- package/dist/lib/components/ZenOverlays.d.ts +48 -0
- package/dist/lib/components/ZenOverlays.d.ts.map +1 -0
- package/dist/lib/components/ZenSegmentedControl.d.ts +18 -0
- package/dist/lib/components/ZenSegmentedControl.d.ts.map +1 -0
- package/dist/lib/components/ZenSwitch.d.ts +11 -0
- package/dist/lib/components/ZenSwitch.d.ts.map +1 -0
- package/dist/lib/components/ZenTabs.d.ts +17 -0
- package/dist/lib/components/ZenTabs.d.ts.map +1 -0
- package/dist/lib/components/ZenTimelineCard.d.ts +14 -0
- package/dist/lib/components/ZenTimelineCard.d.ts.map +1 -0
- package/dist/lib/components/ZenToast.d.ts +18 -0
- package/dist/lib/components/ZenToast.d.ts.map +1 -0
- package/dist/lib/cosmos/ZenCosmosCanvas.d.ts +10 -0
- package/dist/lib/cosmos/ZenCosmosCanvas.d.ts.map +1 -0
- package/dist/lib/cosmos/ZenTelemetryHud.d.ts +45 -0
- package/dist/lib/cosmos/ZenTelemetryHud.d.ts.map +1 -0
- package/dist/lib/index.d.ts +33 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3922 -0
- package/dist/lib/theme/ZenThemeProvider.d.ts +47 -0
- package/dist/lib/theme/ZenThemeProvider.d.ts.map +1 -0
- package/dist/lib/tokens.css +888 -0
- package/dist/lib/universal/zen-universal.d.ts +77 -0
- package/dist/lib/universal/zen-universal.d.ts.map +1 -0
- package/dist/lib/universal/zen-universal.js +18 -0
- package/dist/lib/zen-universal-C0PD8AI9.js +1352 -0
- package/package.json +78 -4
- package/src/lib/audio/ZenAudioController.tsx +114 -0
- package/src/lib/audio/useZenAudioSynth.ts +289 -0
- package/src/lib/capri/capri-engine.ts +939 -0
- package/src/lib/components/ZenAIConstruct.tsx +594 -0
- package/src/lib/components/ZenAmbientBackground.tsx +210 -0
- package/src/lib/components/ZenBadge.tsx +111 -0
- package/src/lib/components/ZenButton.tsx +127 -0
- package/src/lib/components/ZenCard.tsx +180 -0
- package/src/lib/components/ZenDataDisplay.tsx +349 -0
- package/src/lib/components/ZenDataTable.tsx +143 -0
- package/src/lib/components/ZenDossierPanel.tsx +120 -0
- package/src/lib/components/ZenDropdown.tsx +208 -0
- package/src/lib/components/ZenFeedback.tsx +137 -0
- package/src/lib/components/ZenForms.tsx +664 -0
- package/src/lib/components/ZenHeading.tsx +63 -0
- package/src/lib/components/ZenIcon.tsx +220 -0
- package/src/lib/components/ZenInput.tsx +120 -0
- package/src/lib/components/ZenModal.tsx +103 -0
- package/src/lib/components/ZenNavbar.tsx +106 -0
- package/src/lib/components/ZenNavigation.tsx +300 -0
- package/src/lib/components/ZenOverlays.tsx +386 -0
- package/src/lib/components/ZenSegmentedControl.tsx +113 -0
- package/src/lib/components/ZenSwitch.tsx +63 -0
- package/src/lib/components/ZenTabs.tsx +103 -0
- package/src/lib/components/ZenTimelineCard.tsx +64 -0
- package/src/lib/components/ZenToast.tsx +96 -0
- package/src/lib/cosmos/ZenCosmosCanvas.tsx +931 -0
- package/src/lib/cosmos/ZenTelemetryHud.tsx +385 -0
- package/src/lib/index.ts +293 -0
- package/src/lib/styles/zen-tokens.css +888 -0
- package/src/lib/theme/ZenThemeProvider.tsx +328 -0
- package/src/lib/universal/zen-universal.ts +743 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useZenTheme } from '../theme/ZenThemeProvider';
|
|
3
|
+
|
|
4
|
+
export type ZenBackgroundVariant =
|
|
5
|
+
| 'blackwall-lattice'
|
|
6
|
+
| 'aurora-mesh'
|
|
7
|
+
| 'swiss-blueprint'
|
|
8
|
+
| 'minimal-void';
|
|
9
|
+
|
|
10
|
+
export interface ZenAmbientBackgroundProps {
|
|
11
|
+
readonly variant?: ZenBackgroundVariant;
|
|
12
|
+
readonly interactive?: boolean;
|
|
13
|
+
readonly paused?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface DataNode {
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
vx: number;
|
|
20
|
+
vy: number;
|
|
21
|
+
size: number;
|
|
22
|
+
phase: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ZenAmbientBackground({
|
|
26
|
+
variant = 'blackwall-lattice',
|
|
27
|
+
interactive = true,
|
|
28
|
+
paused = false,
|
|
29
|
+
}: ZenAmbientBackgroundProps) {
|
|
30
|
+
const { themeMeta, reducedMotion } = useZenTheme();
|
|
31
|
+
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
32
|
+
const pointerRef = useRef<{ x: number; y: number }>({ x: 0.5, y: 0.35 });
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (!interactive) return;
|
|
36
|
+
const onMove = (e: PointerEvent) => {
|
|
37
|
+
pointerRef.current = {
|
|
38
|
+
x: e.clientX / Math.max(1, window.innerWidth),
|
|
39
|
+
y: e.clientY / Math.max(1, window.innerHeight),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
window.addEventListener('pointermove', onMove, { passive: true });
|
|
43
|
+
return () => window.removeEventListener('pointermove', onMove);
|
|
44
|
+
}, [interactive]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (variant === 'minimal-void') return;
|
|
48
|
+
const canvas = canvasRef.current;
|
|
49
|
+
if (!canvas) return;
|
|
50
|
+
const ctx = canvas.getContext('2d');
|
|
51
|
+
if (!ctx) return;
|
|
52
|
+
|
|
53
|
+
const nodes: DataNode[] = Array.from({ length: 56 }, (_, idx) => ({
|
|
54
|
+
x: (idx * 73) % 1000,
|
|
55
|
+
y: (idx * 137) % 1000,
|
|
56
|
+
vx: ((idx % 5) - 2) * 0.22,
|
|
57
|
+
vy: -0.35 - (idx % 4) * 0.18,
|
|
58
|
+
size: 1.2 + (idx % 3) * 0.9,
|
|
59
|
+
phase: idx * 0.6,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
let rafId = 0;
|
|
63
|
+
let time = 0;
|
|
64
|
+
|
|
65
|
+
const render = () => {
|
|
66
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 1.5);
|
|
67
|
+
const w = window.innerWidth;
|
|
68
|
+
const h = window.innerHeight;
|
|
69
|
+
if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
|
|
70
|
+
canvas.width = w * dpr;
|
|
71
|
+
canvas.height = h * dpr;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ctx.save();
|
|
75
|
+
ctx.scale(dpr, dpr);
|
|
76
|
+
ctx.clearRect(0, 0, w, h);
|
|
77
|
+
|
|
78
|
+
if (!reducedMotion && !paused) {
|
|
79
|
+
time += 0.014;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const primary = themeMeta.accentHex;
|
|
83
|
+
const secondary = themeMeta.secondaryHex;
|
|
84
|
+
const isDark = themeMeta.polarity === 'dark';
|
|
85
|
+
const px = pointerRef.current.x * w;
|
|
86
|
+
const py = pointerRef.current.y * h;
|
|
87
|
+
|
|
88
|
+
if (variant === 'aurora-mesh') {
|
|
89
|
+
const g1 = ctx.createRadialGradient(
|
|
90
|
+
w * (0.25 + Math.sin(time * 0.7) * 0.12),
|
|
91
|
+
h * (0.28 + Math.cos(time * 0.5) * 0.1),
|
|
92
|
+
20,
|
|
93
|
+
w * 0.35,
|
|
94
|
+
h * 0.35,
|
|
95
|
+
Math.max(w, h) * 0.55
|
|
96
|
+
);
|
|
97
|
+
g1.addColorStop(0, `${primary}${isDark ? '24' : '18'}`);
|
|
98
|
+
g1.addColorStop(1, 'transparent');
|
|
99
|
+
ctx.fillStyle = g1;
|
|
100
|
+
ctx.fillRect(0, 0, w, h);
|
|
101
|
+
|
|
102
|
+
const g2 = ctx.createRadialGradient(
|
|
103
|
+
w * (0.75 + Math.cos(time * 0.6) * 0.12),
|
|
104
|
+
h * (0.65 + Math.sin(time * 0.8) * 0.1),
|
|
105
|
+
20,
|
|
106
|
+
w * 0.7,
|
|
107
|
+
h * 0.65,
|
|
108
|
+
Math.max(w, h) * 0.5
|
|
109
|
+
);
|
|
110
|
+
g2.addColorStop(0, `${secondary}${isDark ? '20' : '14'}`);
|
|
111
|
+
g2.addColorStop(1, 'transparent');
|
|
112
|
+
ctx.fillStyle = g2;
|
|
113
|
+
ctx.fillRect(0, 0, w, h);
|
|
114
|
+
} else if (variant === 'swiss-blueprint') {
|
|
115
|
+
const step = 48;
|
|
116
|
+
ctx.strokeStyle = isDark ? `${primary}14` : `${primary}16`;
|
|
117
|
+
ctx.lineWidth = 1;
|
|
118
|
+
|
|
119
|
+
for (let x = 0; x < w; x += step) {
|
|
120
|
+
ctx.beginPath();
|
|
121
|
+
ctx.moveTo(x, 0);
|
|
122
|
+
ctx.lineTo(x, h);
|
|
123
|
+
ctx.stroke();
|
|
124
|
+
}
|
|
125
|
+
for (let y = 0; y < h; y += step) {
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
ctx.moveTo(0, y);
|
|
128
|
+
ctx.lineTo(w, y);
|
|
129
|
+
ctx.stroke();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
for (let x = step; x < w; x += step * 2) {
|
|
133
|
+
for (let y = step; y < h; y += step * 2) {
|
|
134
|
+
const dist = Math.hypot(x - px, y - py);
|
|
135
|
+
const highlight = Math.max(0, 1 - dist / 280);
|
|
136
|
+
ctx.fillStyle = highlight > 0.2 ? primary : secondary;
|
|
137
|
+
ctx.globalAlpha = (isDark ? 0.22 : 0.28) + highlight * 0.55;
|
|
138
|
+
ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
const spot = ctx.createRadialGradient(px, py, 10, px, py, 460);
|
|
143
|
+
spot.addColorStop(0, `${primary}${isDark ? '1c' : '14'}`);
|
|
144
|
+
spot.addColorStop(0.5, `${secondary}${isDark ? '0d' : '08'}`);
|
|
145
|
+
spot.addColorStop(1, 'transparent');
|
|
146
|
+
ctx.fillStyle = spot;
|
|
147
|
+
ctx.fillRect(0, 0, w, h);
|
|
148
|
+
|
|
149
|
+
const horizonY = h * 0.52;
|
|
150
|
+
ctx.strokeStyle = `${primary}${isDark ? '1f' : '22'}`;
|
|
151
|
+
ctx.lineWidth = 1;
|
|
152
|
+
|
|
153
|
+
const cols = 18;
|
|
154
|
+
for (let i = -cols; i <= cols; i++) {
|
|
155
|
+
const topX = w * 0.5 + i * (w / cols) * 0.32;
|
|
156
|
+
const botX = w * 0.5 + i * (w / cols) * 1.45;
|
|
157
|
+
ctx.beginPath();
|
|
158
|
+
ctx.moveTo(topX, horizonY);
|
|
159
|
+
ctx.lineTo(botX, h);
|
|
160
|
+
ctx.stroke();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rows = 10;
|
|
164
|
+
const offset = (time * 0.8) % 1;
|
|
165
|
+
for (let r = 0; r < rows; r++) {
|
|
166
|
+
const p = Math.pow((r + offset) / rows, 1.75);
|
|
167
|
+
const y = horizonY + p * (h - horizonY);
|
|
168
|
+
ctx.globalAlpha = p * (isDark ? 0.35 : 0.28);
|
|
169
|
+
ctx.beginPath();
|
|
170
|
+
ctx.moveTo(0, y);
|
|
171
|
+
ctx.lineTo(w, y);
|
|
172
|
+
ctx.stroke();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
176
|
+
const n = nodes[i];
|
|
177
|
+
if (!reducedMotion && !paused) {
|
|
178
|
+
n.x = (n.x + n.vx + 1000) % 1000;
|
|
179
|
+
n.y = (n.y + n.vy + 1000) % 1000;
|
|
180
|
+
}
|
|
181
|
+
const nx = (n.x / 1000) * w;
|
|
182
|
+
const ny = (n.y / 1000) * h;
|
|
183
|
+
|
|
184
|
+
ctx.globalAlpha =
|
|
185
|
+
(isDark ? 0.35 : 0.4) + Math.sin(time * 2 + n.phase) * 0.2;
|
|
186
|
+
ctx.fillStyle = i % 3 === 0 ? secondary : primary;
|
|
187
|
+
ctx.fillRect(nx, ny, n.size, n.size * 3.2);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
ctx.restore();
|
|
192
|
+
rafId = window.requestAnimationFrame(render);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
rafId = window.requestAnimationFrame(render);
|
|
196
|
+
return () => window.cancelAnimationFrame(rafId);
|
|
197
|
+
}, [variant, themeMeta, reducedMotion, paused]);
|
|
198
|
+
|
|
199
|
+
if (variant === 'minimal-void') {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<canvas
|
|
205
|
+
ref={canvasRef}
|
|
206
|
+
aria-hidden="true"
|
|
207
|
+
className="fixed inset-0 z-0 pointer-events-none"
|
|
208
|
+
/>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
export type ZenBadgeTone =
|
|
4
|
+
| 'gold'
|
|
5
|
+
| 'amber'
|
|
6
|
+
| 'emerald'
|
|
7
|
+
| 'coral'
|
|
8
|
+
| 'cyan'
|
|
9
|
+
| 'neutral';
|
|
10
|
+
|
|
11
|
+
export interface ZenBadgeProps {
|
|
12
|
+
readonly tone?: ZenBadgeTone;
|
|
13
|
+
readonly pulse?: boolean;
|
|
14
|
+
readonly icon?: ReactNode;
|
|
15
|
+
readonly children: ReactNode;
|
|
16
|
+
readonly className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const TONE_STYLES: Record<
|
|
20
|
+
ZenBadgeTone,
|
|
21
|
+
{ readonly pill: string; readonly dot: string }
|
|
22
|
+
> = {
|
|
23
|
+
gold: {
|
|
24
|
+
pill: 'bg-[var(--zen-accent-subtle)] text-[var(--zen-accent-primary)] border-[var(--zen-accent-border)]',
|
|
25
|
+
dot: 'bg-[var(--zen-accent-primary)]',
|
|
26
|
+
},
|
|
27
|
+
amber: {
|
|
28
|
+
pill: 'bg-[color-mix(in_srgb,var(--zen-status-warning)_15%,transparent)] text-[var(--zen-status-warning)] border-[color-mix(in_srgb,var(--zen-status-warning)_45%,transparent)]',
|
|
29
|
+
dot: 'bg-[var(--zen-status-warning)]',
|
|
30
|
+
},
|
|
31
|
+
emerald: {
|
|
32
|
+
pill: 'bg-[color-mix(in_srgb,var(--zen-status-success)_15%,transparent)] text-[var(--zen-status-success)] border-[color-mix(in_srgb,var(--zen-status-success)_45%,transparent)]',
|
|
33
|
+
dot: 'bg-[var(--zen-status-success)]',
|
|
34
|
+
},
|
|
35
|
+
coral: {
|
|
36
|
+
pill: 'bg-[color-mix(in_srgb,var(--zen-status-danger)_15%,transparent)] text-[var(--zen-status-danger)] border-[color-mix(in_srgb,var(--zen-status-danger)_45%,transparent)]',
|
|
37
|
+
dot: 'bg-[var(--zen-status-danger)]',
|
|
38
|
+
},
|
|
39
|
+
cyan: {
|
|
40
|
+
pill: 'bg-[color-mix(in_srgb,var(--zen-status-info)_15%,transparent)] text-[var(--zen-status-info)] border-[color-mix(in_srgb,var(--zen-status-info)_45%,transparent)]',
|
|
41
|
+
dot: 'bg-[var(--zen-status-info)]',
|
|
42
|
+
},
|
|
43
|
+
neutral: {
|
|
44
|
+
pill: 'bg-[var(--zen-surface-subtle)] text-[var(--zen-ink-secondary)] border-[var(--zen-border-default)]',
|
|
45
|
+
dot: 'bg-[var(--zen-ink-muted)]',
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function ZenBadge({
|
|
50
|
+
tone = 'neutral',
|
|
51
|
+
pulse = false,
|
|
52
|
+
icon,
|
|
53
|
+
children,
|
|
54
|
+
className = '',
|
|
55
|
+
}: ZenBadgeProps) {
|
|
56
|
+
const style = TONE_STYLES[tone];
|
|
57
|
+
return (
|
|
58
|
+
<span
|
|
59
|
+
className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full border zen-font-mono text-[11px] font-medium tracking-wide ${style.pill} ${className}`.trim()}
|
|
60
|
+
>
|
|
61
|
+
{pulse && (
|
|
62
|
+
<span
|
|
63
|
+
className={`w-1.5 h-1.5 rounded-full animate-pulse ${style.dot}`}
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
{icon && <span className="shrink-0 inline-flex">{icon}</span>}
|
|
68
|
+
<span>{children}</span>
|
|
69
|
+
</span>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ZenTagListProps {
|
|
74
|
+
readonly tags: readonly string[];
|
|
75
|
+
readonly className?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function ZenTagList({ tags, className = '' }: ZenTagListProps) {
|
|
79
|
+
return (
|
|
80
|
+
<div className={`flex flex-wrap gap-2 ${className}`.trim()}>
|
|
81
|
+
{tags.map((tag) => (
|
|
82
|
+
<span
|
|
83
|
+
key={tag}
|
|
84
|
+
className="px-3 py-1 rounded-full border border-[var(--zen-border-default)] bg-[var(--zen-surface-subtle)] zen-font-mono text-[11px] text-[var(--zen-ink-secondary)]"
|
|
85
|
+
>
|
|
86
|
+
{tag}
|
|
87
|
+
</span>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ZenKbdProps {
|
|
94
|
+
readonly keys: readonly string[];
|
|
95
|
+
readonly className?: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function ZenKbd({ keys, className = '' }: ZenKbdProps) {
|
|
99
|
+
return (
|
|
100
|
+
<span className={`inline-flex items-center gap-1 ${className}`.trim()}>
|
|
101
|
+
{keys.map((k) => (
|
|
102
|
+
<kbd
|
|
103
|
+
key={k}
|
|
104
|
+
className="px-1.5 py-0.5 rounded-md border border-[var(--zen-border-default)] bg-[var(--zen-surface-subtle)] zen-font-mono text-[11px] text-[var(--zen-ink-secondary)] shadow-xs"
|
|
105
|
+
>
|
|
106
|
+
{k}
|
|
107
|
+
</kbd>
|
|
108
|
+
))}
|
|
109
|
+
</span>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useState, useRef, useEffect, type ButtonHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import { Check, Copy, Loader2 } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export type ZenButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
5
|
+
export type ZenButtonSize = 'sm' | 'md' | 'lg';
|
|
6
|
+
|
|
7
|
+
export interface ZenButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
8
|
+
readonly variant?: ZenButtonVariant;
|
|
9
|
+
readonly size?: ZenButtonSize;
|
|
10
|
+
readonly pressed?: boolean;
|
|
11
|
+
readonly mono?: boolean;
|
|
12
|
+
readonly loading?: boolean;
|
|
13
|
+
readonly iconLeft?: ReactNode;
|
|
14
|
+
readonly iconRight?: ReactNode;
|
|
15
|
+
readonly children: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SIZE_CLASSES: Record<ZenButtonSize, string> = {
|
|
19
|
+
sm: 'px-3.5 py-1.5 text-xs',
|
|
20
|
+
md: 'px-5 py-2.5 text-xs sm:text-sm',
|
|
21
|
+
lg: 'px-6 py-3.5 text-sm',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const VARIANT_CLASSES: Record<ZenButtonVariant, string> = {
|
|
25
|
+
primary: 'zen-btn-primary',
|
|
26
|
+
secondary: 'zen-btn-secondary',
|
|
27
|
+
ghost: 'zen-btn-ghost',
|
|
28
|
+
danger: 'zen-btn-danger',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function ZenButton({
|
|
32
|
+
variant = 'primary',
|
|
33
|
+
size = 'md',
|
|
34
|
+
pressed,
|
|
35
|
+
mono = false,
|
|
36
|
+
loading = false,
|
|
37
|
+
iconLeft,
|
|
38
|
+
iconRight,
|
|
39
|
+
disabled,
|
|
40
|
+
className = '',
|
|
41
|
+
children,
|
|
42
|
+
...rest
|
|
43
|
+
}: ZenButtonProps) {
|
|
44
|
+
const isPressed = pressed === true;
|
|
45
|
+
const pressedClasses =
|
|
46
|
+
isPressed && variant !== 'primary'
|
|
47
|
+
? 'bg-[var(--zen-accent-primary)] text-[var(--zen-ink-on-accent)] border-[var(--zen-accent-primary)] shadow-[var(--zen-shadow-btn-primary)]'
|
|
48
|
+
: '';
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
data-zen-occlude="true"
|
|
54
|
+
aria-pressed={pressed}
|
|
55
|
+
disabled={disabled || loading}
|
|
56
|
+
className={`zen-btn-base zen-focus-ring ${VARIANT_CLASSES[variant]} ${SIZE_CLASSES[size]} ${
|
|
57
|
+
mono ? 'zen-font-mono' : 'zen-font-sans'
|
|
58
|
+
} ${pressedClasses} ${className}`.trim()}
|
|
59
|
+
{...rest}
|
|
60
|
+
>
|
|
61
|
+
{loading ? (
|
|
62
|
+
<Loader2 className="w-4 h-4 animate-spin shrink-0" />
|
|
63
|
+
) : (
|
|
64
|
+
iconLeft && <span className="shrink-0 inline-flex">{iconLeft}</span>
|
|
65
|
+
)}
|
|
66
|
+
<span>{children}</span>
|
|
67
|
+
{iconRight && <span className="shrink-0 inline-flex">{iconRight}</span>}
|
|
68
|
+
</button>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface ZenCopyButtonProps {
|
|
73
|
+
readonly value: string;
|
|
74
|
+
readonly label?: string;
|
|
75
|
+
readonly copiedLabel?: string;
|
|
76
|
+
readonly size?: ZenButtonSize;
|
|
77
|
+
readonly variant?: ZenButtonVariant;
|
|
78
|
+
readonly className?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function ZenCopyButton({
|
|
82
|
+
value,
|
|
83
|
+
label = 'COPY',
|
|
84
|
+
copiedLabel = 'COPIED',
|
|
85
|
+
size = 'md',
|
|
86
|
+
variant = 'secondary',
|
|
87
|
+
className = '',
|
|
88
|
+
}: ZenCopyButtonProps) {
|
|
89
|
+
const [copied, setCopied] = useState(false);
|
|
90
|
+
const timeoutRef = useRef<number | null>(null);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
return () => {
|
|
94
|
+
if (timeoutRef.current !== null) {
|
|
95
|
+
window.clearTimeout(timeoutRef.current);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
const handleCopy = () => {
|
|
101
|
+
void navigator.clipboard.writeText(value);
|
|
102
|
+
setCopied(true);
|
|
103
|
+
if (timeoutRef.current !== null) {
|
|
104
|
+
window.clearTimeout(timeoutRef.current);
|
|
105
|
+
}
|
|
106
|
+
timeoutRef.current = window.setTimeout(() => setCopied(false), 2000);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<ZenButton
|
|
111
|
+
variant={variant}
|
|
112
|
+
size={size}
|
|
113
|
+
mono
|
|
114
|
+
onClick={handleCopy}
|
|
115
|
+
className={className}
|
|
116
|
+
iconLeft={
|
|
117
|
+
copied ? (
|
|
118
|
+
<Check className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
|
|
119
|
+
) : (
|
|
120
|
+
<Copy className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
{copied ? copiedLabel : label}
|
|
125
|
+
</ZenButton>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
|
|
2
|
+
import { CheckCircle2 } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
export type ZenCardVariant = 'static' | 'interactive' | 'selected';
|
|
5
|
+
export type ZenCardPadding = 'none' | 'sm' | 'md' | 'lg';
|
|
6
|
+
|
|
7
|
+
export interface ZenCardProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
readonly variant?: ZenCardVariant;
|
|
9
|
+
readonly selected?: boolean;
|
|
10
|
+
readonly disabled?: boolean;
|
|
11
|
+
readonly padding?: ZenCardPadding;
|
|
12
|
+
readonly onSelect?: () => void;
|
|
13
|
+
readonly selectOnHover?: boolean;
|
|
14
|
+
readonly children: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PADDING_MAP: Record<ZenCardPadding, string> = {
|
|
18
|
+
none: 'p-0',
|
|
19
|
+
sm: 'p-3.5 sm:p-5',
|
|
20
|
+
md: 'p-4 sm:p-6 lg:p-8',
|
|
21
|
+
lg: 'p-5 sm:p-8 lg:p-12',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function ZenCard({
|
|
25
|
+
variant = 'static',
|
|
26
|
+
selected = false,
|
|
27
|
+
disabled = false,
|
|
28
|
+
padding = 'md',
|
|
29
|
+
onSelect,
|
|
30
|
+
selectOnHover = false,
|
|
31
|
+
className = '',
|
|
32
|
+
children,
|
|
33
|
+
onClick,
|
|
34
|
+
onMouseEnter,
|
|
35
|
+
onKeyDown,
|
|
36
|
+
...rest
|
|
37
|
+
}: ZenCardProps) {
|
|
38
|
+
const isInteractive = variant === 'interactive' || Boolean(onSelect);
|
|
39
|
+
const isSelected = selected || variant === 'selected';
|
|
40
|
+
|
|
41
|
+
const surfaceClass = isInteractive
|
|
42
|
+
? 'zen-surface-interactive zen-focus-ring cursor-pointer'
|
|
43
|
+
: isSelected
|
|
44
|
+
? 'zen-surface-selected'
|
|
45
|
+
: 'zen-surface-static';
|
|
46
|
+
|
|
47
|
+
const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
|
48
|
+
onKeyDown?.(e);
|
|
49
|
+
if (!disabled && isInteractive && (e.key === 'Enter' || e.key === ' ')) {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
onSelect?.();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
data-zen-occlude="true"
|
|
58
|
+
data-selected={isSelected ? 'true' : 'false'}
|
|
59
|
+
data-disabled={disabled ? 'true' : 'false'}
|
|
60
|
+
role={isInteractive ? 'button' : undefined}
|
|
61
|
+
aria-pressed={isInteractive ? isSelected : undefined}
|
|
62
|
+
aria-disabled={disabled ? true : undefined}
|
|
63
|
+
tabIndex={isInteractive && !disabled ? 0 : undefined}
|
|
64
|
+
onClick={(e) => {
|
|
65
|
+
onClick?.(e);
|
|
66
|
+
if (!disabled) {
|
|
67
|
+
onSelect?.();
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
onMouseEnter={(e) => {
|
|
71
|
+
onMouseEnter?.(e);
|
|
72
|
+
if (!disabled && selectOnHover) {
|
|
73
|
+
onSelect?.();
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
onKeyDown={handleKeyDown}
|
|
77
|
+
className={`rounded-3xl ${surfaceClass} ${PADDING_MAP[padding]} ${
|
|
78
|
+
disabled ? 'opacity-50 pointer-events-none' : ''
|
|
79
|
+
} ${className}`.trim()}
|
|
80
|
+
{...rest}
|
|
81
|
+
>
|
|
82
|
+
{children}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ZenMetricItem {
|
|
88
|
+
readonly value: string;
|
|
89
|
+
readonly label: string;
|
|
90
|
+
readonly accent?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface ZenMetricStripProps {
|
|
94
|
+
readonly metrics: readonly ZenMetricItem[];
|
|
95
|
+
readonly columns?: 2 | 3 | 4;
|
|
96
|
+
readonly className?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function ZenMetricStrip({
|
|
100
|
+
metrics,
|
|
101
|
+
columns = 4,
|
|
102
|
+
className = '',
|
|
103
|
+
}: ZenMetricStripProps) {
|
|
104
|
+
const colClass =
|
|
105
|
+
columns === 2
|
|
106
|
+
? 'grid-cols-2'
|
|
107
|
+
: columns === 3
|
|
108
|
+
? 'grid-cols-2 sm:grid-cols-3'
|
|
109
|
+
: 'grid-cols-2 sm:grid-cols-4';
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className={`grid ${colClass} gap-2.5 p-3 rounded-2xl bg-[var(--zen-surface-subtle)] border border-[var(--zen-border-subtle)] text-center ${className}`.trim()}
|
|
114
|
+
>
|
|
115
|
+
{metrics.map((m) => (
|
|
116
|
+
<div key={m.label}>
|
|
117
|
+
<div
|
|
118
|
+
className={`zen-font-display font-bold text-xs sm:text-sm ${
|
|
119
|
+
m.accent
|
|
120
|
+
? 'text-[var(--zen-accent-primary)]'
|
|
121
|
+
: 'text-[var(--zen-ink-primary)]'
|
|
122
|
+
}`}
|
|
123
|
+
>
|
|
124
|
+
{m.value}
|
|
125
|
+
</div>
|
|
126
|
+
<div className="zen-telemetry-label mt-0.5">{m.label}</div>
|
|
127
|
+
</div>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ZenArchitectureNoteProps {
|
|
134
|
+
readonly label?: string;
|
|
135
|
+
readonly children: ReactNode;
|
|
136
|
+
readonly className?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function ZenArchitectureNote({
|
|
140
|
+
label = 'SYSTEM ARCHITECTURE NOTE',
|
|
141
|
+
children,
|
|
142
|
+
className = '',
|
|
143
|
+
}: ZenArchitectureNoteProps) {
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={`p-4 rounded-2xl bg-[var(--zen-surface-subtle)] border border-[var(--zen-border-default)] ${className}`.trim()}
|
|
147
|
+
>
|
|
148
|
+
<div className="zen-telemetry-label text-[var(--zen-accent-secondary)] mb-1">
|
|
149
|
+
{label}
|
|
150
|
+
</div>
|
|
151
|
+
<p className="text-sm leading-relaxed text-[var(--zen-ink-secondary)]">
|
|
152
|
+
{children}
|
|
153
|
+
</p>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ZenCheckListProps {
|
|
159
|
+
readonly items: readonly string[];
|
|
160
|
+
readonly className?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function ZenCheckList({ items, className = '' }: ZenCheckListProps) {
|
|
164
|
+
return (
|
|
165
|
+
<ul className={`space-y-2 ${className}`.trim()}>
|
|
166
|
+
{items.map((item) => (
|
|
167
|
+
<li
|
|
168
|
+
key={item}
|
|
169
|
+
className="flex items-start gap-2 text-xs sm:text-sm text-[var(--zen-ink-secondary)]"
|
|
170
|
+
>
|
|
171
|
+
<CheckCircle2
|
|
172
|
+
className="w-3.5 h-3.5 text-[var(--zen-accent-primary)] shrink-0 mt-0.5"
|
|
173
|
+
aria-hidden="true"
|
|
174
|
+
/>
|
|
175
|
+
<span>{item}</span>
|
|
176
|
+
</li>
|
|
177
|
+
))}
|
|
178
|
+
</ul>
|
|
179
|
+
);
|
|
180
|
+
}
|