@leitware/dockets 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +18 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/add.d.ts +3 -0
  6. package/dist/commands/add.d.ts.map +1 -0
  7. package/dist/commands/add.js +86 -0
  8. package/dist/commands/add.js.map +1 -0
  9. package/dist/commands/list.d.ts +3 -0
  10. package/dist/commands/list.d.ts.map +1 -0
  11. package/dist/commands/list.js +36 -0
  12. package/dist/commands/list.js.map +1 -0
  13. package/dist/registry.d.ts +18 -0
  14. package/dist/registry.d.ts.map +1 -0
  15. package/dist/registry.js +712 -0
  16. package/dist/registry.js.map +1 -0
  17. package/package.json +40 -0
  18. package/templates/accordion.tsx +77 -0
  19. package/templates/alert-dialog.tsx +66 -0
  20. package/templates/alert.tsx +41 -0
  21. package/templates/aspect-ratio.tsx +15 -0
  22. package/templates/avatar.tsx +27 -0
  23. package/templates/badge.tsx +1 -0
  24. package/templates/block-loader.tsx +1 -0
  25. package/templates/breadcrumb.tsx +31 -0
  26. package/templates/button.tsx +1 -0
  27. package/templates/calendar.tsx +45 -0
  28. package/templates/card.tsx +35 -0
  29. package/templates/carousel.tsx +39 -0
  30. package/templates/checkbox.tsx +50 -0
  31. package/templates/code-block.tsx +1 -0
  32. package/templates/collapsible.tsx +35 -0
  33. package/templates/combobox.tsx +154 -0
  34. package/templates/command.tsx +50 -0
  35. package/templates/contact-footer.tsx +193 -0
  36. package/templates/context-menu.tsx +16 -0
  37. package/templates/dialog.tsx +67 -0
  38. package/templates/drawer.tsx +12 -0
  39. package/templates/dropdown-menu.tsx +95 -0
  40. package/templates/form-input.tsx +64 -0
  41. package/templates/form.tsx +10 -0
  42. package/templates/hover-card.tsx +5 -0
  43. package/templates/input-otp.tsx +6 -0
  44. package/templates/label.tsx +1 -0
  45. package/templates/layout-primitives.tsx +11 -0
  46. package/templates/layouts.tsx +346 -0
  47. package/templates/lib/utils.ts +49 -0
  48. package/templates/list-item.tsx +1 -0
  49. package/templates/list-items.tsx +41 -0
  50. package/templates/list.tsx +89 -0
  51. package/templates/logo.tsx +12 -0
  52. package/templates/marketing-footer.tsx +33 -0
  53. package/templates/marketing-header.tsx +46 -0
  54. package/templates/menubar.tsx +16 -0
  55. package/templates/navigation-menu.tsx +11 -0
  56. package/templates/pagination.tsx +86 -0
  57. package/templates/popover.tsx +8 -0
  58. package/templates/pricing-receipt.tsx +71 -0
  59. package/templates/pricing-tabs.tsx +60 -0
  60. package/templates/progress.tsx +29 -0
  61. package/templates/radio-group.tsx +58 -0
  62. package/templates/receipt-card.tsx +1 -0
  63. package/templates/receipt.tsx +269 -0
  64. package/templates/resizable.tsx +1 -0
  65. package/templates/scroll-area.tsx +1 -0
  66. package/templates/select.tsx +110 -0
  67. package/templates/separator.tsx +1 -0
  68. package/templates/sheet.tsx +12 -0
  69. package/templates/sidebar.tsx +15 -0
  70. package/templates/simple-footer.tsx +43 -0
  71. package/templates/simple-header.tsx +77 -0
  72. package/templates/skeleton.tsx +33 -0
  73. package/templates/slider.tsx +55 -0
  74. package/templates/styles/dockets.css +104 -0
  75. package/templates/switch.tsx +49 -0
  76. package/templates/table.tsx +73 -0
  77. package/templates/tabs.tsx +61 -0
  78. package/templates/theme-toggle.tsx +46 -0
  79. package/templates/toast.tsx +1 -0
  80. package/templates/toggle-group.tsx +1 -0
  81. package/templates/toggle.tsx +1 -0
  82. package/templates/tooltip.tsx +31 -0
  83. package/templates/tree-view.tsx +1 -0
  84. package/templates/ui/accordion.tsx +73 -0
  85. package/templates/ui/alert-dialog.tsx +128 -0
  86. package/templates/ui/alert.tsx +56 -0
  87. package/templates/ui/aspect-ratio.tsx +19 -0
  88. package/templates/ui/avatar.tsx +74 -0
  89. package/templates/ui/badge.tsx +48 -0
  90. package/templates/ui/block-loader.tsx +40 -0
  91. package/templates/ui/button.tsx +77 -0
  92. package/templates/ui/calendar.tsx +160 -0
  93. package/templates/ui/card.tsx +73 -0
  94. package/templates/ui/carousel.tsx +149 -0
  95. package/templates/ui/checkbox.tsx +33 -0
  96. package/templates/ui/code-block.tsx +36 -0
  97. package/templates/ui/collapsible.tsx +48 -0
  98. package/templates/ui/combobox.tsx +295 -0
  99. package/templates/ui/command.tsx +148 -0
  100. package/templates/ui/context-menu.tsx +212 -0
  101. package/templates/ui/dialog.tsx +138 -0
  102. package/templates/ui/drawer.tsx +134 -0
  103. package/templates/ui/dropdown-menu.tsx +254 -0
  104. package/templates/ui/form.tsx +122 -0
  105. package/templates/ui/hover-card.tsx +44 -0
  106. package/templates/ui/input-group.tsx +148 -0
  107. package/templates/ui/input-otp.tsx +153 -0
  108. package/templates/ui/input.tsx +20 -0
  109. package/templates/ui/label.tsx +17 -0
  110. package/templates/ui/layout.tsx +252 -0
  111. package/templates/ui/list-item.tsx +50 -0
  112. package/templates/ui/menubar.tsx +225 -0
  113. package/templates/ui/navigation-menu.tsx +117 -0
  114. package/templates/ui/pagination.tsx +110 -0
  115. package/templates/ui/popover.tsx +77 -0
  116. package/templates/ui/progress.tsx +37 -0
  117. package/templates/ui/radio-group.tsx +41 -0
  118. package/templates/ui/receipt-card.tsx +70 -0
  119. package/templates/ui/resizable.tsx +140 -0
  120. package/templates/ui/scroll-area.tsx +64 -0
  121. package/templates/ui/select.tsx +186 -0
  122. package/templates/ui/separator.tsx +21 -0
  123. package/templates/ui/sheet.tsx +134 -0
  124. package/templates/ui/sidebar.tsx +222 -0
  125. package/templates/ui/skeleton.tsx +35 -0
  126. package/templates/ui/slider.tsx +60 -0
  127. package/templates/ui/switch.tsx +33 -0
  128. package/templates/ui/table.tsx +114 -0
  129. package/templates/ui/tabs.tsx +79 -0
  130. package/templates/ui/textarea.tsx +18 -0
  131. package/templates/ui/toast.tsx +139 -0
  132. package/templates/ui/toggle-group.tsx +68 -0
  133. package/templates/ui/toggle.tsx +47 -0
  134. package/templates/ui/tooltip.tsx +53 -0
  135. package/templates/ui/tree-view.tsx +76 -0
@@ -0,0 +1,222 @@
1
+ 'use client'
2
+
3
+ // Sidebar layout primitives.
4
+ // Implements a collapsible rail sidebar with "borders never stack" applied:
5
+ // - Sidebar has border-r-[length:var(--border-width)] (no left border — page edge is implied)
6
+ // - Content fills remaining space with no left border (avoids double border with sidebar)
7
+
8
+ import * as React from 'react'
9
+ import { PanelLeftIcon } from 'lucide-react'
10
+ import { cn } from '@/lib/utils'
11
+
12
+ interface SidebarContextValue {
13
+ open: boolean
14
+ setOpen: (v: boolean) => void
15
+ toggleOpen: () => void
16
+ }
17
+
18
+ const SidebarContext = React.createContext<SidebarContextValue>({
19
+ open: true,
20
+ setOpen: () => {},
21
+ toggleOpen: () => {},
22
+ })
23
+
24
+ function useSidebar() {
25
+ return React.useContext(SidebarContext)
26
+ }
27
+
28
+ interface SidebarProviderProps extends React.ComponentProps<'div'> {
29
+ defaultOpen?: boolean
30
+ }
31
+
32
+ function SidebarProvider({
33
+ defaultOpen = true,
34
+ className,
35
+ children,
36
+ ...props
37
+ }: SidebarProviderProps) {
38
+ const [open, setOpen] = React.useState(defaultOpen)
39
+ const toggleOpen = () => setOpen((v) => !v)
40
+ return (
41
+ <SidebarContext.Provider value={{ open, setOpen, toggleOpen }}>
42
+ <div
43
+ data-slot="sidebar-provider"
44
+ data-sidebar-open={open}
45
+ className={cn('flex h-full w-full', className)}
46
+ {...props}
47
+ >
48
+ {children}
49
+ </div>
50
+ </SidebarContext.Provider>
51
+ )
52
+ }
53
+
54
+ function Sidebar({ className, children, ...props }: React.ComponentProps<'aside'>) {
55
+ const { open } = useSidebar()
56
+ return (
57
+ <aside
58
+ data-slot="sidebar"
59
+ data-open={open}
60
+ className={cn(
61
+ 'relative flex flex-col border-r-[length:var(--border-width)] border-foreground bg-card text-card-foreground transition-[width] duration-200',
62
+ open ? 'w-60' : 'w-12',
63
+ className,
64
+ )}
65
+ {...props}
66
+ >
67
+ {children}
68
+ </aside>
69
+ )
70
+ }
71
+
72
+ function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
73
+ return (
74
+ <div
75
+ data-slot="sidebar-header"
76
+ className={cn(
77
+ 'flex items-center border-b-[length:var(--border-width)] border-foreground px-3 py-2',
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ )
83
+ }
84
+
85
+ function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
86
+ return (
87
+ <div
88
+ data-slot="sidebar-content"
89
+ className={cn('flex flex-1 flex-col overflow-y-auto overflow-x-hidden py-2', className)}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
96
+ return (
97
+ <div
98
+ data-slot="sidebar-footer"
99
+ className={cn(
100
+ 'flex items-center border-t-[length:var(--border-width)] border-foreground px-3 py-2',
101
+ className,
102
+ )}
103
+ {...props}
104
+ />
105
+ )
106
+ }
107
+
108
+ function SidebarNav({ className, ...props }: React.ComponentProps<'nav'>) {
109
+ return (
110
+ <nav
111
+ data-slot="sidebar-nav"
112
+ className={cn('flex flex-col gap-0 px-1', className)}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function SidebarNavItem({ className, active, ...props }: React.ComponentProps<'a'> & { active?: boolean }) {
119
+ const { open } = useSidebar()
120
+ return (
121
+ <a
122
+ data-slot="sidebar-nav-item"
123
+ data-active={active}
124
+ className={cn(
125
+ 'flex min-h-9 items-center gap-2 rounded-[var(--radius)] px-2 text-xs font-medium uppercase tracking-wider',
126
+ 'hover:bg-accent hover:text-accent-foreground',
127
+ active && 'bg-foreground text-background',
128
+ !open && 'justify-center px-0',
129
+ className,
130
+ )}
131
+ {...props}
132
+ />
133
+ )
134
+ }
135
+
136
+ function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
137
+ return (
138
+ <div
139
+ data-slot="sidebar-group"
140
+ className={cn('py-2', className)}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function SidebarGroupLabel({ className, ...props }: React.ComponentProps<'div'>) {
147
+ const { open } = useSidebar()
148
+ return (
149
+ <div
150
+ data-slot="sidebar-group-label"
151
+ className={cn(
152
+ 'px-2 py-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground',
153
+ !open && 'sr-only',
154
+ className,
155
+ )}
156
+ {...props}
157
+ />
158
+ )
159
+ }
160
+
161
+ function SidebarTrigger({ className, ...props }: React.ComponentProps<'button'>) {
162
+ const { toggleOpen } = useSidebar()
163
+ return (
164
+ <button
165
+ type="button"
166
+ data-slot="sidebar-trigger"
167
+ onClick={toggleOpen}
168
+ className={cn(
169
+ 'flex size-8 items-center justify-center rounded-[var(--radius)] text-muted-foreground hover:text-foreground',
170
+ className,
171
+ )}
172
+ aria-label="Toggle sidebar"
173
+ {...props}
174
+ >
175
+ <PanelLeftIcon className="size-4" />
176
+ </button>
177
+ )
178
+ }
179
+
180
+ function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
181
+ return (
182
+ <main
183
+ data-slot="sidebar-inset"
184
+ className={cn('flex flex-1 flex-col overflow-hidden', className)}
185
+ {...props}
186
+ />
187
+ )
188
+ }
189
+
190
+ function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
191
+ const { toggleOpen } = useSidebar()
192
+ return (
193
+ <button
194
+ type="button"
195
+ data-slot="sidebar-rail"
196
+ onClick={toggleOpen}
197
+ aria-label="Toggle sidebar"
198
+ tabIndex={-1}
199
+ className={cn(
200
+ 'absolute inset-y-0 right-0 z-20 hidden w-1 cursor-col-resize hover:bg-foreground/10 sm:flex',
201
+ className,
202
+ )}
203
+ {...props}
204
+ />
205
+ )
206
+ }
207
+
208
+ export {
209
+ SidebarProvider,
210
+ Sidebar,
211
+ SidebarHeader,
212
+ SidebarContent,
213
+ SidebarFooter,
214
+ SidebarNav,
215
+ SidebarNavItem,
216
+ SidebarGroup,
217
+ SidebarGroupLabel,
218
+ SidebarTrigger,
219
+ SidebarInset,
220
+ SidebarRail,
221
+ useSidebar,
222
+ }
@@ -0,0 +1,35 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
5
+ return (
6
+ <div
7
+ data-slot="skeleton"
8
+ className={cn('animate-pulse rounded-[var(--radius)] bg-muted', className)}
9
+ {...props}
10
+ />
11
+ )
12
+ }
13
+
14
+ // Dot-fill pattern variant — matches the dockets receipt aesthetic
15
+ function SkeletonText({ lines = 3, className, ...props }: React.ComponentProps<'div'> & { lines?: number }) {
16
+ return (
17
+ <div
18
+ data-slot="skeleton-text"
19
+ className={cn('flex flex-col gap-2', className)}
20
+ {...props}
21
+ >
22
+ {Array.from({ length: lines }, (_, i) => (
23
+ <div
24
+ key={i}
25
+ className={cn(
26
+ 'h-3 animate-pulse rounded-[var(--radius)] bg-muted',
27
+ i === lines - 1 && 'w-3/4',
28
+ )}
29
+ />
30
+ ))}
31
+ </div>
32
+ )
33
+ }
34
+
35
+ export { Skeleton, SkeletonText }
@@ -0,0 +1,60 @@
1
+ 'use client'
2
+
3
+ import { Slider as SliderPrimitive } from '@base-ui/react/slider'
4
+ import * as React from 'react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ function Slider({ className, ...props }: SliderPrimitive.Root.Props) {
8
+ return (
9
+ <SliderPrimitive.Root
10
+ data-slot="slider"
11
+ className={cn('relative flex w-full touch-none items-center select-none', className)}
12
+ {...props}
13
+ >
14
+ <SliderTrack>
15
+ <SliderFill />
16
+ <SliderThumb />
17
+ </SliderTrack>
18
+ </SliderPrimitive.Root>
19
+ )
20
+ }
21
+
22
+ function SliderTrack({ className, ...props }: SliderPrimitive.Track.Props) {
23
+ return (
24
+ <SliderPrimitive.Track
25
+ data-slot="slider-track"
26
+ className={cn(
27
+ 'relative h-1.5 w-full grow overflow-hidden rounded-[var(--radius)] bg-muted border-[length:var(--border-width)] border-foreground/30',
28
+ className,
29
+ )}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+
35
+ function SliderFill({ className, ...props }: SliderPrimitive.Fill.Props) {
36
+ return (
37
+ <SliderPrimitive.Fill
38
+ data-slot="slider-fill"
39
+ className={cn('absolute h-full bg-foreground', className)}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+
45
+ function SliderThumb({ className, ...props }: SliderPrimitive.Thumb.Props) {
46
+ return (
47
+ <SliderPrimitive.Thumb
48
+ data-slot="slider-thumb"
49
+ className={cn(
50
+ 'block size-4 rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground bg-card shadow-none',
51
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
52
+ 'disabled:pointer-events-none disabled:opacity-50',
53
+ className,
54
+ )}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ export { Slider, SliderTrack, SliderFill, SliderThumb }
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ import { Switch as SwitchPrimitive } from '@base-ui/react/switch'
4
+ import * as React from 'react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ function Switch({ className, ...props }: SwitchPrimitive.Root.Props) {
8
+ return (
9
+ <SwitchPrimitive.Root
10
+ data-slot="switch"
11
+ className={cn(
12
+ 'peer inline-flex h-5 w-9 shrink-0 items-center rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground bg-transparent',
13
+ 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
14
+ 'disabled:cursor-not-allowed disabled:opacity-50',
15
+ 'data-checked:bg-foreground',
16
+ 'aria-invalid:border-destructive',
17
+ className,
18
+ )}
19
+ {...props}
20
+ >
21
+ <SwitchPrimitive.Thumb
22
+ data-slot="switch-thumb"
23
+ className={cn(
24
+ 'pointer-events-none block size-3.5 rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground bg-foreground shadow-none ring-0 transition-transform duration-100',
25
+ 'translate-x-0.5',
26
+ 'data-checked:translate-x-4 data-checked:bg-background data-checked:border-transparent',
27
+ )}
28
+ />
29
+ </SwitchPrimitive.Root>
30
+ )
31
+ }
32
+
33
+ export { Switch }
@@ -0,0 +1,114 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ // Receipt-style data table.
5
+ // "Borders never stack" rule applied:
6
+ // - Table has border-[length:var(--border-width)] (outer frame)
7
+ // - <tr> cells use border-b-[length:var(--border-width)] except last row
8
+ // - <td>/<th> use border-r-[length:var(--border-width)] except last column
9
+ // No cell has a top or left border — the outer frame + sibling borders handle it.
10
+
11
+ function Table({ className, ...props }: React.ComponentProps<'table'>) {
12
+ return (
13
+ <div
14
+ data-slot="table-wrapper"
15
+ className="relative w-full overflow-x-auto rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground"
16
+ >
17
+ <table
18
+ data-slot="table"
19
+ className={cn('w-full caption-bottom border-collapse text-xs', className)}
20
+ {...props}
21
+ />
22
+ </div>
23
+ )
24
+ }
25
+
26
+ function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
27
+ return (
28
+ <thead
29
+ data-slot="table-header"
30
+ className={cn('bg-card', className)}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
37
+ return (
38
+ <tbody
39
+ data-slot="table-body"
40
+ className={cn('[&_tr:last-child]:border-0', className)}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+
46
+ function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
47
+ return (
48
+ <tfoot
49
+ data-slot="table-footer"
50
+ className={cn(
51
+ 'border-t-[length:var(--border-width)] border-foreground bg-card font-medium',
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
60
+ return (
61
+ <tr
62
+ data-slot="table-row"
63
+ className={cn(
64
+ // Row separator: border-b, except last row (handled by TableBody [last-child]:border-0)
65
+ 'border-b-[length:var(--border-width)] border-foreground/30 hover:bg-accent/50 data-selected:bg-accent',
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ )
71
+ }
72
+
73
+ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
74
+ return (
75
+ <th
76
+ data-slot="table-head"
77
+ className={cn(
78
+ 'h-9 px-3 text-left align-middle text-[10px] font-medium uppercase tracking-wider text-muted-foreground',
79
+ // Column separator: right border except last column
80
+ '[&:not(:last-child)]:border-r-[length:var(--border-width)] [&:not(:last-child)]:border-foreground/30',
81
+ 'border-b-[length:var(--border-width)] border-foreground',
82
+ className,
83
+ )}
84
+ {...props}
85
+ />
86
+ )
87
+ }
88
+
89
+ function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
90
+ return (
91
+ <td
92
+ data-slot="table-cell"
93
+ className={cn(
94
+ 'px-3 py-2 align-middle text-xs/relaxed',
95
+ // Column separator: right border except last column
96
+ '[&:not(:last-child)]:border-r-[length:var(--border-width)] [&:not(:last-child)]:border-foreground/20',
97
+ className,
98
+ )}
99
+ {...props}
100
+ />
101
+ )
102
+ }
103
+
104
+ function TableCaption({ className, ...props }: React.ComponentProps<'caption'>) {
105
+ return (
106
+ <caption
107
+ data-slot="table-caption"
108
+ className={cn('mt-2 text-xs text-muted-foreground', className)}
109
+ {...props}
110
+ />
111
+ )
112
+ }
113
+
114
+ export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption }
@@ -0,0 +1,79 @@
1
+ import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
2
+ import { cva, type VariantProps } from 'class-variance-authority'
3
+
4
+ import { cn } from '@/lib/utils'
5
+
6
+ function Tabs({ className, orientation = 'horizontal', ...props }: TabsPrimitive.Root.Props) {
7
+ return (
8
+ <TabsPrimitive.Root
9
+ data-slot="tabs"
10
+ data-orientation={orientation}
11
+ className={cn('group/tabs flex flex-col gap-0', className)}
12
+ {...props}
13
+ />
14
+ )
15
+ }
16
+
17
+ const tabsListVariants = cva(
18
+ 'group/tabs-list flex w-full items-stretch rounded-[var(--radius)] text-foreground h-10 [&>*:not([role=tab])]:hidden',
19
+ {
20
+ variants: {
21
+ variant: {
22
+ default: 'border-[length:var(--border-width)] border-foreground',
23
+ line: 'border-b-[length:var(--border-width)] border-foreground',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ },
29
+ },
30
+ )
31
+
32
+ function TabsList({
33
+ className,
34
+ variant = 'default',
35
+ ...props
36
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
37
+ return (
38
+ <TabsPrimitive.List
39
+ data-slot="tabs-list"
40
+ data-variant={variant}
41
+ className={cn(tabsListVariants({ variant }), className)}
42
+ {...props}
43
+ />
44
+ )
45
+ }
46
+
47
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
48
+ return (
49
+ <TabsPrimitive.Tab
50
+ data-slot="tabs-trigger"
51
+ className={cn(
52
+ 'flex-1 inline-flex items-center justify-center h-full text-xs font-medium uppercase tracking-wider whitespace-nowrap',
53
+ 'bg-card text-card-foreground',
54
+ // Separator between tabs — single border drawn only once (no stacking)
55
+ '[&:not(:first-child)]:border-l-[length:var(--border-width)] [&:not(:first-child)]:border-foreground',
56
+ 'disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50',
57
+ 'data-active:bg-foreground data-active:text-background',
58
+ 'group-data-[variant=line]/tabs-list:border-l-0 group-data-[variant=line]/tabs-list:[&:not(:first-child)]:border-l-0',
59
+ 'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:text-foreground/60',
60
+ 'group-data-[variant=line]/tabs-list:data-active:bg-transparent group-data-[variant=line]/tabs-list:data-active:text-foreground group-data-[variant=line]/tabs-list:data-active:border-b-2 group-data-[variant=line]/tabs-list:data-active:border-b-foreground',
61
+ '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-4',
62
+ className,
63
+ )}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+
69
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
70
+ return (
71
+ <TabsPrimitive.Panel
72
+ data-slot="tabs-content"
73
+ className={cn('flex-1 text-xs/relaxed outline-none', className)}
74
+ {...props}
75
+ />
76
+ )
77
+ }
78
+
79
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,18 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ 'flex field-sizing-content min-h-16 w-full rounded-[var(--radius)] border-[length:var(--border-width)] border-input bg-transparent px-2.5 py-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 md:text-xs dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
@@ -0,0 +1,139 @@
1
+ 'use client'
2
+
3
+ // Lightweight toast system — no sonner dependency.
4
+ // Usage:
5
+ // 1. Wrap your app with <ToastProvider />
6
+ // 2. Call useToast() to get the `toast` function
7
+ // 3. <Toaster /> renders the stack (add to your root layout)
8
+
9
+ import * as React from 'react'
10
+ import { XIcon } from 'lucide-react'
11
+ import { cn } from '@/lib/utils'
12
+
13
+ export type ToastVariant = 'default' | 'destructive' | 'success'
14
+
15
+ export interface ToastData {
16
+ id: string
17
+ title?: string
18
+ description?: string
19
+ variant?: ToastVariant
20
+ duration?: number
21
+ }
22
+
23
+ interface ToastContextValue {
24
+ toasts: ToastData[]
25
+ toast: (opts: Omit<ToastData, 'id'>) => void
26
+ dismiss: (id: string) => void
27
+ }
28
+
29
+ const ToastContext = React.createContext<ToastContextValue>({
30
+ toasts: [],
31
+ toast: () => {},
32
+ dismiss: () => {},
33
+ })
34
+
35
+ function ToastProvider({ children }: { children: React.ReactNode }) {
36
+ const [toasts, setToasts] = React.useState<ToastData[]>([])
37
+
38
+ const toast = React.useCallback((opts: Omit<ToastData, 'id'>) => {
39
+ const id = Math.random().toString(36).slice(2)
40
+ setToasts((prev) => [...prev, { ...opts, id }])
41
+ setTimeout(() => {
42
+ setToasts((prev) => prev.filter((t) => t.id !== id))
43
+ }, opts.duration ?? 4000)
44
+ }, [])
45
+
46
+ const dismiss = React.useCallback((id: string) => {
47
+ setToasts((prev) => prev.filter((t) => t.id !== id))
48
+ }, [])
49
+
50
+ return (
51
+ <ToastContext.Provider value={{ toasts, toast, dismiss }}>
52
+ {children}
53
+ </ToastContext.Provider>
54
+ )
55
+ }
56
+
57
+ function useToast() {
58
+ return React.useContext(ToastContext)
59
+ }
60
+
61
+ const variantClasses: Record<ToastVariant, string> = {
62
+ default: 'border-foreground bg-card text-card-foreground',
63
+ destructive: 'border-destructive bg-destructive/10 text-destructive',
64
+ success: 'border-foreground bg-foreground text-background',
65
+ }
66
+
67
+ function Toast({
68
+ title,
69
+ description,
70
+ variant = 'default',
71
+ onDismiss,
72
+ className,
73
+ }: Omit<ToastData, 'id'> & {
74
+ onDismiss?: () => void
75
+ className?: string
76
+ }) {
77
+ return (
78
+ <div
79
+ data-slot="toast"
80
+ role="status"
81
+ aria-live="polite"
82
+ className={cn(
83
+ 'relative flex w-full max-w-sm flex-col gap-1 rounded-[var(--radius)] border-[length:var(--border-width)] p-3 text-xs/relaxed shadow-none',
84
+ variantClasses[variant],
85
+ className,
86
+ )}
87
+ >
88
+ {title && (
89
+ <div
90
+ data-slot="toast-title"
91
+ className="font-medium uppercase tracking-wider"
92
+ >
93
+ {title}
94
+ </div>
95
+ )}
96
+ {description && (
97
+ <div data-slot="toast-description" className="text-xs/relaxed opacity-90">
98
+ {description}
99
+ </div>
100
+ )}
101
+ {onDismiss && (
102
+ <button
103
+ type="button"
104
+ onClick={onDismiss}
105
+ className="absolute top-2 right-2 flex size-5 items-center justify-center opacity-60 hover:opacity-100"
106
+ aria-label="Dismiss"
107
+ >
108
+ <XIcon className="size-3.5" />
109
+ </button>
110
+ )}
111
+ </div>
112
+ )
113
+ }
114
+
115
+ function Toaster({ className }: { className?: string }) {
116
+ const { toasts, dismiss } = useToast()
117
+ if (toasts.length === 0) return null
118
+ return (
119
+ <div
120
+ data-slot="toaster"
121
+ className={cn(
122
+ 'fixed bottom-4 right-4 z-[100] flex max-h-screen w-full max-w-sm flex-col-reverse gap-2 sm:bottom-4 sm:right-4',
123
+ className,
124
+ )}
125
+ >
126
+ {toasts.map((t) => (
127
+ <Toast
128
+ key={t.id}
129
+ title={t.title}
130
+ description={t.description}
131
+ variant={t.variant}
132
+ onDismiss={() => dismiss(t.id)}
133
+ />
134
+ ))}
135
+ </div>
136
+ )
137
+ }
138
+
139
+ export { ToastProvider, Toaster, Toast, useToast }