@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,73 @@
1
+ import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion'
2
+ import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
6
+ return (
7
+ <AccordionPrimitive.Root
8
+ data-slot="accordion"
9
+ className={cn(
10
+ 'flex w-full flex-col border-[length:var(--border-width)] border-foreground',
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
19
+ return (
20
+ <AccordionPrimitive.Item
21
+ data-slot="accordion-item"
22
+ // Border-never-stack: items use border-b only; outer container holds border-t + sides
23
+ className={cn('border-b-[length:var(--border-width)] border-foreground last:border-b-0', className)}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
30
+ return (
31
+ <AccordionPrimitive.Header className="flex">
32
+ <AccordionPrimitive.Trigger
33
+ data-slot="accordion-trigger"
34
+ className={cn(
35
+ 'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-[var(--radius)] border border-transparent py-2.5 text-left text-xs font-medium hover:underline aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',
36
+ className,
37
+ )}
38
+ {...props}
39
+ >
40
+ {children}
41
+ <ChevronDownIcon
42
+ data-slot="accordion-trigger-icon"
43
+ className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
44
+ />
45
+ <ChevronUpIcon
46
+ data-slot="accordion-trigger-icon"
47
+ className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
48
+ />
49
+ </AccordionPrimitive.Trigger>
50
+ </AccordionPrimitive.Header>
51
+ )
52
+ }
53
+
54
+ function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {
55
+ return (
56
+ <AccordionPrimitive.Panel
57
+ data-slot="accordion-content"
58
+ className="overflow-hidden text-xs data-open:animate-accordion-down data-closed:animate-accordion-up"
59
+ {...props}
60
+ >
61
+ <div
62
+ className={cn(
63
+ 'h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
64
+ className,
65
+ )}
66
+ >
67
+ {children}
68
+ </div>
69
+ </AccordionPrimitive.Panel>
70
+ )
71
+ }
72
+
73
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
@@ -0,0 +1,128 @@
1
+ 'use client'
2
+
3
+ import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog'
4
+ import type * as React from 'react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
12
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
13
+ }
14
+
15
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
16
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
17
+ }
18
+
19
+ function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
20
+ return (
21
+ <AlertDialogPrimitive.Backdrop
22
+ data-slot="alert-dialog-overlay"
23
+ className={cn(
24
+ 'fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ )
30
+ }
31
+
32
+ function AlertDialogContent({ className, children, ...props }: AlertDialogPrimitive.Popup.Props) {
33
+ return (
34
+ <AlertDialogPortal>
35
+ <AlertDialogOverlay />
36
+ <AlertDialogPrimitive.Popup
37
+ data-slot="alert-dialog-content"
38
+ className={cn(
39
+ 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground bg-card p-4 text-xs/relaxed text-card-foreground duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',
40
+ className,
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ </AlertDialogPrimitive.Popup>
46
+ </AlertDialogPortal>
47
+ )
48
+ }
49
+
50
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
51
+ return (
52
+ <div
53
+ data-slot="alert-dialog-header"
54
+ className={cn('flex flex-col gap-1 text-left', className)}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
61
+ return (
62
+ <div
63
+ data-slot="alert-dialog-footer"
64
+ className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) {
71
+ return (
72
+ <AlertDialogPrimitive.Title
73
+ data-slot="alert-dialog-title"
74
+ className={cn('text-sm font-medium uppercase tracking-wider', className)}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ function AlertDialogDescription({ className, ...props }: AlertDialogPrimitive.Description.Props) {
81
+ return (
82
+ <AlertDialogPrimitive.Description
83
+ data-slot="alert-dialog-description"
84
+ className={cn('text-xs/relaxed text-muted-foreground', className)}
85
+ {...props}
86
+ />
87
+ )
88
+ }
89
+
90
+ function AlertDialogAction({ className, ...props }: AlertDialogPrimitive.Close.Props) {
91
+ return (
92
+ <AlertDialogPrimitive.Close
93
+ data-slot="alert-dialog-action"
94
+ className={cn(
95
+ 'inline-flex h-10 items-center justify-center rounded-[var(--radius)] bg-foreground px-6 py-2 text-xs font-medium uppercase tracking-wider text-background hover:bg-foreground/90 disabled:pointer-events-none disabled:opacity-50',
96
+ className,
97
+ )}
98
+ {...props}
99
+ />
100
+ )
101
+ }
102
+
103
+ function AlertDialogCancel({ className, ...props }: AlertDialogPrimitive.Close.Props) {
104
+ return (
105
+ <AlertDialogPrimitive.Close
106
+ data-slot="alert-dialog-cancel"
107
+ className={cn(
108
+ 'inline-flex h-10 items-center justify-center rounded-[var(--radius)] border-[length:var(--border-width)] border-dashed border-foreground bg-transparent px-6 py-2 text-xs font-medium uppercase tracking-wider hover:bg-foreground/5 disabled:pointer-events-none disabled:opacity-50',
109
+ className,
110
+ )}
111
+ {...props}
112
+ />
113
+ )
114
+ }
115
+
116
+ export {
117
+ AlertDialog,
118
+ AlertDialogTrigger,
119
+ AlertDialogPortal,
120
+ AlertDialogOverlay,
121
+ AlertDialogContent,
122
+ AlertDialogHeader,
123
+ AlertDialogFooter,
124
+ AlertDialogTitle,
125
+ AlertDialogDescription,
126
+ AlertDialogAction,
127
+ AlertDialogCancel,
128
+ }
@@ -0,0 +1,56 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import * as React from 'react'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const alertVariants = cva(
6
+ 'relative w-full rounded-[var(--radius)] border-[length:var(--border-width)] border-dashed p-4 text-xs/relaxed [&>svg]:absolute [&>svg]:top-4 [&>svg]:left-4 [&>svg]:size-4 [&>svg~*]:pl-6',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'border-foreground bg-card text-card-foreground',
11
+ destructive:
12
+ 'border-destructive/50 bg-destructive/5 text-destructive dark:border-destructive [&>svg]:text-destructive',
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: 'default',
17
+ },
18
+ },
19
+ )
20
+
21
+ function Alert({
22
+ className,
23
+ variant,
24
+ ...props
25
+ }: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
26
+ return (
27
+ <div
28
+ data-slot="alert"
29
+ role="alert"
30
+ className={cn(alertVariants({ variant }), className)}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
37
+ return (
38
+ <div
39
+ data-slot="alert-title"
40
+ className={cn('mb-1 font-medium uppercase tracking-wider leading-none', className)}
41
+ {...props}
42
+ />
43
+ )
44
+ }
45
+
46
+ function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {
47
+ return (
48
+ <div
49
+ data-slot="alert-description"
50
+ className={cn('text-xs/relaxed [&_p]:leading-relaxed', className)}
51
+ {...props}
52
+ />
53
+ )
54
+ }
55
+
56
+ export { Alert, AlertTitle, AlertDescription, alertVariants }
@@ -0,0 +1,19 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ interface AspectRatioProps extends React.ComponentProps<'div'> {
5
+ ratio?: number
6
+ }
7
+
8
+ function AspectRatio({ ratio = 1, className, style, ...props }: AspectRatioProps) {
9
+ return (
10
+ <div
11
+ data-slot="aspect-ratio"
12
+ style={{ aspectRatio: ratio, ...style }}
13
+ className={cn('relative w-full overflow-hidden', className)}
14
+ {...props}
15
+ />
16
+ )
17
+ }
18
+
19
+ export { AspectRatio }
@@ -0,0 +1,74 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ interface AvatarProps extends React.ComponentProps<'span'> {
5
+ src?: string
6
+ alt?: string
7
+ fallback?: React.ReactNode
8
+ size?: 'sm' | 'default' | 'lg'
9
+ }
10
+
11
+ const sizeClasses = {
12
+ sm: 'size-7 text-[10px]',
13
+ default: 'size-9 text-xs',
14
+ lg: 'size-12 text-sm',
15
+ }
16
+
17
+ function Avatar({ className, src, alt, fallback, size = 'default', ...props }: AvatarProps) {
18
+ const [imgError, setImgError] = React.useState(false)
19
+ const showFallback = !src || imgError
20
+
21
+ return (
22
+ <span
23
+ data-slot="avatar"
24
+ className={cn(
25
+ 'relative inline-flex shrink-0 overflow-hidden rounded-[var(--radius)] border-[length:var(--border-width)] border-dashed border-foreground',
26
+ sizeClasses[size],
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ {!showFallback && (
32
+ <img
33
+ src={src}
34
+ alt={alt ?? ''}
35
+ className="h-full w-full object-cover"
36
+ onError={() => setImgError(true)}
37
+ />
38
+ )}
39
+ {showFallback && (
40
+ <span
41
+ data-slot="avatar-fallback"
42
+ className="flex h-full w-full items-center justify-center bg-muted font-medium uppercase tracking-wider text-muted-foreground"
43
+ >
44
+ {fallback}
45
+ </span>
46
+ )}
47
+ </span>
48
+ )
49
+ }
50
+
51
+ function AvatarImage({ className, ...props }: React.ComponentProps<'img'>) {
52
+ return (
53
+ <img
54
+ data-slot="avatar-image"
55
+ className={cn('h-full w-full object-cover', className)}
56
+ {...props}
57
+ />
58
+ )
59
+ }
60
+
61
+ function AvatarFallback({ className, ...props }: React.ComponentProps<'span'>) {
62
+ return (
63
+ <span
64
+ data-slot="avatar-fallback"
65
+ className={cn(
66
+ 'flex h-full w-full items-center justify-center bg-muted font-medium uppercase tracking-wider text-muted-foreground',
67
+ className,
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ }
73
+
74
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,48 @@
1
+ import { mergeProps } from '@base-ui/react/merge-props'
2
+ import { useRender } from '@base-ui/react/use-render'
3
+ import { cva, type VariantProps } from 'class-variance-authority'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const badgeVariants = cva(
8
+ 'group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-[var(--radius)] border border-transparent px-2 py-0.5 text-xs font-medium uppercase tracking-wider whitespace-nowrap has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 [&>svg]:pointer-events-none [&>svg]:size-3!',
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-foreground text-background',
13
+ secondary: 'bg-card text-card-foreground',
14
+ destructive: 'bg-destructive text-destructive-foreground',
15
+ outline: 'border-foreground border-dashed text-foreground',
16
+ ghost: 'text-foreground/60',
17
+ link: 'text-foreground underline-offset-4 underline',
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: 'default',
22
+ },
23
+ },
24
+ )
25
+
26
+ function Badge({
27
+ className,
28
+ variant = 'default',
29
+ render,
30
+ ...props
31
+ }: useRender.ComponentProps<'span'> & VariantProps<typeof badgeVariants>) {
32
+ return useRender({
33
+ defaultTagName: 'span',
34
+ props: mergeProps<'span'>(
35
+ {
36
+ className: cn(badgeVariants({ variant }), className),
37
+ },
38
+ props,
39
+ ),
40
+ render,
41
+ state: {
42
+ slot: 'badge',
43
+ variant,
44
+ },
45
+ })
46
+ }
47
+
48
+ export { Badge, badgeVariants }
@@ -0,0 +1,40 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@/lib/utils'
3
+
4
+ const SEQUENCES = [
5
+ ['⠁', '⠂', '⠄', '⡀', '⢀', '⠠', '⠐', '⠈'],
6
+ ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'],
7
+ ['▖', '▘', '▝', '▗'],
8
+ ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▇', '▆', '▅', '▄', '▃', '▁'],
9
+ ['▉', '▊', '▋', '▌', '▍', '▎', '▏', '▎', '▍', '▌', '▋', '▊', '▉'],
10
+ ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'],
11
+ ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐'],
12
+ ['◢', '◣', '◤', '◥'],
13
+ ['◰', '◳', '◲', '◱'],
14
+ ['◴', '◷', '◶', '◵'],
15
+ ['◐', '◓', '◑', '◒'],
16
+ ] as const
17
+
18
+ interface BlockLoaderProps extends Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'> {
19
+ mode?: number
20
+ }
21
+
22
+ function BlockLoader({ mode = 1, className, ...props }: BlockLoaderProps) {
23
+ const sequence = SEQUENCES[mode] ?? SEQUENCES[0]
24
+ const [index, setIndex] = React.useState(0)
25
+
26
+ React.useEffect(() => {
27
+ const id = window.setInterval(() => {
28
+ setIndex((prev) => (prev + 1) % sequence.length)
29
+ }, 100)
30
+ return () => clearInterval(id)
31
+ }, [sequence.length])
32
+
33
+ return (
34
+ <span data-slot="block-loader" className={cn('inline-block w-[1em] text-center', className)} aria-label="Loading" {...props}>
35
+ {sequence[index]}
36
+ </span>
37
+ )
38
+ }
39
+
40
+ export { BlockLoader, SEQUENCES }
@@ -0,0 +1,77 @@
1
+ import { cva, type VariantProps } from 'class-variance-authority'
2
+ import * as React from 'react'
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const buttonVariants = cva(
6
+ 'inline-flex items-center justify-center gap-2 whitespace-nowrap text-xs font-medium uppercase tracking-wider disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: 'bg-foreground text-background hover:bg-foreground/90',
11
+ destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12
+ outline:
13
+ 'border-[length:var(--border-width)] border-dashed border-foreground bg-transparent hover:bg-foreground/5',
14
+ secondary: 'bg-card text-card-foreground',
15
+ ghost: 'hover:text-foreground',
16
+ link: 'text-foreground underline-offset-4 hover:underline',
17
+ },
18
+ size: {
19
+ default: 'h-10 px-6 py-2',
20
+ sm: 'h-9 px-4',
21
+ lg: 'h-11 px-6',
22
+ xl: 'h-12 px-8',
23
+ icon: 'h-10 w-10',
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: 'default',
28
+ size: 'default',
29
+ },
30
+ },
31
+ )
32
+
33
+ export interface ButtonProps
34
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
35
+ VariantProps<typeof buttonVariants> {
36
+ asChild?: boolean
37
+ render?: React.ReactElement<Record<string, unknown>>
38
+ }
39
+
40
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
41
+ ({ className, variant, size, asChild = false, render, children, ...props }, ref) => {
42
+ const classes = cn(buttonVariants({ variant, size, className }))
43
+
44
+ // Base UI render prop pattern (preferred)
45
+ if (render) {
46
+ const renderProps = render.props as Record<string, unknown>
47
+ return React.cloneElement(render, {
48
+ ...props,
49
+ ref,
50
+ className: cn(classes, renderProps.className as string | undefined),
51
+ children: (renderProps.children as React.ReactNode) ?? children,
52
+ })
53
+ }
54
+
55
+ // Legacy asChild pattern (backwards-compatible without Radix Slot)
56
+ if (asChild && React.isValidElement(children)) {
57
+ const childProps = (children as React.ReactElement<Record<string, unknown>>).props as Record<
58
+ string,
59
+ unknown
60
+ >
61
+ return React.cloneElement(children as React.ReactElement<Record<string, unknown>>, {
62
+ ...props,
63
+ ref,
64
+ className: cn(classes, childProps.className as string | undefined),
65
+ })
66
+ }
67
+
68
+ return (
69
+ <button data-slot="button" className={classes} ref={ref} {...props}>
70
+ {children}
71
+ </button>
72
+ )
73
+ },
74
+ )
75
+ Button.displayName = 'Button'
76
+
77
+ export { Button, buttonVariants }
@@ -0,0 +1,160 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const DAYS = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']
8
+ const MONTHS = [
9
+ 'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE',
10
+ 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER',
11
+ ]
12
+
13
+ interface CalendarProps {
14
+ className?: string
15
+ selected?: Date
16
+ onSelect?: (date: Date) => void
17
+ defaultMonth?: Date
18
+ disabled?: (date: Date) => boolean
19
+ mode?: 'single'
20
+ }
21
+
22
+ function Calendar({
23
+ className,
24
+ selected,
25
+ onSelect,
26
+ defaultMonth,
27
+ disabled,
28
+ }: CalendarProps) {
29
+ const today = new Date()
30
+ const [viewDate, setViewDate] = React.useState(
31
+ defaultMonth ?? selected ?? today,
32
+ )
33
+
34
+ const year = viewDate.getFullYear()
35
+ const month = viewDate.getMonth()
36
+
37
+ const firstDay = new Date(year, month, 1).getDay()
38
+ const daysInMonth = new Date(year, month + 1, 0).getDate()
39
+
40
+ const cells: (number | null)[] = [
41
+ ...Array(firstDay).fill(null),
42
+ ...Array.from({ length: daysInMonth }, (_, i) => i + 1),
43
+ ]
44
+ // Pad to complete last row
45
+ while (cells.length % 7 !== 0) cells.push(null)
46
+
47
+ const prevMonth = () => setViewDate(new Date(year, month - 1, 1))
48
+ const nextMonth = () => setViewDate(new Date(year, month + 1, 1))
49
+
50
+ const isSelected = (day: number) => {
51
+ if (!selected) return false
52
+ return (
53
+ selected.getFullYear() === year &&
54
+ selected.getMonth() === month &&
55
+ selected.getDate() === day
56
+ )
57
+ }
58
+
59
+ const isToday = (day: number) =>
60
+ today.getFullYear() === year &&
61
+ today.getMonth() === month &&
62
+ today.getDate() === day
63
+
64
+ const isDisabled = (day: number) => {
65
+ if (!disabled) return false
66
+ return disabled(new Date(year, month, day))
67
+ }
68
+
69
+ return (
70
+ <div
71
+ data-slot="calendar"
72
+ className={cn(
73
+ 'w-full max-w-xs rounded-[var(--radius)] border-[length:var(--border-width)] border-foreground bg-card text-card-foreground',
74
+ className,
75
+ )}
76
+ >
77
+ {/* Header — no stacking: outer border owns top+sides */}
78
+ <div className="flex items-center justify-between border-b-[length:var(--border-width)] border-foreground px-3 py-2">
79
+ <button
80
+ type="button"
81
+ onClick={prevMonth}
82
+ className="flex size-6 items-center justify-center text-muted-foreground hover:text-foreground"
83
+ aria-label="Previous month"
84
+ >
85
+ <ChevronLeftIcon className="size-3.5" />
86
+ </button>
87
+ <span className="text-xs font-medium uppercase tracking-wider">
88
+ {MONTHS[month]} {year}
89
+ </span>
90
+ <button
91
+ type="button"
92
+ onClick={nextMonth}
93
+ className="flex size-6 items-center justify-center text-muted-foreground hover:text-foreground"
94
+ aria-label="Next month"
95
+ >
96
+ <ChevronRightIcon className="size-3.5" />
97
+ </button>
98
+ </div>
99
+
100
+ {/* Day-of-week header */}
101
+ {/* Newspaper grid: container holds border-t + border-l; cells add border-r + border-b */}
102
+ <div className="grid grid-cols-7 border-b-[length:var(--border-width)] border-foreground">
103
+ {DAYS.map((d) => (
104
+ <div
105
+ key={d}
106
+ className="flex h-7 items-center justify-center text-[10px] font-medium uppercase tracking-wider text-muted-foreground"
107
+ >
108
+ {d.slice(0, 2)}
109
+ </div>
110
+ ))}
111
+ </div>
112
+
113
+ {/* Day cells */}
114
+ <div className="grid grid-cols-7">
115
+ {cells.map((day, idx) => {
116
+ if (day === null) {
117
+ return (
118
+ <div
119
+ key={`empty-${idx}`}
120
+ className={cn(
121
+ 'h-8',
122
+ // Newspaper border rule: right+bottom only
123
+ (idx + 1) % 7 !== 0 && 'border-r-[length:var(--border-width)] border-foreground/20',
124
+ idx < cells.length - 7 && 'border-b-[length:var(--border-width)] border-foreground/20',
125
+ )}
126
+ />
127
+ )
128
+ }
129
+ const sel = isSelected(day)
130
+ const tod = isToday(day)
131
+ const dis = isDisabled(day)
132
+ return (
133
+ <button
134
+ key={day}
135
+ type="button"
136
+ disabled={dis}
137
+ onClick={() => !dis && onSelect?.(new Date(year, month, day))}
138
+ className={cn(
139
+ 'flex h-8 items-center justify-center text-xs',
140
+ // Newspaper border rule
141
+ (idx + 1) % 7 !== 0 && 'border-r-[length:var(--border-width)] border-foreground/20',
142
+ idx < cells.length - 7 && 'border-b-[length:var(--border-width)] border-foreground/20',
143
+ sel
144
+ ? 'bg-foreground text-background'
145
+ : tod
146
+ ? 'border-[length:var(--border-width)] border-dashed border-foreground font-medium'
147
+ : 'hover:bg-accent hover:text-accent-foreground',
148
+ dis && 'pointer-events-none opacity-30',
149
+ )}
150
+ >
151
+ {day}
152
+ </button>
153
+ )
154
+ })}
155
+ </div>
156
+ </div>
157
+ )
158
+ }
159
+
160
+ export { Calendar }