drivn 1.0.1
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/LICENSE.md +21 -0
- package/README.md +97 -0
- package/dist/index.js +1667 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1667 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {Command}from'commander';import*as e from'@clack/prompts';import l from'picocolors';import {join,dirname}from'path';import {execSync}from'child_process';import {existsSync,readFileSync,writeFileSync,mkdirSync}from'fs';var E={next:"Next.js",react:"React"};function A(t){let s=join(t,"package.json");if(!existsSync(s))throw new Error("package.json not found");let r=JSON.parse(readFileSync(s,"utf-8")),b={...r.dependencies,...r.devDependencies},d="react";b.next&&(d="next");let m=existsSync(join(t,"src")),f=existsSync(join(t,"tsconfig.json"));return {framework:d,srcDir:m,typescript:f}}var M="drivn.config.json";function V(t){let s=join(t,M);return existsSync(s)?JSON.parse(readFileSync(s,"utf-8")):null}function $(t,s){let r=join(t,M);writeFileSync(r,JSON.stringify(s,null,2));}function me(t){existsSync(t)||mkdirSync(t,{recursive:true});}function p(t,s){me(dirname(t)),writeFileSync(t,s);}function z(t){return readFileSync(t,"utf-8")}function v(t){return existsSync(t)}var R=`@import "tailwindcss";
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
/* Surfaces */
|
|
6
|
+
--background: hsl(0 0% 100%);
|
|
7
|
+
--foreground: hsl(222 47% 11%);
|
|
8
|
+
--card: hsl(0 0% 100%);
|
|
9
|
+
--card-foreground: hsl(222 47% 11%);
|
|
10
|
+
--muted: hsl(212 30% 93.5%);
|
|
11
|
+
--muted-foreground: hsl(220 17% 17%);
|
|
12
|
+
--accent: hsl(210 40% 96%);
|
|
13
|
+
--accent-foreground: hsl(222 47% 11%);
|
|
14
|
+
|
|
15
|
+
/* Brand */
|
|
16
|
+
--primary: hsl(239 84% 67%);
|
|
17
|
+
--primary-light: hsl(239 84% 74%);
|
|
18
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
19
|
+
--secondary: hsl(189 90% 36%);
|
|
20
|
+
--secondary-foreground: hsl(0 0% 100%);
|
|
21
|
+
|
|
22
|
+
/* Semantic */
|
|
23
|
+
--success: hsl(142 76% 36%);
|
|
24
|
+
--success-foreground: hsl(0 0% 100%);
|
|
25
|
+
--destructive: hsl(0 72% 51%);
|
|
26
|
+
--destructive-foreground: hsl(0 0% 100%);
|
|
27
|
+
|
|
28
|
+
/* Borders & Inputs */
|
|
29
|
+
--border: hsl(214 32% 91%);
|
|
30
|
+
--input: hsl(214 32% 91%);
|
|
31
|
+
|
|
32
|
+
/* Special Surfaces */
|
|
33
|
+
--overlay: hsl(0 0% 0% / 0.18);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@theme inline {
|
|
37
|
+
/* Surfaces */
|
|
38
|
+
--color-background: var(--background);
|
|
39
|
+
--color-foreground: var(--foreground);
|
|
40
|
+
--color-card: var(--card);
|
|
41
|
+
--color-card-foreground: var(--card-foreground);
|
|
42
|
+
--color-muted: var(--muted);
|
|
43
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
44
|
+
--color-accent: var(--accent);
|
|
45
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
46
|
+
|
|
47
|
+
/* Brand */
|
|
48
|
+
--color-primary: var(--primary);
|
|
49
|
+
--color-primary-light: var(--primary-light);
|
|
50
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
51
|
+
--color-secondary: var(--secondary);
|
|
52
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
53
|
+
|
|
54
|
+
/* Semantic */
|
|
55
|
+
--color-destructive: var(--destructive);
|
|
56
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
57
|
+
--color-success: var(--success);
|
|
58
|
+
--color-success-foreground: var(--success-foreground);
|
|
59
|
+
|
|
60
|
+
/* Borders & Inputs */
|
|
61
|
+
--color-border: var(--border);
|
|
62
|
+
--color-input: var(--input);
|
|
63
|
+
|
|
64
|
+
/* Special Surfaces */
|
|
65
|
+
--color-overlay: var(--overlay);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
body {
|
|
69
|
+
background: var(--color-background);
|
|
70
|
+
color: var(--color-foreground);
|
|
71
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
72
|
+
}
|
|
73
|
+
`,O=`
|
|
74
|
+
/* Drivn Dark/Light Theme */
|
|
75
|
+
:root,
|
|
76
|
+
[data-theme="dark"] {
|
|
77
|
+
/* Surfaces */
|
|
78
|
+
--background: hsl(240 6% 4%);
|
|
79
|
+
--foreground: hsl(0 0% 98%);
|
|
80
|
+
--card: hsl(240 5% 7%);
|
|
81
|
+
--card-foreground: hsl(0 0% 98%);
|
|
82
|
+
--muted: hsl(240 4% 16%);
|
|
83
|
+
--muted-foreground: hsl(220, 17%, 83%);
|
|
84
|
+
--accent: hsl(240 4% 10%);
|
|
85
|
+
--accent-foreground: hsl(0 0% 98%);
|
|
86
|
+
|
|
87
|
+
/* Brand */
|
|
88
|
+
--primary: hsl(239 84% 67%);
|
|
89
|
+
--primary-light: hsl(239 84% 74%);
|
|
90
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
91
|
+
--secondary: hsl(189 94% 53%);
|
|
92
|
+
--secondary-foreground: hsl(0 0% 100%);
|
|
93
|
+
|
|
94
|
+
/* Semantic */
|
|
95
|
+
--success: hsl(142 71% 59%);
|
|
96
|
+
--success-foreground: hsl(0 0% 100%);
|
|
97
|
+
--destructive: hsl(0 84% 60%);
|
|
98
|
+
--destructive-foreground: hsl(0 0% 100%);
|
|
99
|
+
|
|
100
|
+
/* Borders & Inputs */
|
|
101
|
+
--border: hsl(240 4% 16%);
|
|
102
|
+
--input: hsl(240 4% 16%);
|
|
103
|
+
|
|
104
|
+
/* Special Surfaces */
|
|
105
|
+
--overlay: hsl(0 0% 0% / 0.5);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
[data-theme="light"] {
|
|
109
|
+
/* Surfaces */
|
|
110
|
+
--background: hsl(0 0% 100%);
|
|
111
|
+
--foreground: hsl(222 47% 11%);
|
|
112
|
+
--card: hsl(0 0% 100%);
|
|
113
|
+
--card-foreground: hsl(222 47% 11%);
|
|
114
|
+
--muted: hsl(212 30% 93.5%);
|
|
115
|
+
--muted-foreground: hsl(220 17% 17%);
|
|
116
|
+
--accent: hsl(210 40% 96%);
|
|
117
|
+
--accent-foreground: hsl(222 47% 11%);
|
|
118
|
+
|
|
119
|
+
/* Brand */
|
|
120
|
+
--primary: hsl(239 84% 67%);
|
|
121
|
+
--primary-light: hsl(239 84% 74%);
|
|
122
|
+
--primary-foreground: hsl(0 0% 100%);
|
|
123
|
+
--secondary: hsl(189 90% 36%);
|
|
124
|
+
--secondary-foreground: hsl(0 0% 100%);
|
|
125
|
+
|
|
126
|
+
/* Semantic */
|
|
127
|
+
--success: hsl(142 76% 36%);
|
|
128
|
+
--success-foreground: hsl(0 0% 100%);
|
|
129
|
+
--destructive: hsl(0 72% 51%);
|
|
130
|
+
--destructive-foreground: hsl(0 0% 100%);
|
|
131
|
+
|
|
132
|
+
/* Borders & Inputs */
|
|
133
|
+
--border: hsl(214 32% 91%);
|
|
134
|
+
--input: hsl(214 32% 91%);
|
|
135
|
+
|
|
136
|
+
/* Special Surfaces */
|
|
137
|
+
--overlay: hsl(0 0% 0% / 0.18);
|
|
138
|
+
}
|
|
139
|
+
`;var ge=`import { type ClassValue, clsx } from 'clsx'
|
|
140
|
+
import { twMerge } from 'tailwind-merge'
|
|
141
|
+
|
|
142
|
+
export function cn(...inputs: ClassValue[]) {
|
|
143
|
+
return twMerge(clsx(inputs))
|
|
144
|
+
}
|
|
145
|
+
`,he=["src/app/globals.css","src/styles/globals.css","src/styles/globals.scss","app/globals.css"];function ve(t){for(let s of he)if(v(join(t,s)))return s;return null}async function H(){let t=process.cwd();console.log(""),console.log(l.bgCyan(l.bold(l.black(" Drivn "))));let s;try{s=A(t),e.log.success(`Detected ${l.cyan(E[s.framework])}`);}catch{e.log.error("No package.json found. Run this command in a project directory."),e.outro("Setup cancelled"),process.exit(1);}if(v(join(t,"drivn.config.json"))){let a=await e.confirm({message:"Config already exists. Overwrite?",initialValue:false});(e.isCancel(a)||!a)&&(e.cancel("Setup cancelled"),process.exit(0));}let r=s.srcDir?"src/components/ui":"components/ui",b=s.srcDir?"src/utils":"utils",d=await e.group({components:()=>e.text({message:"Where should components be installed?",placeholder:r,defaultValue:r}),utils:()=>e.text({message:"Where should utilities be placed?",placeholder:b,defaultValue:b})},{onCancel:()=>{e.cancel("Setup cancelled"),process.exit(0);}}),m={framework:s.framework,typescript:s.typescript,paths:{components:d.components,utils:d.utils},installed:[]},f=s.typescript?"ts":"js",g=join(t,d.utils,`cn.${f}`);v(g)||p(g,ge);let h=ve(t);if(h){let a=await e.confirm({message:`Found ${l.cyan(h)}. Add Drivn color tokens?`,initialValue:true});!e.isCancel(a)&&a&&(p(join(t,h),R),m.paths.globals=h,e.log.success(`Color tokens written to ${l.cyan(h)}`));}else {let a=s.srcDir?"src/styles/globals.css":"styles/globals.css",x=await e.text({message:"Where should the globals CSS file be created?",placeholder:a,defaultValue:a});e.isCancel(x)||(p(join(t,x),R),m.paths.globals=x,e.log.success(`Color tokens written to ${l.cyan(x)}`));}$(t,m);let y=e.spinner();y.start("Installing dependencies");try{execSync("npm install clsx tailwind-merge lucide-react",{cwd:t,stdio:"ignore"}),y.stop("Dependencies installed");}catch{y.stop("Failed to install dependencies"),e.log.warn("Run manually: npm install clsx tailwind-merge lucide-react");}e.log.info(`Add components with: ${l.cyan("npx drivn add button")}`),e.log.info(`Add dark/light theme: ${l.cyan("npx drivn add theme")}`),e.outro("Drivn initialized");}var F=`'use client'
|
|
146
|
+
|
|
147
|
+
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
148
|
+
|
|
149
|
+
export function ThemeProvider({
|
|
150
|
+
children,
|
|
151
|
+
}: {
|
|
152
|
+
children: React.ReactNode
|
|
153
|
+
}) {
|
|
154
|
+
return (
|
|
155
|
+
<NextThemesProvider
|
|
156
|
+
attribute="data-theme"
|
|
157
|
+
defaultTheme="dark"
|
|
158
|
+
disableTransitionOnChange={false}
|
|
159
|
+
>
|
|
160
|
+
{children}
|
|
161
|
+
</NextThemesProvider>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
`;var _=`'use client'
|
|
165
|
+
|
|
166
|
+
import * as React from 'react'
|
|
167
|
+
import { ChevronDown } from 'lucide-react'
|
|
168
|
+
import { cn } from '@/utils/cn'
|
|
169
|
+
|
|
170
|
+
const styles = {
|
|
171
|
+
base: 'w-full divide-y divide-border border-y border-border',
|
|
172
|
+
trigger: cn(
|
|
173
|
+
'flex items-center justify-between w-full py-4',
|
|
174
|
+
'text-sm font-medium text-foreground',
|
|
175
|
+
'hover:text-foreground/80 transition-colors cursor-pointer'
|
|
176
|
+
),
|
|
177
|
+
icon: cn(
|
|
178
|
+
'w-4 h-4 text-muted-foreground',
|
|
179
|
+
'transition-transform duration-200'
|
|
180
|
+
),
|
|
181
|
+
panel: 'grid transition-[grid-template-rows] duration-200',
|
|
182
|
+
content: 'overflow-hidden text-sm text-muted-foreground',
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface AccordionCtx {
|
|
186
|
+
openItem: string | null
|
|
187
|
+
toggle: (value: string) => void
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const Ctx = React.createContext<AccordionCtx | null>(null)
|
|
191
|
+
const ItemCtx = React.createContext<string | null>(null)
|
|
192
|
+
|
|
193
|
+
function useAccordion() {
|
|
194
|
+
const ctx = React.useContext(Ctx)
|
|
195
|
+
if (!ctx) throw new Error('Accordion compound used outside <Accordion>')
|
|
196
|
+
return ctx
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function useItemValue() {
|
|
200
|
+
const value = React.useContext(ItemCtx)
|
|
201
|
+
if (!value) throw new Error(
|
|
202
|
+
'Accordion sub-component used outside <Accordion.Item>'
|
|
203
|
+
)
|
|
204
|
+
return value
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function AccordionRoot({
|
|
208
|
+
children,
|
|
209
|
+
defaultValue,
|
|
210
|
+
className,
|
|
211
|
+
}: {
|
|
212
|
+
children: React.ReactNode
|
|
213
|
+
defaultValue?: string
|
|
214
|
+
className?: string
|
|
215
|
+
}) {
|
|
216
|
+
const [openItem, setOpenItem] = React.useState<string | null>(
|
|
217
|
+
defaultValue ?? null
|
|
218
|
+
)
|
|
219
|
+
const toggle = React.useCallback(
|
|
220
|
+
(value: string) =>
|
|
221
|
+
setOpenItem((prev) => (prev === value ? null : value)),
|
|
222
|
+
[]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return (
|
|
226
|
+
<Ctx.Provider value={{ openItem, toggle }}>
|
|
227
|
+
<div className={cn(styles.base, className)}>
|
|
228
|
+
{children}
|
|
229
|
+
</div>
|
|
230
|
+
</Ctx.Provider>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function Item({
|
|
235
|
+
value,
|
|
236
|
+
children,
|
|
237
|
+
className,
|
|
238
|
+
}: {
|
|
239
|
+
value: string
|
|
240
|
+
children: React.ReactNode
|
|
241
|
+
className?: string
|
|
242
|
+
}) {
|
|
243
|
+
return (
|
|
244
|
+
<ItemCtx.Provider value={value}>
|
|
245
|
+
<div className={className}>{children}</div>
|
|
246
|
+
</ItemCtx.Provider>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function Trigger({
|
|
251
|
+
children,
|
|
252
|
+
className,
|
|
253
|
+
}: {
|
|
254
|
+
children: React.ReactNode
|
|
255
|
+
className?: string
|
|
256
|
+
}) {
|
|
257
|
+
const { openItem, toggle } = useAccordion()
|
|
258
|
+
const value = useItemValue()
|
|
259
|
+
const isOpen = openItem === value
|
|
260
|
+
return (
|
|
261
|
+
<button
|
|
262
|
+
className={cn(styles.trigger, className)}
|
|
263
|
+
onClick={() => toggle(value)}
|
|
264
|
+
>
|
|
265
|
+
{children}
|
|
266
|
+
<ChevronDown className={cn(styles.icon, isOpen && 'rotate-180')} />
|
|
267
|
+
</button>
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function Content({
|
|
272
|
+
children,
|
|
273
|
+
className,
|
|
274
|
+
}: {
|
|
275
|
+
children: React.ReactNode
|
|
276
|
+
className?: string
|
|
277
|
+
}) {
|
|
278
|
+
const { openItem } = useAccordion()
|
|
279
|
+
const value = useItemValue()
|
|
280
|
+
const isOpen = openItem === value
|
|
281
|
+
return (
|
|
282
|
+
<div
|
|
283
|
+
className={styles.panel}
|
|
284
|
+
style={{ gridTemplateRows: isOpen ? '1fr' : '0fr' }}
|
|
285
|
+
>
|
|
286
|
+
<div className={cn(styles.content, className)}>
|
|
287
|
+
<div className="pb-4">{children}</div>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export const Accordion = Object.assign(AccordionRoot, {
|
|
294
|
+
Item,
|
|
295
|
+
Trigger,
|
|
296
|
+
Content,
|
|
297
|
+
})
|
|
298
|
+
`;var U=`import * as React from 'react'
|
|
299
|
+
import { cn } from '@/utils/cn'
|
|
300
|
+
|
|
301
|
+
const styles = {
|
|
302
|
+
base: 'flex gap-3 p-4 rounded-[10px] border text-sm',
|
|
303
|
+
variants: {
|
|
304
|
+
default: 'bg-accent/50 border-border text-foreground',
|
|
305
|
+
info: 'bg-primary/10 border-primary/20 text-primary-light',
|
|
306
|
+
success: 'bg-success/10 border-success/20 text-success',
|
|
307
|
+
destructive: 'bg-destructive/10 border-destructive/20 text-destructive',
|
|
308
|
+
},
|
|
309
|
+
title: 'font-semibold mb-1',
|
|
310
|
+
description: 'text-sm opacity-90',
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
type IconProp =
|
|
314
|
+
| React.ComponentType<{ className?: string }>
|
|
315
|
+
| React.ReactElement
|
|
316
|
+
|
|
317
|
+
interface AlertProps {
|
|
318
|
+
variant?: keyof typeof styles.variants
|
|
319
|
+
icon?: IconProp
|
|
320
|
+
title?: string
|
|
321
|
+
className?: string
|
|
322
|
+
children: React.ReactNode
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function Alert({
|
|
326
|
+
variant = 'default',
|
|
327
|
+
icon: Icon,
|
|
328
|
+
title,
|
|
329
|
+
className,
|
|
330
|
+
children,
|
|
331
|
+
}: AlertProps) {
|
|
332
|
+
return (
|
|
333
|
+
<div className={cn(styles.base, styles.variants[variant], className)}>
|
|
334
|
+
{Icon && (
|
|
335
|
+
<span className="flex-shrink-0 mt-0.5">
|
|
336
|
+
{React.isValidElement(Icon)
|
|
337
|
+
? Icon
|
|
338
|
+
: <Icon />}
|
|
339
|
+
</span>
|
|
340
|
+
)}
|
|
341
|
+
<div>
|
|
342
|
+
{title && <p className={styles.title}>{title}</p>}
|
|
343
|
+
<div className={styles.description}>{children}</div>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
)
|
|
347
|
+
}
|
|
348
|
+
`;var G=`import * as React from 'react'
|
|
349
|
+
import { cn } from '@/utils/cn'
|
|
350
|
+
|
|
351
|
+
const styles = {
|
|
352
|
+
base: cn(
|
|
353
|
+
'relative inline-flex items-center justify-center',
|
|
354
|
+
'rounded-full overflow-hidden',
|
|
355
|
+
'bg-gradient-to-br from-primary to-secondary'
|
|
356
|
+
),
|
|
357
|
+
sizes: {
|
|
358
|
+
sm: 'w-8 h-8 text-xs',
|
|
359
|
+
md: 'w-10 h-10 text-sm',
|
|
360
|
+
lg: 'w-12 h-12 text-base',
|
|
361
|
+
xl: 'w-16 h-16 text-lg',
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
interface AvatarProps {
|
|
366
|
+
src?: string
|
|
367
|
+
alt?: string
|
|
368
|
+
fallback?: string
|
|
369
|
+
size?: keyof typeof styles.sizes
|
|
370
|
+
className?: string
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function Avatar({
|
|
374
|
+
src,
|
|
375
|
+
alt,
|
|
376
|
+
fallback,
|
|
377
|
+
size = 'md',
|
|
378
|
+
className,
|
|
379
|
+
}: AvatarProps) {
|
|
380
|
+
return (
|
|
381
|
+
<div className={cn(styles.base, styles.sizes[size], className)}>
|
|
382
|
+
{src ? (
|
|
383
|
+
<img src={src} alt={alt} className="w-full h-full object-cover" />
|
|
384
|
+
) : (
|
|
385
|
+
<span className="font-medium text-primary-foreground">
|
|
386
|
+
{fallback?.slice(0, 2).toUpperCase()}
|
|
387
|
+
</span>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
`;var W=`import * as React from 'react'
|
|
393
|
+
import { cn } from '@/utils/cn'
|
|
394
|
+
|
|
395
|
+
const styles = {
|
|
396
|
+
base: cn(
|
|
397
|
+
'inline-flex items-center gap-1.5 px-2.5 py-0.5',
|
|
398
|
+
'text-xs font-semibold rounded-full'
|
|
399
|
+
),
|
|
400
|
+
variants: {
|
|
401
|
+
default: 'bg-primary/15 text-primary-light border border-primary/20',
|
|
402
|
+
secondary: 'bg-secondary/15 text-secondary border border-secondary/30',
|
|
403
|
+
success: 'bg-success/15 text-success border border-success/20',
|
|
404
|
+
outline: 'border border-border text-muted-foreground',
|
|
405
|
+
destructive: 'bg-destructive/15 text-destructive border border-destructive/20',
|
|
406
|
+
},
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
interface BadgeProps {
|
|
410
|
+
variant?: keyof typeof styles.variants
|
|
411
|
+
className?: string
|
|
412
|
+
children: React.ReactNode
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function Badge({ variant = 'default', className, children }: BadgeProps) {
|
|
416
|
+
return (
|
|
417
|
+
<span className={cn(styles.base, styles.variants[variant], className)}>
|
|
418
|
+
{children}
|
|
419
|
+
</span>
|
|
420
|
+
)
|
|
421
|
+
}
|
|
422
|
+
`;var K=`import * as React from 'react'
|
|
423
|
+
import { Loader2 } from 'lucide-react'
|
|
424
|
+
import { cn } from '@/utils/cn'
|
|
425
|
+
|
|
426
|
+
const styles = {
|
|
427
|
+
base: cn(
|
|
428
|
+
'inline-flex items-center justify-center',
|
|
429
|
+
'font-semibold transition-all duration-150',
|
|
430
|
+
'cursor-pointer disabled:opacity-50',
|
|
431
|
+
'disabled:pointer-events-none'
|
|
432
|
+
),
|
|
433
|
+
sizes: {
|
|
434
|
+
sm: 'h-8 px-3 text-sm gap-1.5',
|
|
435
|
+
md: 'h-10 px-4 text-sm gap-2',
|
|
436
|
+
lg: 'h-12 px-6 text-base gap-2',
|
|
437
|
+
},
|
|
438
|
+
variants: {
|
|
439
|
+
default: 'bg-foreground text-background hover:scale-[1.02]',
|
|
440
|
+
secondary: cn(
|
|
441
|
+
'bg-card text-foreground border border-border',
|
|
442
|
+
'hover:bg-accent hover:border-border'
|
|
443
|
+
),
|
|
444
|
+
outline: 'border border-border text-foreground hover:border-foreground/20',
|
|
445
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
446
|
+
},
|
|
447
|
+
rounded: {
|
|
448
|
+
md: 'rounded-md',
|
|
449
|
+
full: 'rounded-full',
|
|
450
|
+
},
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
type IconProp =
|
|
454
|
+
| React.ComponentType<{ className?: string }>
|
|
455
|
+
| React.ReactElement
|
|
456
|
+
|
|
457
|
+
interface ButtonProps
|
|
458
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
459
|
+
variant?: keyof typeof styles.variants
|
|
460
|
+
size?: keyof typeof styles.sizes
|
|
461
|
+
rounded?: keyof typeof styles.rounded
|
|
462
|
+
loading?: boolean
|
|
463
|
+
leftIcon?: IconProp
|
|
464
|
+
rightIcon?: IconProp
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
export const Button = React.forwardRef<
|
|
468
|
+
HTMLButtonElement,
|
|
469
|
+
ButtonProps
|
|
470
|
+
>(({
|
|
471
|
+
className,
|
|
472
|
+
variant = 'default',
|
|
473
|
+
size = 'md',
|
|
474
|
+
rounded = 'full',
|
|
475
|
+
loading,
|
|
476
|
+
disabled,
|
|
477
|
+
leftIcon: LeftIcon,
|
|
478
|
+
rightIcon: RightIcon,
|
|
479
|
+
children,
|
|
480
|
+
...props
|
|
481
|
+
},
|
|
482
|
+
ref
|
|
483
|
+
) => (
|
|
484
|
+
<button
|
|
485
|
+
ref={ref}
|
|
486
|
+
disabled={loading || disabled}
|
|
487
|
+
className={cn(
|
|
488
|
+
styles.base,
|
|
489
|
+
styles.sizes[size],
|
|
490
|
+
styles.variants[variant],
|
|
491
|
+
styles.rounded[rounded],
|
|
492
|
+
className
|
|
493
|
+
)}
|
|
494
|
+
{...props}
|
|
495
|
+
>
|
|
496
|
+
{loading && (
|
|
497
|
+
<Loader2 className="h-4 w-4 animate-spin" />
|
|
498
|
+
)}
|
|
499
|
+
{!loading && LeftIcon && (
|
|
500
|
+
React.isValidElement(LeftIcon)
|
|
501
|
+
? LeftIcon
|
|
502
|
+
: <LeftIcon />
|
|
503
|
+
)}
|
|
504
|
+
{children}
|
|
505
|
+
{!loading && RightIcon && (
|
|
506
|
+
React.isValidElement(RightIcon)
|
|
507
|
+
? RightIcon
|
|
508
|
+
: <RightIcon />
|
|
509
|
+
)}
|
|
510
|
+
</button>
|
|
511
|
+
)
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
Button.displayName = 'Button'
|
|
515
|
+
`;var X=`import * as React from 'react'
|
|
516
|
+
import { cn } from '@/utils/cn'
|
|
517
|
+
|
|
518
|
+
const styles = {
|
|
519
|
+
base: cn(
|
|
520
|
+
'bg-card border border-border rounded-[20px]',
|
|
521
|
+
'overflow-hidden transition-all duration-200'
|
|
522
|
+
),
|
|
523
|
+
hover: 'hover:bg-accent hover:border-border hover:-translate-y-1',
|
|
524
|
+
preview: cn(
|
|
525
|
+
'h-[140px] flex items-center justify-center',
|
|
526
|
+
'border-b border-border p-6',
|
|
527
|
+
'bg-[radial-gradient(ellipse_at_50%_50%,hsl(239_84%_67%_/_0.08)_0%,transparent_70%)]'
|
|
528
|
+
),
|
|
529
|
+
info: 'p-5 flex justify-between items-center',
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function CardRoot({
|
|
533
|
+
hover = true,
|
|
534
|
+
className,
|
|
535
|
+
children,
|
|
536
|
+
}: {
|
|
537
|
+
hover?: boolean
|
|
538
|
+
className?: string
|
|
539
|
+
children: React.ReactNode
|
|
540
|
+
}) {
|
|
541
|
+
return (
|
|
542
|
+
<div
|
|
543
|
+
className={cn(styles.base, hover && styles.hover, className)}
|
|
544
|
+
>
|
|
545
|
+
{children}
|
|
546
|
+
</div>
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function Preview({
|
|
551
|
+
className,
|
|
552
|
+
children,
|
|
553
|
+
}: {
|
|
554
|
+
className?: string
|
|
555
|
+
children: React.ReactNode
|
|
556
|
+
}) {
|
|
557
|
+
return (
|
|
558
|
+
<div className={cn(styles.preview, className)}>
|
|
559
|
+
{children}
|
|
560
|
+
</div>
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function Info({
|
|
565
|
+
className,
|
|
566
|
+
children,
|
|
567
|
+
}: {
|
|
568
|
+
className?: string
|
|
569
|
+
children: React.ReactNode
|
|
570
|
+
}) {
|
|
571
|
+
return (
|
|
572
|
+
<div className={cn(styles.info, className)}>
|
|
573
|
+
{children}
|
|
574
|
+
</div>
|
|
575
|
+
)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export const Card = Object.assign(CardRoot, {
|
|
579
|
+
Preview,
|
|
580
|
+
Info,
|
|
581
|
+
})
|
|
582
|
+
`;var J=`'use client'
|
|
583
|
+
|
|
584
|
+
import * as React from 'react'
|
|
585
|
+
import { Check } from 'lucide-react'
|
|
586
|
+
import { cn } from '@/utils/cn'
|
|
587
|
+
|
|
588
|
+
const styles = {
|
|
589
|
+
base: 'flex items-center gap-2 cursor-pointer',
|
|
590
|
+
box: cn(
|
|
591
|
+
'w-4 h-4 rounded-[4px] border border-border',
|
|
592
|
+
'transition-colors flex-shrink-0',
|
|
593
|
+
'flex items-center justify-center'
|
|
594
|
+
),
|
|
595
|
+
checked: 'bg-primary border-primary',
|
|
596
|
+
label: 'text-sm text-foreground select-none',
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
interface CheckboxProps
|
|
600
|
+
extends Omit<
|
|
601
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
602
|
+
'type'
|
|
603
|
+
> {
|
|
604
|
+
label?: string
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
export const Checkbox = React.forwardRef<
|
|
608
|
+
HTMLInputElement,
|
|
609
|
+
CheckboxProps
|
|
610
|
+
>(({
|
|
611
|
+
className,
|
|
612
|
+
label,
|
|
613
|
+
checked,
|
|
614
|
+
defaultChecked,
|
|
615
|
+
onChange,
|
|
616
|
+
disabled,
|
|
617
|
+
...props
|
|
618
|
+
},
|
|
619
|
+
ref
|
|
620
|
+
) => {
|
|
621
|
+
const [internal, setInternal] = React.useState(
|
|
622
|
+
defaultChecked ?? false
|
|
623
|
+
)
|
|
624
|
+
const isControlled = checked !== undefined
|
|
625
|
+
const isChecked = isControlled ? checked : internal
|
|
626
|
+
|
|
627
|
+
return (
|
|
628
|
+
<label
|
|
629
|
+
className={cn(
|
|
630
|
+
styles.base,
|
|
631
|
+
disabled && 'opacity-50 cursor-default',
|
|
632
|
+
className
|
|
633
|
+
)}
|
|
634
|
+
>
|
|
635
|
+
<input
|
|
636
|
+
ref={ref}
|
|
637
|
+
type="checkbox"
|
|
638
|
+
className="sr-only"
|
|
639
|
+
checked={isChecked}
|
|
640
|
+
disabled={disabled}
|
|
641
|
+
onChange={(e) => {
|
|
642
|
+
if (!isControlled)
|
|
643
|
+
setInternal(e.target.checked)
|
|
644
|
+
onChange?.(e)
|
|
645
|
+
}}
|
|
646
|
+
{...props}
|
|
647
|
+
/>
|
|
648
|
+
<span
|
|
649
|
+
className={cn(
|
|
650
|
+
styles.box,
|
|
651
|
+
isChecked && styles.checked
|
|
652
|
+
)}
|
|
653
|
+
>
|
|
654
|
+
{isChecked && (
|
|
655
|
+
<Check className="w-2.5 h-2.5 text-primary-foreground" />
|
|
656
|
+
)}
|
|
657
|
+
</span>
|
|
658
|
+
{label && (
|
|
659
|
+
<span className={styles.label}>{label}</span>
|
|
660
|
+
)}
|
|
661
|
+
</label>
|
|
662
|
+
)
|
|
663
|
+
}
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
Checkbox.displayName = 'Checkbox'
|
|
667
|
+
`;var q=`'use client'
|
|
668
|
+
|
|
669
|
+
import * as React from 'react'
|
|
670
|
+
import { X } from 'lucide-react'
|
|
671
|
+
import { cn } from '@/utils/cn'
|
|
672
|
+
import { Button } from '@/components/ui/button'
|
|
673
|
+
|
|
674
|
+
const styles = {
|
|
675
|
+
overlay: cn(
|
|
676
|
+
'fixed inset-0 z-[200] flex items-center justify-center',
|
|
677
|
+
'bg-overlay backdrop-blur-sm',
|
|
678
|
+
'transition-opacity duration-150 ease-out'
|
|
679
|
+
),
|
|
680
|
+
content: cn(
|
|
681
|
+
'relative w-full max-w-md mx-4 p-6',
|
|
682
|
+
'bg-card border border-border rounded-[20px]',
|
|
683
|
+
'shadow-xl',
|
|
684
|
+
'transition-[scale] duration-150 ease-out'
|
|
685
|
+
),
|
|
686
|
+
title: 'text-lg font-semibold text-foreground mb-4',
|
|
687
|
+
close: cn(
|
|
688
|
+
'absolute top-4 right-4 w-8 h-8',
|
|
689
|
+
'flex items-center justify-center rounded-full',
|
|
690
|
+
'text-muted-foreground hover:text-foreground',
|
|
691
|
+
'hover:bg-accent transition-colors cursor-pointer'
|
|
692
|
+
),
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
interface DialogCtx {
|
|
696
|
+
open: boolean
|
|
697
|
+
setOpen: (v: boolean) => void
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const Ctx = React.createContext<DialogCtx | null>(null)
|
|
701
|
+
|
|
702
|
+
function useDialog() {
|
|
703
|
+
const ctx = React.useContext(Ctx)
|
|
704
|
+
if (!ctx) throw new Error('Dialog compound used outside <Dialog>')
|
|
705
|
+
return ctx
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function DialogRoot({
|
|
709
|
+
children,
|
|
710
|
+
open: controlled,
|
|
711
|
+
onOpenChange,
|
|
712
|
+
}: {
|
|
713
|
+
children: React.ReactNode
|
|
714
|
+
open?: boolean
|
|
715
|
+
onOpenChange?: (open: boolean) => void
|
|
716
|
+
}) {
|
|
717
|
+
const [uncontrolled, setUncontrolled] = React.useState(false)
|
|
718
|
+
const open = controlled ?? uncontrolled
|
|
719
|
+
const setOpen = React.useCallback(
|
|
720
|
+
(v: boolean) => {
|
|
721
|
+
onOpenChange?.(v)
|
|
722
|
+
if (controlled === undefined) setUncontrolled(v)
|
|
723
|
+
},
|
|
724
|
+
[controlled, onOpenChange]
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
return <Ctx.Provider value={{ open, setOpen }}>{children}</Ctx.Provider>
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function Trigger({
|
|
731
|
+
children,
|
|
732
|
+
className,
|
|
733
|
+
...props
|
|
734
|
+
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
735
|
+
const { setOpen } = useDialog()
|
|
736
|
+
return (
|
|
737
|
+
<Button
|
|
738
|
+
variant="outline"
|
|
739
|
+
rounded="md"
|
|
740
|
+
className={className}
|
|
741
|
+
onClick={() => setOpen(true)}
|
|
742
|
+
{...props}
|
|
743
|
+
>
|
|
744
|
+
{children}
|
|
745
|
+
</Button>
|
|
746
|
+
)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function Content({
|
|
750
|
+
children,
|
|
751
|
+
title,
|
|
752
|
+
className,
|
|
753
|
+
}: {
|
|
754
|
+
children: React.ReactNode
|
|
755
|
+
title?: string
|
|
756
|
+
className?: string
|
|
757
|
+
}) {
|
|
758
|
+
const { open, setOpen } = useDialog()
|
|
759
|
+
|
|
760
|
+
React.useEffect(() => {
|
|
761
|
+
if (!open) return
|
|
762
|
+
const onKey = (e: KeyboardEvent) => {
|
|
763
|
+
if (e.key === 'Escape') setOpen(false)
|
|
764
|
+
}
|
|
765
|
+
document.addEventListener('keydown', onKey)
|
|
766
|
+
document.body.style.overflow = 'hidden'
|
|
767
|
+
return () => {
|
|
768
|
+
document.removeEventListener('keydown', onKey)
|
|
769
|
+
document.body.style.overflow = ''
|
|
770
|
+
}
|
|
771
|
+
}, [open, setOpen])
|
|
772
|
+
|
|
773
|
+
return (
|
|
774
|
+
<div
|
|
775
|
+
className={cn(
|
|
776
|
+
styles.overlay,
|
|
777
|
+
open
|
|
778
|
+
? 'opacity-100'
|
|
779
|
+
: 'opacity-0 pointer-events-none'
|
|
780
|
+
)}
|
|
781
|
+
onClick={(e) => {
|
|
782
|
+
if (e.target === e.currentTarget) setOpen(false)
|
|
783
|
+
}}
|
|
784
|
+
>
|
|
785
|
+
<div
|
|
786
|
+
className={cn(
|
|
787
|
+
styles.content,
|
|
788
|
+
open ? 'scale-100' : 'scale-95',
|
|
789
|
+
className
|
|
790
|
+
)}
|
|
791
|
+
>
|
|
792
|
+
{title && <h2 className={styles.title}>{title}</h2>}
|
|
793
|
+
{children}
|
|
794
|
+
</div>
|
|
795
|
+
</div>
|
|
796
|
+
)
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function Close({ className }: { className?: string }) {
|
|
800
|
+
const { setOpen } = useDialog()
|
|
801
|
+
return (
|
|
802
|
+
<button
|
|
803
|
+
className={cn(styles.close, className)}
|
|
804
|
+
onClick={() => setOpen(false)}
|
|
805
|
+
>
|
|
806
|
+
<X className="w-3.5 h-3.5" />
|
|
807
|
+
</button>
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export const Dialog = Object.assign(DialogRoot, {
|
|
812
|
+
Trigger,
|
|
813
|
+
Content,
|
|
814
|
+
Close,
|
|
815
|
+
})
|
|
816
|
+
`;var Q=`'use client'
|
|
817
|
+
|
|
818
|
+
import * as React from 'react'
|
|
819
|
+
import { cn } from '@/utils/cn'
|
|
820
|
+
import { Button } from '@/components/ui/button'
|
|
821
|
+
|
|
822
|
+
const styles = {
|
|
823
|
+
base: 'relative inline-flex',
|
|
824
|
+
content: cn(
|
|
825
|
+
'absolute top-full mt-1 min-w-[180px] z-50',
|
|
826
|
+
'bg-card border border-border rounded-[10px] p-1',
|
|
827
|
+
'shadow-lg',
|
|
828
|
+
'transition-[opacity,scale] duration-150 ease-out'
|
|
829
|
+
),
|
|
830
|
+
item: cn(
|
|
831
|
+
'flex items-center gap-2 w-full px-3 py-2',
|
|
832
|
+
'text-sm text-foreground rounded-lg',
|
|
833
|
+
'hover:bg-accent transition-colors cursor-pointer'
|
|
834
|
+
),
|
|
835
|
+
destructive: 'text-destructive hover:bg-destructive/10',
|
|
836
|
+
group: 'py-1',
|
|
837
|
+
label: cn(
|
|
838
|
+
'px-3 py-1.5 text-xs font-medium',
|
|
839
|
+
'text-muted-foreground'
|
|
840
|
+
),
|
|
841
|
+
separator: 'my-1 h-px bg-border',
|
|
842
|
+
align: {
|
|
843
|
+
left: 'left-0',
|
|
844
|
+
right: 'right-0',
|
|
845
|
+
},
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
interface DropdownCtx {
|
|
849
|
+
open: boolean
|
|
850
|
+
setOpen: (v: boolean) => void
|
|
851
|
+
align: keyof typeof styles.align
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const Ctx = React.createContext<DropdownCtx | null>(null)
|
|
855
|
+
|
|
856
|
+
function useDropdown() {
|
|
857
|
+
const ctx = React.useContext(Ctx)
|
|
858
|
+
if (!ctx) throw new Error(
|
|
859
|
+
'Dropdown compound used outside <Dropdown>'
|
|
860
|
+
)
|
|
861
|
+
return ctx
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function DropdownRoot({
|
|
865
|
+
children,
|
|
866
|
+
align = 'left',
|
|
867
|
+
className,
|
|
868
|
+
}: {
|
|
869
|
+
children: React.ReactNode
|
|
870
|
+
align?: keyof typeof styles.align
|
|
871
|
+
className?: string
|
|
872
|
+
}) {
|
|
873
|
+
const [open, setOpen] = React.useState(false)
|
|
874
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
875
|
+
|
|
876
|
+
const close = React.useCallback(
|
|
877
|
+
() => setOpen(false),
|
|
878
|
+
[]
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
React.useEffect(() => {
|
|
882
|
+
const onClick = (e: MouseEvent) => {
|
|
883
|
+
if (!ref.current?.contains(e.target as Node))
|
|
884
|
+
close()
|
|
885
|
+
}
|
|
886
|
+
document.addEventListener('mousedown', onClick)
|
|
887
|
+
return () =>
|
|
888
|
+
document.removeEventListener('mousedown', onClick)
|
|
889
|
+
}, [close])
|
|
890
|
+
|
|
891
|
+
return (
|
|
892
|
+
<Ctx.Provider value={{ open, setOpen, align }}>
|
|
893
|
+
<div ref={ref} className={cn(styles.base, className)}>
|
|
894
|
+
{children}
|
|
895
|
+
</div>
|
|
896
|
+
</Ctx.Provider>
|
|
897
|
+
)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function Trigger({
|
|
901
|
+
children,
|
|
902
|
+
className,
|
|
903
|
+
...props
|
|
904
|
+
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
905
|
+
const { open, setOpen } = useDropdown()
|
|
906
|
+
return (
|
|
907
|
+
<Button
|
|
908
|
+
variant="outline"
|
|
909
|
+
rounded="md"
|
|
910
|
+
className={className}
|
|
911
|
+
onClick={() => setOpen(!open)}
|
|
912
|
+
{...props}
|
|
913
|
+
>
|
|
914
|
+
{children}
|
|
915
|
+
</Button>
|
|
916
|
+
)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function Content({
|
|
920
|
+
children,
|
|
921
|
+
className,
|
|
922
|
+
}: {
|
|
923
|
+
children: React.ReactNode
|
|
924
|
+
className?: string
|
|
925
|
+
}) {
|
|
926
|
+
const { open, align } = useDropdown()
|
|
927
|
+
return (
|
|
928
|
+
<div
|
|
929
|
+
className={cn(
|
|
930
|
+
styles.content,
|
|
931
|
+
styles.align[align],
|
|
932
|
+
open
|
|
933
|
+
? 'opacity-100 scale-100'
|
|
934
|
+
: 'opacity-0 scale-95 pointer-events-none',
|
|
935
|
+
className
|
|
936
|
+
)}
|
|
937
|
+
>
|
|
938
|
+
{children}
|
|
939
|
+
</div>
|
|
940
|
+
)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
type IconProp =
|
|
944
|
+
| React.ComponentType<{ className?: string }>
|
|
945
|
+
| React.ReactElement
|
|
946
|
+
|
|
947
|
+
function Item({
|
|
948
|
+
children,
|
|
949
|
+
onClick,
|
|
950
|
+
icon: Icon,
|
|
951
|
+
destructive,
|
|
952
|
+
className,
|
|
953
|
+
}: {
|
|
954
|
+
children: React.ReactNode
|
|
955
|
+
onClick?: () => void
|
|
956
|
+
icon?: IconProp
|
|
957
|
+
destructive?: boolean
|
|
958
|
+
className?: string
|
|
959
|
+
}) {
|
|
960
|
+
const { setOpen } = useDropdown()
|
|
961
|
+
return (
|
|
962
|
+
<button
|
|
963
|
+
className={cn(
|
|
964
|
+
styles.item,
|
|
965
|
+
destructive && styles.destructive,
|
|
966
|
+
className
|
|
967
|
+
)}
|
|
968
|
+
onClick={() => {
|
|
969
|
+
onClick?.()
|
|
970
|
+
setOpen(false)
|
|
971
|
+
}}
|
|
972
|
+
>
|
|
973
|
+
{Icon && (
|
|
974
|
+
React.isValidElement(Icon)
|
|
975
|
+
? Icon
|
|
976
|
+
: <Icon className="w-4 h-4" />
|
|
977
|
+
)}
|
|
978
|
+
{children}
|
|
979
|
+
</button>
|
|
980
|
+
)
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function Group({
|
|
984
|
+
children,
|
|
985
|
+
className,
|
|
986
|
+
}: {
|
|
987
|
+
children: React.ReactNode
|
|
988
|
+
className?: string
|
|
989
|
+
}) {
|
|
990
|
+
return (
|
|
991
|
+
<div className={cn(styles.group, className)}>
|
|
992
|
+
{children}
|
|
993
|
+
</div>
|
|
994
|
+
)
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function Label({
|
|
998
|
+
children,
|
|
999
|
+
className,
|
|
1000
|
+
}: {
|
|
1001
|
+
children: React.ReactNode
|
|
1002
|
+
className?: string
|
|
1003
|
+
}) {
|
|
1004
|
+
return (
|
|
1005
|
+
<div className={cn(styles.label, className)}>
|
|
1006
|
+
{children}
|
|
1007
|
+
</div>
|
|
1008
|
+
)
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function DropdownSeparator({
|
|
1012
|
+
className,
|
|
1013
|
+
}: {
|
|
1014
|
+
className?: string
|
|
1015
|
+
}) {
|
|
1016
|
+
return (
|
|
1017
|
+
<div className={cn(styles.separator, className)} />
|
|
1018
|
+
)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
export const Dropdown = Object.assign(DropdownRoot, {
|
|
1022
|
+
Trigger,
|
|
1023
|
+
Content,
|
|
1024
|
+
Item,
|
|
1025
|
+
Group,
|
|
1026
|
+
Label,
|
|
1027
|
+
Separator: DropdownSeparator,
|
|
1028
|
+
})
|
|
1029
|
+
`;var Y=`import * as React from 'react'
|
|
1030
|
+
import { cn } from '@/utils/cn'
|
|
1031
|
+
|
|
1032
|
+
const styles = {
|
|
1033
|
+
base: cn(
|
|
1034
|
+
'w-full h-10 px-4 border border-input rounded-[10px]',
|
|
1035
|
+
'text-foreground placeholder:text-muted-foreground text-sm',
|
|
1036
|
+
'focus:outline-none transition-colors',
|
|
1037
|
+
'disabled:opacity-50 disabled:cursor-default'
|
|
1038
|
+
),
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
type InputProps = React.InputHTMLAttributes<HTMLInputElement>
|
|
1042
|
+
|
|
1043
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
1044
|
+
({ className, ...props }, ref) => (
|
|
1045
|
+
<input ref={ref} className={cn(styles.base, className)} {...props} />
|
|
1046
|
+
)
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
Input.displayName = 'Input'
|
|
1050
|
+
`;var Z=`'use client'
|
|
1051
|
+
|
|
1052
|
+
import * as React from 'react'
|
|
1053
|
+
import { cn } from '@/utils/cn'
|
|
1054
|
+
import { Button } from '@/components/ui/button'
|
|
1055
|
+
|
|
1056
|
+
const styles = {
|
|
1057
|
+
base: 'relative inline-flex',
|
|
1058
|
+
content: cn(
|
|
1059
|
+
'absolute z-50 min-w-[200px] p-4',
|
|
1060
|
+
'bg-card border border-border rounded-[12px]',
|
|
1061
|
+
'shadow-lg',
|
|
1062
|
+
'transition-[opacity,scale] duration-150 ease-out'
|
|
1063
|
+
),
|
|
1064
|
+
positions: {
|
|
1065
|
+
top: 'bottom-full mb-2 left-1/2 -translate-x-1/2',
|
|
1066
|
+
bottom: 'top-full mt-2 left-1/2 -translate-x-1/2',
|
|
1067
|
+
left: 'right-full mr-2 top-1/2 -translate-y-1/2',
|
|
1068
|
+
right: 'left-full ml-2 top-1/2 -translate-y-1/2',
|
|
1069
|
+
},
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
interface PopoverCtx {
|
|
1073
|
+
open: boolean
|
|
1074
|
+
setOpen: (v: boolean) => void
|
|
1075
|
+
position: keyof typeof styles.positions
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
const Ctx = React.createContext<PopoverCtx | null>(null)
|
|
1079
|
+
|
|
1080
|
+
function usePopover() {
|
|
1081
|
+
const ctx = React.useContext(Ctx)
|
|
1082
|
+
if (!ctx) throw new Error('Popover compound used outside <Popover>')
|
|
1083
|
+
return ctx
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function PopoverRoot({
|
|
1087
|
+
children,
|
|
1088
|
+
position = 'bottom',
|
|
1089
|
+
className,
|
|
1090
|
+
}: {
|
|
1091
|
+
children: React.ReactNode
|
|
1092
|
+
position?: keyof typeof styles.positions
|
|
1093
|
+
className?: string
|
|
1094
|
+
}) {
|
|
1095
|
+
const [open, setOpen] = React.useState(false)
|
|
1096
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
1097
|
+
|
|
1098
|
+
const close = React.useCallback(() => setOpen(false), [])
|
|
1099
|
+
|
|
1100
|
+
React.useEffect(() => {
|
|
1101
|
+
const onClick = (e: MouseEvent) => {
|
|
1102
|
+
if (!ref.current?.contains(e.target as Node)) close()
|
|
1103
|
+
}
|
|
1104
|
+
document.addEventListener('mousedown', onClick)
|
|
1105
|
+
return () => document.removeEventListener('mousedown', onClick)
|
|
1106
|
+
}, [close])
|
|
1107
|
+
|
|
1108
|
+
return (
|
|
1109
|
+
<Ctx.Provider value={{ open, setOpen, position }}>
|
|
1110
|
+
<div ref={ref} className={cn(styles.base, className)}>
|
|
1111
|
+
{children}
|
|
1112
|
+
</div>
|
|
1113
|
+
</Ctx.Provider>
|
|
1114
|
+
)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function Trigger({
|
|
1118
|
+
children,
|
|
1119
|
+
className,
|
|
1120
|
+
...props
|
|
1121
|
+
}: React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
1122
|
+
const { open, setOpen } = usePopover()
|
|
1123
|
+
return (
|
|
1124
|
+
<Button
|
|
1125
|
+
variant="outline"
|
|
1126
|
+
rounded="md"
|
|
1127
|
+
className={className}
|
|
1128
|
+
onClick={() => setOpen(!open)}
|
|
1129
|
+
{...props}
|
|
1130
|
+
>
|
|
1131
|
+
{children}
|
|
1132
|
+
</Button>
|
|
1133
|
+
)
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function Content({
|
|
1137
|
+
children,
|
|
1138
|
+
className,
|
|
1139
|
+
}: {
|
|
1140
|
+
children: React.ReactNode
|
|
1141
|
+
className?: string
|
|
1142
|
+
}) {
|
|
1143
|
+
const { open, position } = usePopover()
|
|
1144
|
+
return (
|
|
1145
|
+
<div
|
|
1146
|
+
className={cn(
|
|
1147
|
+
styles.content,
|
|
1148
|
+
styles.positions[position],
|
|
1149
|
+
open
|
|
1150
|
+
? 'opacity-100 scale-100'
|
|
1151
|
+
: 'opacity-0 scale-95 pointer-events-none',
|
|
1152
|
+
className
|
|
1153
|
+
)}
|
|
1154
|
+
>
|
|
1155
|
+
{children}
|
|
1156
|
+
</div>
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
export const Popover = Object.assign(PopoverRoot, {
|
|
1161
|
+
Trigger,
|
|
1162
|
+
Content,
|
|
1163
|
+
})
|
|
1164
|
+
`;var ee=`import * as React from 'react'
|
|
1165
|
+
import { cn } from '@/utils/cn'
|
|
1166
|
+
|
|
1167
|
+
const styles = {
|
|
1168
|
+
track: 'w-full h-2 bg-accent rounded-full overflow-hidden',
|
|
1169
|
+
bar: cn(
|
|
1170
|
+
'h-full bg-primary rounded-full',
|
|
1171
|
+
'transition-all duration-300 ease-out'
|
|
1172
|
+
),
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
interface ProgressProps {
|
|
1176
|
+
value?: number
|
|
1177
|
+
max?: number
|
|
1178
|
+
className?: string
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export function Progress({
|
|
1182
|
+
value = 0,
|
|
1183
|
+
max = 100,
|
|
1184
|
+
className,
|
|
1185
|
+
}: ProgressProps) {
|
|
1186
|
+
const pct = Math.min(100, Math.max(0, (value / max) * 100))
|
|
1187
|
+
return (
|
|
1188
|
+
<div
|
|
1189
|
+
role="progressbar"
|
|
1190
|
+
aria-valuenow={value}
|
|
1191
|
+
aria-valuemin={0}
|
|
1192
|
+
aria-valuemax={max}
|
|
1193
|
+
className={cn(styles.track, className)}
|
|
1194
|
+
>
|
|
1195
|
+
<div
|
|
1196
|
+
className={styles.bar}
|
|
1197
|
+
style={{ width: \`\${pct}%\` }}
|
|
1198
|
+
/>
|
|
1199
|
+
</div>
|
|
1200
|
+
)
|
|
1201
|
+
}
|
|
1202
|
+
`;var te=`'use client'
|
|
1203
|
+
|
|
1204
|
+
import * as React from 'react'
|
|
1205
|
+
import { ChevronDown } from 'lucide-react'
|
|
1206
|
+
import { cn } from '@/utils/cn'
|
|
1207
|
+
|
|
1208
|
+
const styles = {
|
|
1209
|
+
base: 'relative',
|
|
1210
|
+
trigger: cn(
|
|
1211
|
+
'flex items-center justify-between w-full h-10 px-4',
|
|
1212
|
+
'border border-input rounded-[10px] text-sm',
|
|
1213
|
+
'focus:outline-none transition-colors',
|
|
1214
|
+
'cursor-pointer'
|
|
1215
|
+
),
|
|
1216
|
+
placeholder: 'text-muted-foreground',
|
|
1217
|
+
chevron: cn(
|
|
1218
|
+
'w-4 h-4 text-muted-foreground',
|
|
1219
|
+
'transition-transform duration-200'
|
|
1220
|
+
),
|
|
1221
|
+
menu: cn(
|
|
1222
|
+
'absolute top-full left-0 right-0 mt-1 z-50',
|
|
1223
|
+
'bg-card border border-border rounded-[10px] p-1',
|
|
1224
|
+
'shadow-lg max-h-[200px] overflow-y-auto',
|
|
1225
|
+
'transition-[opacity,scale] duration-150 ease-out'
|
|
1226
|
+
),
|
|
1227
|
+
option: cn(
|
|
1228
|
+
'flex items-center w-full px-3 py-2 text-sm rounded-lg',
|
|
1229
|
+
'hover:bg-accent transition-colors cursor-pointer'
|
|
1230
|
+
),
|
|
1231
|
+
selected: 'text-primary font-medium',
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
interface SelectCtx {
|
|
1235
|
+
open: boolean
|
|
1236
|
+
setOpen: (v: boolean) => void
|
|
1237
|
+
value: string | undefined
|
|
1238
|
+
onSelect: (v: string) => void
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const Ctx = React.createContext<SelectCtx | null>(null)
|
|
1242
|
+
|
|
1243
|
+
function useSelect() {
|
|
1244
|
+
const ctx = React.useContext(Ctx)
|
|
1245
|
+
if (!ctx) throw new Error('Select compound used outside <Select>')
|
|
1246
|
+
return ctx
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
function SelectRoot({
|
|
1250
|
+
children,
|
|
1251
|
+
value,
|
|
1252
|
+
onChange,
|
|
1253
|
+
className,
|
|
1254
|
+
}: {
|
|
1255
|
+
children: React.ReactNode
|
|
1256
|
+
value?: string
|
|
1257
|
+
onChange?: (value: string) => void
|
|
1258
|
+
className?: string
|
|
1259
|
+
}) {
|
|
1260
|
+
const [open, setOpen] = React.useState(false)
|
|
1261
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
1262
|
+
|
|
1263
|
+
const close = React.useCallback(() => setOpen(false), [])
|
|
1264
|
+
const onSelect = React.useCallback(
|
|
1265
|
+
(v: string) => {
|
|
1266
|
+
onChange?.(v)
|
|
1267
|
+
close()
|
|
1268
|
+
},
|
|
1269
|
+
[onChange, close]
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1272
|
+
React.useEffect(() => {
|
|
1273
|
+
const onClick = (e: MouseEvent) => {
|
|
1274
|
+
if (!ref.current?.contains(e.target as Node)) close()
|
|
1275
|
+
}
|
|
1276
|
+
document.addEventListener('mousedown', onClick)
|
|
1277
|
+
return () => document.removeEventListener('mousedown', onClick)
|
|
1278
|
+
}, [close])
|
|
1279
|
+
|
|
1280
|
+
return (
|
|
1281
|
+
<Ctx.Provider value={{ open, setOpen, value, onSelect }}>
|
|
1282
|
+
<div ref={ref} className={cn(styles.base, className)}>
|
|
1283
|
+
{children}
|
|
1284
|
+
</div>
|
|
1285
|
+
</Ctx.Provider>
|
|
1286
|
+
)
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function Trigger({
|
|
1290
|
+
placeholder = 'Select...',
|
|
1291
|
+
children,
|
|
1292
|
+
className,
|
|
1293
|
+
}: {
|
|
1294
|
+
placeholder?: string
|
|
1295
|
+
children?: React.ReactNode
|
|
1296
|
+
className?: string
|
|
1297
|
+
}) {
|
|
1298
|
+
const { open, setOpen } = useSelect()
|
|
1299
|
+
return (
|
|
1300
|
+
<button
|
|
1301
|
+
className={cn(styles.trigger, className)}
|
|
1302
|
+
onClick={() => setOpen(!open)}
|
|
1303
|
+
>
|
|
1304
|
+
<span className={!children ? styles.placeholder : undefined}>
|
|
1305
|
+
{children ?? placeholder}
|
|
1306
|
+
</span>
|
|
1307
|
+
<ChevronDown className={cn(styles.chevron, open && 'rotate-180')} />
|
|
1308
|
+
</button>
|
|
1309
|
+
)
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function Menu({
|
|
1313
|
+
children,
|
|
1314
|
+
className,
|
|
1315
|
+
}: {
|
|
1316
|
+
children: React.ReactNode
|
|
1317
|
+
className?: string
|
|
1318
|
+
}) {
|
|
1319
|
+
const { open } = useSelect()
|
|
1320
|
+
return (
|
|
1321
|
+
<div
|
|
1322
|
+
className={cn(
|
|
1323
|
+
styles.menu,
|
|
1324
|
+
open
|
|
1325
|
+
? 'opacity-100 scale-100'
|
|
1326
|
+
: 'opacity-0 scale-95 pointer-events-none',
|
|
1327
|
+
className
|
|
1328
|
+
)}
|
|
1329
|
+
>
|
|
1330
|
+
{children}
|
|
1331
|
+
</div>
|
|
1332
|
+
)
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function Option({
|
|
1336
|
+
value: optValue,
|
|
1337
|
+
children,
|
|
1338
|
+
className,
|
|
1339
|
+
}: {
|
|
1340
|
+
value: string
|
|
1341
|
+
children: React.ReactNode
|
|
1342
|
+
className?: string
|
|
1343
|
+
}) {
|
|
1344
|
+
const { value, onSelect } = useSelect()
|
|
1345
|
+
return (
|
|
1346
|
+
<button
|
|
1347
|
+
className={cn(
|
|
1348
|
+
styles.option,
|
|
1349
|
+
optValue === value && styles.selected,
|
|
1350
|
+
className
|
|
1351
|
+
)}
|
|
1352
|
+
onClick={() => onSelect(optValue)}
|
|
1353
|
+
>
|
|
1354
|
+
{children}
|
|
1355
|
+
</button>
|
|
1356
|
+
)
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
export const Select = Object.assign(SelectRoot, {
|
|
1360
|
+
Trigger,
|
|
1361
|
+
Menu,
|
|
1362
|
+
Option,
|
|
1363
|
+
})
|
|
1364
|
+
`;var oe=`import * as React from 'react'
|
|
1365
|
+
import { cn } from '@/utils/cn'
|
|
1366
|
+
|
|
1367
|
+
const styles = {
|
|
1368
|
+
horizontal: 'w-full h-px bg-border',
|
|
1369
|
+
vertical: 'h-full w-px bg-border',
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
interface SeparatorProps {
|
|
1373
|
+
orientation?: 'horizontal' | 'vertical'
|
|
1374
|
+
className?: string
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
export function Separator({
|
|
1378
|
+
orientation = 'horizontal',
|
|
1379
|
+
className,
|
|
1380
|
+
}: SeparatorProps) {
|
|
1381
|
+
return (
|
|
1382
|
+
<div
|
|
1383
|
+
role="separator"
|
|
1384
|
+
className={cn(styles[orientation], className)}
|
|
1385
|
+
/>
|
|
1386
|
+
)
|
|
1387
|
+
}
|
|
1388
|
+
`;var ne=`'use client'
|
|
1389
|
+
|
|
1390
|
+
import * as React from 'react'
|
|
1391
|
+
import { cn } from '@/utils/cn'
|
|
1392
|
+
|
|
1393
|
+
const styles = {
|
|
1394
|
+
base: cn(
|
|
1395
|
+
'relative w-12 h-[26px] rounded-full',
|
|
1396
|
+
'transition-colors duration-200 overflow-hidden'
|
|
1397
|
+
),
|
|
1398
|
+
thumb: cn(
|
|
1399
|
+
'absolute left-0 top-[3px] w-5 h-5',
|
|
1400
|
+
'bg-white rounded-full shadow-md',
|
|
1401
|
+
'transition-transform duration-200'
|
|
1402
|
+
),
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
interface SwitchProps {
|
|
1406
|
+
checked?: boolean
|
|
1407
|
+
onChange?: (checked: boolean) => void
|
|
1408
|
+
className?: string
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
export function Switch({
|
|
1412
|
+
checked = false,
|
|
1413
|
+
onChange,
|
|
1414
|
+
className,
|
|
1415
|
+
}: SwitchProps) {
|
|
1416
|
+
return (
|
|
1417
|
+
<button
|
|
1418
|
+
role="switch"
|
|
1419
|
+
aria-checked={checked}
|
|
1420
|
+
onClick={() => onChange?.(!checked)}
|
|
1421
|
+
className={cn(
|
|
1422
|
+
styles.base,
|
|
1423
|
+
checked ? 'bg-primary' : 'bg-border',
|
|
1424
|
+
className
|
|
1425
|
+
)}
|
|
1426
|
+
>
|
|
1427
|
+
<span
|
|
1428
|
+
className={cn(
|
|
1429
|
+
styles.thumb,
|
|
1430
|
+
checked
|
|
1431
|
+
? 'translate-x-[25px]'
|
|
1432
|
+
: 'translate-x-[3px]'
|
|
1433
|
+
)}
|
|
1434
|
+
/>
|
|
1435
|
+
</button>
|
|
1436
|
+
)
|
|
1437
|
+
}
|
|
1438
|
+
`;var se=`'use client'
|
|
1439
|
+
|
|
1440
|
+
import * as React from 'react'
|
|
1441
|
+
import { cn } from '@/utils/cn'
|
|
1442
|
+
|
|
1443
|
+
const styles = {
|
|
1444
|
+
list: 'flex w-fit bg-accent border border-border rounded-[10px] p-1',
|
|
1445
|
+
tab: cn(
|
|
1446
|
+
'px-4 py-2 text-sm font-medium rounded-lg',
|
|
1447
|
+
'transition-colors cursor-pointer'
|
|
1448
|
+
),
|
|
1449
|
+
active: 'bg-background text-foreground',
|
|
1450
|
+
inactive: 'text-muted-foreground hover:text-foreground',
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
interface TabsCtx {
|
|
1454
|
+
value: string
|
|
1455
|
+
setValue: (v: string) => void
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const Ctx = React.createContext<TabsCtx | null>(null)
|
|
1459
|
+
|
|
1460
|
+
function useTabs() {
|
|
1461
|
+
const ctx = React.useContext(Ctx)
|
|
1462
|
+
if (!ctx) throw new Error('Tabs compound used outside <Tabs>')
|
|
1463
|
+
return ctx
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function TabsRoot({
|
|
1467
|
+
children,
|
|
1468
|
+
defaultValue,
|
|
1469
|
+
value: controlled,
|
|
1470
|
+
onChange,
|
|
1471
|
+
className,
|
|
1472
|
+
}: {
|
|
1473
|
+
children: React.ReactNode
|
|
1474
|
+
defaultValue?: string
|
|
1475
|
+
value?: string
|
|
1476
|
+
onChange?: (value: string) => void
|
|
1477
|
+
className?: string
|
|
1478
|
+
}) {
|
|
1479
|
+
const [uncontrolled, setUncontrolled] = React.useState(
|
|
1480
|
+
defaultValue ?? ''
|
|
1481
|
+
)
|
|
1482
|
+
const value = controlled ?? uncontrolled
|
|
1483
|
+
const setValue = React.useCallback(
|
|
1484
|
+
(v: string) => {
|
|
1485
|
+
onChange?.(v)
|
|
1486
|
+
if (controlled === undefined) setUncontrolled(v)
|
|
1487
|
+
},
|
|
1488
|
+
[controlled, onChange]
|
|
1489
|
+
)
|
|
1490
|
+
|
|
1491
|
+
return (
|
|
1492
|
+
<Ctx.Provider value={{ value, setValue }}>
|
|
1493
|
+
<div className={className}>{children}</div>
|
|
1494
|
+
</Ctx.Provider>
|
|
1495
|
+
)
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function List({
|
|
1499
|
+
children,
|
|
1500
|
+
className,
|
|
1501
|
+
}: {
|
|
1502
|
+
children: React.ReactNode
|
|
1503
|
+
className?: string
|
|
1504
|
+
}) {
|
|
1505
|
+
return (
|
|
1506
|
+
<div className={cn(styles.list, className)}>
|
|
1507
|
+
{children}
|
|
1508
|
+
</div>
|
|
1509
|
+
)
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function Tab({
|
|
1513
|
+
value: tabValue,
|
|
1514
|
+
children,
|
|
1515
|
+
className,
|
|
1516
|
+
}: {
|
|
1517
|
+
value: string
|
|
1518
|
+
children: React.ReactNode
|
|
1519
|
+
className?: string
|
|
1520
|
+
}) {
|
|
1521
|
+
const { value, setValue } = useTabs()
|
|
1522
|
+
const isActive = value === tabValue
|
|
1523
|
+
return (
|
|
1524
|
+
<button
|
|
1525
|
+
className={cn(
|
|
1526
|
+
styles.tab,
|
|
1527
|
+
isActive ? styles.active : styles.inactive,
|
|
1528
|
+
className
|
|
1529
|
+
)}
|
|
1530
|
+
onClick={() => setValue(tabValue)}
|
|
1531
|
+
>
|
|
1532
|
+
{children}
|
|
1533
|
+
</button>
|
|
1534
|
+
)
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function Panel({
|
|
1538
|
+
value: panelValue,
|
|
1539
|
+
children,
|
|
1540
|
+
className,
|
|
1541
|
+
}: {
|
|
1542
|
+
value: string
|
|
1543
|
+
children: React.ReactNode
|
|
1544
|
+
className?: string
|
|
1545
|
+
}) {
|
|
1546
|
+
const { value } = useTabs()
|
|
1547
|
+
if (value !== panelValue) return null
|
|
1548
|
+
return <div className={className}>{children}</div>
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
export const Tabs = Object.assign(TabsRoot, {
|
|
1552
|
+
List,
|
|
1553
|
+
Tab,
|
|
1554
|
+
Panel,
|
|
1555
|
+
})
|
|
1556
|
+
`;var re=`import * as React from 'react'
|
|
1557
|
+
import { cn } from '@/utils/cn'
|
|
1558
|
+
|
|
1559
|
+
const styles = {
|
|
1560
|
+
base: cn(
|
|
1561
|
+
'w-full min-h-[80px] px-4 py-3',
|
|
1562
|
+
'border border-input rounded-[10px]',
|
|
1563
|
+
'text-foreground placeholder:text-muted-foreground text-sm',
|
|
1564
|
+
'focus:outline-none transition-colors',
|
|
1565
|
+
'resize-y disabled:opacity-50 disabled:cursor-default'
|
|
1566
|
+
),
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
|
|
1570
|
+
|
|
1571
|
+
export const Textarea = React.forwardRef<
|
|
1572
|
+
HTMLTextAreaElement,
|
|
1573
|
+
TextareaProps
|
|
1574
|
+
>(({ className, ...props }, ref) => (
|
|
1575
|
+
<textarea
|
|
1576
|
+
ref={ref}
|
|
1577
|
+
className={cn(styles.base, className)}
|
|
1578
|
+
{...props}
|
|
1579
|
+
/>
|
|
1580
|
+
))
|
|
1581
|
+
|
|
1582
|
+
Textarea.displayName = 'Textarea'
|
|
1583
|
+
`;var ae=`'use client'
|
|
1584
|
+
|
|
1585
|
+
import * as React from 'react'
|
|
1586
|
+
import { Toaster as Sonner, toast } from 'sonner'
|
|
1587
|
+
import {
|
|
1588
|
+
CheckCircle2,
|
|
1589
|
+
XCircle,
|
|
1590
|
+
Info,
|
|
1591
|
+
AlertTriangle,
|
|
1592
|
+
Loader2,
|
|
1593
|
+
} from 'lucide-react'
|
|
1594
|
+
|
|
1595
|
+
function Toaster() {
|
|
1596
|
+
return (
|
|
1597
|
+
<Sonner
|
|
1598
|
+
icons={{
|
|
1599
|
+
success: (
|
|
1600
|
+
<CheckCircle2 className="w-4 h-4" />
|
|
1601
|
+
),
|
|
1602
|
+
info: (
|
|
1603
|
+
<Info className="w-4 h-4" />
|
|
1604
|
+
),
|
|
1605
|
+
warning: (
|
|
1606
|
+
<AlertTriangle className="w-4 h-4" />
|
|
1607
|
+
),
|
|
1608
|
+
error: (
|
|
1609
|
+
<XCircle className="w-4 h-4" />
|
|
1610
|
+
),
|
|
1611
|
+
loading: (
|
|
1612
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
1613
|
+
),
|
|
1614
|
+
}}
|
|
1615
|
+
style={{
|
|
1616
|
+
'--normal-bg': 'var(--card)',
|
|
1617
|
+
'--normal-text': 'var(--card-foreground)',
|
|
1618
|
+
'--normal-border': 'var(--border)',
|
|
1619
|
+
'--border-radius': '12px',
|
|
1620
|
+
}}
|
|
1621
|
+
/>
|
|
1622
|
+
)
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
export { Toaster, toast }
|
|
1626
|
+
`;var ce=`import * as React from 'react'
|
|
1627
|
+
import { cn } from '@/utils/cn'
|
|
1628
|
+
|
|
1629
|
+
const styles = {
|
|
1630
|
+
base: 'relative inline-flex group',
|
|
1631
|
+
tip: cn(
|
|
1632
|
+
'absolute z-50 px-2.5 py-1.5 text-xs font-medium',
|
|
1633
|
+
'rounded-lg bg-foreground text-background',
|
|
1634
|
+
'whitespace-nowrap pointer-events-none',
|
|
1635
|
+
'opacity-0 group-hover:opacity-100 transition-opacity'
|
|
1636
|
+
),
|
|
1637
|
+
positions: {
|
|
1638
|
+
top: 'bottom-full left-1/2 -translate-x-1/2 mb-2',
|
|
1639
|
+
bottom: 'top-full left-1/2 -translate-x-1/2 mt-2',
|
|
1640
|
+
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
|
|
1641
|
+
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
|
|
1642
|
+
},
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
interface TooltipProps {
|
|
1646
|
+
content: string
|
|
1647
|
+
position?: keyof typeof styles.positions
|
|
1648
|
+
className?: string
|
|
1649
|
+
children: React.ReactNode
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
export function Tooltip({
|
|
1653
|
+
content,
|
|
1654
|
+
position = 'top',
|
|
1655
|
+
className,
|
|
1656
|
+
children,
|
|
1657
|
+
}: TooltipProps) {
|
|
1658
|
+
return (
|
|
1659
|
+
<span className={cn(styles.base, className)}>
|
|
1660
|
+
{children}
|
|
1661
|
+
<span className={cn(styles.tip, styles.positions[position])}>
|
|
1662
|
+
{content}
|
|
1663
|
+
</span>
|
|
1664
|
+
</span>
|
|
1665
|
+
)
|
|
1666
|
+
}
|
|
1667
|
+
`;var C=[{name:"accordion",description:"Collapsible content sections with dot notation and smooth animation",dependencies:[],npmDependencies:[]},{name:"alert",description:"Contextual feedback messages with variants and icons",dependencies:[],npmDependencies:[]},{name:"avatar",description:"User avatar with image support and fallback initials",dependencies:[],npmDependencies:[]},{name:"badge",description:"Small status indicator with color variants",dependencies:[],npmDependencies:[]},{name:"button",description:"Button with variants, sizes, loading state, and icon slots",dependencies:[],npmDependencies:[]},{name:"card",description:"Container with dot notation preview and info sub-components",dependencies:[],npmDependencies:[]},{name:"checkbox",description:"Checkbox input with label and CSS-only checkmark",dependencies:[],npmDependencies:[]},{name:"dialog",description:"Modal dialog with dot notation, overlay, and escape key",dependencies:["button"],npmDependencies:[]},{name:"dropdown",description:"Dropdown menu with dot notation, groups, separators, and click-outside",dependencies:["button"],npmDependencies:[]},{name:"input",description:"Text input with focus ring and disabled state",dependencies:[],npmDependencies:[]},{name:"popover",description:"Floating content panel with dot notation and click-outside",dependencies:["button"],npmDependencies:[]},{name:"progress",description:"Progress bar with animated fill and ARIA attributes",dependencies:[],npmDependencies:[]},{name:"select",description:"Custom select with dot notation and composable options",dependencies:[],npmDependencies:[]},{name:"separator",description:"Visual divider with horizontal and vertical orientation",dependencies:[],npmDependencies:[]},{name:"switch",description:"Toggle switch with smooth transition",dependencies:[],npmDependencies:[]},{name:"tabs",description:"Tab navigation with dot notation and panel content",dependencies:[],npmDependencies:[]},{name:"textarea",description:"Multi-line text input with consistent styling",dependencies:[],npmDependencies:[]},{name:"theme",description:"Dark/light theme support with next-themes and ThemeProvider",dependencies:[],npmDependencies:["next-themes"]},{name:"toast",description:"Toast notifications powered by Sonner",dependencies:[],npmDependencies:["sonner"]},{name:"tooltip",description:"Pure CSS tooltip with 4 position options",dependencies:[],npmDependencies:[]}],k={accordion:_,alert:U,avatar:G,badge:W,button:K,card:X,checkbox:J,dialog:q,dropdown:Q,input:Y,popover:Z,progress:ee,select:te,separator:oe,switch:ne,tabs:se,textarea:re,theme:F,toast:ae,tooltip:ce};async function ie(t){let s=process.cwd();e.intro("drivn add");let r=V(s);if(r||(e.log.error("Drivn is not initialized. Run npx drivn@latest create"),e.outro("Cancelled"),process.exit(1)),!t||!t.length){let o=await e.multiselect({message:"Select components to add",options:C.map(i=>({label:i.name,hint:i.description,value:i.name})),required:true});e.isCancel(o)&&(e.cancel("Cancelled"),process.exit(0)),t=o;}let b=t.filter(o=>!C.find(i=>i.name===o));b.length&&(e.log.error(`Unknown components: ${b.join(", ")}`),e.log.info("Available: "+C.map(o=>o.name).join(", ")),e.outro("Cancelled"),process.exit(1));let d=t.includes("theme"),m=t.filter(o=>o!=="theme"),f=new Set,g=new Set,h=o=>{if(f.has(o))return;let i=C.find(c=>c.name===o);i&&(i.dependencies.forEach(c=>h(c)),i.npmDependencies?.forEach(c=>g.add(c)),f.add(o));};m.forEach(h),d&&g.add("next-themes");let y=[...f].filter(o=>!m.includes(o));y.length&&e.log.info(`Required dependency: ${y.join(", ")}`);let a=r.typescript?"tsx":"jsx",x=join(s,r.paths.components);for(let o of f){let i=join(x,`${o}.${a}`);if(v(i)){let N=await e.confirm({message:`${o}.${a} exists. Overwrite?`,initialValue:false});if(e.isCancel(N)||!N){e.log.warn(`Skipped ${o}`);continue}}let c=k[o];c=c.replace(/@\/utils/g,`@/${r.paths.utils.replace(/^src\//,"")}`),p(i,c),e.log.success(`${o} \u2192 ${r.paths.components}/${o}.${a}`);}if(d){let o=join(x,`theme-provider.${a}`);if(v(o)){let c=await e.confirm({message:`theme-provider.${a} exists. Overwrite?`,initialValue:false});!e.isCancel(c)&&c?(p(o,k.theme),e.log.success(`theme-provider \u2192 ${r.paths.components}/theme-provider.${a}`)):e.log.warn("Skipped theme-provider");}else p(o,k.theme),e.log.success(`theme-provider \u2192 ${r.paths.components}/theme-provider.${a}`);if(r.paths.globals){let c=join(s,r.paths.globals);if(v(c)){let N=z(c);N.includes('[data-theme="dark"]')?e.log.warn("Theme tokens already exist in globals \u2014 skipped"):(p(c,N+O),e.log.success(`Theme tokens appended to ${l.cyan(r.paths.globals)}`));}else e.log.warn(`Globals file not found at ${r.paths.globals}`);}else e.log.warn('No globals path in drivn.config.json. Add "globals" to paths');let i=r.paths.components.replace(/^src\//,"@/");e.log.message(""),e.log.info(l.bold("Complete the setup:")),e.log.message(""),e.log.message(l.bold(`${l.cyan("1.")} Import ThemeProvider in your root layout:`)),e.log.message(l.cyan(` import { ThemeProvider } from "${i}/theme-provider"`)),e.log.message(""),e.log.message(l.bold(`${l.cyan("2.")} Add suppressHydrationWarning to <html>:`)),e.log.message(l.cyan(" <html suppressHydrationWarning>")),e.log.message(""),e.log.message(l.bold(`${l.cyan("3.")} Wrap your app with ThemeProvider:`)),e.log.message(l.cyan(" <ThemeProvider>")),e.log.message(l.cyan(" {children}")),e.log.message(l.cyan(" </ThemeProvider>")),e.log.message("");}if(g.size){let o=e.spinner();o.start("Installing packages");try{execSync(`npm install ${[...g].join(" ")}`,{cwd:s,stdio:"ignore"}),o.stop("Packages installed");}catch{o.stop("Failed to install packages"),e.log.warn(`Run manually: npm install ${[...g].join(" ")}`);}}e.outro("Done.");}var le={version:"1.0.1"};var I=new Command;I.name("drivn").description("Drivn \u2014 Modern UI components").version(le.version);I.command("create").description("Initialize Drivn in your project").action(H);I.command("add [components...]").description("Add components to your project").action(ie);I.parse();
|