@modern-admin/ui 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 (268) hide show
  1. package/dist/components/accordion.d.ts +7 -0
  2. package/dist/components/accordion.d.ts.map +1 -0
  3. package/dist/components/accordion.jsx +19 -0
  4. package/dist/components/accordion.jsx.map +1 -0
  5. package/dist/components/alert-dialog.d.ts +22 -0
  6. package/dist/components/alert-dialog.d.ts.map +1 -0
  7. package/dist/components/alert-dialog.jsx +27 -0
  8. package/dist/components/alert-dialog.jsx.map +1 -0
  9. package/dist/components/audit-timeline.d.ts +24 -0
  10. package/dist/components/audit-timeline.d.ts.map +1 -0
  11. package/dist/components/audit-timeline.jsx +60 -0
  12. package/dist/components/audit-timeline.jsx.map +1 -0
  13. package/dist/components/avatar.d.ts +6 -0
  14. package/dist/components/avatar.d.ts.map +1 -0
  15. package/dist/components/avatar.jsx +10 -0
  16. package/dist/components/avatar.jsx.map +1 -0
  17. package/dist/components/badge.d.ts +10 -0
  18. package/dist/components/badge.d.ts.map +1 -0
  19. package/dist/components/badge.jsx +19 -0
  20. package/dist/components/badge.jsx.map +1 -0
  21. package/dist/components/breadcrumb.d.ts +17 -0
  22. package/dist/components/breadcrumb.d.ts.map +1 -0
  23. package/dist/components/breadcrumb.jsx +27 -0
  24. package/dist/components/breadcrumb.jsx.map +1 -0
  25. package/dist/components/button.d.ts +12 -0
  26. package/dist/components/button.d.ts.map +1 -0
  27. package/dist/components/button.jsx +37 -0
  28. package/dist/components/button.jsx.map +1 -0
  29. package/dist/components/calendar.d.ts +9 -0
  30. package/dist/components/calendar.d.ts.map +1 -0
  31. package/dist/components/calendar.jsx +102 -0
  32. package/dist/components/calendar.jsx.map +1 -0
  33. package/dist/components/card.d.ts +8 -0
  34. package/dist/components/card.d.ts.map +1 -0
  35. package/dist/components/card.jsx +18 -0
  36. package/dist/components/card.jsx.map +1 -0
  37. package/dist/components/chart.d.ts +97 -0
  38. package/dist/components/chart.d.ts.map +1 -0
  39. package/dist/components/chart.jsx +233 -0
  40. package/dist/components/chart.jsx.map +1 -0
  41. package/dist/components/checkbox.d.ts +4 -0
  42. package/dist/components/checkbox.d.ts.map +1 -0
  43. package/dist/components/checkbox.jsx +11 -0
  44. package/dist/components/checkbox.jsx.map +1 -0
  45. package/dist/components/combobox.d.ts +46 -0
  46. package/dist/components/combobox.d.ts.map +1 -0
  47. package/dist/components/combobox.jsx +145 -0
  48. package/dist/components/combobox.jsx.map +1 -0
  49. package/dist/components/command.d.ts +80 -0
  50. package/dist/components/command.d.ts.map +1 -0
  51. package/dist/components/command.jsx +32 -0
  52. package/dist/components/command.jsx.map +1 -0
  53. package/dist/components/date-picker.d.ts +24 -0
  54. package/dist/components/date-picker.d.ts.map +1 -0
  55. package/dist/components/date-picker.jsx +149 -0
  56. package/dist/components/date-picker.jsx.map +1 -0
  57. package/dist/components/date-range-input.d.ts +22 -0
  58. package/dist/components/date-range-input.d.ts.map +1 -0
  59. package/dist/components/date-range-input.jsx +202 -0
  60. package/dist/components/date-range-input.jsx.map +1 -0
  61. package/dist/components/dialog.d.ts +19 -0
  62. package/dist/components/dialog.d.ts.map +1 -0
  63. package/dist/components/dialog.jsx +30 -0
  64. package/dist/components/dialog.jsx.map +1 -0
  65. package/dist/components/diff-view.d.ts +24 -0
  66. package/dist/components/diff-view.d.ts.map +1 -0
  67. package/dist/components/diff-view.jsx +69 -0
  68. package/dist/components/diff-view.jsx.map +1 -0
  69. package/dist/components/dropdown-menu.d.ts +27 -0
  70. package/dist/components/dropdown-menu.d.ts.map +1 -0
  71. package/dist/components/dropdown-menu.jsx +48 -0
  72. package/dist/components/dropdown-menu.jsx.map +1 -0
  73. package/dist/components/empty.d.ts +15 -0
  74. package/dist/components/empty.d.ts.map +1 -0
  75. package/dist/components/empty.jsx +27 -0
  76. package/dist/components/empty.jsx.map +1 -0
  77. package/dist/components/field.d.ts +23 -0
  78. package/dist/components/field.d.ts.map +1 -0
  79. package/dist/components/field.jsx +60 -0
  80. package/dist/components/field.jsx.map +1 -0
  81. package/dist/components/file-input.d.ts +50 -0
  82. package/dist/components/file-input.d.ts.map +1 -0
  83. package/dist/components/file-input.jsx +104 -0
  84. package/dist/components/file-input.jsx.map +1 -0
  85. package/dist/components/form.d.ts +20 -0
  86. package/dist/components/form.d.ts.map +1 -0
  87. package/dist/components/form.jsx +66 -0
  88. package/dist/components/form.jsx.map +1 -0
  89. package/dist/components/info-tooltip.d.ts +11 -0
  90. package/dist/components/info-tooltip.d.ts.map +1 -0
  91. package/dist/components/info-tooltip.jsx +17 -0
  92. package/dist/components/info-tooltip.jsx.map +1 -0
  93. package/dist/components/input.d.ts +13 -0
  94. package/dist/components/input.d.ts.map +1 -0
  95. package/dist/components/input.jsx +19 -0
  96. package/dist/components/input.jsx.map +1 -0
  97. package/dist/components/json-editor.d.ts +23 -0
  98. package/dist/components/json-editor.d.ts.map +1 -0
  99. package/dist/components/json-editor.jsx +143 -0
  100. package/dist/components/json-editor.jsx.map +1 -0
  101. package/dist/components/kbd.d.ts +15 -0
  102. package/dist/components/kbd.d.ts.map +1 -0
  103. package/dist/components/kbd.jsx +23 -0
  104. package/dist/components/kbd.jsx.map +1 -0
  105. package/dist/components/key-value-editor.d.ts +92 -0
  106. package/dist/components/key-value-editor.d.ts.map +1 -0
  107. package/dist/components/key-value-editor.jsx +187 -0
  108. package/dist/components/key-value-editor.jsx.map +1 -0
  109. package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
  110. package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
  111. package/dist/components/keyboard-shortcuts-help.jsx +97 -0
  112. package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
  113. package/dist/components/label.d.ts +5 -0
  114. package/dist/components/label.d.ts.map +1 -0
  115. package/dist/components/label.jsx +8 -0
  116. package/dist/components/label.jsx.map +1 -0
  117. package/dist/components/media-preview.d.ts +30 -0
  118. package/dist/components/media-preview.d.ts.map +1 -0
  119. package/dist/components/media-preview.jsx +189 -0
  120. package/dist/components/media-preview.jsx.map +1 -0
  121. package/dist/components/multi-file-input.d.ts +76 -0
  122. package/dist/components/multi-file-input.d.ts.map +1 -0
  123. package/dist/components/multi-file-input.jsx +131 -0
  124. package/dist/components/multi-file-input.jsx.map +1 -0
  125. package/dist/components/password-input.d.ts +10 -0
  126. package/dist/components/password-input.d.ts.map +1 -0
  127. package/dist/components/password-input.jsx +18 -0
  128. package/dist/components/password-input.jsx.map +1 -0
  129. package/dist/components/popover.d.ts +7 -0
  130. package/dist/components/popover.d.ts.map +1 -0
  131. package/dist/components/popover.jsx +11 -0
  132. package/dist/components/popover.jsx.map +1 -0
  133. package/dist/components/revision-timeline.d.ts +30 -0
  134. package/dist/components/revision-timeline.d.ts.map +1 -0
  135. package/dist/components/revision-timeline.jsx +42 -0
  136. package/dist/components/revision-timeline.jsx.map +1 -0
  137. package/dist/components/richtext-editor.d.ts +43 -0
  138. package/dist/components/richtext-editor.d.ts.map +1 -0
  139. package/dist/components/richtext-editor.jsx +319 -0
  140. package/dist/components/richtext-editor.jsx.map +1 -0
  141. package/dist/components/richtext-mode.d.ts +23 -0
  142. package/dist/components/richtext-mode.d.ts.map +1 -0
  143. package/dist/components/richtext-mode.js +36 -0
  144. package/dist/components/richtext-mode.js.map +1 -0
  145. package/dist/components/richtext-render.d.ts +8 -0
  146. package/dist/components/richtext-render.d.ts.map +1 -0
  147. package/dist/components/richtext-render.jsx +33 -0
  148. package/dist/components/richtext-render.jsx.map +1 -0
  149. package/dist/components/richtext-sync.d.ts +37 -0
  150. package/dist/components/richtext-sync.d.ts.map +1 -0
  151. package/dist/components/richtext-sync.js +46 -0
  152. package/dist/components/richtext-sync.js.map +1 -0
  153. package/dist/components/scroll-area.d.ts +5 -0
  154. package/dist/components/scroll-area.d.ts.map +1 -0
  155. package/dist/components/scroll-area.jsx +16 -0
  156. package/dist/components/scroll-area.jsx.map +1 -0
  157. package/dist/components/select.d.ts +36 -0
  158. package/dist/components/select.d.ts.map +1 -0
  159. package/dist/components/select.jsx +87 -0
  160. package/dist/components/select.jsx.map +1 -0
  161. package/dist/components/separator.d.ts +4 -0
  162. package/dist/components/separator.d.ts.map +1 -0
  163. package/dist/components/separator.jsx +6 -0
  164. package/dist/components/separator.jsx.map +1 -0
  165. package/dist/components/sheet.d.ts +29 -0
  166. package/dist/components/sheet.d.ts.map +1 -0
  167. package/dist/components/sheet.jsx +44 -0
  168. package/dist/components/sheet.jsx.map +1 -0
  169. package/dist/components/sidebar.d.ts +70 -0
  170. package/dist/components/sidebar.d.ts.map +1 -0
  171. package/dist/components/sidebar.jsx +245 -0
  172. package/dist/components/sidebar.jsx.map +1 -0
  173. package/dist/components/skeleton.d.ts +3 -0
  174. package/dist/components/skeleton.d.ts.map +1 -0
  175. package/dist/components/skeleton.jsx +6 -0
  176. package/dist/components/skeleton.jsx.map +1 -0
  177. package/dist/components/sonner.d.ts +6 -0
  178. package/dist/components/sonner.d.ts.map +1 -0
  179. package/dist/components/sonner.jsx +29 -0
  180. package/dist/components/sonner.jsx.map +1 -0
  181. package/dist/components/switch.d.ts +4 -0
  182. package/dist/components/switch.d.ts.map +1 -0
  183. package/dist/components/switch.jsx +8 -0
  184. package/dist/components/switch.jsx.map +1 -0
  185. package/dist/components/table.d.ts +10 -0
  186. package/dist/components/table.d.ts.map +1 -0
  187. package/dist/components/table.jsx +21 -0
  188. package/dist/components/table.jsx.map +1 -0
  189. package/dist/components/tabs.d.ts +7 -0
  190. package/dist/components/tabs.d.ts.map +1 -0
  191. package/dist/components/tabs.jsx +14 -0
  192. package/dist/components/tabs.jsx.map +1 -0
  193. package/dist/components/textarea.d.ts +4 -0
  194. package/dist/components/textarea.d.ts.map +1 -0
  195. package/dist/components/textarea.jsx +5 -0
  196. package/dist/components/textarea.jsx.map +1 -0
  197. package/dist/components/tooltip.d.ts +7 -0
  198. package/dist/components/tooltip.d.ts.map +1 -0
  199. package/dist/components/tooltip.jsx +11 -0
  200. package/dist/components/tooltip.jsx.map +1 -0
  201. package/dist/index.d.ts +52 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +72 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/lib/theme.d.ts +11 -0
  206. package/dist/lib/theme.d.ts.map +1 -0
  207. package/dist/lib/theme.js +44 -0
  208. package/dist/lib/theme.js.map +1 -0
  209. package/dist/lib/utils.d.ts +3 -0
  210. package/dist/lib/utils.d.ts.map +1 -0
  211. package/dist/lib/utils.js +6 -0
  212. package/dist/lib/utils.js.map +1 -0
  213. package/dist/styles.css +242 -0
  214. package/package.json +85 -0
  215. package/src/components/accordion.tsx +48 -0
  216. package/src/components/alert-dialog.tsx +113 -0
  217. package/src/components/audit-timeline.tsx +102 -0
  218. package/src/components/avatar.tsx +42 -0
  219. package/src/components/badge.tsx +34 -0
  220. package/src/components/breadcrumb.tsx +99 -0
  221. package/src/components/button.tsx +58 -0
  222. package/src/components/calendar.tsx +176 -0
  223. package/src/components/card.tsx +60 -0
  224. package/src/components/chart.tsx +558 -0
  225. package/src/components/checkbox.tsx +23 -0
  226. package/src/components/combobox.tsx +264 -0
  227. package/src/components/command.tsx +120 -0
  228. package/src/components/date-picker.tsx +221 -0
  229. package/src/components/date-range-input.tsx +295 -0
  230. package/src/components/dialog.tsx +94 -0
  231. package/src/components/diff-view.tsx +182 -0
  232. package/src/components/dropdown-menu.tsx +165 -0
  233. package/src/components/empty.tsx +100 -0
  234. package/src/components/field.tsx +168 -0
  235. package/src/components/file-input.tsx +233 -0
  236. package/src/components/form.tsx +152 -0
  237. package/src/components/info-tooltip.tsx +40 -0
  238. package/src/components/input.tsx +55 -0
  239. package/src/components/json-editor.tsx +210 -0
  240. package/src/components/kbd.tsx +35 -0
  241. package/src/components/key-value-editor.tsx +423 -0
  242. package/src/components/keyboard-shortcuts-help.tsx +136 -0
  243. package/src/components/label.tsx +16 -0
  244. package/src/components/media-preview.tsx +278 -0
  245. package/src/components/multi-file-input.tsx +315 -0
  246. package/src/components/password-input.tsx +50 -0
  247. package/src/components/popover.tsx +26 -0
  248. package/src/components/revision-timeline.tsx +93 -0
  249. package/src/components/richtext-editor.tsx +624 -0
  250. package/src/components/richtext-mode.ts +39 -0
  251. package/src/components/richtext-render.tsx +51 -0
  252. package/src/components/richtext-sync.ts +57 -0
  253. package/src/components/scroll-area.tsx +41 -0
  254. package/src/components/select.tsx +200 -0
  255. package/src/components/separator.tsx +21 -0
  256. package/src/components/sheet.tsx +109 -0
  257. package/src/components/sidebar.tsx +660 -0
  258. package/src/components/skeleton.tsx +9 -0
  259. package/src/components/sonner.tsx +45 -0
  260. package/src/components/switch.tsx +24 -0
  261. package/src/components/table.tsx +93 -0
  262. package/src/components/tabs.tsx +57 -0
  263. package/src/components/textarea.tsx +18 -0
  264. package/src/components/tooltip.tsx +25 -0
  265. package/src/index.ts +342 -0
  266. package/src/lib/theme.ts +45 -0
  267. package/src/lib/utils.ts +6 -0
  268. package/src/styles.css +242 -0
@@ -0,0 +1,165 @@
1
+ import * as React from 'react'
2
+ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
3
+ import { Check, ChevronRight, Circle } from 'lucide-react'
4
+ import { cn } from '../lib/utils.js'
5
+
6
+ export const DropdownMenu = DropdownMenuPrimitive.Root
7
+ export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
8
+ export const DropdownMenuGroup = DropdownMenuPrimitive.Group
9
+ export const DropdownMenuPortal = DropdownMenuPrimitive.Portal
10
+ export const DropdownMenuSub = DropdownMenuPrimitive.Sub
11
+ export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
12
+
13
+ export const DropdownMenuSubTrigger = React.forwardRef<
14
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
15
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
16
+ inset?: boolean
17
+ }
18
+ >(({ className, inset, children, ...props }, ref) => (
19
+ <DropdownMenuPrimitive.SubTrigger
20
+ ref={ref}
21
+ className={cn(
22
+ 'flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
23
+ inset && 'pl-8',
24
+ className,
25
+ )}
26
+ {...props}
27
+ >
28
+ {children}
29
+ <ChevronRight className="ml-auto h-4 w-4" />
30
+ </DropdownMenuPrimitive.SubTrigger>
31
+ ))
32
+ DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
33
+
34
+ export const DropdownMenuSubContent = React.forwardRef<
35
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
36
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
37
+ >(({ className, ...props }, ref) => (
38
+ <DropdownMenuPrimitive.SubContent
39
+ ref={ref}
40
+ className={cn(
41
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ ))
47
+ DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
48
+
49
+ export const DropdownMenuContent = React.forwardRef<
50
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
51
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
52
+ >(({ className, sideOffset = 4, ...props }, ref) => (
53
+ <DropdownMenuPrimitive.Portal>
54
+ <DropdownMenuPrimitive.Content
55
+ ref={ref}
56
+ sideOffset={sideOffset}
57
+ className={cn(
58
+ 'z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
59
+ className,
60
+ )}
61
+ {...props}
62
+ />
63
+ </DropdownMenuPrimitive.Portal>
64
+ ))
65
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
66
+
67
+ export const DropdownMenuItem = React.forwardRef<
68
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
69
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
70
+ inset?: boolean
71
+ }
72
+ >(({ className, inset, ...props }, ref) => (
73
+ <DropdownMenuPrimitive.Item
74
+ ref={ref}
75
+ className={cn(
76
+ 'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
77
+ inset && 'pl-8',
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ ))
83
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
84
+
85
+ export const DropdownMenuCheckboxItem = React.forwardRef<
86
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
87
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
88
+ >(({ className, children, checked, ...props }, ref) => (
89
+ <DropdownMenuPrimitive.CheckboxItem
90
+ ref={ref}
91
+ className={cn(
92
+ 'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
93
+ className,
94
+ )}
95
+ checked={checked}
96
+ {...props}
97
+ >
98
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
99
+ <DropdownMenuPrimitive.ItemIndicator>
100
+ <Check className="h-4 w-4" />
101
+ </DropdownMenuPrimitive.ItemIndicator>
102
+ </span>
103
+ {children}
104
+ </DropdownMenuPrimitive.CheckboxItem>
105
+ ))
106
+ DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
107
+
108
+ export const DropdownMenuRadioItem = React.forwardRef<
109
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
110
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
111
+ >(({ className, children, ...props }, ref) => (
112
+ <DropdownMenuPrimitive.RadioItem
113
+ ref={ref}
114
+ className={cn(
115
+ 'relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
116
+ className,
117
+ )}
118
+ {...props}
119
+ >
120
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
121
+ <DropdownMenuPrimitive.ItemIndicator>
122
+ <Circle className="h-2 w-2 fill-current" />
123
+ </DropdownMenuPrimitive.ItemIndicator>
124
+ </span>
125
+ {children}
126
+ </DropdownMenuPrimitive.RadioItem>
127
+ ))
128
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
129
+
130
+ export const DropdownMenuLabel = React.forwardRef<
131
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
132
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
133
+ inset?: boolean
134
+ }
135
+ >(({ className, inset, ...props }, ref) => (
136
+ <DropdownMenuPrimitive.Label
137
+ ref={ref}
138
+ className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
139
+ {...props}
140
+ />
141
+ ))
142
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
143
+
144
+ export const DropdownMenuSeparator = React.forwardRef<
145
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
146
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
147
+ >(({ className, ...props }, ref) => (
148
+ <DropdownMenuPrimitive.Separator
149
+ ref={ref}
150
+ className={cn('-mx-1 my-1 h-px bg-muted', className)}
151
+ {...props}
152
+ />
153
+ ))
154
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
155
+
156
+ export const DropdownMenuShortcut = ({
157
+ className,
158
+ ...props
159
+ }: React.HTMLAttributes<HTMLSpanElement>): React.ReactElement => (
160
+ <span
161
+ className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
162
+ {...props}
163
+ />
164
+ )
165
+ DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
@@ -0,0 +1,100 @@
1
+ // shadcn-style Empty — placeholder for empty states.
2
+ // Compose: <Empty><EmptyHeader><EmptyMedia/><EmptyTitle/><EmptyDescription/></EmptyHeader><EmptyContent>…</EmptyContent></Empty>
3
+
4
+ import * as React from 'react'
5
+ import { cva, type VariantProps } from 'class-variance-authority'
6
+ import { cn } from '../lib/utils.js'
7
+
8
+ export const Empty = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
9
+ ({ className, ...props }, ref) => (
10
+ <div
11
+ ref={ref}
12
+ data-slot="empty"
13
+ className={cn(
14
+ 'flex w-full flex-col items-center justify-center gap-6 rounded-lg border border-dashed border-border bg-card/40 p-8 text-center',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ ),
20
+ )
21
+ Empty.displayName = 'Empty'
22
+
23
+ export const EmptyHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
24
+ ({ className, ...props }, ref) => (
25
+ <div
26
+ ref={ref}
27
+ data-slot="empty-header"
28
+ className={cn('flex flex-col items-center gap-3 text-center', className)}
29
+ {...props}
30
+ />
31
+ ),
32
+ )
33
+ EmptyHeader.displayName = 'EmptyHeader'
34
+
35
+ const emptyMediaVariants = cva(
36
+ 'flex shrink-0 items-center justify-center [&>svg]:size-6',
37
+ {
38
+ variants: {
39
+ variant: {
40
+ default: 'text-muted-foreground',
41
+ icon: 'flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground [&>svg]:size-6',
42
+ },
43
+ },
44
+ defaultVariants: { variant: 'icon' },
45
+ },
46
+ )
47
+
48
+ export interface EmptyMediaProps
49
+ extends React.HTMLAttributes<HTMLDivElement>,
50
+ VariantProps<typeof emptyMediaVariants> {}
51
+
52
+ export const EmptyMedia = React.forwardRef<HTMLDivElement, EmptyMediaProps>(
53
+ ({ className, variant, ...props }, ref) => (
54
+ <div
55
+ ref={ref}
56
+ data-slot="empty-media"
57
+ className={cn(emptyMediaVariants({ variant }), className)}
58
+ {...props}
59
+ />
60
+ ),
61
+ )
62
+ EmptyMedia.displayName = 'EmptyMedia'
63
+
64
+ export const EmptyTitle = React.forwardRef<
65
+ HTMLHeadingElement,
66
+ React.HTMLAttributes<HTMLHeadingElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <h3
69
+ ref={ref}
70
+ data-slot="empty-title"
71
+ className={cn('text-base font-semibold tracking-tight', className)}
72
+ {...props}
73
+ />
74
+ ))
75
+ EmptyTitle.displayName = 'EmptyTitle'
76
+
77
+ export const EmptyDescription = React.forwardRef<
78
+ HTMLParagraphElement,
79
+ React.HTMLAttributes<HTMLParagraphElement>
80
+ >(({ className, ...props }, ref) => (
81
+ <p
82
+ ref={ref}
83
+ data-slot="empty-description"
84
+ className={cn('max-w-sm text-sm text-muted-foreground', className)}
85
+ {...props}
86
+ />
87
+ ))
88
+ EmptyDescription.displayName = 'EmptyDescription'
89
+
90
+ export const EmptyContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
91
+ ({ className, ...props }, ref) => (
92
+ <div
93
+ ref={ref}
94
+ data-slot="empty-content"
95
+ className={cn('flex flex-wrap items-center justify-center gap-2', className)}
96
+ {...props}
97
+ />
98
+ ),
99
+ )
100
+ EmptyContent.displayName = 'EmptyContent'
@@ -0,0 +1,168 @@
1
+ // shadcn-style Field — composable form-field layout primitives.
2
+ // Pure layout; no react-hook-form binding here. Pair with FormField from
3
+ // `./form.js` when wiring to RHF, or use standalone for lightweight forms.
4
+ //
5
+ // Conventions match the canonical shadcn `field` recipe:
6
+ // <Field>
7
+ // <FieldLabel>…</FieldLabel>
8
+ // <Input />
9
+ // <FieldDescription>…</FieldDescription>
10
+ // <FieldError>…</FieldError>
11
+ // </Field>
12
+ //
13
+ // <FieldGroup>…</FieldGroup>
14
+ // <FieldSet><FieldLegend/><FieldGroup/></FieldSet>
15
+
16
+ import * as React from 'react'
17
+ import { Slot } from '@radix-ui/react-slot'
18
+ import { cva, type VariantProps } from 'class-variance-authority'
19
+ import { cn } from '../lib/utils.js'
20
+ import { Label } from './label.js'
21
+
22
+ const fieldVariants = cva('group/field flex w-full', {
23
+ variants: {
24
+ orientation: {
25
+ vertical: 'flex-col gap-2',
26
+ horizontal: 'flex-row items-center gap-3',
27
+ responsive: 'flex-col gap-2 sm:flex-row sm:items-center sm:gap-3',
28
+ },
29
+ },
30
+ defaultVariants: { orientation: 'vertical' },
31
+ })
32
+
33
+ export interface FieldProps
34
+ extends React.HTMLAttributes<HTMLDivElement>,
35
+ VariantProps<typeof fieldVariants> {
36
+ /** Render with a custom element via Radix Slot. */
37
+ asChild?: boolean
38
+ }
39
+
40
+ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
41
+ ({ className, orientation, asChild, ...props }, ref) => {
42
+ const Comp = asChild ? Slot : 'div'
43
+ return (
44
+ <Comp
45
+ ref={ref}
46
+ data-slot="field"
47
+ data-orientation={orientation ?? 'vertical'}
48
+ className={cn(fieldVariants({ orientation }), className)}
49
+ {...props}
50
+ />
51
+ )
52
+ },
53
+ )
54
+ Field.displayName = 'Field'
55
+
56
+ export const FieldLabel = React.forwardRef<
57
+ React.ElementRef<typeof Label>,
58
+ React.ComponentPropsWithoutRef<typeof Label>
59
+ >(({ className, ...props }, ref) => (
60
+ <Label
61
+ ref={ref}
62
+ data-slot="field-label"
63
+ className={cn(
64
+ 'group-data-[invalid=true]/field:text-destructive flex items-center gap-2 text-sm font-medium leading-none',
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ ))
70
+ FieldLabel.displayName = 'FieldLabel'
71
+
72
+ export const FieldDescription = React.forwardRef<
73
+ HTMLParagraphElement,
74
+ React.HTMLAttributes<HTMLParagraphElement>
75
+ >(({ className, ...props }, ref) => (
76
+ <p
77
+ ref={ref}
78
+ data-slot="field-description"
79
+ className={cn('text-sm text-muted-foreground leading-snug', className)}
80
+ {...props}
81
+ />
82
+ ))
83
+ FieldDescription.displayName = 'FieldDescription'
84
+
85
+ export const FieldError = React.forwardRef<
86
+ HTMLParagraphElement,
87
+ React.HTMLAttributes<HTMLParagraphElement> & { errors?: ReadonlyArray<string | undefined> }
88
+ >(({ className, children, errors, ...props }, ref) => {
89
+ const list = errors?.filter((e): e is string => Boolean(e))
90
+ const body = list && list.length > 0 ? list.join(', ') : children
91
+ if (!body) return null
92
+ return (
93
+ <p
94
+ ref={ref}
95
+ data-slot="field-error"
96
+ className={cn('text-sm font-medium text-destructive', className)}
97
+ {...props}
98
+ >
99
+ {body}
100
+ </p>
101
+ )
102
+ })
103
+ FieldError.displayName = 'FieldError'
104
+
105
+ export const FieldGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
106
+ ({ className, ...props }, ref) => (
107
+ <div
108
+ ref={ref}
109
+ data-slot="field-group"
110
+ role="group"
111
+ className={cn('flex flex-col gap-5', className)}
112
+ {...props}
113
+ />
114
+ ),
115
+ )
116
+ FieldGroup.displayName = 'FieldGroup'
117
+
118
+ export const FieldSet = React.forwardRef<HTMLFieldSetElement, React.FieldsetHTMLAttributes<HTMLFieldSetElement>>(
119
+ ({ className, ...props }, ref) => (
120
+ <fieldset
121
+ ref={ref}
122
+ data-slot="field-set"
123
+ className={cn('flex flex-col gap-4 rounded-lg border border-border p-4', className)}
124
+ {...props}
125
+ />
126
+ ),
127
+ )
128
+ FieldSet.displayName = 'FieldSet'
129
+
130
+ export const FieldLegend = React.forwardRef<
131
+ HTMLLegendElement,
132
+ React.HTMLAttributes<HTMLLegendElement>
133
+ >(({ className, ...props }, ref) => (
134
+ <legend
135
+ ref={ref}
136
+ data-slot="field-legend"
137
+ className={cn('px-1 text-sm font-medium leading-none', className)}
138
+ {...props}
139
+ />
140
+ ))
141
+ FieldLegend.displayName = 'FieldLegend'
142
+
143
+ export const FieldSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
144
+ ({ className, ...props }, ref) => (
145
+ <div
146
+ ref={ref}
147
+ data-slot="field-separator"
148
+ role="separator"
149
+ aria-orientation="horizontal"
150
+ className={cn('h-px w-full bg-border', className)}
151
+ {...props}
152
+ />
153
+ ),
154
+ )
155
+ FieldSeparator.displayName = 'FieldSeparator'
156
+
157
+ /** Wrapper for label + description + control + error stacked vertically. */
158
+ export const FieldContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
159
+ ({ className, ...props }, ref) => (
160
+ <div
161
+ ref={ref}
162
+ data-slot="field-content"
163
+ className={cn('flex min-w-0 flex-col gap-1.5', className)}
164
+ {...props}
165
+ />
166
+ ),
167
+ )
168
+ FieldContent.displayName = 'FieldContent'
@@ -0,0 +1,233 @@
1
+ /**
2
+ * FileInput — styled file picker with optional drag-and-drop, current-file
3
+ * display, and a remove button. Purely presentational: upload logic lives in
4
+ * the `packages/react` property renderer.
5
+ *
6
+ * Mobile-first: full-width by default, tap-friendly hit areas.
7
+ */
8
+
9
+ import * as React from 'react'
10
+ import { Upload, X, FileText, Loader2 } from 'lucide-react'
11
+ import { cn } from '../lib/utils.js'
12
+ import { Button } from './button.js'
13
+
14
+ export interface FileInputLabels {
15
+ /** Dropzone aria-label. Default: "Choose file". */
16
+ chooseFile?: string
17
+ /** Drop zone hint before the link. Default: "Drag and drop or". */
18
+ dragAndDrop?: string
19
+ /** Link text when no file selected. Default: "choose a file". */
20
+ chooseAFile?: string
21
+ /** Upload spinner text (no filename). Default: "Uploading…". */
22
+ uploading?: string
23
+ /** Upload spinner text with filename — `{name}` is replaced. Default: "Uploading {name}…". */
24
+ uploadingFile?: string
25
+ /** Remove button aria-label. Default: "Remove file". */
26
+ removeFile?: string
27
+ }
28
+
29
+ export interface FileInputProps {
30
+ /** Current stored value (storage key or URL). Shown as the "current file". */
31
+ value?: string | null
32
+ /** Human-readable display name for the current file. Falls back to `value`. */
33
+ displayName?: string | null
34
+ /** Public URL used for image thumbnail previews. */
35
+ previewUrl?: string | null
36
+ /** HTML `accept` attribute (e.g. `'image/*'` or `'.pdf,.docx'`). */
37
+ accept?: string
38
+ /** Whether a file is currently being uploaded. Shows a spinner. */
39
+ uploading?: boolean
40
+ /** Upload progress 0–100 (overrides the spinner with a determinate bar when set). */
41
+ uploadProgress?: number
42
+ /** Local file name shown next to the progress indicator. */
43
+ uploadingName?: string
44
+ /** Upload error message. */
45
+ error?: string
46
+ disabled?: boolean
47
+ className?: string
48
+ /** Translated UI labels. All optional — English strings are the defaults. */
49
+ labels?: FileInputLabels
50
+ /** Called when the user picks a new file. */
51
+ onFileSelect: (file: File) => void
52
+ /** Called when the user removes the current file. */
53
+ onRemove?: () => void
54
+ }
55
+
56
+ export function FileInput({
57
+ value,
58
+ displayName,
59
+ previewUrl,
60
+ accept,
61
+ uploading = false,
62
+ uploadProgress,
63
+ uploadingName,
64
+ error,
65
+ disabled = false,
66
+ className,
67
+ labels,
68
+ onFileSelect,
69
+ onRemove,
70
+ }: FileInputProps): React.ReactElement {
71
+ const l = {
72
+ chooseFile: labels?.chooseFile ?? 'Choose file',
73
+ dragAndDrop: labels?.dragAndDrop ?? 'Drag and drop or',
74
+ chooseAFile: labels?.chooseAFile ?? 'choose a file',
75
+ uploading: labels?.uploading ?? 'Uploading…',
76
+ uploadingFile: labels?.uploadingFile ?? 'Uploading {name}…',
77
+ removeFile: labels?.removeFile ?? 'Remove file',
78
+ }
79
+ const inputRef = React.useRef<HTMLInputElement>(null)
80
+ const [isDragging, setIsDragging] = React.useState(false)
81
+
82
+ const handleFiles = (files: FileList | null) => {
83
+ const first = files?.[0]
84
+ if (!first || disabled || uploading) return
85
+ onFileSelect(first)
86
+ }
87
+
88
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) =>
89
+ handleFiles(e.target.files)
90
+
91
+ const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
92
+ e.preventDefault()
93
+ setIsDragging(false)
94
+ handleFiles(e.dataTransfer.files)
95
+ }
96
+
97
+ const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
98
+ e.preventDefault()
99
+ if (!disabled && !uploading) setIsDragging(true)
100
+ }
101
+
102
+ const handleDragLeave = () => setIsDragging(false)
103
+
104
+ const label = displayName || (value ? value.split('/').pop() : null)
105
+
106
+ // Detect image for thumbnail preview
107
+ const isImage =
108
+ previewUrl
109
+ ? /\.(jpe?g|png|gif|webp|avif|svg|bmp)(\?|$)/i.test(previewUrl)
110
+ : false
111
+
112
+ return (
113
+ <div className={cn('w-full space-y-2', className)}>
114
+ {/* Drop zone / trigger */}
115
+ <div
116
+ role="button"
117
+ tabIndex={disabled ? -1 : 0}
118
+ aria-label={l.chooseFile}
119
+ onClick={() => !disabled && !uploading && inputRef.current?.click()}
120
+ onKeyDown={(e) => {
121
+ if ((e.key === 'Enter' || e.key === ' ') && !disabled && !uploading) {
122
+ e.preventDefault()
123
+ inputRef.current?.click()
124
+ }
125
+ }}
126
+ onDrop={handleDrop}
127
+ onDragOver={handleDragOver}
128
+ onDragLeave={handleDragLeave}
129
+ className={cn(
130
+ 'flex min-h-[5rem] w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-md border-2 border-dashed border-border bg-muted/30 px-4 py-6 text-center transition-colors',
131
+ 'hover:border-primary/50 hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
132
+ isDragging && 'border-primary bg-primary/5',
133
+ (disabled || uploading) && 'cursor-not-allowed opacity-60',
134
+ )}
135
+ >
136
+ {uploading ? (
137
+ <div className="flex w-full max-w-sm flex-col items-center gap-2">
138
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
139
+ <Loader2 className="size-5 animate-spin" />
140
+ <span className="truncate" title={uploadingName ?? undefined}>
141
+ {uploadingName
142
+ ? l.uploadingFile.replace('{name}', uploadingName)
143
+ : l.uploading}
144
+ </span>
145
+ </div>
146
+ {uploadProgress != null && (
147
+ <div className="flex w-full items-center gap-2">
148
+ <div
149
+ className="h-1.5 flex-1 overflow-hidden rounded-full bg-muted"
150
+ role="progressbar"
151
+ aria-valuemin={0}
152
+ aria-valuemax={100}
153
+ aria-valuenow={uploadProgress}
154
+ >
155
+ <div
156
+ className="h-full rounded-full bg-primary transition-[width] duration-150 ease-out"
157
+ style={{ width: `${uploadProgress}%` }}
158
+ />
159
+ </div>
160
+ <span className="w-10 shrink-0 text-right text-xs tabular-nums text-muted-foreground">
161
+ {uploadProgress}%
162
+ </span>
163
+ </div>
164
+ )}
165
+ </div>
166
+ ) : (
167
+ <>
168
+ <Upload className="size-6 text-muted-foreground" />
169
+ <span className="text-sm font-medium text-foreground">
170
+ <span className="hidden sm:inline">{l.dragAndDrop} </span>
171
+ <span className="text-primary underline-offset-2 hover:underline">
172
+ {l.chooseAFile}
173
+ </span>
174
+ </span>
175
+ {accept && (
176
+ <span className="text-xs text-muted-foreground">{accept}</span>
177
+ )}
178
+ </>
179
+ )}
180
+ </div>
181
+
182
+ {/* Current file row */}
183
+ {value && !uploading && (
184
+ <div className="flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2">
185
+ {isImage && previewUrl ? (
186
+ <img
187
+ src={previewUrl}
188
+ alt={label ?? 'preview'}
189
+ className="size-10 shrink-0 rounded object-cover"
190
+ />
191
+ ) : (
192
+ <FileText className="size-5 shrink-0 text-muted-foreground" />
193
+ )}
194
+ <span className="min-w-0 flex-1 truncate text-sm text-foreground" title={label ?? value}>
195
+ {label ?? value}
196
+ </span>
197
+ {onRemove && !disabled && (
198
+ <Button
199
+ type="button"
200
+ variant="ghost"
201
+ size="icon"
202
+ className="size-7 shrink-0 text-muted-foreground hover:text-destructive"
203
+ aria-label={l.removeFile}
204
+ onClick={(e) => {
205
+ e.stopPropagation()
206
+ onRemove()
207
+ }}
208
+ >
209
+ <X className="size-4" />
210
+ </Button>
211
+ )}
212
+ </div>
213
+ )}
214
+
215
+ {/* Error message */}
216
+ {error && (
217
+ <p className="text-sm text-destructive">{error}</p>
218
+ )}
219
+
220
+ {/* Hidden native input */}
221
+ <input
222
+ ref={inputRef}
223
+ type="file"
224
+ accept={accept}
225
+ className="sr-only"
226
+ onChange={handleInputChange}
227
+ disabled={disabled || uploading}
228
+ tabIndex={-1}
229
+ aria-hidden
230
+ />
231
+ </div>
232
+ )
233
+ }