vibe-ui-suite 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,992 +0,0 @@
1
- /**
2
- * Vibe UI Component Registry
3
- * Production-ready, accessible, AI-native React component templates.
4
- */
5
- export const COMPONENT_REGISTRY = {
6
- // ─── AI Contracts & Observation Primitives ───────────────────────────
7
- 'thinking-drawer': {
8
- name: 'thinking-drawer',
9
- filename: 'AiThinkingDrawer.tsx',
10
- category: 'ai-contracts',
11
- description: 'Collapsible AI reasoning drawer with CSS grid zero-JS transition, radar pulse & telemetry chips',
12
- code: `'use client';
13
-
14
- import React, { useState } from 'react';
15
-
16
- export interface ThinkingStep {
17
- id: string;
18
- label: string;
19
- status: 'completed' | 'in_progress' | 'pending';
20
- duration?: string;
21
- }
22
-
23
- export interface ToolExecutionChip {
24
- name: string;
25
- latency: string;
26
- status: 'cached' | 'executed' | 'verified';
27
- }
28
-
29
- export interface AiThinkingDrawerProps {
30
- initialOpen?: boolean;
31
- title?: string;
32
- durationMs?: number;
33
- steps?: ThinkingStep[];
34
- tools?: ToolExecutionChip[];
35
- className?: string;
36
- }
37
-
38
- const DEFAULT_STEPS: ThinkingStep[] = [
39
- { id: 's1', label: 'Ingested prompt & computed ambiguity budget', status: 'completed', duration: '18ms' },
40
- { id: 's2', label: 'Validated design-spec against JSON Schema', status: 'completed', duration: '42ms' },
41
- { id: 's3', label: 'Enforced WCAG AA contrast (>= 4.5:1) in OKLCH space', status: 'completed', duration: '95ms' },
42
- ];
43
-
44
- const DEFAULT_TOOLS: ToolExecutionChip[] = [
45
- { name: 'vector_search()', latency: '42ms', status: 'cached' },
46
- { name: 'contrast_lint()', latency: '18ms', status: 'verified' },
47
- ];
48
-
49
- export function AiThinkingDrawer({
50
- initialOpen = false,
51
- title = 'Reasoning Architecture & Contract Execution',
52
- durationMs = 155,
53
- steps = DEFAULT_STEPS,
54
- tools = DEFAULT_TOOLS,
55
- className = '',
56
- }: AiThinkingDrawerProps) {
57
- const [open, setOpen] = useState(initialOpen);
58
-
59
- return (
60
- <div
61
- className={\`w-full max-w-2xl mx-auto rounded-xl border border-white/10 bg-slate-950/80 backdrop-blur-md overflow-hidden text-slate-100 font-sans shadow-xl \${className}\`}
62
- role="region"
63
- aria-label="AI Reasoning Details"
64
- >
65
- <button
66
- onClick={() => setOpen(!open)}
67
- aria-expanded={open}
68
- className="w-full flex items-center justify-between px-4 py-3 text-left hover:bg-white/5 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500/50"
69
- >
70
- <div className="flex items-center gap-3">
71
- <span className="relative flex h-2.5 w-2.5">
72
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
73
- <span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" />
74
- </span>
75
- <span className="text-sm font-medium text-slate-200">{title}</span>
76
- <span className="text-xs px-2 py-0.5 rounded-full bg-slate-800 text-slate-400 font-mono">
77
- {durationMs}ms
78
- </span>
79
- </div>
80
- <svg
81
- className={\`w-4 h-4 text-slate-400 transition-transform duration-200 \${open ? 'rotate-180' : ''}\`}
82
- fill="none"
83
- stroke="currentColor"
84
- viewBox="0 0 24 24"
85
- >
86
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
87
- </svg>
88
- </button>
89
-
90
- {/* Zero-JS height transition via CSS Grid */}
91
- <div
92
- className="grid transition-all duration-300 ease-out"
93
- style={{ gridTemplateRows: open ? '1fr' : '0fr' }}
94
- >
95
- <div className="overflow-hidden">
96
- <div className="p-4 pt-1 border-t border-white/5 space-y-3">
97
- <div className="space-y-2">
98
- {steps.map((step) => (
99
- <div key={step.id} className="flex items-center justify-between text-xs text-slate-300">
100
- <div className="flex items-center gap-2">
101
- <svg className="w-3.5 h-3.5 text-emerald-400 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
102
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7" />
103
- </svg>
104
- <span>{step.label}</span>
105
- </div>
106
- {step.duration && <span className="font-mono text-slate-500 text-[11px]">{step.duration}</span>}
107
- </div>
108
- ))}
109
- </div>
110
-
111
- {tools.length > 0 && (
112
- <div className="pt-2 border-t border-white/5 flex flex-wrap gap-1.5">
113
- {tools.map((t, idx) => (
114
- <span
115
- key={idx}
116
- className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded bg-slate-900 border border-slate-800 text-[11px] font-mono text-slate-300"
117
- >
118
- <span className="w-1.5 h-1.5 rounded-full bg-indigo-400" />
119
- {t.name}
120
- <span className="text-slate-500">{t.latency}</span>
121
- </span>
122
- ))}
123
- </div>
124
- )}
125
- </div>
126
- </div>
127
- </div>
128
- </div>
129
- );
130
- }
131
- `,
132
- },
133
- 'telemetry-hud': {
134
- name: 'telemetry-hud',
135
- filename: 'TelemetryHud.tsx',
136
- category: 'ai-contracts',
137
- description: 'LTR-isolated technical metric HUD with micro-borders for latency, tokens, and model status',
138
- code: `'use client';
139
-
140
- import React from 'react';
141
-
142
- export interface TelemetryMetric {
143
- label: string;
144
- value: string | number;
145
- unit?: string;
146
- status?: 'nominal' | 'warning' | 'alert';
147
- }
148
-
149
- export interface TelemetryHudProps {
150
- metrics?: TelemetryMetric[];
151
- className?: string;
152
- }
153
-
154
- const DEFAULT_METRICS: TelemetryMetric[] = [
155
- { label: 'Latency', value: '42', unit: 'ms', status: 'nominal' },
156
- { label: 'Entropy', value: '0.12', status: 'nominal' },
157
- { label: 'Contrast', value: '9.4:1', unit: 'AAA', status: 'nominal' },
158
- { label: 'Tokens', value: '1,420', status: 'nominal' },
159
- ];
160
-
161
- export function TelemetryHud({ metrics = DEFAULT_METRICS, className = '' }: TelemetryHudProps) {
162
- return (
163
- <div
164
- dir="ltr"
165
- className={\`inline-flex items-center gap-4 px-3 py-1.5 rounded-lg border border-white/10 bg-slate-950/70 backdrop-blur-sm text-xs font-mono text-slate-300 shadow-sm \${className}\`}
166
- >
167
- {metrics.map((m, i) => (
168
- <div key={i} className="flex items-center gap-1.5">
169
- <span className="text-slate-500 uppercase tracking-wider text-[10px]">{m.label}:</span>
170
- <span className="font-semibold text-slate-200">
171
- {m.value}
172
- {m.unit && <span className="ml-0.5 text-slate-400 text-[10px] font-normal">{m.unit}</span>}
173
- </span>
174
- {i < metrics.length - 1 && <span className="text-slate-700">|</span>}
175
- </div>
176
- ))}
177
- </div>
178
- );
179
- }
180
- `,
181
- },
182
- 'contrast-badge': {
183
- name: 'contrast-badge',
184
- filename: 'ContrastBadge.tsx',
185
- category: 'ai-contracts',
186
- description: 'WCAG 2.2 contrast compliance badge indicating AA (4.5:1) or AAA (7.0:1) verification',
187
- code: `'use client';
188
-
189
- import React from 'react';
190
-
191
- export interface ContrastBadgeProps {
192
- ratio: number;
193
- label?: string;
194
- className?: string;
195
- }
196
-
197
- export function ContrastBadge({ ratio, label = 'WCAG 2.2', className = '' }: ContrastBadgeProps) {
198
- const isAAA = ratio >= 7.0;
199
- const isAA = ratio >= 4.5;
200
- const isLargeOnly = ratio >= 3.0;
201
-
202
- const status = isAAA ? 'AAA' : isAA ? 'AA' : isLargeOnly ? 'Large Only' : 'Fail';
203
- const colorClass = isAA
204
- ? 'border-emerald-500/30 bg-emerald-950/40 text-emerald-300'
205
- : isLargeOnly
206
- ? 'border-amber-500/30 bg-amber-950/40 text-amber-300'
207
- : 'border-rose-500/30 bg-rose-950/40 text-rose-300';
208
-
209
- return (
210
- <span
211
- dir="ltr"
212
- className={\`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full border text-xs font-mono font-medium \${colorClass} \${className}\`}
213
- >
214
- <span className="w-1.5 h-1.5 rounded-full bg-current" />
215
- <span>{label}</span>
216
- <span className="opacity-60">•</span>
217
- <span>{ratio.toFixed(1)}:1</span>
218
- <span className="px-1 py-0.2 rounded bg-white/10 text-[10px] uppercase">{status}</span>
219
- </span>
220
- );
221
- }
222
- `,
223
- },
224
- 'theme-toggle': {
225
- name: 'theme-toggle',
226
- filename: 'ThemeToggle.tsx',
227
- category: 'ai-contracts',
228
- description: 'Accessible dual-theme switch (light/dark) with SVG vector icons, focus ring, and localStorage persistence',
229
- code: `'use client';
230
-
231
- import React, { useEffect, useState } from 'react';
232
-
233
- export interface ThemeToggleProps {
234
- className?: string;
235
- defaultTheme?: 'light' | 'dark' | 'system';
236
- }
237
-
238
- export function ThemeToggle({ className = '', defaultTheme = 'system' }: ThemeToggleProps) {
239
- const [theme, setTheme] = useState<'light' | 'dark'>('dark');
240
- const [mounted, setMounted] = useState(false);
241
-
242
- useEffect(() => {
243
- setMounted(true);
244
- const stored = localStorage.getItem('vibe-theme');
245
- if (stored === 'light' || stored === 'dark') {
246
- setTheme(stored);
247
- document.documentElement.classList.toggle('dark', stored === 'dark');
248
- document.documentElement.setAttribute('data-theme', stored);
249
- } else {
250
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
251
- const initial = defaultTheme === 'system' ? (prefersDark ? 'dark' : 'light') : defaultTheme;
252
- setTheme(initial);
253
- document.documentElement.classList.toggle('dark', initial === 'dark');
254
- document.documentElement.setAttribute('data-theme', initial);
255
- }
256
- }, [defaultTheme]);
257
-
258
- const toggleTheme = () => {
259
- const next = theme === 'dark' ? 'light' : 'dark';
260
- setTheme(next);
261
- localStorage.setItem('vibe-theme', next);
262
- document.documentElement.classList.toggle('dark', next === 'dark');
263
- document.documentElement.setAttribute('data-theme', next);
264
- };
265
-
266
- if (!mounted) {
267
- return (
268
- <button
269
- type="button"
270
- aria-label="Toggle theme"
271
- className={\`inline-flex items-center justify-center min-h-[44px] min-w-[44px] p-2.5 rounded-lg border border-neutral-800 bg-neutral-900/50 text-neutral-400 opacity-60 \${className}\`}
272
- disabled
273
- >
274
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
275
- <circle cx="12" cy="12" r="4" strokeWidth="2" />
276
- </svg>
277
- </button>
278
- );
279
- }
280
-
281
- return (
282
- <button
283
- type="button"
284
- onClick={toggleTheme}
285
- aria-label={\`Switch to \${theme === 'dark' ? 'light' : 'dark'} mode\`}
286
- className={\`inline-flex items-center justify-center min-h-[44px] min-w-[44px] p-2.5 rounded-lg border transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 \${
287
- theme === 'dark'
288
- ? 'border-neutral-800 bg-neutral-900/80 text-neutral-200 hover:bg-neutral-800 hover:text-white'
289
- : 'border-neutral-200 bg-white text-neutral-700 hover:bg-neutral-100 hover:text-neutral-900'
290
- } \${className}\`}
291
- >
292
- {theme === 'dark' ? (
293
- <svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" aria-hidden="true">
294
- <circle cx="12" cy="12" r="4" />
295
- <path strokeLinecap="round" d="M12 2v2m0 16v2M4.93 4.93l1.41 1.41m11.32 11.32l1.41 1.41M2 12h2m16 0h2M6.34 17.66l-1.41 1.41m14.14-14.14l-1.41 1.41" />
296
- </svg>
297
- ) : (
298
- <svg className="w-5 h-5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2" aria-hidden="true">
299
- <path strokeLinecap="round" strokeLinejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
300
- </svg>
301
- )}
302
- </button>
303
- );
304
- }
305
- `,
306
- },
307
- // ─── Curated Living Micro-Interactions ────────────────────────────────
308
- 'shimmer-button': {
309
- name: 'shimmer-button',
310
- filename: 'ShimmerButton.tsx',
311
- category: 'living-micro-interactions',
312
- description: 'Animated shimmer gradient button with smooth scale, OKLCH accents, SVG slot, and WCAG focus ring',
313
- code: `'use client';
314
-
315
- import React from 'react';
316
-
317
- export interface ShimmerButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
318
- children: React.ReactNode;
319
- variant?: 'primary' | 'secondary' | 'outline';
320
- size?: 'sm' | 'md' | 'lg';
321
- icon?: React.ReactNode;
322
- shimmerColor?: string;
323
- className?: string;
324
- }
325
-
326
- export function ShimmerButton({
327
- children,
328
- variant = 'primary',
329
- size = 'md',
330
- icon,
331
- shimmerColor = 'rgba(255, 255, 255, 0.25)',
332
- className = '',
333
- disabled,
334
- ...props
335
- }: ShimmerButtonProps) {
336
- const sizeClasses = {
337
- sm: 'px-3.5 py-1.5 text-xs min-h-[36px]',
338
- md: 'px-5 py-2.5 text-sm min-h-[44px]',
339
- lg: 'px-6 py-3.5 text-base min-h-[50px]',
340
- }[size];
341
-
342
- const variantClasses = {
343
- primary: 'bg-indigo-600 hover:bg-indigo-500 text-white shadow-lg shadow-indigo-500/25 border-indigo-400/30',
344
- secondary: 'bg-slate-900 hover:bg-slate-800 text-slate-100 border-slate-700/60 shadow-md shadow-black/20',
345
- outline: 'bg-transparent hover:bg-white/5 text-slate-200 border-slate-600/60',
346
- }[variant];
347
-
348
- return (
349
- <button
350
- type="button"
351
- disabled={disabled}
352
- className={\`group relative inline-flex items-center justify-center gap-2 overflow-hidden rounded-xl border font-medium tracking-wide transition-all duration-300 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 \${sizeClasses} \${variantClasses} \${className}\`}
353
- {...props}
354
- >
355
- {/* Dynamic Animated Shimmer Flare */}
356
- <span
357
- aria-hidden="true"
358
- className="pointer-events-none absolute inset-0 -translate-x-full transition-transform duration-1000 ease-out group-hover:translate-x-full bg-gradient-to-r from-transparent via-[var(--shimmer-color,rgba(255,255,255,0.25))] to-transparent"
359
- style={{ '--shimmer-color': shimmerColor } as React.CSSProperties}
360
- />
361
-
362
- {icon && (
363
- <span className="shrink-0 transition-transform duration-200 group-hover:scale-110" aria-hidden="true">
364
- {icon}
365
- </span>
366
- )}
367
- <span className="relative z-10">{children}</span>
368
- </button>
369
- );
370
- }
371
- `,
372
- },
373
- 'cyber-loader': {
374
- name: 'cyber-loader',
375
- filename: 'CyberLoader.tsx',
376
- category: 'living-micro-interactions',
377
- description: 'High-efficiency dual-ring neon pulse spinner without heavy blur lag, with ARIA live region',
378
- code: `'use client';
379
-
380
- import React from 'react';
381
-
382
- export interface CyberLoaderProps {
383
- size?: 'sm' | 'md' | 'lg';
384
- label?: string;
385
- speed?: 'normal' | 'fast' | 'slow';
386
- className?: string;
387
- }
388
-
389
- export function CyberLoader({
390
- size = 'md',
391
- label = 'Loading system telemetry...',
392
- speed = 'normal',
393
- className = '',
394
- }: CyberLoaderProps) {
395
- const sizeMap = {
396
- sm: { container: 'w-8 h-8', inner: 'w-4 h-4', border: 'border-2' },
397
- md: { container: 'w-12 h-12', inner: 'w-6 h-6', border: 'border-2' },
398
- lg: { container: 'w-16 h-16', inner: 'w-8 h-8', border: 'border-[3px]' },
399
- }[size];
400
-
401
- const durationClass = {
402
- fast: 'duration-700',
403
- normal: 'duration-1000',
404
- slow: 'duration-1500',
405
- }[speed];
406
-
407
- return (
408
- <div
409
- role="status"
410
- aria-live="polite"
411
- className={\`inline-flex flex-col items-center justify-center gap-3 \${className}\`}
412
- >
413
- <div className={\`relative flex items-center justify-center \${sizeMap.container}\`}>
414
- {/* Outer orbital neon ring */}
415
- <div
416
- className={\`absolute inset-0 rounded-full \${sizeMap.border} border-t-indigo-500 border-r-indigo-400/40 border-b-transparent border-l-transparent animate-spin \${durationClass}\`}
417
- />
418
- {/* Reverse counter-orbital ring */}
419
- <div
420
- className={\`absolute \${sizeMap.inner} rounded-full \${sizeMap.border} border-t-transparent border-r-transparent border-b-emerald-400 border-l-emerald-500/50 animate-spin \${durationClass}\`}
421
- style={{ animationDirection: 'reverse' }}
422
- />
423
- {/* Center core pulse */}
424
- <div className="w-1.5 h-1.5 rounded-full bg-indigo-400 animate-ping opacity-75" />
425
- </div>
426
-
427
- {label && (
428
- <span className="text-xs font-mono font-medium tracking-wider uppercase text-slate-400 dark:text-slate-300">
429
- {label}
430
- </span>
431
- )}
432
- <span className="sr-only">{label}</span>
433
- </div>
434
- );
435
- }
436
- `,
437
- },
438
- 'holographic-card': {
439
- name: 'holographic-card',
440
- filename: 'HolographicCard.tsx',
441
- category: 'living-micro-interactions',
442
- description: 'Interactive 3D tilt card with specular border highlight, glass gradient reflection, and depth control',
443
- code: `'use client';
444
-
445
- import React, { useRef, useState } from 'react';
446
-
447
- export interface HolographicCardProps {
448
- title?: string;
449
- description?: string;
450
- badge?: string;
451
- children?: React.ReactNode;
452
- interactive?: boolean;
453
- className?: string;
454
- }
455
-
456
- export function HolographicCard({
457
- title,
458
- description,
459
- badge,
460
- children,
461
- interactive = true,
462
- className = '',
463
- }: HolographicCardProps) {
464
- const cardRef = useRef<HTMLDivElement>(null);
465
- const [rotate, setRotate] = useState({ x: 0, y: 0 });
466
- const [glow, setGlow] = useState({ x: 50, y: 50, opacity: 0 });
467
-
468
- const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
469
- if (!interactive || !cardRef.current) return;
470
- const rect = cardRef.current.getBoundingClientRect();
471
- const x = e.clientX - rect.left;
472
- const y = e.clientY - rect.top;
473
- const centerX = rect.width / 2;
474
- const centerY = rect.height / 2;
475
-
476
- const rotX = ((y - centerY) / centerY) * -7;
477
- const rotY = ((x - centerX) / centerX) * 7;
478
- setRotate({ x: rotX, y: rotY });
479
- setGlow({
480
- x: (x / rect.width) * 100,
481
- y: (y / rect.height) * 100,
482
- opacity: 0.15,
483
- });
484
- };
485
-
486
- const handleMouseLeave = () => {
487
- if (!interactive) return;
488
- setRotate({ x: 0, y: 0 });
489
- setGlow({ x: 50, y: 50, opacity: 0 });
490
- };
491
-
492
- return (
493
- <div
494
- ref={cardRef}
495
- onMouseMove={handleMouseMove}
496
- onMouseLeave={handleMouseLeave}
497
- style={{
498
- transform: interactive
499
- ? \`perspective(1000px) rotateX(\${rotate.x}deg) rotateY(\${rotate.y}deg)\`
500
- : undefined,
501
- transition: 'transform 0.15s ease-out',
502
- }}
503
- className={\`group relative overflow-hidden rounded-2xl border border-white/10 bg-slate-900/80 p-6 text-slate-100 shadow-xl backdrop-blur-md transition-shadow duration-300 hover:shadow-2xl hover:shadow-indigo-500/10 \${className}\`}
504
- >
505
- {/* Specular Radial Holographic Flare */}
506
- <div
507
- aria-hidden="true"
508
- className="pointer-events-none absolute -inset-px rounded-2xl transition-opacity duration-300"
509
- style={{
510
- background: \`radial-gradient(circle 250px at \${glow.x}% \${glow.y}%, rgba(99, 102, 241, \${glow.opacity}), transparent 80%)\`,
511
- }}
512
- />
513
-
514
- {/* Header section */}
515
- {(title || badge) && (
516
- <div className="relative z-10 flex items-start justify-between gap-4 mb-3">
517
- {title && <h3 className="text-lg font-semibold tracking-tight text-white">{title}</h3>}
518
- {badge && (
519
- <span className="inline-flex items-center rounded-full border border-indigo-400/30 bg-indigo-500/10 px-2.5 py-0.5 text-xs font-medium text-indigo-300">
520
- {badge}
521
- </span>
522
- )}
523
- </div>
524
- )}
525
-
526
- {description && (
527
- <p className="relative z-10 text-sm text-slate-400 mb-4 leading-relaxed">{description}</p>
528
- )}
529
-
530
- <div className="relative z-10">{children}</div>
531
- </div>
532
- );
533
- }
534
- `,
535
- },
536
- 'tactile-switch': {
537
- name: 'tactile-switch',
538
- filename: 'TactileSwitch.tsx',
539
- category: 'living-micro-interactions',
540
- description: 'Ergonomic tactile toggle switch with spring-bounce animation physics and full keyboard accessibility',
541
- code: `'use client';
542
-
543
- import React, { useId } from 'react';
544
-
545
- export interface TactileSwitchProps {
546
- checked: boolean;
547
- onChange: (checked: boolean) => void;
548
- label?: string;
549
- description?: string;
550
- disabled?: boolean;
551
- id?: string;
552
- className?: string;
553
- }
554
-
555
- export function TactileSwitch({
556
- checked,
557
- onChange,
558
- label,
559
- description,
560
- disabled = false,
561
- id: customId,
562
- className = '',
563
- }: TactileSwitchProps) {
564
- const generatedId = useId();
565
- const switchId = customId || generatedId;
566
- const descId = \`\${switchId}-desc\`;
567
-
568
- const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
569
- if (disabled) return;
570
- if (e.key === ' ' || e.key === 'Enter') {
571
- e.preventDefault();
572
- onChange(!checked);
573
- }
574
- };
575
-
576
- return (
577
- <div className={\`flex items-center justify-between gap-4 select-none \${disabled ? 'opacity-50 pointer-events-none' : ''} \${className}\`}>
578
- {(label || description) && (
579
- <label htmlFor={switchId} className="cursor-pointer flex flex-col text-sm">
580
- {label && <span className="font-medium text-slate-200 dark:text-slate-100">{label}</span>}
581
- {description && <span id={descId} className="text-xs text-slate-400 dark:text-slate-400">{description}</span>}
582
- </label>
583
- )}
584
-
585
- <button
586
- id={switchId}
587
- type="button"
588
- role="switch"
589
- aria-checked={checked}
590
- aria-describedby={description ? descId : undefined}
591
- disabled={disabled}
592
- onClick={() => onChange(!checked)}
593
- onKeyDown={handleKeyDown}
594
- className={\`relative inline-flex h-7 w-12 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950 min-h-[44px] min-w-[44px] p-2 items-center \${
595
- checked ? 'bg-indigo-600' : 'bg-slate-700'
596
- }\`}
597
- >
598
- <span
599
- aria-hidden="true"
600
- className={\`pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow-md ring-0 transition duration-200 ease-out \${
601
- checked ? 'translate-x-5' : 'translate-x-0'
602
- }\`}
603
- />
604
- </button>
605
- </div>
606
- );
607
- }
608
- `,
609
- },
610
- 'glow-input': {
611
- name: 'glow-input',
612
- filename: 'GlowInput.tsx',
613
- category: 'living-micro-interactions',
614
- description: 'Floating neon halo input field with dynamic focus state, error styling, and RTL compatibility',
615
- code: `'use client';
616
-
617
- import React, { useId } from 'react';
618
-
619
- export interface GlowInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
620
- label?: string;
621
- error?: string;
622
- helperText?: string;
623
- icon?: React.ReactNode;
624
- }
625
-
626
- export function GlowInput({
627
- label,
628
- error,
629
- helperText,
630
- icon,
631
- id: customId,
632
- className = '',
633
- disabled,
634
- ...props
635
- }: GlowInputProps) {
636
- const generatedId = useId();
637
- const inputId = customId || generatedId;
638
- const errorId = \`\${inputId}-error\`;
639
- const helperId = \`\${inputId}-helper\`;
640
-
641
- return (
642
- <div className={\`w-full flex flex-col gap-1.5 \${className}\`}>
643
- {label && (
644
- <label htmlFor={inputId} className="text-xs font-medium text-slate-300 dark:text-slate-200">
645
- {label}
646
- </label>
647
- )}
648
-
649
- <div className="relative group">
650
- {/* Animated Glow Border Halo on Focus */}
651
- <div
652
- aria-hidden="true"
653
- className={\`absolute -inset-0.5 rounded-xl blur-sm transition-opacity duration-300 opacity-0 group-focus-within:opacity-100 pointer-events-none \${
654
- error ? 'bg-rose-500/30' : 'bg-indigo-500/30'
655
- }\`}
656
- />
657
-
658
- <div className="relative flex items-center">
659
- {icon && (
660
- <div className="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none text-slate-400" aria-hidden="true">
661
- {icon}
662
- </div>
663
- )}
664
-
665
- <input
666
- id={inputId}
667
- disabled={disabled}
668
- aria-invalid={Boolean(error)}
669
- aria-describedby={error ? errorId : helperText ? helperId : undefined}
670
- className={\`w-full rounded-lg border bg-slate-900/90 text-sm text-slate-100 placeholder:text-slate-500 transition-all duration-200 focus:outline-none min-h-[44px] \${
671
- icon ? 'ps-10 pe-4' : 'px-4'
672
- } py-2.5 \${
673
- error
674
- ? 'border-rose-500/70 focus:border-rose-400 focus:ring-1 focus:ring-rose-500'
675
- : 'border-slate-800 focus:border-indigo-500/80 focus:ring-1 focus:ring-indigo-500/50'
676
- } disabled:opacity-50 disabled:cursor-not-allowed\`}
677
- {...props}
678
- />
679
- </div>
680
- </div>
681
-
682
- {error ? (
683
- <p id={errorId} className="text-xs text-rose-400 flex items-center gap-1 mt-0.5" role="alert">
684
- <svg className="w-3.5 h-3.5 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
685
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
686
- </svg>
687
- {error}
688
- </p>
689
- ) : helperText ? (
690
- <p id={helperId} className="text-xs text-slate-500 mt-0.5">
691
- {helperText}
692
- </p>
693
- ) : null}
694
- </div>
695
- );
696
- }
697
- `,
698
- },
699
- // ─── Flagship Living Primitives ───────────────────────────────────────
700
- 'device-mockup': {
701
- name: 'device-mockup',
702
- filename: 'DeviceMockup.tsx',
703
- category: 'living-micro-interactions',
704
- description: '3D titanium iPhone frame mockup with gyroscopic hover tilt, dynamic island, and specular glass reflex',
705
- code: `'use client';
706
-
707
- import React, { useRef, useState } from 'react';
708
-
709
- export interface DeviceMockupProps {
710
- children?: React.ReactNode;
711
- model?: 'iphone-16-pro' | 'minimal-frame';
712
- className?: string;
713
- enableTilt?: boolean;
714
- }
715
-
716
- export function DeviceMockup({
717
- children,
718
- model = 'iphone-16-pro',
719
- className = '',
720
- enableTilt = true,
721
- }: DeviceMockupProps) {
722
- const frameRef = useRef<HTMLDivElement>(null);
723
- const [rotate, setRotate] = useState({ x: 0, y: 0 });
724
-
725
- const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
726
- if (!enableTilt || !frameRef.current) return;
727
- const rect = frameRef.current.getBoundingClientRect();
728
- const x = e.clientX - rect.left;
729
- const y = e.clientY - rect.top;
730
- const centerX = rect.width / 2;
731
- const centerY = rect.height / 2;
732
-
733
- const rotX = ((y - centerY) / centerY) * -10;
734
- const rotY = ((x - centerX) / centerX) * 10;
735
- setRotate({ x: rotX, y: rotY });
736
- };
737
-
738
- const handleMouseLeave = () => {
739
- if (!enableTilt) return;
740
- setRotate({ x: 0, y: 0 });
741
- };
742
-
743
- return (
744
- <div
745
- className={\`relative mx-auto w-full max-w-[340px] select-none \${className}\`}
746
- style={{ perspective: '1000px' }}
747
- onMouseMove={handleMouseMove}
748
- onMouseLeave={handleMouseLeave}
749
- >
750
- <div
751
- ref={frameRef}
752
- style={{
753
- transform: enableTilt
754
- ? \`rotateX(\${rotate.x}deg) rotateY(\${rotate.y}deg)\`
755
- : undefined,
756
- transition: 'transform 0.4s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.4s ease',
757
- }}
758
- className="relative overflow-hidden rounded-[48px] border-[10px] border-[#1c1c1e] bg-black p-3.5 shadow-2xl shadow-indigo-950/30 ring-1 ring-white/20"
759
- >
760
- {/* Dynamic Island */}
761
- <div className="absolute top-4 left-1/2 -translate-x-1/2 z-30 h-[26px] w-[95px] rounded-full bg-black flex items-center justify-between px-2.5 shadow-sm">
762
- <div className="h-2 w-2 rounded-full bg-indigo-950/80 ring-1 ring-indigo-500/30" />
763
- <div className="h-2.5 w-2.5 rounded-full bg-emerald-500/20 flex items-center justify-center">
764
- <span className="h-1.5 w-1.5 rounded-full bg-emerald-400 animate-pulse" />
765
- </div>
766
- </div>
767
-
768
- {/* Specular Edge Sheen */}
769
- <div
770
- aria-hidden="true"
771
- className="pointer-events-none absolute inset-0 z-20 rounded-[38px] bg-gradient-to-tr from-white/10 via-transparent to-transparent opacity-60"
772
- />
773
-
774
- {/* Screen Content Container */}
775
- <div className="relative z-10 min-h-[580px] w-full overflow-hidden rounded-[36px] bg-slate-950 text-white flex flex-col">
776
- {children || (
777
- <div className="flex flex-1 flex-col items-center justify-center p-6 text-center">
778
- <div className="h-12 w-12 rounded-2xl bg-indigo-500/20 border border-indigo-400/30 flex items-center justify-center mb-4 text-indigo-400">
779
- <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
780
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
781
- </svg>
782
- </div>
783
- <h4 className="text-base font-semibold text-slate-100 mb-1">Vibe UI Native Screen</h4>
784
- <p className="text-xs text-slate-400 max-w-[200px]">
785
- Drop any responsive React or Tailwind component inside children.
786
- </p>
787
- </div>
788
- )}
789
- </div>
790
- </div>
791
- </div>
792
- );
793
- }
794
- `,
795
- },
796
- 'before-after-lens': {
797
- name: 'before-after-lens',
798
- filename: 'BeforeAfterLens.tsx',
799
- category: 'living-micro-interactions',
800
- description: 'Interactive split-screen comparison slider with drag physics, touch support, and accessible ARIA keyboard navigation',
801
- code: `'use client';
802
-
803
- import React, { useRef, useState, useCallback } from 'react';
804
-
805
- export interface BeforeAfterLensProps {
806
- beforeContent?: React.ReactNode;
807
- afterContent?: React.ReactNode;
808
- beforeLabel?: string;
809
- afterLabel?: string;
810
- initialPosition?: number;
811
- className?: string;
812
- }
813
-
814
- export function BeforeAfterLens({
815
- beforeContent,
816
- afterContent,
817
- beforeLabel = 'Before (Flat Baseline)',
818
- afterLabel = 'After (Vibe Masterpiece)',
819
- initialPosition = 50,
820
- className = '',
821
- }: BeforeAfterLensProps) {
822
- const [sliderPos, setSliderPos] = useState(initialPosition);
823
- const [isDragging, setIsDragging] = useState(false);
824
- const containerRef = useRef<HTMLDivElement>(null);
825
-
826
- const updatePosition = useCallback((clientX: number) => {
827
- if (!containerRef.current) return;
828
- const rect = containerRef.current.getBoundingClientRect();
829
- const x = clientX - rect.left;
830
- const percent = Math.max(5, Math.min(95, (x / rect.width) * 100));
831
- setSliderPos(percent);
832
- }, []);
833
-
834
- const handleMouseDown = () => setIsDragging(true);
835
- const handleMouseUp = () => setIsDragging(false);
836
-
837
- const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
838
- if (!isDragging) return;
839
- updatePosition(e.clientX);
840
- };
841
-
842
- const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
843
- if (e.touches[0]) {
844
- updatePosition(e.touches[0].clientX);
845
- }
846
- };
847
-
848
- const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
849
- if (e.key === 'ArrowLeft') {
850
- setSliderPos((prev) => Math.max(5, prev - 5));
851
- } else if (e.key === 'ArrowRight') {
852
- setSliderPos((prev) => Math.min(95, prev + 5));
853
- } else if (e.key === 'Home') {
854
- setSliderPos(5);
855
- } else if (e.key === 'End') {
856
- setSliderPos(95);
857
- }
858
- };
859
-
860
- return (
861
- <div
862
- ref={containerRef}
863
- onMouseMove={handleMouseMove}
864
- onMouseUp={handleMouseUp}
865
- onTouchMove={handleTouchMove}
866
- className={\`relative w-full overflow-hidden rounded-2xl border border-white/10 select-none \${className}\`}
867
- style={{ minHeight: '380px' }}
868
- >
869
- {/* After Layer (Background / Base) */}
870
- <div className="absolute inset-0 h-full w-full bg-slate-900 text-white p-6 flex flex-col justify-center">
871
- {afterContent || (
872
- <div className="max-w-md mx-auto text-center space-y-3">
873
- <span className="inline-flex px-3 py-1 rounded-full bg-indigo-500/20 text-indigo-400 text-xs font-mono font-medium border border-indigo-500/30">
874
- {afterLabel}
875
- </span>
876
- <h3 className="text-2xl font-bold text-white tracking-tight">Vibe UI Living Experience</h3>
877
- <p className="text-sm text-slate-300">
878
- Refined spatial rhythm, OKLCH perceptual colors, tactile micro-interactions, and fluid spring physics.
879
- </p>
880
- </div>
881
- )}
882
- </div>
883
-
884
- {/* Before Layer (Foreground clipped by slider percentage) */}
885
- <div
886
- className="absolute inset-0 h-full w-full bg-slate-950 text-slate-300 p-6 flex flex-col justify-center border-r border-indigo-400/50"
887
- style={{ clipPath: \`polygon(0 0, \${sliderPos}% 0, \${sliderPos}% 100%, 0 100%)\` }}
888
- >
889
- {beforeContent || (
890
- <div className="max-w-md mx-auto text-center space-y-3 opacity-70">
891
- <span className="inline-flex px-3 py-1 rounded-full bg-slate-800 text-slate-400 text-xs font-mono font-medium border border-slate-700">
892
- {beforeLabel}
893
- </span>
894
- <h3 className="text-2xl font-medium text-slate-400 tracking-tight">Generic AI Code Baseline</h3>
895
- <p className="text-sm text-slate-500">
896
- Dead layout, generic purple gradients, harsh contrast, raw emojis, and missing accessible states.
897
- </p>
898
- </div>
899
- )}
900
- </div>
901
-
902
- {/* Draggable Divider Line & Handle */}
903
- <div
904
- role="slider"
905
- tabIndex={0}
906
- aria-label="Before and After visual comparison"
907
- aria-valuenow={Math.round(sliderPos)}
908
- aria-valuemin={0}
909
- aria-valuemax={100}
910
- onKeyDown={handleKeyDown}
911
- onMouseDown={handleMouseDown}
912
- style={{ left: \`\${sliderPos}%\` }}
913
- className="absolute top-0 bottom-0 w-1 -ml-0.5 bg-gradient-to-b from-indigo-400 via-white to-indigo-400 cursor-ew-resize flex items-center justify-center z-30 focus:outline-none focus:ring-2 focus:ring-indigo-400"
914
- >
915
- <div className="h-9 w-9 rounded-full bg-slate-900 border-2 border-indigo-400 shadow-xl flex items-center justify-center text-indigo-300 text-xs transition-transform hover:scale-110 active:scale-95">
916
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
917
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M8 9l-3 3 3 3m8-6l3 3-3 3" />
918
- </svg>
919
- </div>
920
- </div>
921
- </div>
922
- );
923
- }
924
- `,
925
- },
926
- 'specular-card': {
927
- name: 'specular-card',
928
- filename: 'SpecularCard.tsx',
929
- category: 'living-micro-interactions',
930
- description: 'Photographic visual card with dynamic specular light sweep, category badge, and hover zoom physics',
931
- code: `'use client';
932
-
933
- import React from 'react';
934
-
935
- export interface SpecularCardProps {
936
- title: string;
937
- category?: string;
938
- description?: string;
939
- imageSrc?: string;
940
- children?: React.ReactNode;
941
- className?: string;
942
- }
943
-
944
- export function SpecularCard({
945
- title,
946
- category,
947
- description,
948
- imageSrc,
949
- children,
950
- className = '',
951
- }: SpecularCardProps) {
952
- return (
953
- <div
954
- className={\`group relative overflow-hidden rounded-2xl border border-white/10 bg-slate-900/80 p-5 text-white shadow-xl backdrop-blur-md transition-all duration-300 hover:-translate-y-1 hover:border-indigo-500/40 hover:shadow-2xl hover:shadow-indigo-500/10 \${className}\`}
955
- >
956
- {/* Dynamic Specular Sheen Sweep on Hover */}
957
- <div
958
- aria-hidden="true"
959
- className="pointer-events-none absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/10 to-transparent transition-transform duration-1000 ease-out group-hover:translate-x-full"
960
- />
961
-
962
- {imageSrc && (
963
- <div className="relative mb-4 h-48 w-full overflow-hidden rounded-xl bg-slate-800">
964
- <img
965
- src={imageSrc}
966
- alt={title}
967
- className="h-full w-full object-cover transition-transform duration-500 ease-out group-hover:scale-105"
968
- loading="lazy"
969
- />
970
- <div className="absolute inset-0 bg-gradient-to-t from-slate-950/80 via-transparent to-transparent" />
971
- </div>
972
- )}
973
-
974
- {category && (
975
- <span className="inline-flex items-center text-[11px] font-mono font-medium uppercase tracking-wider text-indigo-400 mb-2">
976
- {category}
977
- </span>
978
- )}
979
-
980
- <h3 className="text-lg font-semibold tracking-tight text-white mb-2">{title}</h3>
981
-
982
- {description && (
983
- <p className="text-sm text-slate-400 leading-relaxed mb-4">{description}</p>
984
- )}
985
-
986
- {children && <div className="mt-auto">{children}</div>}
987
- </div>
988
- );
989
- }
990
- `,
991
- },
992
- };