@craftzbay/ui 0.9.0 → 0.10.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 (171) hide show
  1. package/README.md +72 -6
  2. package/dist-lib/components/ui/Accordion.js +60 -0
  3. package/dist-lib/components/ui/Accordion.js.map +1 -0
  4. package/dist-lib/components/ui/Alert.d.ts +1 -1
  5. package/dist-lib/components/ui/Alert.js +66 -0
  6. package/dist-lib/components/ui/Alert.js.map +1 -0
  7. package/dist-lib/components/ui/Avatar.d.ts +2 -2
  8. package/dist-lib/components/ui/Avatar.js +96 -0
  9. package/dist-lib/components/ui/Avatar.js.map +1 -0
  10. package/dist-lib/components/ui/Badge.d.ts +1 -1
  11. package/dist-lib/components/ui/Badge.js +67 -0
  12. package/dist-lib/components/ui/Badge.js.map +1 -0
  13. package/dist-lib/components/ui/Breadcrumbs.js +34 -0
  14. package/dist-lib/components/ui/Breadcrumbs.js.map +1 -0
  15. package/dist-lib/components/ui/Button.d.ts +1 -1
  16. package/dist-lib/components/ui/Button.js +117 -0
  17. package/dist-lib/components/ui/Button.js.map +1 -0
  18. package/dist-lib/components/ui/Calendar.d.ts +7 -1
  19. package/dist-lib/components/ui/Calendar.js +111 -0
  20. package/dist-lib/components/ui/Calendar.js.map +1 -0
  21. package/dist-lib/components/ui/Card.d.ts +1 -1
  22. package/dist-lib/components/ui/Card.js +116 -0
  23. package/dist-lib/components/ui/Card.js.map +1 -0
  24. package/dist-lib/components/ui/Carousel.d.ts +5 -1
  25. package/dist-lib/components/ui/Carousel.js +143 -0
  26. package/dist-lib/components/ui/Carousel.js.map +1 -0
  27. package/dist-lib/components/ui/Chart.d.ts +26 -4
  28. package/dist-lib/components/ui/Chart.js +253 -0
  29. package/dist-lib/components/ui/Chart.js.map +1 -0
  30. package/dist-lib/components/ui/Checkbox.js +56 -0
  31. package/dist-lib/components/ui/Checkbox.js.map +1 -0
  32. package/dist-lib/components/ui/Combobox.d.ts +7 -0
  33. package/dist-lib/components/ui/Combobox.js +164 -0
  34. package/dist-lib/components/ui/Combobox.js.map +1 -0
  35. package/dist-lib/components/ui/CommandPalette.d.ts +3 -3
  36. package/dist-lib/components/ui/CommandPalette.js +174 -0
  37. package/dist-lib/components/ui/CommandPalette.js.map +1 -0
  38. package/dist-lib/components/ui/ContextMenu.js +150 -0
  39. package/dist-lib/components/ui/ContextMenu.js.map +1 -0
  40. package/dist-lib/components/ui/DataGrid.js +84 -0
  41. package/dist-lib/components/ui/DataGrid.js.map +1 -0
  42. package/dist-lib/components/ui/DatePicker.d.ts +16 -13
  43. package/dist-lib/components/ui/DatePicker.js +174 -0
  44. package/dist-lib/components/ui/DatePicker.js.map +1 -0
  45. package/dist-lib/components/ui/DesignSystemProvider.d.ts +8 -1
  46. package/dist-lib/components/ui/DesignSystemProvider.js +79 -0
  47. package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -0
  48. package/dist-lib/components/ui/Dialog.d.ts +1 -1
  49. package/dist-lib/components/ui/Dialog.js +140 -0
  50. package/dist-lib/components/ui/Dialog.js.map +1 -0
  51. package/dist-lib/components/ui/Drawer.d.ts +6 -0
  52. package/dist-lib/components/ui/Drawer.js +98 -0
  53. package/dist-lib/components/ui/Drawer.js.map +1 -0
  54. package/dist-lib/components/ui/DropdownMenu.js +164 -0
  55. package/dist-lib/components/ui/DropdownMenu.js.map +1 -0
  56. package/dist-lib/components/ui/EmptyState.js +32 -0
  57. package/dist-lib/components/ui/EmptyState.js.map +1 -0
  58. package/dist-lib/components/ui/ErrorState.js +48 -0
  59. package/dist-lib/components/ui/ErrorState.js.map +1 -0
  60. package/dist-lib/components/ui/FileUpload.d.ts +3 -0
  61. package/dist-lib/components/ui/FileUpload.js +108 -0
  62. package/dist-lib/components/ui/FileUpload.js.map +1 -0
  63. package/dist-lib/components/ui/Form.d.ts +1 -0
  64. package/dist-lib/components/ui/Form.js +105 -0
  65. package/dist-lib/components/ui/Form.js.map +1 -0
  66. package/dist-lib/components/ui/Icon.js +26 -0
  67. package/dist-lib/components/ui/Icon.js.map +1 -0
  68. package/dist-lib/components/ui/IconButton.d.ts +1 -1
  69. package/dist-lib/components/ui/IconButton.js +56 -0
  70. package/dist-lib/components/ui/IconButton.js.map +1 -0
  71. package/dist-lib/components/ui/Input.d.ts +6 -3
  72. package/dist-lib/components/ui/Input.js +135 -0
  73. package/dist-lib/components/ui/Input.js.map +1 -0
  74. package/dist-lib/components/ui/Kbd.js +24 -0
  75. package/dist-lib/components/ui/Kbd.js.map +1 -0
  76. package/dist-lib/components/ui/MultiSelect.d.ts +3 -1
  77. package/dist-lib/components/ui/MultiSelect.js +196 -0
  78. package/dist-lib/components/ui/MultiSelect.js.map +1 -0
  79. package/dist-lib/components/ui/Pagination.d.ts +23 -0
  80. package/dist-lib/components/ui/Pagination.js +148 -0
  81. package/dist-lib/components/ui/Pagination.js.map +1 -0
  82. package/dist-lib/components/ui/Popover.js +35 -0
  83. package/dist-lib/components/ui/Popover.js.map +1 -0
  84. package/dist-lib/components/ui/Progress.js +95 -0
  85. package/dist-lib/components/ui/Progress.js.map +1 -0
  86. package/dist-lib/components/ui/RadioGroup.js +64 -0
  87. package/dist-lib/components/ui/RadioGroup.js.map +1 -0
  88. package/dist-lib/components/ui/ScrollArea.js +44 -0
  89. package/dist-lib/components/ui/ScrollArea.js.map +1 -0
  90. package/dist-lib/components/ui/Select.js +131 -0
  91. package/dist-lib/components/ui/Select.js.map +1 -0
  92. package/dist-lib/components/ui/Separator.js +26 -0
  93. package/dist-lib/components/ui/Separator.js.map +1 -0
  94. package/dist-lib/components/ui/Sheet.d.ts +1 -1
  95. package/dist-lib/components/ui/Sheet.js +113 -0
  96. package/dist-lib/components/ui/Sheet.js.map +1 -0
  97. package/dist-lib/components/ui/Sidebar.d.ts +18 -6
  98. package/dist-lib/components/ui/Sidebar.js +169 -0
  99. package/dist-lib/components/ui/Sidebar.js.map +1 -0
  100. package/dist-lib/components/ui/Skeleton.js +28 -0
  101. package/dist-lib/components/ui/Skeleton.js.map +1 -0
  102. package/dist-lib/components/ui/Slider.js +52 -0
  103. package/dist-lib/components/ui/Slider.js.map +1 -0
  104. package/dist-lib/components/ui/Snackbar.d.ts +1 -1
  105. package/dist-lib/components/ui/Snackbar.js +56 -0
  106. package/dist-lib/components/ui/Snackbar.js.map +1 -0
  107. package/dist-lib/components/ui/Spinner.js +42 -0
  108. package/dist-lib/components/ui/Spinner.js.map +1 -0
  109. package/dist-lib/components/ui/Stepper.js +88 -0
  110. package/dist-lib/components/ui/Stepper.js.map +1 -0
  111. package/dist-lib/components/ui/Switch.js +77 -0
  112. package/dist-lib/components/ui/Switch.js.map +1 -0
  113. package/dist-lib/components/ui/Table.d.ts +9 -1
  114. package/dist-lib/components/ui/Table.js +157 -0
  115. package/dist-lib/components/ui/Table.js.map +1 -0
  116. package/dist-lib/components/ui/Tabs.d.ts +1 -1
  117. package/dist-lib/components/ui/Tabs.js +89 -0
  118. package/dist-lib/components/ui/Tabs.js.map +1 -0
  119. package/dist-lib/components/ui/TagInput.d.ts +17 -2
  120. package/dist-lib/components/ui/TagInput.js +111 -0
  121. package/dist-lib/components/ui/TagInput.js.map +1 -0
  122. package/dist-lib/components/ui/Textarea.js +77 -0
  123. package/dist-lib/components/ui/Textarea.js.map +1 -0
  124. package/dist-lib/components/ui/Timeline.d.ts +16 -6
  125. package/dist-lib/components/ui/Timeline.js +52 -0
  126. package/dist-lib/components/ui/Timeline.js.map +1 -0
  127. package/dist-lib/components/ui/Toast.d.ts +1 -1
  128. package/dist-lib/components/ui/Toast.js +171 -0
  129. package/dist-lib/components/ui/Toast.js.map +1 -0
  130. package/dist-lib/components/ui/Tooltip.js +58 -0
  131. package/dist-lib/components/ui/Tooltip.js.map +1 -0
  132. package/dist-lib/components/ui/TopNav.js +52 -0
  133. package/dist-lib/components/ui/TopNav.js.map +1 -0
  134. package/dist-lib/components/ui/Tree.d.ts +27 -5
  135. package/dist-lib/components/ui/Tree.js +167 -0
  136. package/dist-lib/components/ui/Tree.js.map +1 -0
  137. package/dist-lib/hooks/use-field-ids.d.ts +29 -0
  138. package/dist-lib/hooks/use-field-ids.js +18 -0
  139. package/dist-lib/hooks/use-field-ids.js.map +1 -0
  140. package/dist-lib/hooks/use-media-query.js +18 -0
  141. package/dist-lib/hooks/use-media-query.js.map +1 -0
  142. package/dist-lib/hooks/use-strings.d.ts +5 -0
  143. package/dist-lib/hooks/use-strings.js +12 -0
  144. package/dist-lib/hooks/use-strings.js.map +1 -0
  145. package/dist-lib/hooks/use-toast.js +36 -0
  146. package/dist-lib/hooks/use-toast.js.map +1 -0
  147. package/dist-lib/icon.d.ts +8 -0
  148. package/dist-lib/icon.js +7 -0
  149. package/dist-lib/icon.js.map +1 -0
  150. package/dist-lib/icons/index.d.ts +71 -88
  151. package/dist-lib/icons/index.js +145 -0
  152. package/dist-lib/icons/index.js.map +1 -0
  153. package/dist-lib/illustrations/index.js +105 -0
  154. package/dist-lib/illustrations/index.js.map +1 -0
  155. package/dist-lib/index.d.ts +62 -59
  156. package/dist-lib/index.js +267 -4529
  157. package/dist-lib/index.js.map +1 -1
  158. package/dist-lib/lib/strings.d.ts +129 -0
  159. package/dist-lib/lib/strings.js +85 -0
  160. package/dist-lib/lib/strings.js.map +1 -0
  161. package/dist-lib/lib/strings.mn.d.ts +3 -0
  162. package/dist-lib/lib/strings.mn.js +70 -0
  163. package/dist-lib/lib/strings.mn.js.map +1 -0
  164. package/dist-lib/lib/utils.d.ts +0 -1
  165. package/dist-lib/lib/utils.js +10 -0
  166. package/dist-lib/lib/utils.js.map +1 -0
  167. package/dist-lib/styles.css +1 -1
  168. package/dist-lib/theme.css +777 -0
  169. package/package.json +31 -8
  170. package/dist-lib/index.cjs +0 -8
  171. package/dist-lib/index.cjs.map +0 -1
@@ -0,0 +1,164 @@
1
+ "use client";
2
+ import { jsxs as c, jsx as r } from "react/jsx-runtime";
3
+ import { forwardRef as i } from "react";
4
+ import * as o from "@radix-ui/react-dropdown-menu";
5
+ import { Check as l, Circle as m, ChevronRight as f } from "../../icons/index.js";
6
+ import { cn as d } from "../../lib/utils.js";
7
+ const R = o.Root, k = o.Trigger, T = o.Group, g = o.Portal, j = o.Sub, G = o.RadioGroup, p = d(
8
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground outline-none",
9
+ "data-[highlighted]:bg-background-muted data-[highlighted]:text-foreground",
10
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
11
+ "transition-colors duration-[var(--duration-fast)]"
12
+ ), b = i(function({ className: e, inset: t, children: n, ...a }, u) {
13
+ return /* @__PURE__ */ c(
14
+ o.SubTrigger,
15
+ {
16
+ ref: u,
17
+ className: d(p, "data-[state=open]:bg-background-muted", t && "pl-8", e),
18
+ ...a,
19
+ children: [
20
+ n,
21
+ /* @__PURE__ */ r(f, { className: "ml-auto size-4 text-foreground-subtle", "aria-hidden": !0 })
22
+ ]
23
+ }
24
+ );
25
+ });
26
+ b.displayName = "DropdownMenuSubTrigger";
27
+ const w = i(function({ className: e, ...t }, n) {
28
+ return /* @__PURE__ */ r(
29
+ o.SubContent,
30
+ {
31
+ ref: n,
32
+ className: d(
33
+ "z-[var(--z-popover)] min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md",
34
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
35
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
36
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
37
+ e
38
+ ),
39
+ ...t
40
+ }
41
+ );
42
+ });
43
+ w.displayName = "DropdownMenuSubContent";
44
+ const h = i(function({ className: e, sideOffset: t = 4, align: n = "end", ...a }, u) {
45
+ return /* @__PURE__ */ r(g, { children: /* @__PURE__ */ r(
46
+ o.Content,
47
+ {
48
+ ref: u,
49
+ sideOffset: t,
50
+ align: n,
51
+ className: d(
52
+ "z-[var(--z-popover)] min-w-[12rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md",
53
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
54
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
55
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
56
+ "data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1",
57
+ e
58
+ ),
59
+ ...a
60
+ }
61
+ ) });
62
+ });
63
+ h.displayName = "DropdownMenuContent";
64
+ const D = i(function({ className: e, inset: t, destructive: n, ...a }, u) {
65
+ return /* @__PURE__ */ r(
66
+ o.Item,
67
+ {
68
+ ref: u,
69
+ className: d(
70
+ p,
71
+ t && "pl-8",
72
+ n && "text-danger-text data-[highlighted]:bg-danger-soft data-[highlighted]:text-danger-text",
73
+ e
74
+ ),
75
+ ...a
76
+ }
77
+ );
78
+ });
79
+ D.displayName = "DropdownMenuItem";
80
+ const M = i(function({ className: e, children: t, checked: n, ...a }, u) {
81
+ return /* @__PURE__ */ c(
82
+ o.CheckboxItem,
83
+ {
84
+ ref: u,
85
+ checked: n,
86
+ className: d(p, "pl-8", e),
87
+ ...a,
88
+ children: [
89
+ /* @__PURE__ */ r("span", { className: "absolute left-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ r(o.ItemIndicator, { children: /* @__PURE__ */ r(l, { className: "size-4 text-accent", "aria-hidden": !0 }) }) }),
90
+ t
91
+ ]
92
+ }
93
+ );
94
+ });
95
+ M.displayName = "DropdownMenuCheckboxItem";
96
+ const x = i(function({ className: e, children: t, ...n }, a) {
97
+ return /* @__PURE__ */ c(
98
+ o.RadioItem,
99
+ {
100
+ ref: a,
101
+ className: d(p, "pl-8", e),
102
+ ...n,
103
+ children: [
104
+ /* @__PURE__ */ r("span", { className: "absolute left-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ r(o.ItemIndicator, { children: /* @__PURE__ */ r(m, { className: "size-2 fill-accent text-accent", "aria-hidden": !0 }) }) }),
105
+ t
106
+ ]
107
+ }
108
+ );
109
+ });
110
+ x.displayName = "DropdownMenuRadioItem";
111
+ const N = i(function({ className: e, inset: t, ...n }, a) {
112
+ return /* @__PURE__ */ r(
113
+ o.Label,
114
+ {
115
+ ref: a,
116
+ className: d(
117
+ "px-2 py-1.5 text-xs font-medium text-foreground-subtle",
118
+ t && "pl-8",
119
+ e
120
+ ),
121
+ ...n
122
+ }
123
+ );
124
+ });
125
+ N.displayName = "DropdownMenuLabel";
126
+ const C = i(function({ className: e, ...t }, n) {
127
+ return /* @__PURE__ */ r(
128
+ o.Separator,
129
+ {
130
+ ref: n,
131
+ className: d("-mx-1 my-1 h-px bg-border", e),
132
+ ...t
133
+ }
134
+ );
135
+ });
136
+ C.displayName = "DropdownMenuSeparator";
137
+ function I({ className: s, ...e }) {
138
+ return /* @__PURE__ */ r(
139
+ "span",
140
+ {
141
+ className: d("ml-auto text-xs tracking-widest text-foreground-subtle font-mono", s),
142
+ ...e
143
+ }
144
+ );
145
+ }
146
+ I.displayName = "DropdownMenuShortcut";
147
+ export {
148
+ R as DropdownMenu,
149
+ M as DropdownMenuCheckboxItem,
150
+ h as DropdownMenuContent,
151
+ T as DropdownMenuGroup,
152
+ D as DropdownMenuItem,
153
+ N as DropdownMenuLabel,
154
+ g as DropdownMenuPortal,
155
+ G as DropdownMenuRadioGroup,
156
+ x as DropdownMenuRadioItem,
157
+ C as DropdownMenuSeparator,
158
+ I as DropdownMenuShortcut,
159
+ j as DropdownMenuSub,
160
+ w as DropdownMenuSubContent,
161
+ b as DropdownMenuSubTrigger,
162
+ k as DropdownMenuTrigger
163
+ };
164
+ //# sourceMappingURL=DropdownMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DropdownMenu.js","sources":["../../../src/components/ui/DropdownMenu.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ElementRef,\n type HTMLAttributes,\n} from 'react';\nimport * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';\nimport { Check, ChevronRight, Circle } from '@/icons';\nimport { cn } from '@/lib/utils';\n\n/* -----------------------------------------------------------------------------\n * Full dropdown menu surface with submenu support, separators, checkbox /\n * radio items, and keyboard shortcut labels (Kbd).\n * --------------------------------------------------------------------------- */\n\nexport const DropdownMenu = DropdownMenuPrimitive.Root;\nexport const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;\nexport const DropdownMenuGroup = DropdownMenuPrimitive.Group;\nexport const DropdownMenuPortal = DropdownMenuPrimitive.Portal;\nexport const DropdownMenuSub = DropdownMenuPrimitive.Sub;\nexport const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;\n\nconst itemClasses = cn(\n 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground outline-none',\n 'data-[highlighted]:bg-background-muted data-[highlighted]:text-foreground',\n 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',\n 'transition-colors duration-[var(--duration-fast)]',\n);\n\nexport const DropdownMenuSubTrigger = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean;\n }\n>(function DropdownMenuSubTrigger({ className, inset, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n ref={ref}\n className={cn(itemClasses, 'data-[state=open]:bg-background-muted', inset && 'pl-8', className)}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto size-4 text-foreground-subtle\" aria-hidden />\n </DropdownMenuPrimitive.SubTrigger>\n );\n});\nDropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger';\n\nexport const DropdownMenuSubContent = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.SubContent>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>\n>(function DropdownMenuSubContent({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.SubContent\n ref={ref}\n className={cn(\n 'z-[var(--z-popover)] min-w-[8rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n className,\n )}\n {...props}\n />\n );\n});\nDropdownMenuSubContent.displayName = 'DropdownMenuSubContent';\n\nexport const DropdownMenuContent = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.Content>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>\n>(function DropdownMenuContent({ className, sideOffset = 4, align = 'end', ...props }, ref) {\n return (\n <DropdownMenuPortal>\n <DropdownMenuPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n align={align}\n className={cn(\n 'z-[var(--z-popover)] min-w-[12rem] overflow-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n 'data-[side=bottom]:slide-in-from-top-1 data-[side=top]:slide-in-from-bottom-1',\n className,\n )}\n {...props}\n />\n </DropdownMenuPortal>\n );\n});\nDropdownMenuContent.displayName = 'DropdownMenuContent';\n\nexport const DropdownMenuItem = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.Item>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean;\n destructive?: boolean;\n }\n>(function DropdownMenuItem({ className, inset, destructive, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Item\n ref={ref}\n className={cn(\n itemClasses,\n inset && 'pl-8',\n destructive &&\n 'text-danger-text data-[highlighted]:bg-danger-soft data-[highlighted]:text-danger-text',\n className,\n )}\n {...props}\n />\n );\n});\nDropdownMenuItem.displayName = 'DropdownMenuItem';\n\nexport const DropdownMenuCheckboxItem = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>\n>(function DropdownMenuCheckboxItem({ className, children, checked, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n ref={ref}\n checked={checked}\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Check className=\"size-4 text-accent\" aria-hidden />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n );\n});\nDropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem';\n\nexport const DropdownMenuRadioItem = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.RadioItem>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>\n>(function DropdownMenuRadioItem({ className, children, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.RadioItem\n ref={ref}\n className={cn(itemClasses, 'pl-8', className)}\n {...props}\n >\n <span className=\"absolute left-2 flex size-4 items-center justify-center\">\n <DropdownMenuPrimitive.ItemIndicator>\n <Circle className=\"size-2 fill-accent text-accent\" aria-hidden />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n );\n});\nDropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';\n\nexport const DropdownMenuLabel = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.Label>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean;\n }\n>(function DropdownMenuLabel({ className, inset, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Label\n ref={ref}\n className={cn(\n 'px-2 py-1.5 text-xs font-medium text-foreground-subtle',\n inset && 'pl-8',\n className,\n )}\n {...props}\n />\n );\n});\nDropdownMenuLabel.displayName = 'DropdownMenuLabel';\n\nexport const DropdownMenuSeparator = forwardRef<\n ElementRef<typeof DropdownMenuPrimitive.Separator>,\n ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>\n>(function DropdownMenuSeparator({ className, ...props }, ref) {\n return (\n <DropdownMenuPrimitive.Separator\n ref={ref}\n className={cn('-mx-1 my-1 h-px bg-border', className)}\n {...props}\n />\n );\n});\nDropdownMenuSeparator.displayName = 'DropdownMenuSeparator';\n\n/**\n * Trailing right-aligned shortcut label inside a menu item. Pair with `<Kbd>`\n * inside the Kbd component file if you want stylised keys.\n */\nexport function DropdownMenuShortcut({ className, ...props }: HTMLAttributes<HTMLSpanElement>) {\n return (\n <span\n className={cn('ml-auto text-xs tracking-widest text-foreground-subtle font-mono', className)}\n {...props}\n />\n );\n}\nDropdownMenuShortcut.displayName = 'DropdownMenuShortcut';\n\n/**\n * @example\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><IconButton aria-label=\"More\" icon={<MoreHorizontal />} /></DropdownMenuTrigger>\n * <DropdownMenuContent>\n * <DropdownMenuItem>Open<DropdownMenuShortcut>⌘O</DropdownMenuShortcut></DropdownMenuItem>\n * <DropdownMenuItem>Rename</DropdownMenuItem>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem destructive>Delete</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n *\n * @do Group destructive items at the bottom with a separator.\n * @dont Mix link navigation and action items in the same menu — split into\n * two menus or two groups with labels.\n */\n"],"names":["DropdownMenu","DropdownMenuPrimitive","DropdownMenuTrigger","DropdownMenuGroup","DropdownMenuPortal","DropdownMenuSub","DropdownMenuRadioGroup","itemClasses","cn","DropdownMenuSubTrigger","forwardRef","className","inset","children","props","ref","jsxs","jsx","ChevronRight","DropdownMenuSubContent","DropdownMenuContent","sideOffset","align","DropdownMenuItem","destructive","DropdownMenuCheckboxItem","checked","Check","DropdownMenuRadioItem","Circle","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuShortcut"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsxs as a, jsx as e } from "react/jsx-runtime";
3
+ import { forwardRef as c } from "react";
4
+ import { cn as f } from "../../lib/utils.js";
5
+ import { InboxEmpty as u } from "../../illustrations/index.js";
6
+ const x = c(function({ icon: t, illustration: r, title: l, description: d, action: m, secondaryAction: s, className: n, ...o }, i) {
7
+ return /* @__PURE__ */ a(
8
+ "div",
9
+ {
10
+ ref: i,
11
+ className: f(
12
+ "flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-background-subtle p-10 text-center",
13
+ n
14
+ ),
15
+ ...o,
16
+ children: [
17
+ r || (t ? /* @__PURE__ */ e("div", { className: "inline-flex size-12 items-center justify-center rounded-full bg-background-muted text-foreground-muted [&_svg]:size-6", children: t }) : /* @__PURE__ */ e(u, { className: "size-24" })),
18
+ /* @__PURE__ */ e("h3", { className: "text-base font-semibold text-foreground leading-tight", children: l }),
19
+ d && /* @__PURE__ */ e("p", { className: "max-w-md text-sm text-foreground-muted leading-relaxed", children: d }),
20
+ (m || s) && /* @__PURE__ */ a("div", { className: "mt-2 flex items-center gap-2", children: [
21
+ m,
22
+ s
23
+ ] })
24
+ ]
25
+ }
26
+ );
27
+ });
28
+ x.displayName = "EmptyState";
29
+ export {
30
+ x as EmptyState
31
+ };
32
+ //# sourceMappingURL=EmptyState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EmptyState.js","sources":["../../../src/components/ui/EmptyState.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cn } from '@/lib/utils';\nimport { InboxEmpty } from '@/illustrations';\n\nexport interface EmptyStateProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /**\n * Small Lucide-sized icon rendered inside a 48px circular container.\n * Use for compact empty states (table cells, sidebar panes, cards).\n */\n icon?: ReactNode;\n /**\n * Full-size illustration rendered without the icon container. Takes\n * precedence over `icon`. If neither is set, the default `InboxEmpty`\n * line illustration is used.\n */\n illustration?: ReactNode;\n /** Heading. One short sentence. */\n title: ReactNode;\n /** Description. One or two sentences max. */\n description?: ReactNode;\n /** Primary action — usually a `<Button>` that creates the missing item. */\n action?: ReactNode;\n /** Secondary helper link — \"Learn more\", \"Import existing\", etc. */\n secondaryAction?: ReactNode;\n}\n\n/**\n * Shown when a list / dataset / surface has no content yet. Tone is helpful,\n * never apologetic.\n *\n * @example Default — uses the built-in InboxEmpty illustration\n * <EmptyState\n * title=\"No projects yet\"\n * description=\"Create a project to start tracking work.\"\n * action={<Button>New project</Button>}\n * />\n *\n * @example Compact — small Lucide icon for dense layouts\n * <EmptyState\n * icon={<Folder className=\"size-6\" />}\n * title=\"No items\"\n * />\n *\n * @example Custom illustration\n * <EmptyState\n * illustration={<Illustrations.NoSearchResults className=\"size-32\" />}\n * title=\"No results\"\n * />\n */\nexport const EmptyState = forwardRef<HTMLDivElement, EmptyStateProps>(function EmptyState(\n { icon, illustration, title, description, action, secondaryAction, className, ...props },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col items-center justify-center gap-3 rounded-lg border border-dashed border-border bg-background-subtle p-10 text-center',\n className,\n )}\n {...props}\n >\n {/* Visual: illustration > icon > default illustration */}\n {illustration ? (\n illustration\n ) : icon ? (\n <div className=\"inline-flex size-12 items-center justify-center rounded-full bg-background-muted text-foreground-muted [&_svg]:size-6\">\n {icon}\n </div>\n ) : (\n <InboxEmpty className=\"size-24\" />\n )}\n <h3 className=\"text-base font-semibold text-foreground leading-tight\">{title}</h3>\n {description && (\n <p className=\"max-w-md text-sm text-foreground-muted leading-relaxed\">{description}</p>\n )}\n {(action || secondaryAction) && (\n <div className=\"mt-2 flex items-center gap-2\">\n {action}\n {secondaryAction}\n </div>\n )}\n </div>\n );\n});\nEmptyState.displayName = 'EmptyState';\n"],"names":["EmptyState","forwardRef","icon","illustration","title","description","action","secondaryAction","className","props","ref","jsxs","cn","jsx","InboxEmpty"],"mappings":";;;;;AAmDO,MAAMA,IAAaC,EAA4C,SACpE,EAAE,MAAAC,GAAM,cAAAC,GAAc,OAAAC,GAAO,aAAAC,GAAa,QAAAC,GAAQ,iBAAAC,GAAiB,WAAAC,GAAW,GAAGC,EAAA,GACjFC,GACA;AACA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,WAAWE;AAAA,QACT;AAAA,QACAJ;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,MAGH,UAAA;AAAA,QAAAN,MAEGD,IACF,gBAAAW,EAAC,OAAA,EAAI,WAAU,yHACZ,UAAAX,EAAA,CACH,IAEA,gBAAAW,EAACC,GAAA,EAAW,WAAU,WAAU;AAAA,QAElC,gBAAAD,EAAC,MAAA,EAAG,WAAU,yDAAyD,UAAAT,GAAM;AAAA,QAC5EC,KACC,gBAAAQ,EAAC,KAAA,EAAE,WAAU,0DAA0D,UAAAR,GAAY;AAAA,SAEnFC,KAAUC,MACV,gBAAAI,EAAC,OAAA,EAAI,WAAU,gCACZ,UAAA;AAAA,UAAAL;AAAA,UACAC;AAAA,QAAA,EAAA,CACH;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AACDP,EAAW,cAAc;"}
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsxs as u, jsx as r } from "react/jsx-runtime";
3
+ import { forwardRef as g } from "react";
4
+ import { cn as f } from "../../lib/utils.js";
5
+ import { useStrings as x } from "../../hooks/use-strings.js";
6
+ import { ConnectionLost as S, ServerError as N, NotFound as b } from "../../illustrations/index.js";
7
+ import { Button as h } from "./Button.js";
8
+ const v = (e) => ({
9
+ 404: {
10
+ illustration: /* @__PURE__ */ r(b, { className: "size-32" }),
11
+ title: e.errorState.notFoundTitle,
12
+ description: e.errorState.notFoundDescription
13
+ },
14
+ 500: {
15
+ illustration: /* @__PURE__ */ r(N, { className: "size-32" }),
16
+ title: e.errorState.serverTitle,
17
+ description: e.errorState.serverDescription
18
+ },
19
+ generic: {
20
+ illustration: /* @__PURE__ */ r(S, { className: "size-32" }),
21
+ title: e.errorState.genericTitle,
22
+ description: e.errorState.genericDescription
23
+ }
24
+ }), E = g(function({ variant: s = "generic", title: a, description: l, illustration: c, action: i, onRetry: o, className: d, ...m }, p) {
25
+ const n = x(), t = v(n)[s];
26
+ return /* @__PURE__ */ u(
27
+ "div",
28
+ {
29
+ ref: p,
30
+ className: f(
31
+ "flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-background-subtle p-10 text-center",
32
+ d
33
+ ),
34
+ ...m,
35
+ children: [
36
+ c ?? t.illustration,
37
+ /* @__PURE__ */ r("h3", { className: "text-base font-semibold text-foreground leading-tight", children: a ?? t.title }),
38
+ /* @__PURE__ */ r("p", { className: "max-w-md text-sm text-foreground-muted leading-relaxed", children: l ?? t.description }),
39
+ (i || o) && /* @__PURE__ */ r("div", { className: "mt-2 flex items-center gap-2", children: i ?? /* @__PURE__ */ r(h, { onClick: o, variant: "outline", children: n.errorState.tryAgain }) })
40
+ ]
41
+ }
42
+ );
43
+ });
44
+ E.displayName = "ErrorState";
45
+ export {
46
+ E as ErrorState
47
+ };
48
+ //# sourceMappingURL=ErrorState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorState.js","sources":["../../../src/components/ui/ErrorState.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport type { UiStrings } from '@/lib/strings';\nimport { NotFound, ServerError, ConnectionLost } from '@/illustrations';\nimport { Button } from './Button';\n\nexport interface ErrorStateProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** `404` not-found, `500` server, or `generic` (catch-all). */\n variant?: '404' | '500' | 'generic';\n /** Override the default title. */\n title?: ReactNode;\n /** Override the default description. */\n description?: ReactNode;\n /**\n * Override the variant's default illustration. Pass any ReactNode (e.g.\n * `<Illustrations.Construction className=\"size-32\" />`).\n */\n illustration?: ReactNode;\n /** Custom action node. Replaces the default retry button. */\n action?: ReactNode;\n /** When provided, renders a default \"Try again\" button calling this handler. */\n onRetry?: () => void;\n}\n\nconst presets = (s: UiStrings) => ({\n '404': {\n illustration: <NotFound className=\"size-32\" />,\n title: s.errorState.notFoundTitle,\n description: s.errorState.notFoundDescription,\n },\n '500': {\n illustration: <ServerError className=\"size-32\" />,\n title: s.errorState.serverTitle,\n description: s.errorState.serverDescription,\n },\n generic: {\n illustration: <ConnectionLost className=\"size-32\" />,\n title: s.errorState.genericTitle,\n description: s.errorState.genericDescription,\n },\n});\n\n/**\n * Page-level error placeholder. Pair with `onRetry` for transient failures.\n *\n * @example 404 (uses built-in line illustration)\n * <ErrorState variant=\"404\" />\n *\n * @example 500 with retry\n * <ErrorState variant=\"500\" onRetry={refetch} />\n *\n * @example Custom\n * <ErrorState\n * title=\"Quota exceeded\"\n * description=\"Your plan allows 1,000 events/day.\"\n * illustration={<Illustrations.Construction className=\"size-32\" />}\n * action={<Button>Upgrade plan</Button>}\n * />\n *\n * @do Match the tone to the cause — server errors apologise, user errors\n * explain. Always offer a next step.\n * @dont Show a raw stack trace to end users.\n */\nexport const ErrorState = forwardRef<HTMLDivElement, ErrorStateProps>(function ErrorState(\n { variant = 'generic', title, description, illustration, action, onRetry, className, ...props },\n ref,\n) {\n const strings = useStrings();\n const preset = presets(strings)[variant];\n return (\n <div\n ref={ref}\n className={cn(\n 'flex flex-col items-center justify-center gap-3 rounded-lg border border-border bg-background-subtle p-10 text-center',\n className,\n )}\n {...props}\n >\n {illustration ?? preset.illustration}\n <h3 className=\"text-base font-semibold text-foreground leading-tight\">\n {title ?? preset.title}\n </h3>\n <p className=\"max-w-md text-sm text-foreground-muted leading-relaxed\">\n {description ?? preset.description}\n </p>\n {(action || onRetry) && (\n <div className=\"mt-2 flex items-center gap-2\">\n {action ?? (\n <Button onClick={onRetry} variant=\"outline\">\n {strings.errorState.tryAgain}\n </Button>\n )}\n </div>\n )}\n </div>\n );\n});\nErrorState.displayName = 'ErrorState';\n"],"names":["presets","s","jsx","NotFound","ServerError","ConnectionLost","ErrorState","forwardRef","variant","title","description","illustration","action","onRetry","className","props","ref","strings","useStrings","preset","jsxs","cn","Button"],"mappings":";;;;;;;AA2BA,MAAMA,IAAU,CAACC,OAAkB;AAAA,EACjC,KAAO;AAAA,IACL,cAAc,gBAAAC,EAACC,GAAA,EAAS,WAAU,UAAA,CAAU;AAAA,IAC5C,OAAOF,EAAE,WAAW;AAAA,IACpB,aAAaA,EAAE,WAAW;AAAA,EAAA;AAAA,EAE5B,KAAO;AAAA,IACL,cAAc,gBAAAC,EAACE,GAAA,EAAY,WAAU,UAAA,CAAU;AAAA,IAC/C,OAAOH,EAAE,WAAW;AAAA,IACpB,aAAaA,EAAE,WAAW;AAAA,EAAA;AAAA,EAE5B,SAAS;AAAA,IACP,cAAc,gBAAAC,EAACG,GAAA,EAAe,WAAU,UAAA,CAAU;AAAA,IAClD,OAAOJ,EAAE,WAAW;AAAA,IACpB,aAAaA,EAAE,WAAW;AAAA,EAAA;AAE9B,IAuBaK,IAAaC,EAA4C,SACpE,EAAE,SAAAC,IAAU,WAAW,OAAAC,GAAO,aAAAC,GAAa,cAAAC,GAAc,QAAAC,GAAQ,SAAAC,GAAS,WAAAC,GAAW,GAAGC,EAAA,GACxFC,GACA;AACA,QAAMC,IAAUC,EAAA,GACVC,IAASnB,EAAQiB,CAAO,EAAET,CAAO;AACvC,SACE,gBAAAY;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAJ;AAAA,MACA,WAAWK;AAAA,QACT;AAAA,QACAP;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,MAEH,UAAA;AAAA,QAAAJ,KAAgBQ,EAAO;AAAA,0BACvB,MAAA,EAAG,WAAU,yDACX,UAAAV,KAASU,EAAO,OACnB;AAAA,0BACC,KAAA,EAAE,WAAU,0DACV,UAAAT,KAAeS,EAAO,aACzB;AAAA,SACEP,KAAUC,MACV,gBAAAX,EAAC,OAAA,EAAI,WAAU,gCACZ,UAAAU,KACC,gBAAAV,EAACoB,GAAA,EAAO,SAAST,GAAS,SAAQ,WAC/B,UAAAI,EAAQ,WAAW,UACtB,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AACDX,EAAW,cAAc;"}
@@ -1,4 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
+ export type FileRejectReason = 'size' | 'type';
2
3
  export interface FileUploadProps {
3
4
  /** Accepted file types, e.g. `image/*` or `.pdf,.docx`. */
4
5
  accept?: string;
@@ -10,6 +11,8 @@ export interface FileUploadProps {
10
11
  value?: File[];
11
12
  /** Called when files are added or removed. */
12
13
  onChange?: (files: File[]) => void;
14
+ /** Called with the files that were dropped because of `maxSize` or `accept`. */
15
+ onReject?: (files: File[], reason: FileRejectReason) => void;
13
16
  /** Helper text inside the drop zone. */
14
17
  hint?: ReactNode;
15
18
  /** Disable the picker entirely. */
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsxs as m, jsx as t } from "react/jsx-runtime";
3
+ import { forwardRef as A, useRef as O, useId as S, useState as y, useCallback as F } from "react";
4
+ import { Upload as T, File as C, X as K } from "../../icons/index.js";
5
+ import { cn as k } from "../../lib/utils.js";
6
+ import { useStrings as M } from "../../hooks/use-strings.js";
7
+ import { formatString as X } from "../../lib/strings.js";
8
+ function _(s) {
9
+ return s < 1024 ? `${s} B` : s < 1024 * 1024 ? `${(s / 1024).toFixed(1)} KB` : `${(s / 1024 / 1024).toFixed(1)} MB`;
10
+ }
11
+ function q(s, i) {
12
+ if (!i) return !0;
13
+ const a = i.split(",").map((r) => r.trim().toLowerCase()).filter(Boolean);
14
+ if (!a.length) return !0;
15
+ const d = s.name.toLowerCase(), l = s.type.toLowerCase();
16
+ return a.some((r) => r.startsWith(".") ? d.endsWith(r) : r.endsWith("/*") ? l.startsWith(r.slice(0, -1)) : l === r);
17
+ }
18
+ const E = A(function({ accept: i, multiple: a, maxSize: d, value: l, onChange: r, onReject: n, hint: b, disabled: p, className: U }, z) {
19
+ const v = M(), D = O(null), N = S(), [L, $] = y([]), [B, g] = y(!1), f = l ?? L, h = F(
20
+ (e) => {
21
+ l === void 0 && $(e), r == null || r(e);
22
+ },
23
+ [r, l]
24
+ ), w = F(
25
+ (e) => {
26
+ const o = [], c = [], x = [];
27
+ for (const u of Array.from(e))
28
+ q(u, i) ? d && u.size > d ? c.push(u) : o.push(u) : x.push(u);
29
+ x.length && (n == null || n(x, "type")), c.length && (n == null || n(c, "size")), o.length && h(a ? [...f, ...o] : o.slice(0, 1));
30
+ },
31
+ [i, f, d, a, n, h]
32
+ ), W = (e) => h(f.filter((o, c) => c !== e)), I = (e) => {
33
+ e.preventDefault(), g(!1), !p && w(e.dataTransfer.files);
34
+ };
35
+ return /* @__PURE__ */ m("div", { ref: z, className: k("flex flex-col gap-3", U), children: [
36
+ /* @__PURE__ */ m(
37
+ "label",
38
+ {
39
+ htmlFor: N,
40
+ onDragOver: (e) => {
41
+ e.preventDefault(), p || g(!0);
42
+ },
43
+ onDragLeave: () => g(!1),
44
+ onDrop: I,
45
+ className: k(
46
+ "group relative flex cursor-pointer flex-col items-center justify-center gap-2",
47
+ "border-border bg-background-subtle rounded-md border border-dashed px-6 py-8",
48
+ "text-center transition-colors",
49
+ "hover:border-border-strong hover:bg-background-muted",
50
+ "focus-within:ring-ring focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2",
51
+ B && "border-accent bg-accent-soft",
52
+ p && "pointer-events-none opacity-50"
53
+ ),
54
+ children: [
55
+ /* @__PURE__ */ t(T, { className: "text-foreground-muted size-5", "aria-hidden": !0 }),
56
+ /* @__PURE__ */ m("div", { className: "space-y-1", children: [
57
+ /* @__PURE__ */ t("p", { className: "text-sm font-medium", children: v.fileUpload.drop }),
58
+ b && /* @__PURE__ */ t("p", { className: "text-foreground-muted text-xs", children: b })
59
+ ] }),
60
+ /* @__PURE__ */ t(
61
+ "input",
62
+ {
63
+ ref: D,
64
+ id: N,
65
+ type: "file",
66
+ accept: i,
67
+ multiple: a,
68
+ disabled: p,
69
+ className: "sr-only",
70
+ onChange: (e) => {
71
+ e.target.files && w(e.target.files), e.target.value = "";
72
+ }
73
+ }
74
+ )
75
+ ]
76
+ }
77
+ ),
78
+ f.length > 0 && /* @__PURE__ */ t("ul", { className: "flex flex-col gap-2", children: f.map((e, o) => /* @__PURE__ */ m(
79
+ "li",
80
+ {
81
+ className: "border-border bg-card flex items-center gap-3 rounded-md border px-3 py-2",
82
+ children: [
83
+ /* @__PURE__ */ t(C, { className: "text-foreground-muted size-4 shrink-0", "aria-hidden": !0 }),
84
+ /* @__PURE__ */ m("div", { className: "min-w-0 flex-1", children: [
85
+ /* @__PURE__ */ t("p", { className: "truncate text-sm", title: e.name, children: e.name }),
86
+ /* @__PURE__ */ t("p", { className: "text-foreground-muted text-xs", children: _(e.size) })
87
+ ] }),
88
+ /* @__PURE__ */ t(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => W(o),
93
+ className: "text-foreground-muted hover:bg-background-muted hover:text-foreground focus-visible:ring-ring rounded-sm p-1 outline-none focus-visible:ring-2",
94
+ "aria-label": X(v.fileUpload.remove, { name: e.name }),
95
+ children: /* @__PURE__ */ t(K, { className: "size-4", "aria-hidden": !0 })
96
+ }
97
+ )
98
+ ]
99
+ },
100
+ `${e.name}-${o}`
101
+ )) })
102
+ ] });
103
+ });
104
+ E.displayName = "FileUpload";
105
+ export {
106
+ E as FileUpload
107
+ };
108
+ //# sourceMappingURL=FileUpload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileUpload.js","sources":["../../../src/components/ui/FileUpload.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useId,\n useRef,\n useState,\n type DragEvent as ReactDragEvent,\n type ReactNode,\n} from 'react';\nimport { File as FileIcon, Upload, X } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { formatString } from '@/lib/strings';\n\nexport type FileRejectReason = 'size' | 'type';\n\nexport interface FileUploadProps {\n /** Accepted file types, e.g. `image/*` or `.pdf,.docx`. */\n accept?: string;\n /** Allow multiple files. */\n multiple?: boolean;\n /** Max file size in bytes. Files larger than this are rejected. */\n maxSize?: number;\n /** Controlled file list. */\n value?: File[];\n /** Called when files are added or removed. */\n onChange?: (files: File[]) => void;\n /** Called with the files that were dropped because of `maxSize` or `accept`. */\n onReject?: (files: File[], reason: FileRejectReason) => void;\n /** Helper text inside the drop zone. */\n hint?: ReactNode;\n /** Disable the picker entirely. */\n disabled?: boolean;\n className?: string;\n}\n\nfunction formatSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\n/** Mirror the browser's `accept` matching for dropped files (which skip the picker filter). */\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const rules = accept\n .split(',')\n .map((r) => r.trim().toLowerCase())\n .filter(Boolean);\n if (!rules.length) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return rules.some((rule) => {\n if (rule.startsWith('.')) return name.endsWith(rule);\n if (rule.endsWith('/*')) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n\n/**\n * Drag-and-drop file picker with an inline file list. Uncontrolled by default;\n * pass `value` + `onChange` to control externally.\n */\nexport const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(function FileUpload(\n { accept, multiple, maxSize, value, onChange, onReject, hint, disabled, className },\n ref,\n) {\n const strings = useStrings();\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const [internal, setInternal] = useState<File[]>([]);\n const [over, setOver] = useState(false);\n const files = value ?? internal;\n\n const update = useCallback(\n (next: File[]) => {\n if (value === undefined) setInternal(next);\n onChange?.(next);\n },\n [onChange, value],\n );\n\n const addFiles = useCallback(\n (incoming: FileList | File[]) => {\n const accepted: File[] = [];\n const tooLarge: File[] = [];\n const wrongType: File[] = [];\n for (const f of Array.from(incoming)) {\n if (!matchesAccept(f, accept)) wrongType.push(f);\n else if (maxSize && f.size > maxSize) tooLarge.push(f);\n else accepted.push(f);\n }\n if (wrongType.length) onReject?.(wrongType, 'type');\n if (tooLarge.length) onReject?.(tooLarge, 'size');\n if (!accepted.length) return;\n update(multiple ? [...files, ...accepted] : accepted.slice(0, 1));\n },\n [accept, files, maxSize, multiple, onReject, update],\n );\n\n const remove = (idx: number) => update(files.filter((_, i) => i !== idx));\n\n const onDrop = (e: ReactDragEvent<HTMLLabelElement>) => {\n e.preventDefault();\n setOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n };\n\n return (\n <div ref={ref} className={cn('flex flex-col gap-3', className)}>\n <label\n htmlFor={inputId}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={onDrop}\n className={cn(\n 'group relative flex cursor-pointer flex-col items-center justify-center gap-2',\n 'border-border bg-background-subtle rounded-md border border-dashed px-6 py-8',\n 'text-center transition-colors',\n 'hover:border-border-strong hover:bg-background-muted',\n 'focus-within:ring-ring focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2',\n over && 'border-accent bg-accent-soft',\n disabled && 'pointer-events-none opacity-50',\n )}\n >\n <Upload className=\"text-foreground-muted size-5\" aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"text-sm font-medium\">{strings.fileUpload.drop}</p>\n {hint && <p className=\"text-foreground-muted text-xs\">{hint}</p>}\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className=\"sr-only\"\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n // Allow re-selecting the same file after removing it.\n e.target.value = '';\n }}\n />\n </label>\n\n {files.length > 0 && (\n <ul className=\"flex flex-col gap-2\">\n {files.map((file, idx) => (\n <li\n key={`${file.name}-${idx}`}\n className=\"border-border bg-card flex items-center gap-3 rounded-md border px-3 py-2\"\n >\n <FileIcon className=\"text-foreground-muted size-4 shrink-0\" aria-hidden />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm\" title={file.name}>\n {file.name}\n </p>\n <p className=\"text-foreground-muted text-xs\">{formatSize(file.size)}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => remove(idx)}\n className=\"text-foreground-muted hover:bg-background-muted hover:text-foreground focus-visible:ring-ring rounded-sm p-1 outline-none focus-visible:ring-2\"\n aria-label={formatString(strings.fileUpload.remove, { name: file.name })}\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n});\nFileUpload.displayName = 'FileUpload';\n"],"names":["formatSize","bytes","matchesAccept","file","accept","rules","name","type","rule","FileUpload","forwardRef","multiple","maxSize","value","onChange","onReject","hint","disabled","className","ref","strings","useStrings","inputRef","useRef","inputId","useId","internal","setInternal","useState","over","setOver","files","update","useCallback","next","addFiles","incoming","accepted","tooLarge","wrongType","f","remove","idx","_","i","onDrop","cn","jsxs","jsx","Upload","FileIcon","formatString","X"],"mappings":"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"}
@@ -14,6 +14,7 @@ export declare function useFormField(): {
14
14
  formItemId: string;
15
15
  formDescriptionId: string;
16
16
  formMessageId: string;
17
+ hasDescription: boolean;
17
18
  };
18
19
  export declare const FormItem: import('react').ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & import('react').RefAttributes<HTMLDivElement>>;
19
20
  export declare const FormLabel: import('react').ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & import('react').RefAttributes<HTMLLabelElement>, "ref"> & import('react').RefAttributes<HTMLLabelElement>>;
@@ -0,0 +1,105 @@
1
+ "use client";
2
+ import { jsx as m } from "react/jsx-runtime";
3
+ import { forwardRef as a, useContext as f, useEffect as I, useId as g, useState as b, createContext as F } from "react";
4
+ import * as h from "@radix-ui/react-label";
5
+ import { FormProvider as v, Controller as D, useFormContext as N } from "react-hook-form";
6
+ import { Slot as y } from "@radix-ui/react-slot";
7
+ import { cn as c } from "../../lib/utils.js";
8
+ const k = v, p = F(null);
9
+ function q(n) {
10
+ return /* @__PURE__ */ m(p.Provider, { value: { name: n.name }, children: /* @__PURE__ */ m(D, { ...n }) });
11
+ }
12
+ const u = F(null);
13
+ function d() {
14
+ const n = f(p), o = f(u), { getFieldState: s, formState: e } = N();
15
+ if (!n)
16
+ throw new Error("useFormField must be used inside <FormField>");
17
+ const i = s(n.name, e), r = (o == null ? void 0 : o.id) ?? "";
18
+ return {
19
+ id: r,
20
+ name: n.name,
21
+ formItemId: `${r}-item`,
22
+ formDescriptionId: `${r}-desc`,
23
+ formMessageId: `${r}-error`,
24
+ hasDescription: (o == null ? void 0 : o.hasDescription) ?? !1,
25
+ ...i
26
+ };
27
+ }
28
+ const C = a(
29
+ function({ className: o, ...s }, e) {
30
+ const i = g(), [r, t] = b(!1);
31
+ return /* @__PURE__ */ m(u.Provider, { value: { id: i, hasDescription: r, setHasDescription: t }, children: /* @__PURE__ */ m("div", { ref: e, className: c("flex flex-col gap-1.5", o), ...s }) });
32
+ }
33
+ );
34
+ C.displayName = "FormItem";
35
+ const P = a(function({ className: o, ...s }, e) {
36
+ const { formItemId: i, error: r } = d();
37
+ return /* @__PURE__ */ m(
38
+ h.Root,
39
+ {
40
+ ref: e,
41
+ htmlFor: i,
42
+ className: c("text-foreground text-sm font-medium", r && "text-danger-text", o),
43
+ ...s
44
+ }
45
+ );
46
+ });
47
+ P.displayName = "FormLabel";
48
+ const S = a(
49
+ function(o, s) {
50
+ const { error: e, formItemId: i, formDescriptionId: r, formMessageId: t, hasDescription: l } = d(), x = [l ? r : null, e ? t : null].filter(Boolean).join(" ") || void 0;
51
+ return /* @__PURE__ */ m(
52
+ y,
53
+ {
54
+ ref: s,
55
+ id: i,
56
+ "aria-describedby": x,
57
+ "aria-invalid": !!e,
58
+ ...e ? { tone: "error" } : {},
59
+ ...o
60
+ }
61
+ );
62
+ }
63
+ );
64
+ S.displayName = "FormControl";
65
+ const E = a(function({ className: o, ...s }, e) {
66
+ const { formDescriptionId: i } = d(), r = f(u), t = r == null ? void 0 : r.setHasDescription;
67
+ return I(() => (t == null || t(!0), () => t == null ? void 0 : t(!1)), [t]), /* @__PURE__ */ m(
68
+ "p",
69
+ {
70
+ ref: e,
71
+ id: i,
72
+ className: c("text-foreground-subtle text-xs", o),
73
+ ...s
74
+ }
75
+ );
76
+ });
77
+ E.displayName = "FormDescription";
78
+ const L = a(
79
+ function({ className: o, children: s, ...e }, i) {
80
+ const { error: r, formMessageId: t } = d(), l = r ? String((r == null ? void 0 : r.message) ?? "") : s;
81
+ return l ? /* @__PURE__ */ m(
82
+ "p",
83
+ {
84
+ ref: i,
85
+ id: t,
86
+ role: "alert",
87
+ className: c("text-danger-text text-xs", o),
88
+ ...e,
89
+ children: l
90
+ }
91
+ ) : null;
92
+ }
93
+ );
94
+ L.displayName = "FormError";
95
+ export {
96
+ k as Form,
97
+ S as FormControl,
98
+ E as FormDescription,
99
+ L as FormError,
100
+ q as FormField,
101
+ C as FormItem,
102
+ P as FormLabel,
103
+ d as useFormField
104
+ };
105
+ //# sourceMappingURL=Form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Form.js","sources":["../../../src/components/ui/Form.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useId,\n useState,\n type HTMLAttributes,\n} from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from 'react-hook-form';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn } from '@/lib/utils';\n\n/* -----------------------------------------------------------------------------\n * react-hook-form bindings + accessible label / description / error wiring.\n *\n * Usage:\n * const form = useForm<Values>(…);\n * <Form {...form}>\n * <FormField\n * control={form.control}\n * name=\"email\"\n * render={({ field }) => (\n * <FormItem>\n * <FormLabel>Email</FormLabel>\n * <FormControl><Input type=\"email\" {...field} /></FormControl>\n * <FormDescription>We never share this.</FormDescription>\n * <FormError />\n * </FormItem>\n * )}\n * />\n * </Form>\n * --------------------------------------------------------------------------- */\n\nexport const Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = createContext<FormFieldContextValue | null>(null);\n\nexport function FormField<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>(props: ControllerProps<TFieldValues, TName>) {\n return (\n <FormFieldContext.Provider value={{ name: props.name as string }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n}\n\ninterface FormItemContextValue {\n id: string;\n /** Whether a <FormDescription> is mounted — drives aria-describedby. */\n hasDescription: boolean;\n setHasDescription: (v: boolean) => void;\n}\nconst FormItemContext = createContext<FormItemContextValue | null>(null);\n\nexport function useFormField() {\n const fieldContext = useContext(FormFieldContext);\n const itemContext = useContext(FormItemContext);\n const { getFieldState, formState } = useFormContext();\n if (!fieldContext) {\n throw new Error('useFormField must be used inside <FormField>');\n }\n const fieldState = getFieldState(fieldContext.name, formState);\n const id = itemContext?.id ?? '';\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-item`,\n formDescriptionId: `${id}-desc`,\n formMessageId: `${id}-error`,\n hasDescription: itemContext?.hasDescription ?? false,\n ...fieldState,\n };\n}\n\nexport const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FormItem({ className, ...props }, ref) {\n const id = useId();\n const [hasDescription, setHasDescription] = useState(false);\n return (\n <FormItemContext.Provider value={{ id, hasDescription, setHasDescription }}>\n <div ref={ref} className={cn('flex flex-col gap-1.5', className)} {...props} />\n </FormItemContext.Provider>\n );\n },\n);\nFormItem.displayName = 'FormItem';\n\nexport const FormLabel = forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(function FormLabel({ className, ...props }, ref) {\n const { formItemId, error } = useFormField();\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={formItemId}\n className={cn('text-foreground text-sm font-medium', error && 'text-danger-text', className)}\n {...props}\n />\n );\n});\nFormLabel.displayName = 'FormLabel';\n\nexport const FormControl = forwardRef<HTMLElement, React.ComponentPropsWithoutRef<typeof Slot>>(\n function FormControl(props, ref) {\n const { error, formItemId, formDescriptionId, formMessageId, hasDescription } = useFormField();\n // Only reference ids that actually render: the description when mounted,\n // the message only while there is an error (FormError returns null otherwise).\n const describedBy =\n [hasDescription ? formDescriptionId : null, error ? formMessageId : null]\n .filter(Boolean)\n .join(' ') || undefined;\n // Input-style children read `tone=\"error\"` for the red border; plain\n // elements ignore the unknown prop via Slot merging only when they accept it,\n // so it is only set while there is an error.\n const toneProps = error ? { tone: 'error' as const } : {};\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={describedBy}\n aria-invalid={!!error}\n {...toneProps}\n {...props}\n />\n );\n },\n);\nFormControl.displayName = 'FormControl';\n\nexport const FormDescription = forwardRef<\n HTMLParagraphElement,\n HTMLAttributes<HTMLParagraphElement>\n>(function FormDescription({ className, ...props }, ref) {\n const { formDescriptionId } = useFormField();\n const itemContext = useContext(FormItemContext);\n const setHasDescription = itemContext?.setHasDescription;\n useEffect(() => {\n setHasDescription?.(true);\n return () => setHasDescription?.(false);\n }, [setHasDescription]);\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn('text-foreground-subtle text-xs', className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = 'FormDescription';\n\nexport const FormError = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n function FormError({ className, children, ...props }, ref) {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? '') : children;\n if (!body) return null;\n return (\n <p\n ref={ref}\n id={formMessageId}\n role=\"alert\"\n className={cn('text-danger-text text-xs', className)}\n {...props}\n >\n {body}\n </p>\n );\n },\n);\nFormError.displayName = 'FormError';\n"],"names":["Form","FormProvider","FormFieldContext","createContext","FormField","props","jsx","Controller","FormItemContext","useFormField","fieldContext","useContext","itemContext","getFieldState","formState","useFormContext","fieldState","id","FormItem","forwardRef","className","ref","useId","hasDescription","setHasDescription","useState","cn","FormLabel","formItemId","error","LabelPrimitive","FormControl","formDescriptionId","formMessageId","describedBy","Slot","FormDescription","useEffect","FormError","children","body"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx as n } from "react/jsx-runtime";
3
+ import { useMemo as a, Suspense as l, lazy as d } from "react";
4
+ import i from "lucide-react/dynamicIconImports";
5
+ const p = Object.keys(i), o = /* @__PURE__ */ new Map();
6
+ function u(e) {
7
+ const t = o.get(e);
8
+ if (t) return t;
9
+ const c = d(i[e]);
10
+ return o.set(e, c), c;
11
+ }
12
+ function y({ name: e, fallback: t, ...c }) {
13
+ const s = a(() => u(e), [e]), r = t ?? /* @__PURE__ */ n(
14
+ "span",
15
+ {
16
+ "aria-hidden": !0,
17
+ style: { display: "inline-block", width: c.size ?? "1em", height: c.size ?? "1em" }
18
+ }
19
+ );
20
+ return /* @__PURE__ */ n(l, { fallback: r, children: /* @__PURE__ */ n(s, { ...c }) });
21
+ }
22
+ export {
23
+ y as Icon,
24
+ p as iconNames
25
+ };
26
+ //# sourceMappingURL=Icon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Icon.js","sources":["../../../src/components/ui/Icon.tsx"],"sourcesContent":["'use client';\n\nimport {\n lazy,\n Suspense,\n useMemo,\n type LazyExoticComponent,\n type ReactNode,\n} from 'react';\nimport dynamicIconImports from 'lucide-react/dynamicIconImports';\nimport type { LucideIcon, LucideProps } from 'lucide-react';\n\n/** Every lucide icon name, kebab-case — e.g. `calendar`, `chevrons-up-down`. */\nexport type IconName = keyof typeof dynamicIconImports;\n\n/** All available icon names (useful for pickers and docs). */\nexport const iconNames = Object.keys(dynamicIconImports) as IconName[];\n\n// lazy() must return a stable component per name or React remounts (and\n// re-suspends) on every render.\nconst cache = new Map<IconName, LazyExoticComponent<LucideIcon>>();\n\nfunction getIcon(name: IconName): LazyExoticComponent<LucideIcon> {\n const cached = cache.get(name);\n if (cached) return cached;\n const created = lazy(dynamicIconImports[name]);\n cache.set(name, created);\n return created;\n}\n\nexport interface IconProps extends Omit<LucideProps, 'ref'> {\n /** Lucide icon name in kebab-case, e.g. `name=\"calendar\"`. Autocompletes. */\n name: IconName;\n /**\n * Shown while the icon chunk loads (first use only — icons are cached).\n * Defaults to an invisible square so layout doesn't shift.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Name-addressed lucide icon. Use when the icon is dynamic (data-driven\n * nav items, user-configurable dashboards) or when importing components for\n * every icon is friction.\n *\n * Each icon is code-split and loaded on first use, so referencing the full\n * lucide set costs nothing up front — `<Icon name=\"rocket\" />` ships only\n * the rocket.\n *\n * @example\n * <Icon name=\"calendar\" className=\"size-4\" />\n * <Icon name=\"chevrons-up-down\" size={16} strokeWidth={1.5} />\n *\n * @do Use `size-4` (16px) inline with text, `size-5` (20px) inside buttons.\n * @dont Reach for this in hot lists with hundreds of distinct icons on first\n * paint — statically imported icons from `Icons` skip the lazy hop.\n */\nexport function Icon({ name, fallback, ...props }: IconProps) {\n const LucideIcon = useMemo(() => getIcon(name), [name]);\n\n const placeholder = fallback ?? (\n <span\n aria-hidden\n style={{ display: 'inline-block', width: props.size ?? '1em', height: props.size ?? '1em' }}\n />\n );\n\n return (\n <Suspense fallback={placeholder}>\n <LucideIcon {...props} />\n </Suspense>\n );\n}\n"],"names":["iconNames","dynamicIconImports","cache","getIcon","name","cached","created","lazy","Icon","fallback","props","LucideIcon","useMemo","placeholder","jsx","Suspense"],"mappings":";;;;AAgBO,MAAMA,IAAY,OAAO,KAAKC,CAAkB,GAIjDC,wBAAY,IAAA;AAElB,SAASC,EAAQC,GAAiD;AAChE,QAAMC,IAASH,EAAM,IAAIE,CAAI;AAC7B,MAAIC,EAAQ,QAAOA;AACnB,QAAMC,IAAUC,EAAKN,EAAmBG,CAAI,CAAC;AAC7C,SAAAF,EAAM,IAAIE,GAAME,CAAO,GAChBA;AACT;AA6BO,SAASE,EAAK,EAAE,MAAAJ,GAAM,UAAAK,GAAU,GAAGC,KAAoB;AAC5D,QAAMC,IAAaC,EAAQ,MAAMT,EAAQC,CAAI,GAAG,CAACA,CAAI,CAAC,GAEhDS,IAAcJ,KAClB,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAW;AAAA,MACX,OAAO,EAAE,SAAS,gBAAgB,OAAOJ,EAAM,QAAQ,OAAO,QAAQA,EAAM,QAAQ,MAAA;AAAA,IAAM;AAAA,EAAA;AAI9F,SACE,gBAAAI,EAACC,KAAS,UAAUF,GAClB,4BAACF,GAAA,EAAY,GAAGD,GAAO,EAAA,CACzB;AAEJ;"}
@@ -1,5 +1,5 @@
1
1
  import { ButtonHTMLAttributes, ReactNode } from 'react';
2
- import { VariantProps } from '../../lib/cva';
2
+ import { VariantProps } from '../../lib/cva.js';
3
3
  declare const iconButton: (props?: ({
4
4
  variant?: "outline" | "primary" | "secondary" | "ghost" | "destructive" | null | undefined;
5
5
  size?: "md" | "sm" | "lg" | null | undefined;