@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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +3 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +86 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/registry.d.ts +18 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +712 -0
- package/dist/registry.js.map +1 -0
- package/package.json +40 -0
- package/templates/accordion.tsx +77 -0
- package/templates/alert-dialog.tsx +66 -0
- package/templates/alert.tsx +41 -0
- package/templates/aspect-ratio.tsx +15 -0
- package/templates/avatar.tsx +27 -0
- package/templates/badge.tsx +1 -0
- package/templates/block-loader.tsx +1 -0
- package/templates/breadcrumb.tsx +31 -0
- package/templates/button.tsx +1 -0
- package/templates/calendar.tsx +45 -0
- package/templates/card.tsx +35 -0
- package/templates/carousel.tsx +39 -0
- package/templates/checkbox.tsx +50 -0
- package/templates/code-block.tsx +1 -0
- package/templates/collapsible.tsx +35 -0
- package/templates/combobox.tsx +154 -0
- package/templates/command.tsx +50 -0
- package/templates/contact-footer.tsx +193 -0
- package/templates/context-menu.tsx +16 -0
- package/templates/dialog.tsx +67 -0
- package/templates/drawer.tsx +12 -0
- package/templates/dropdown-menu.tsx +95 -0
- package/templates/form-input.tsx +64 -0
- package/templates/form.tsx +10 -0
- package/templates/hover-card.tsx +5 -0
- package/templates/input-otp.tsx +6 -0
- package/templates/label.tsx +1 -0
- package/templates/layout-primitives.tsx +11 -0
- package/templates/layouts.tsx +346 -0
- package/templates/lib/utils.ts +49 -0
- package/templates/list-item.tsx +1 -0
- package/templates/list-items.tsx +41 -0
- package/templates/list.tsx +89 -0
- package/templates/logo.tsx +12 -0
- package/templates/marketing-footer.tsx +33 -0
- package/templates/marketing-header.tsx +46 -0
- package/templates/menubar.tsx +16 -0
- package/templates/navigation-menu.tsx +11 -0
- package/templates/pagination.tsx +86 -0
- package/templates/popover.tsx +8 -0
- package/templates/pricing-receipt.tsx +71 -0
- package/templates/pricing-tabs.tsx +60 -0
- package/templates/progress.tsx +29 -0
- package/templates/radio-group.tsx +58 -0
- package/templates/receipt-card.tsx +1 -0
- package/templates/receipt.tsx +269 -0
- package/templates/resizable.tsx +1 -0
- package/templates/scroll-area.tsx +1 -0
- package/templates/select.tsx +110 -0
- package/templates/separator.tsx +1 -0
- package/templates/sheet.tsx +12 -0
- package/templates/sidebar.tsx +15 -0
- package/templates/simple-footer.tsx +43 -0
- package/templates/simple-header.tsx +77 -0
- package/templates/skeleton.tsx +33 -0
- package/templates/slider.tsx +55 -0
- package/templates/styles/dockets.css +104 -0
- package/templates/switch.tsx +49 -0
- package/templates/table.tsx +73 -0
- package/templates/tabs.tsx +61 -0
- package/templates/theme-toggle.tsx +46 -0
- package/templates/toast.tsx +1 -0
- package/templates/toggle-group.tsx +1 -0
- package/templates/toggle.tsx +1 -0
- package/templates/tooltip.tsx +31 -0
- package/templates/tree-view.tsx +1 -0
- package/templates/ui/accordion.tsx +73 -0
- package/templates/ui/alert-dialog.tsx +128 -0
- package/templates/ui/alert.tsx +56 -0
- package/templates/ui/aspect-ratio.tsx +19 -0
- package/templates/ui/avatar.tsx +74 -0
- package/templates/ui/badge.tsx +48 -0
- package/templates/ui/block-loader.tsx +40 -0
- package/templates/ui/button.tsx +77 -0
- package/templates/ui/calendar.tsx +160 -0
- package/templates/ui/card.tsx +73 -0
- package/templates/ui/carousel.tsx +149 -0
- package/templates/ui/checkbox.tsx +33 -0
- package/templates/ui/code-block.tsx +36 -0
- package/templates/ui/collapsible.tsx +48 -0
- package/templates/ui/combobox.tsx +295 -0
- package/templates/ui/command.tsx +148 -0
- package/templates/ui/context-menu.tsx +212 -0
- package/templates/ui/dialog.tsx +138 -0
- package/templates/ui/drawer.tsx +134 -0
- package/templates/ui/dropdown-menu.tsx +254 -0
- package/templates/ui/form.tsx +122 -0
- package/templates/ui/hover-card.tsx +44 -0
- package/templates/ui/input-group.tsx +148 -0
- package/templates/ui/input-otp.tsx +153 -0
- package/templates/ui/input.tsx +20 -0
- package/templates/ui/label.tsx +17 -0
- package/templates/ui/layout.tsx +252 -0
- package/templates/ui/list-item.tsx +50 -0
- package/templates/ui/menubar.tsx +225 -0
- package/templates/ui/navigation-menu.tsx +117 -0
- package/templates/ui/pagination.tsx +110 -0
- package/templates/ui/popover.tsx +77 -0
- package/templates/ui/progress.tsx +37 -0
- package/templates/ui/radio-group.tsx +41 -0
- package/templates/ui/receipt-card.tsx +70 -0
- package/templates/ui/resizable.tsx +140 -0
- package/templates/ui/scroll-area.tsx +64 -0
- package/templates/ui/select.tsx +186 -0
- package/templates/ui/separator.tsx +21 -0
- package/templates/ui/sheet.tsx +134 -0
- package/templates/ui/sidebar.tsx +222 -0
- package/templates/ui/skeleton.tsx +35 -0
- package/templates/ui/slider.tsx +60 -0
- package/templates/ui/switch.tsx +33 -0
- package/templates/ui/table.tsx +114 -0
- package/templates/ui/tabs.tsx +79 -0
- package/templates/ui/textarea.tsx +18 -0
- package/templates/ui/toast.tsx +139 -0
- package/templates/ui/toggle-group.tsx +68 -0
- package/templates/ui/toggle.tsx +47 -0
- package/templates/ui/tooltip.tsx +53 -0
- 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 }
|