mycontext-cli 4.2.6 → 4.2.8

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.
Files changed (117) hide show
  1. package/README.md +531 -144
  2. package/dist/README.md +531 -144
  3. package/dist/cli.js +14 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/generate.d.ts.map +1 -1
  6. package/dist/commands/generate.js +19 -47
  7. package/dist/commands/generate.js.map +1 -1
  8. package/dist/commands/init-interactive.d.ts +147 -0
  9. package/dist/commands/init-interactive.d.ts.map +1 -0
  10. package/dist/commands/init-interactive.js +917 -0
  11. package/dist/commands/init-interactive.js.map +1 -0
  12. package/dist/config/shadcn-catalog.json +93 -0
  13. package/dist/core/brain/BrainClient.d.ts +1 -1
  14. package/dist/core/brain/BrainClient.d.ts.map +1 -1
  15. package/dist/core/brain/BrainClient.js +5 -5
  16. package/dist/core/brain/BrainClient.js.map +1 -1
  17. package/dist/doctor/DoctorEngine.d.ts.map +1 -1
  18. package/dist/doctor/DoctorEngine.js +21 -11
  19. package/dist/doctor/DoctorEngine.js.map +1 -1
  20. package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -1
  21. package/dist/doctor/rules/dead-code-rules.js +33 -0
  22. package/dist/doctor/rules/dead-code-rules.js.map +1 -1
  23. package/dist/doctor/rules/index.d.ts +3 -1
  24. package/dist/doctor/rules/index.d.ts.map +1 -1
  25. package/dist/doctor/rules/index.js +7 -1
  26. package/dist/doctor/rules/index.js.map +1 -1
  27. package/dist/doctor/rules/instantdb-rules.d.ts +3 -0
  28. package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -0
  29. package/dist/doctor/rules/instantdb-rules.js +544 -0
  30. package/dist/doctor/rules/instantdb-rules.js.map +1 -0
  31. package/dist/doctor/rules/nextjs-rules.d.ts.map +1 -1
  32. package/dist/doctor/rules/nextjs-rules.js +53 -3
  33. package/dist/doctor/rules/nextjs-rules.js.map +1 -1
  34. package/dist/doctor/rules/typescript-rules.d.ts +3 -0
  35. package/dist/doctor/rules/typescript-rules.d.ts.map +1 -0
  36. package/dist/doctor/rules/typescript-rules.js +177 -0
  37. package/dist/doctor/rules/typescript-rules.js.map +1 -0
  38. package/dist/package.json +4 -2
  39. package/dist/services/ComponentInferenceEngine.d.ts +66 -0
  40. package/dist/services/ComponentInferenceEngine.d.ts.map +1 -0
  41. package/dist/services/ComponentInferenceEngine.js +302 -0
  42. package/dist/services/ComponentInferenceEngine.js.map +1 -0
  43. package/dist/services/ComponentRegistry.d.ts +61 -0
  44. package/dist/services/ComponentRegistry.d.ts.map +1 -0
  45. package/dist/services/ComponentRegistry.js +128 -0
  46. package/dist/services/ComponentRegistry.js.map +1 -0
  47. package/dist/services/InferenceEngine.d.ts +41 -0
  48. package/dist/services/InferenceEngine.d.ts.map +1 -0
  49. package/dist/services/InferenceEngine.js +307 -0
  50. package/dist/services/InferenceEngine.js.map +1 -0
  51. package/dist/services/Planner.d.ts +77 -0
  52. package/dist/services/Planner.d.ts.map +1 -0
  53. package/dist/services/Planner.js +828 -0
  54. package/dist/services/Planner.js.map +1 -0
  55. package/dist/services/ProjectScanner.d.ts.map +1 -1
  56. package/dist/services/ProjectScanner.js +15 -1
  57. package/dist/services/ProjectScanner.js.map +1 -1
  58. package/dist/services/ScaffoldEngine.d.ts +87 -0
  59. package/dist/services/ScaffoldEngine.d.ts.map +1 -0
  60. package/dist/services/ScaffoldEngine.js +409 -0
  61. package/dist/services/ScaffoldEngine.js.map +1 -0
  62. package/dist/services/ScaffoldPreview.d.ts +62 -0
  63. package/dist/services/ScaffoldPreview.d.ts.map +1 -0
  64. package/dist/services/ScaffoldPreview.js +292 -0
  65. package/dist/services/ScaffoldPreview.js.map +1 -0
  66. package/dist/services/TemplateEngine.d.ts +136 -0
  67. package/dist/services/TemplateEngine.d.ts.map +1 -0
  68. package/dist/services/TemplateEngine.js +483 -0
  69. package/dist/services/TemplateEngine.js.map +1 -0
  70. package/dist/services/TemplateHelpers.d.ts +9 -0
  71. package/dist/services/TemplateHelpers.d.ts.map +1 -0
  72. package/dist/services/TemplateHelpers.js +212 -0
  73. package/dist/services/TemplateHelpers.js.map +1 -0
  74. package/dist/templates/actions/auth-actions.ts.hbs +140 -0
  75. package/dist/templates/actions/crud-actions.ts.hbs +113 -0
  76. package/dist/templates/components/auth/login-form.tsx.hbs +67 -0
  77. package/dist/templates/components/auth/login-skeleton.tsx.hbs +24 -0
  78. package/dist/templates/components/auth/register-form.tsx.hbs +116 -0
  79. package/dist/templates/components/crud/entity-card.tsx.hbs +71 -0
  80. package/dist/templates/components/crud/entity-form.tsx.hbs +158 -0
  81. package/dist/templates/components/crud/entity-skeleton.tsx.hbs +90 -0
  82. package/dist/templates/components/crud/entity-table.tsx.hbs +129 -0
  83. package/dist/templates/components/ui/button.tsx.hbs +53 -0
  84. package/dist/templates/components/ui/card.tsx.hbs +68 -0
  85. package/dist/templates/components/ui/input.tsx.hbs +33 -0
  86. package/dist/templates/components/ui/label.tsx.hbs +20 -0
  87. package/dist/templates/components/ui/skeleton.tsx.hbs +15 -0
  88. package/dist/templates/components/ui/theme-provider.tsx.hbs +66 -0
  89. package/dist/templates/components/ui/theme-toggle.tsx.hbs +30 -0
  90. package/dist/templates/config/app.css.hbs +150 -0
  91. package/dist/templates/layouts/dashboard-layout.tsx.hbs +69 -0
  92. package/dist/templates/layouts/error.tsx.hbs +51 -0
  93. package/dist/templates/layouts/loading.tsx.hbs +22 -0
  94. package/dist/templates/layouts/not-found.tsx.hbs +24 -0
  95. package/dist/templates/layouts/root-layout.tsx.hbs +40 -0
  96. package/dist/templates/lib/instant.ts.hbs +19 -0
  97. package/dist/templates/lib/utils.ts.hbs +24 -0
  98. package/dist/templates/pages/auth/login-page.tsx.hbs +30 -0
  99. package/dist/templates/pages/auth/register-page.tsx.hbs +38 -0
  100. package/dist/templates/pages/crud/create-page.tsx.hbs +42 -0
  101. package/dist/templates/pages/crud/detail-page.tsx.hbs +90 -0
  102. package/dist/templates/pages/crud/list-page.tsx.hbs +60 -0
  103. package/dist/templates/pages/crud/loading.tsx.hbs +13 -0
  104. package/dist/templates/pages/landing-page.tsx.hbs +111 -0
  105. package/dist/types/asl.d.ts +387 -0
  106. package/dist/types/asl.d.ts.map +1 -0
  107. package/dist/types/asl.js +139 -0
  108. package/dist/types/asl.js.map +1 -0
  109. package/dist/types/living-context.d.ts +1 -1
  110. package/dist/types/living-context.d.ts.map +1 -1
  111. package/dist/utils/FileGenerator.js +3 -3
  112. package/dist/utils/FileGenerator.js.map +1 -1
  113. package/dist/utils/generateTypesFromSchema.d.ts +47 -0
  114. package/dist/utils/generateTypesFromSchema.d.ts.map +1 -0
  115. package/dist/utils/generateTypesFromSchema.js +298 -0
  116. package/dist/utils/generateTypesFromSchema.js.map +1 -0
  117. package/package.json +4 -2
@@ -0,0 +1,71 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import { cn } from '@/lib/utils'
3
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
4
+
5
+ const entityCardVariants = cva(
6
+ 'transition-all',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'hover:shadow-md',
11
+ interactive: 'cursor-pointer hover:shadow-md hover:border-primary',
12
+ selected: 'border-primary shadow-md',
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: 'default',
17
+ },
18
+ }
19
+ )
20
+
21
+ export interface EntityCardProps extends VariantProps<typeof entityCardVariants> {
22
+ title: string
23
+ description?: string
24
+ metadata?: React.ReactNode
25
+ actions?: React.ReactNode
26
+ children?: React.ReactNode
27
+ onClick?: () => void
28
+ className?: string
29
+ }
30
+
31
+ export function EntityCard({
32
+ title,
33
+ description,
34
+ metadata,
35
+ actions,
36
+ children,
37
+ variant,
38
+ onClick,
39
+ className,
40
+ }: EntityCardProps) {
41
+ return (
42
+ <Card
43
+ className={cn(entityCardVariants({ variant }), className)}
44
+ onClick={onClick}
45
+ role={onClick ? 'button' : undefined}
46
+ tabIndex={onClick ? 0 : undefined}
47
+ onKeyDown={onClick ? (e) => {
48
+ if (e.key === 'Enter' || e.key === ' ') {
49
+ e.preventDefault()
50
+ onClick()
51
+ }
52
+ } : undefined}
53
+ >
54
+ <CardHeader>
55
+ <div className="flex items-start justify-between">
56
+ <div className="flex-1 space-y-1">
57
+ <CardTitle>{title}</CardTitle>
58
+ {description && <CardDescription>{description}</CardDescription>}
59
+ </div>
60
+ {actions && <div className="ml-4">{actions}</div>}
61
+ </div>
62
+ </CardHeader>
63
+ {(metadata || children) && (
64
+ <CardContent>
65
+ {metadata && <div className="space-y-2">{metadata}</div>}
66
+ {children}
67
+ </CardContent>
68
+ )}
69
+ </Card>
70
+ )
71
+ }
@@ -0,0 +1,158 @@
1
+ 'use client'
2
+
3
+ import { useState, useTransition } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import { Button } from '@/components/ui/button'
6
+ import { Input } from '@/components/ui/input'
7
+ import { Label } from '@/components/ui/label'
8
+
9
+ interface EntityFormProps {
10
+ entityName: string
11
+ entityLower: string
12
+ fields: Array<{
13
+ name: string
14
+ type: string
15
+ required?: boolean
16
+ label: string
17
+ }>
18
+ initialData?: Record<string, any>
19
+ action: (formData: FormData) => Promise<{ success: boolean; error?: string }>
20
+ submitLabel?: string
21
+ onSuccess?: () => void
22
+ }
23
+
24
+ export function EntityForm({
25
+ entityName,
26
+ entityLower,
27
+ fields,
28
+ initialData,
29
+ action,
30
+ submitLabel,
31
+ onSuccess,
32
+ }: EntityFormProps) {
33
+ const router = useRouter()
34
+ const [isPending, startTransition] = useTransition()
35
+ const [error, setError] = useState<string | null>(null)
36
+ const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({})
37
+
38
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
39
+ e.preventDefault()
40
+ setError(null)
41
+ setFieldErrors({})
42
+
43
+ const formData = new FormData(e.currentTarget)
44
+
45
+ startTransition(async () => {
46
+ const result = await action(formData)
47
+ if (result.error) {
48
+ setError(result.error)
49
+ } else {
50
+ if (onSuccess) {
51
+ onSuccess()
52
+ } else {
53
+ router.push(`/${entityLower}s`)
54
+ }
55
+ }
56
+ })
57
+ }
58
+
59
+ const renderField = (field: typeof fields[0]) => {
60
+ const value = initialData?.[field.name] || ''
61
+
62
+ switch (field.type) {
63
+ case 'string':
64
+ case 'text':
65
+ return (
66
+ <Input
67
+ id={field.name}
68
+ name={field.name}
69
+ type="text"
70
+ defaultValue={value}
71
+ required={field.required}
72
+ disabled={isPending}
73
+ error={fieldErrors[field.name]}
74
+ />
75
+ )
76
+ case 'number':
77
+ return (
78
+ <Input
79
+ id={field.name}
80
+ name={field.name}
81
+ type="number"
82
+ defaultValue={value}
83
+ required={field.required}
84
+ disabled={isPending}
85
+ error={fieldErrors[field.name]}
86
+ />
87
+ )
88
+ case 'boolean':
89
+ return (
90
+ <input
91
+ id={field.name}
92
+ name={field.name}
93
+ type="checkbox"
94
+ defaultChecked={value}
95
+ disabled={isPending}
96
+ className="size-4 rounded border-border"
97
+ />
98
+ )
99
+ case 'date':
100
+ return (
101
+ <Input
102
+ id={field.name}
103
+ name={field.name}
104
+ type="date"
105
+ defaultValue={value}
106
+ required={field.required}
107
+ disabled={isPending}
108
+ error={fieldErrors[field.name]}
109
+ />
110
+ )
111
+ default:
112
+ return (
113
+ <Input
114
+ id={field.name}
115
+ name={field.name}
116
+ type="text"
117
+ defaultValue={value}
118
+ required={field.required}
119
+ disabled={isPending}
120
+ error={fieldErrors[field.name]}
121
+ />
122
+ )
123
+ }
124
+ }
125
+
126
+ return (
127
+ <form onSubmit={handleSubmit} className="space-y-6">
128
+ {fields.map((field) => (
129
+ <div key={field.name} className="space-y-2">
130
+ <Label htmlFor={field.name} required={field.required}>
131
+ {field.label}
132
+ </Label>
133
+ {renderField(field)}
134
+ </div>
135
+ ))}
136
+
137
+ {error && (
138
+ <p className="text-sm text-destructive" role="alert">
139
+ {error}
140
+ </p>
141
+ )}
142
+
143
+ <div className="flex justify-end gap-4">
144
+ <Button
145
+ type="button"
146
+ variant="outline"
147
+ onClick={() => router.back()}
148
+ disabled={isPending}
149
+ >
150
+ Cancel
151
+ </Button>
152
+ <Button type="submit" disabled={isPending}>
153
+ {isPending ? 'Saving...' : (submitLabel || `Save ${entityName}`)}
154
+ </Button>
155
+ </div>
156
+ </form>
157
+ )
158
+ }
@@ -0,0 +1,90 @@
1
+ import { Skeleton } from '@/components/ui/skeleton'
2
+ import { Card, CardContent, CardHeader } from '@/components/ui/card'
3
+
4
+ export function EntityListSkeleton({ count = 3 }: { count?: number }) {
5
+ return (
6
+ <div className="space-y-4">
7
+ {Array.from({ length: count }).map((_, i) => (
8
+ <Card key={i}>
9
+ <CardHeader>
10
+ <Skeleton className="h-6 w-3/4" />
11
+ <Skeleton className="h-4 w-full" />
12
+ </CardHeader>
13
+ <CardContent>
14
+ <div className="space-y-2">
15
+ <Skeleton className="h-4 w-1/2" />
16
+ <Skeleton className="h-4 w-2/3" />
17
+ </div>
18
+ </CardContent>
19
+ </Card>
20
+ ))}
21
+ </div>
22
+ )
23
+ }
24
+
25
+ export function EntityDetailSkeleton() {
26
+ return (
27
+ <div className="space-y-6">
28
+ <div className="space-y-2">
29
+ <Skeleton className="h-10 w-3/4" />
30
+ <Skeleton className="h-4 w-full" />
31
+ </div>
32
+
33
+ <Card>
34
+ <CardHeader>
35
+ <Skeleton className="h-6 w-1/4" />
36
+ </CardHeader>
37
+ <CardContent className="space-y-4">
38
+ {Array.from({ length: 5 }).map((_, i) => (
39
+ <div key={i} className="space-y-2">
40
+ <Skeleton className="h-4 w-1/4" />
41
+ <Skeleton className="h-10 w-full" />
42
+ </div>
43
+ ))}
44
+ </CardContent>
45
+ </Card>
46
+ </div>
47
+ )
48
+ }
49
+
50
+ export function EntityFormSkeleton() {
51
+ return (
52
+ <div className="space-y-6">
53
+ {Array.from({ length: 4 }).map((_, i) => (
54
+ <div key={i} className="space-y-2">
55
+ <Skeleton className="h-4 w-1/4" />
56
+ <Skeleton className="h-10 w-full" />
57
+ </div>
58
+ ))}
59
+ <div className="flex justify-end gap-4">
60
+ <Skeleton className="h-10 w-24" />
61
+ <Skeleton className="h-10 w-32" />
62
+ </div>
63
+ </div>
64
+ )
65
+ }
66
+
67
+ export function EntityTableSkeleton({ rows = 5 }: { rows?: number }) {
68
+ return (
69
+ <div className="rounded-md border">
70
+ <div className="border-b bg-muted/50 p-4">
71
+ <div className="flex gap-4">
72
+ <Skeleton className="h-4 w-32" />
73
+ <Skeleton className="h-4 w-24" />
74
+ <Skeleton className="h-4 w-28" />
75
+ <Skeleton className="h-4 w-20" />
76
+ </div>
77
+ </div>
78
+ {Array.from({ length: rows }).map((_, i) => (
79
+ <div key={i} className="border-b p-4 last:border-0">
80
+ <div className="flex gap-4">
81
+ <Skeleton className="h-4 w-32" />
82
+ <Skeleton className="h-4 w-24" />
83
+ <Skeleton className="h-4 w-28" />
84
+ <Skeleton className="h-4 w-20" />
85
+ </div>
86
+ </div>
87
+ ))}
88
+ </div>
89
+ )
90
+ }
@@ -0,0 +1,129 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import Link from 'next/link'
5
+ import { Button } from '@/components/ui/button'
6
+ import { ArrowUpDown, MoreHorizontal } from 'lucide-react'
7
+
8
+ interface Column<T> {
9
+ key: keyof T
10
+ label: string
11
+ sortable?: boolean
12
+ render?: (value: any, item: T) => React.ReactNode
13
+ }
14
+
15
+ interface EntityTableProps<T extends { id: string }> {
16
+ data: T[]
17
+ columns: Column<T>[]
18
+ entityLower: string
19
+ onDelete?: (id: string) => Promise<void>
20
+ }
21
+
22
+ export function EntityTable<T extends { id: string }>({
23
+ data,
24
+ columns,
25
+ entityLower,
26
+ onDelete,
27
+ }: EntityTableProps<T>) {
28
+ const [sortKey, setSortKey] = useState<keyof T | null>(null)
29
+ const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
30
+
31
+ const handleSort = (key: keyof T) => {
32
+ if (sortKey === key) {
33
+ setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
34
+ } else {
35
+ setSortKey(key)
36
+ setSortOrder('asc')
37
+ }
38
+ }
39
+
40
+ const sortedData = [...data].sort((a, b) => {
41
+ if (!sortKey) return 0
42
+
43
+ const aVal = a[sortKey]
44
+ const bVal = b[sortKey]
45
+
46
+ if (aVal === bVal) return 0
47
+
48
+ const comparison = aVal < bVal ? -1 : 1
49
+ return sortOrder === 'asc' ? comparison : -comparison
50
+ })
51
+
52
+ return (
53
+ <div className="rounded-md border">
54
+ <table className="w-full">
55
+ <thead className="border-b bg-muted/50">
56
+ <tr>
57
+ {columns.map((column) => (
58
+ <th
59
+ key={String(column.key)}
60
+ className="h-12 px-4 text-left align-middle font-medium text-muted-foreground"
61
+ >
62
+ {column.sortable ? (
63
+ <Button
64
+ variant="ghost"
65
+ size="sm"
66
+ onClick={() => handleSort(column.key)}
67
+ className="-ml-3"
68
+ >
69
+ {column.label}
70
+ <ArrowUpDown className="ml-2 size-4" />
71
+ </Button>
72
+ ) : (
73
+ column.label
74
+ )}
75
+ </th>
76
+ ))}
77
+ <th className="h-12 px-4 text-left align-middle font-medium text-muted-foreground">
78
+ Actions
79
+ </th>
80
+ </tr>
81
+ </thead>
82
+ <tbody>
83
+ {sortedData.length === 0 ? (
84
+ <tr>
85
+ <td
86
+ colSpan={columns.length + 1}
87
+ className="h-24 text-center text-muted-foreground"
88
+ >
89
+ No items found
90
+ </td>
91
+ </tr>
92
+ ) : (
93
+ sortedData.map((item) => (
94
+ <tr key={item.id} className="border-b transition-colors hover:bg-muted/50">
95
+ {columns.map((column) => (
96
+ <td key={String(column.key)} className="p-4 align-middle">
97
+ {column.render
98
+ ? column.render(item[column.key], item)
99
+ : String(item[column.key])}
100
+ </td>
101
+ ))}
102
+ <td className="p-4 align-middle">
103
+ <div className="flex items-center gap-2">
104
+ <Button variant="ghost" size="sm" asChild>
105
+ <Link href={`/${entityLower}s/${item.id}`}>View</Link>
106
+ </Button>
107
+ <Button variant="ghost" size="sm" asChild>
108
+ <Link href={`/${entityLower}s/${item.id}/edit`}>Edit</Link>
109
+ </Button>
110
+ {onDelete && (
111
+ <Button
112
+ variant="ghost"
113
+ size="sm"
114
+ onClick={() => onDelete(item.id)}
115
+ className="text-destructive hover:text-destructive"
116
+ >
117
+ Delete
118
+ </Button>
119
+ )}
120
+ </div>
121
+ </td>
122
+ </tr>
123
+ ))
124
+ )}
125
+ </tbody>
126
+ </table>
127
+ </div>
128
+ )
129
+ }
@@ -0,0 +1,53 @@
1
+ import { Slot } from '@radix-ui/react-slot'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const buttonVariants = cva(
6
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
11
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12
+ outline: 'border border-border bg-background hover:bg-accent hover:text-accent-foreground',
13
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
14
+ ghost: 'hover:bg-accent hover:text-accent-foreground',
15
+ link: 'text-primary underline-offset-4 hover:underline',
16
+ },
17
+ size: {
18
+ default: 'h-10 px-4 py-2',
19
+ sm: 'h-9 rounded-md px-3',
20
+ lg: 'h-11 rounded-md px-8',
21
+ icon: 'size-10',
22
+ },
23
+ },
24
+ defaultVariants: {
25
+ variant: 'default',
26
+ size: 'default',
27
+ },
28
+ }
29
+ )
30
+
31
+ export interface ButtonProps
32
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
+ VariantProps<typeof buttonVariants> {
34
+ asChild?: boolean
35
+ }
36
+
37
+ export function Button({
38
+ className,
39
+ variant,
40
+ size,
41
+ asChild = false,
42
+ ref,
43
+ ...props
44
+ }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {
45
+ const Comp = asChild ? Slot : 'button'
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
@@ -0,0 +1,68 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
4
+
5
+ export function Card({ className, ...props }: CardProps) {
6
+ return (
7
+ <div
8
+ className={cn(
9
+ 'rounded-lg border border-border bg-card text-card-foreground shadow-sm',
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
18
+
19
+ export function CardHeader({ className, ...props }: CardHeaderProps) {
20
+ return (
21
+ <div
22
+ className={cn('flex flex-col space-y-1.5 p-6', className)}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+
28
+ export interface CardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
29
+
30
+ export function CardTitle({ className, ...props }: CardTitleProps) {
31
+ return (
32
+ <h3
33
+ className={cn(
34
+ 'text-2xl font-semibold leading-none tracking-tight',
35
+ className
36
+ )}
37
+ {...props}
38
+ />
39
+ )
40
+ }
41
+
42
+ export interface CardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
43
+
44
+ export function CardDescription({ className, ...props }: CardDescriptionProps) {
45
+ return (
46
+ <p
47
+ className={cn('text-sm text-muted-foreground', className)}
48
+ {...props}
49
+ />
50
+ )
51
+ }
52
+
53
+ export interface CardContentProps extends React.HTMLAttributes<HTMLDivElement> {}
54
+
55
+ export function CardContent({ className, ...props }: CardContentProps) {
56
+ return <div className={cn('p-6 pt-0', className)} {...props} />
57
+ }
58
+
59
+ export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
60
+
61
+ export function CardFooter({ className, ...props }: CardFooterProps) {
62
+ return (
63
+ <div
64
+ className={cn('flex items-center p-6 pt-0', className)}
65
+ {...props}
66
+ />
67
+ )
68
+ }
@@ -0,0 +1,33 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
+ error?: string
5
+ }
6
+
7
+ export function Input({ className, type, error, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
8
+ return (
9
+ <div className="relative">
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ 'flex h-10 w-full rounded-md border border-border bg-background px-3 py-2 text-sm ring-offset-background',
14
+ 'file:border-0 file:bg-transparent file:text-sm file:font-medium',
15
+ 'placeholder:text-muted-foreground',
16
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
17
+ 'disabled:cursor-not-allowed disabled:opacity-50',
18
+ error && 'border-destructive focus-visible:ring-destructive',
19
+ className
20
+ )}
21
+ ref={ref}
22
+ aria-invalid={!!error}
23
+ aria-describedby={error ? `${props.id}-error` : undefined}
24
+ {...props}
25
+ />
26
+ {error && (
27
+ <p id={`${props.id}-error`} className="mt-1 text-sm text-destructive" role="alert">
28
+ {error}
29
+ </p>
30
+ )}
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,20 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
4
+ required?: boolean
5
+ }
6
+
7
+ export function Label({ className, required, children, ...props }: LabelProps) {
8
+ return (
9
+ <label
10
+ className={cn(
11
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
12
+ className
13
+ )}
14
+ {...props}
15
+ >
16
+ {children}
17
+ {required && <span className="ml-1 text-destructive">*</span>}
18
+ </label>
19
+ )
20
+ }
@@ -0,0 +1,15 @@
1
+ import { cn } from '@/lib/utils'
2
+
3
+ export interface SkeletonProps extends React.HTMLAttributes<HTMLDivElement> {}
4
+
5
+ export function Skeleton({ className, ...props }: SkeletonProps) {
6
+ return (
7
+ <div
8
+ className={cn(
9
+ 'animate-pulse rounded-md bg-muted',
10
+ className
11
+ )}
12
+ {...props}
13
+ />
14
+ )
15
+ }