@datum-cloud/datum-ui 0.2.0-alpha.3 → 0.2.0-alpha.5

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 (145) hide show
  1. package/README.md +66 -32
  2. package/dist/alert/index.mjs +3 -0
  3. package/dist/alert-BC2Mccfo.mjs +95 -0
  4. package/dist/autocomplete/index.mjs +7 -0
  5. package/dist/autocomplete-DZtI97HP.mjs +295 -0
  6. package/dist/avatar-stack/index.mjs +5 -0
  7. package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
  8. package/dist/badge/index.mjs +3 -0
  9. package/dist/badge-bFgeYceE.mjs +185 -0
  10. package/dist/breadcrumb/index.mjs +4 -0
  11. package/dist/breadcrumb-BGYJgom_.mjs +71 -0
  12. package/dist/button/index.mjs +4 -0
  13. package/dist/button-AzpnV-WB.mjs +49 -0
  14. package/dist/button-C1wRfGtT.mjs +230 -0
  15. package/dist/button-group/index.mjs +5 -0
  16. package/dist/button-group-C1IB2K5s.mjs +40 -0
  17. package/dist/calendar/index.mjs +5 -0
  18. package/dist/calendar-DlIHeWb0.mjs +113 -0
  19. package/dist/card/index.mjs +4 -0
  20. package/dist/card-3Kd0VdNf.mjs +63 -0
  21. package/dist/chart/index.mjs +4 -0
  22. package/dist/chart-BZqUKpkh.mjs +143 -0
  23. package/dist/checkbox/index.mjs +4 -0
  24. package/dist/checkbox-LG1OKTpG.mjs +34 -0
  25. package/dist/col-lrLMZaTJ.mjs +184 -0
  26. package/dist/collapsible/index.mjs +3 -0
  27. package/dist/collapsible-Bt9UYfv3.mjs +9 -0
  28. package/dist/command/index.mjs +5 -0
  29. package/dist/command-s0Yv3abE.mjs +86 -0
  30. package/dist/components/features/date-picker/index.d.ts +3 -0
  31. package/dist/components/features/date-picker/index.d.ts.map +1 -0
  32. package/dist/components/features/dropzone/index.d.ts +1 -0
  33. package/dist/components/features/dropzone/index.d.ts.map +1 -1
  34. package/dist/components/themes/index.d.ts +1 -1
  35. package/dist/components/themes/index.d.ts.map +1 -1
  36. package/dist/components/themes/types.d.ts +0 -2
  37. package/dist/components/themes/types.d.ts.map +1 -1
  38. package/dist/date-picker/index.mjs +9 -0
  39. package/dist/dialog/index.mjs +5 -0
  40. package/dist/dialog-DXBaT9gA.mjs +86 -0
  41. package/dist/dialog-bnMMf9GD.mjs +73 -0
  42. package/dist/dropdown/index.mjs +3 -0
  43. package/dist/dropdown-DtSa_lqc.mjs +112 -0
  44. package/dist/dropzone/index.mjs +5 -0
  45. package/dist/dropzone-BkOnwrS4.mjs +221 -0
  46. package/dist/empty-content/index.mjs +3 -0
  47. package/dist/empty-content-BM9rzI13.mjs +196 -0
  48. package/dist/exports/map.d.ts +3 -0
  49. package/dist/exports/map.d.ts.map +1 -0
  50. package/dist/fonts/AllianceNo1-Medium.ttf +0 -0
  51. package/dist/fonts/AllianceNo1-Regular.ttf +0 -0
  52. package/dist/fonts/AllianceNo1-SemiBold.ttf +0 -0
  53. package/dist/form/index.mjs +146 -0
  54. package/dist/grid/index.mjs +3 -0
  55. package/dist/hooks/index.mjs +2 -3
  56. package/dist/hover-card/index.mjs +4 -0
  57. package/dist/hover-card-CUPfFUqE.mjs +33 -0
  58. package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
  59. package/dist/icons/index.mjs +3 -3
  60. package/dist/index.d.ts +0 -1
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.mjs +65 -9
  63. package/dist/input/index.mjs +5 -0
  64. package/dist/input-DuyjEKEW.mjs +17 -0
  65. package/dist/input-fzXBheCN.mjs +17 -0
  66. package/dist/input-group/index.mjs +7 -0
  67. package/dist/input-group-CPaFSTEV.mjs +80 -0
  68. package/dist/input-number/index.mjs +6 -0
  69. package/dist/input-number-9o62JHRl.mjs +106 -0
  70. package/dist/input-with-addons/index.mjs +3 -0
  71. package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
  72. package/dist/label/index.mjs +4 -0
  73. package/dist/label-_ste_Re3.mjs +44 -0
  74. package/dist/link-button-TIF2Zdrk.mjs +36 -0
  75. package/dist/loader-overlay/index.mjs +3 -0
  76. package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
  77. package/dist/map/index.mjs +13 -0
  78. package/dist/map-WL6jhkSM.mjs +1094 -0
  79. package/dist/more-actions/index.mjs +5 -0
  80. package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
  81. package/dist/nprogress/index.mjs +32 -0
  82. package/dist/page-title/index.mjs +3 -0
  83. package/dist/page-title-BJuo81rT.mjs +26 -0
  84. package/dist/popover/index.mjs +4 -0
  85. package/dist/popover-SQlKSz6L.mjs +36 -0
  86. package/dist/radio-group/index.mjs +4 -0
  87. package/dist/radio-group-Oshv0b-U.mjs +49 -0
  88. package/dist/select/index.mjs +4 -0
  89. package/dist/select-DVlEzD2W.mjs +166 -0
  90. package/dist/separator/index.mjs +4 -0
  91. package/dist/separator-T2ppyD-8.mjs +18 -0
  92. package/dist/sheet/index.mjs +5 -0
  93. package/dist/sheet-BKiCwtNO.mjs +45 -0
  94. package/dist/sheet-CtnP6gTD.mjs +77 -0
  95. package/dist/sidebar/index.mjs +11 -0
  96. package/dist/sidebar-DfqezV8t.mjs +945 -0
  97. package/dist/skeleton/index.mjs +4 -0
  98. package/dist/skeleton-vzbxA-DQ.mjs +13 -0
  99. package/dist/spinner/index.mjs +4 -0
  100. package/dist/spinner-BE7k2bAD.mjs +16 -0
  101. package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
  102. package/dist/stepper/index.mjs +5 -0
  103. package/dist/stepper-SWB-u_nM.mjs +323 -0
  104. package/dist/{style.css → styles.css} +317 -575
  105. package/dist/styles.mjs +1 -0
  106. package/dist/switch/index.mjs +4 -0
  107. package/dist/switch-Calk7Gyw.mjs +32 -0
  108. package/dist/table/index.mjs +4 -0
  109. package/dist/table-CsXBcQLI.mjs +68 -0
  110. package/dist/tabs/index.mjs +3 -0
  111. package/dist/tabs-D8n-dqnw.mjs +52 -0
  112. package/dist/tag-input/index.mjs +5 -0
  113. package/dist/tag-input-Di7SDNbK.mjs +284 -0
  114. package/dist/task-queue/index.mjs +7 -0
  115. package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
  116. package/dist/textarea/index.mjs +5 -0
  117. package/dist/textarea-CxE3YbC7.mjs +17 -0
  118. package/dist/textarea-QYRcDEpK.mjs +15 -0
  119. package/dist/theme/index.mjs +3 -0
  120. package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-CzCxEFFh.mjs} +63 -1
  121. package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
  122. package/dist/toast/index.mjs +3 -0
  123. package/dist/tooltip/index.mjs +4 -0
  124. package/dist/tooltip-Dd3ActSS.mjs +74 -0
  125. package/dist/typography/index.mjs +3 -0
  126. package/dist/typography-UA7ZZvgJ.mjs +200 -0
  127. package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
  128. package/dist/use-stepper-BaToCYMs.mjs +2017 -0
  129. package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
  130. package/dist/utils/index.mjs +0 -1
  131. package/dist/utils-Bfgoe-Gm.mjs +20 -0
  132. package/dist/visually-hidden/index.mjs +3 -0
  133. package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
  134. package/package.json +223 -24
  135. package/dist/components/index.mjs +0 -8
  136. package/dist/datum.provider-D6VMjSV0.mjs +0 -37
  137. package/dist/providers/datum.provider.d.ts +0 -20
  138. package/dist/providers/datum.provider.d.ts.map +0 -1
  139. package/dist/providers/index.d.ts +0 -3
  140. package/dist/providers/index.d.ts.map +0 -1
  141. package/dist/providers/index.mjs +0 -4
  142. package/dist/theme-script-DHyLk25i.mjs +0 -11128
  143. /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
  144. /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-C4JYls8q.mjs} +0 -0
  145. /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-B6wPrZV8.mjs} +0 -0
package/README.md CHANGED
@@ -16,32 +16,68 @@ bun add @datum-cloud/datum-ui
16
16
 
17
17
  ## Setup
18
18
 
19
- Wrap your app with `DatumProvider` to enable theming and styles:
19
+ ### 1. Import Styles
20
+
21
+ Add datum-ui styles to your CSS file, after Tailwind:
22
+
23
+ ```css
24
+ /* app.css */
25
+ @import 'tailwindcss';
26
+ @import '@datum-cloud/datum-ui/styles';
27
+ ```
28
+
29
+ This loads all design tokens, theme variables, fonts, and component styles. Tokens are registered with Tailwind via `@theme inline`, so utilities like `bg-primary` and `text-foreground` work automatically.
30
+
31
+ ### 2. Add ThemeProvider
32
+
33
+ Wrap your app with `ThemeProvider` for light/dark/system theme switching:
20
34
 
21
35
  ```tsx
22
- import { DatumProvider } from '@datum-cloud/datum-ui'
23
- import '@datum-cloud/datum-ui/styles'
36
+ import { ThemeProvider } from '@datum-cloud/datum-ui/theme'
24
37
 
25
38
  function App() {
26
39
  return (
27
- <DatumProvider>
40
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
28
41
  {/* your app */}
29
- </DatumProvider>
42
+ </ThemeProvider>
30
43
  )
31
44
  }
32
45
  ```
33
46
 
34
- ## Entry Points
47
+ ## Imports
48
+
49
+ Each component has its own subpath export. Import only what you need:
50
+
51
+ ```tsx
52
+ import { Button } from '@datum-cloud/datum-ui/button'
53
+ import { Dialog } from '@datum-cloud/datum-ui/dialog'
54
+ import { Form, FormField, FormInput } from '@datum-cloud/datum-ui/form'
55
+ ```
56
+
57
+ This keeps your bundle small and means you only install peer dependencies for the components you actually use.
58
+
59
+ ### Shared Exports
35
60
 
36
- | Import Path | Description |
37
- | ---------------------------------- | ----------------------------------------------- |
38
- | `@datum-cloud/datum-ui` | All components, hooks, providers, and utilities |
39
- | `@datum-cloud/datum-ui/components` | Components only (base + features) |
40
- | `@datum-cloud/datum-ui/providers` | `DatumProvider` |
41
- | `@datum-cloud/datum-ui/hooks` | `useCopyToClipboard`, `useDebounce` |
42
- | `@datum-cloud/datum-ui/icons` | `CloseIcon`, `IconWrapper`, `SpinnerIcon` |
43
- | `@datum-cloud/datum-ui/utils` | `cn` (className merge utility) |
44
- | `@datum-cloud/datum-ui/styles` | Global CSS (fonts, tokens, component styles) |
61
+ | Import Path | Description |
62
+ | ------------------------------ | ------------------------------------------------------ |
63
+ | `@datum-cloud/datum-ui` | Root barrel — all components (requires all peer deps) |
64
+ | `@datum-cloud/datum-ui/styles` | CSS (fonts, tokens, theme variables, component styles) |
65
+ | `@datum-cloud/datum-ui/theme` | Theme utilities and types |
66
+ | `@datum-cloud/datum-ui/hooks` | `useCopyToClipboard`, `useDebounce` |
67
+ | `@datum-cloud/datum-ui/icons` | `CloseIcon`, `IconWrapper`, `SpinnerIcon` |
68
+ | `@datum-cloud/datum-ui/utils` | `cn` (className merge utility) |
69
+
70
+ ### Grouped Exports
71
+
72
+ Some components with shared heavy dependencies are grouped under a single subpath:
73
+
74
+ | Import Path | Includes | Peer Dependencies |
75
+ | ----------------------------------- | ------------------------------------------ | --------------------------------------------- |
76
+ | `@datum-cloud/datum-ui/date-picker` | `CalendarDatePicker`, `TimeRangePicker` | `react-day-picker`, `date-fns`, `date-fns-tz` |
77
+ | `@datum-cloud/datum-ui/map` | `Map`, `PlaceAutocomplete`, + map controls | `leaflet`, `react-leaflet`, + leaflet plugins |
78
+ | `@datum-cloud/datum-ui/dropzone` | `Dropzone`, `FileInputButton` | `react-dropzone` |
79
+ | `@datum-cloud/datum-ui/chart` | `ChartContainer`, `ChartTooltip`, etc. | `recharts` |
80
+ | `@datum-cloud/datum-ui/form` | `Form`, `FormField`, `FormInput`, etc. | `@conform-to/react`, `@conform-to/zod`, `zod` |
45
81
 
46
82
  ## Components
47
83
 
@@ -117,7 +153,7 @@ Complex, fully-customized components with significant business logic.
117
153
  ### Button
118
154
 
119
155
  ```tsx
120
- import { Button } from '@datum-cloud/datum-ui'
156
+ import { Button } from '@datum-cloud/datum-ui/button'
121
157
 
122
158
  // Variants: type × theme
123
159
  <Button type="primary" theme="solid">Save</Button>
@@ -128,7 +164,7 @@ import { Button } from '@datum-cloud/datum-ui'
128
164
  ### Typography
129
165
 
130
166
  ```tsx
131
- import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui'
167
+ import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui/typography'
132
168
 
133
169
  <Title level={1}>Page Title</Title>
134
170
  <Title level={3} color="primary">Section</Title>
@@ -140,7 +176,7 @@ import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui'
140
176
  ### Form with Validation
141
177
 
142
178
  ```tsx
143
- import { Form, FormField, FormInput, FormSelect } from '@datum-cloud/datum-ui'
179
+ import { Form, FormField, FormInput, FormSelect } from '@datum-cloud/datum-ui/form'
144
180
  import { z } from 'zod'
145
181
 
146
182
  const schema = z.object({
@@ -166,7 +202,7 @@ const schema = z.object({
166
202
  ```tsx
167
203
  import {
168
204
  Map, MapTileLayer, MapMarker, MapPopup, MapZoomControl,
169
- } from '@datum-cloud/datum-ui'
205
+ } from '@datum-cloud/datum-ui/map'
170
206
 
171
207
  <div className="h-[500px] w-full">
172
208
  <Map center={[51.505, -0.09]} zoom={13}>
@@ -185,7 +221,7 @@ import {
185
221
  import {
186
222
  Dialog, DialogTrigger, DialogContent,
187
223
  DialogHeader, DialogTitle, DialogDescription,
188
- } from '@datum-cloud/datum-ui'
224
+ } from '@datum-cloud/datum-ui/dialog'
189
225
 
190
226
  <Dialog>
191
227
  <DialogTrigger asChild>
@@ -203,14 +239,15 @@ import {
203
239
  ### Toast
204
240
 
205
241
  ```tsx
206
- import { Toaster, useToast } from '@datum-cloud/datum-ui'
242
+ import { ThemeProvider } from '@datum-cloud/datum-ui/theme'
243
+ import { Toaster, useToast } from '@datum-cloud/datum-ui/toast'
207
244
 
208
245
  function App() {
209
246
  return (
210
- <DatumProvider>
247
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
211
248
  <Toaster />
212
249
  <MyComponent />
213
- </DatumProvider>
250
+ </ThemeProvider>
214
251
  )
215
252
  }
216
253
 
@@ -225,7 +262,7 @@ function MyComponent() {
225
262
  ```tsx
226
263
  import {
227
264
  TaskQueueProvider, TaskQueueDropdown, useTaskQueue,
228
- } from '@datum-cloud/datum-ui'
265
+ } from '@datum-cloud/datum-ui/task-queue'
229
266
 
230
267
  <TaskQueueProvider config={{ concurrency: 3 }}>
231
268
  <TaskQueueDropdown />
@@ -249,23 +286,20 @@ function MyComponent() {
249
286
 
250
287
  ## Theming
251
288
 
252
- `DatumProvider` includes a theme provider with dark mode support:
289
+ `ThemeProvider` supports light, dark, and system theme modes:
253
290
 
254
291
  ```tsx
255
- import { DatumProvider } from '@datum-cloud/datum-ui'
292
+ import { ThemeProvider } from '@datum-cloud/datum-ui/theme'
256
293
 
257
- // Props: defaultTheme, storageKey, disableTransitionOnChange
258
- <DatumProvider defaultTheme="system">
294
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
259
295
  {children}
260
- </DatumProvider>
296
+ </ThemeProvider>
261
297
  ```
262
298
 
263
- Theme values: `'light'`, `'dark'`, `'system'`.
264
-
265
299
  Access the current theme:
266
300
 
267
301
  ```tsx
268
- import { useTheme } from '@datum-cloud/datum-ui'
302
+ import { useTheme } from '@datum-cloud/datum-ui/theme'
269
303
 
270
304
  const { theme, setTheme, resolvedTheme } = useTheme()
271
305
  ```
@@ -0,0 +1,3 @@
1
+ import { n as AlertDescription, r as AlertTitle, t as Alert } from "../alert-BC2Mccfo.mjs";
2
+
3
+ export { Alert, AlertDescription, AlertTitle };
@@ -0,0 +1,95 @@
1
+ import { t as cn } from "./cn-DWCc1QRE.mjs";
2
+ import { cva } from "class-variance-authority";
3
+ import { CircleXIcon } from "lucide-react";
4
+ import * as React$1 from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region src/components/base/alert/alert.tsx
8
+ /**
9
+ * Datum Alert Component
10
+ * Extends shadcn Alert with Datum-specific variants: success, info, warning
11
+ */
12
+ const variantDefinitions = {
13
+ default: {
14
+ classes: "bg-background text-foreground",
15
+ closeButtonColor: "text-foreground"
16
+ },
17
+ secondary: {
18
+ classes: "bg-muted text-primary [&>svg]:text-primary",
19
+ closeButtonColor: "text-primary"
20
+ },
21
+ outline: {
22
+ classes: "border-muted text-muted-foreground",
23
+ closeButtonColor: "text-muted-foreground"
24
+ },
25
+ destructive: {
26
+ classes: "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
27
+ closeButtonColor: "text-destructive"
28
+ },
29
+ success: {
30
+ classes: "border-success-300 bg-success-100 text-success-500",
31
+ closeButtonColor: "text-success-500"
32
+ },
33
+ info: {
34
+ classes: "border-info-300 bg-info-100 text-info-500! [&>svg]:text-info-500",
35
+ closeButtonColor: "text-info-500"
36
+ },
37
+ warning: {
38
+ classes: "border-yellow-500 bg-yellow-50 text-yellow-700! [&>svg]:text-yellow-700",
39
+ closeButtonColor: "text-yellow-700"
40
+ }
41
+ };
42
+ const alertVariants = cva("relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", {
43
+ variants: { variant: {
44
+ default: variantDefinitions.default.classes,
45
+ secondary: variantDefinitions.secondary.classes,
46
+ outline: variantDefinitions.outline.classes,
47
+ destructive: variantDefinitions.destructive.classes,
48
+ success: variantDefinitions.success.classes,
49
+ info: variantDefinitions.info.classes,
50
+ warning: variantDefinitions.warning.classes
51
+ } },
52
+ defaultVariants: { variant: "default" }
53
+ });
54
+ function Alert({ className, variant, closable = false, onClose, ...props }) {
55
+ const [isVisible, setIsVisible] = React$1.useState(true);
56
+ const handleClose = () => {
57
+ if (onClose) onClose();
58
+ else setIsVisible(false);
59
+ };
60
+ if (!isVisible) return null;
61
+ return /* @__PURE__ */ jsxs("div", {
62
+ role: "alert",
63
+ className: cn(alertVariants({ variant }), closable && "pr-10", className),
64
+ ...props,
65
+ children: [props.children, closable && /* @__PURE__ */ jsx("span", {
66
+ onClick: handleClose,
67
+ role: "button",
68
+ tabIndex: 0,
69
+ onKeyDown: (e) => {
70
+ if (e.key === "Enter" || e.key === " ") {
71
+ e.preventDefault();
72
+ handleClose();
73
+ }
74
+ },
75
+ className: "absolute top-4 right-4 z-10 cursor-pointer opacity-70 transition-opacity hover:opacity-100",
76
+ "aria-label": "Close alert",
77
+ children: /* @__PURE__ */ jsx(CircleXIcon, { className: cn("size-4", variant && variantDefinitions[variant]?.closeButtonColor) })
78
+ })]
79
+ });
80
+ }
81
+ function AlertTitle({ className, ...props }) {
82
+ return /* @__PURE__ */ jsx("div", {
83
+ className: cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className),
84
+ ...props
85
+ });
86
+ }
87
+ function AlertDescription({ className, ...props }) {
88
+ return /* @__PURE__ */ jsx("div", {
89
+ className: cn("text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed", className),
90
+ ...props
91
+ });
92
+ }
93
+
94
+ //#endregion
95
+ export { AlertDescription as n, AlertTitle as r, Alert as t };
@@ -0,0 +1,7 @@
1
+ import "../utils-Bfgoe-Gm.mjs";
2
+ import "../dialog-DXBaT9gA.mjs";
3
+ import "../command-s0Yv3abE.mjs";
4
+ import "../popover-SQlKSz6L.mjs";
5
+ import { t as Autocomplete } from "../autocomplete-DZtI97HP.mjs";
6
+
7
+ export { Autocomplete };
@@ -0,0 +1,295 @@
1
+ import { t as cn } from "./cn-DWCc1QRE.mjs";
2
+ import { a as CommandInput, i as CommandGroup, o as CommandItem, r as CommandEmpty, s as CommandList, t as Command } from "./command-s0Yv3abE.mjs";
3
+ import { i as PopoverTrigger, r as PopoverContent, t as Popover } from "./popover-SQlKSz6L.mjs";
4
+ import { t as LoaderOverlay } from "./loader-overlay-DUaQSZQP.mjs";
5
+ import { CheckIcon, ChevronDown } from "lucide-react";
6
+ import * as React$1 from "react";
7
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
+ import { useVirtualizer } from "@tanstack/react-virtual";
9
+
10
+ //#region src/components/features/autocomplete/autocomplete.tsx
11
+ function isGroupedOptions(options) {
12
+ return options.length > 0 && "options" in options[0];
13
+ }
14
+ function flattenOptions(options) {
15
+ if (isGroupedOptions(options)) return options.flatMap((g) => g.options);
16
+ return options;
17
+ }
18
+ function Trigger({ ref, selectedOption, renderValue, placeholder, loading, disabled, open, id, className, ...rest }) {
19
+ let displayContent;
20
+ if (!selectedOption) displayContent = /* @__PURE__ */ jsx("span", {
21
+ className: "text-muted-foreground",
22
+ children: placeholder
23
+ });
24
+ else if (renderValue) displayContent = renderValue(selectedOption);
25
+ else displayContent = /* @__PURE__ */ jsx("span", {
26
+ className: "truncate",
27
+ children: selectedOption.label
28
+ });
29
+ return /* @__PURE__ */ jsxs("button", {
30
+ ref,
31
+ type: "button",
32
+ id,
33
+ role: "combobox",
34
+ "aria-expanded": open,
35
+ disabled: disabled || loading,
36
+ className: cn("text-input-foreground placeholder:text-input-placeholder", "border-input-border bg-input-background/50 relative flex h-10 w-full items-center justify-between rounded-lg border px-3 py-2 text-left text-sm transition-all", "focus-visible:border-input-focus-border focus-visible:shadow-(--input-focus-shadow)", "focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-hidden", "aria-invalid:border-destructive", (disabled || loading) && "cursor-not-allowed opacity-50", className),
37
+ ...rest,
38
+ children: [
39
+ loading && /* @__PURE__ */ jsx(LoaderOverlay, {}),
40
+ /* @__PURE__ */ jsx("div", {
41
+ className: "min-w-0 flex-1",
42
+ children: displayContent
43
+ }),
44
+ /* @__PURE__ */ jsx(ChevronDown, { className: "text-muted-foreground ml-2 size-4 shrink-0" })
45
+ ]
46
+ });
47
+ }
48
+ Trigger.displayName = "AutocompleteTrigger";
49
+ function DefaultOptionContent({ option, isSelected }) {
50
+ return /* @__PURE__ */ jsxs("div", {
51
+ className: "flex w-full items-center justify-between gap-2",
52
+ children: [/* @__PURE__ */ jsxs("div", {
53
+ className: "min-w-0 flex-1",
54
+ children: [/* @__PURE__ */ jsx("span", {
55
+ className: "truncate text-sm",
56
+ children: option.label
57
+ }), option.description && /* @__PURE__ */ jsx("p", {
58
+ className: "text-muted-foreground mt-0.5 line-clamp-2 text-xs",
59
+ children: option.description
60
+ })]
61
+ }), isSelected && /* @__PURE__ */ jsx(CheckIcon, { className: "text-primary size-4 shrink-0" })]
62
+ });
63
+ }
64
+ function StaticOptions({ options, selectedValue, onSelect, renderOption }) {
65
+ const renderItem = (option) => {
66
+ const isSelected = option.value === selectedValue;
67
+ return /* @__PURE__ */ jsx(CommandItem, {
68
+ value: option.value,
69
+ keywords: [option.label, option.description ?? ""],
70
+ disabled: option.disabled,
71
+ onSelect: () => onSelect(option.value),
72
+ className: "cursor-pointer justify-between px-3 py-2 text-xs",
73
+ children: renderOption ? renderOption(option, isSelected) : /* @__PURE__ */ jsx(DefaultOptionContent, {
74
+ option,
75
+ isSelected
76
+ })
77
+ }, option.value);
78
+ };
79
+ if (isGroupedOptions(options)) return /* @__PURE__ */ jsx(Fragment$1, { children: options.map((group, index) => /* @__PURE__ */ jsx(CommandGroup, {
80
+ heading: group.label,
81
+ className: index > 0 ? "border-t pt-1" : "",
82
+ children: group.options.map(renderItem)
83
+ }, group.label)) });
84
+ return /* @__PURE__ */ jsx(CommandGroup, {
85
+ className: "p-0",
86
+ children: options.map(renderItem)
87
+ });
88
+ }
89
+ function VirtualizedOptions({ options, selectedValue, onSelect, renderOption, itemSize = 36, listClassName }) {
90
+ const flatOptions = flattenOptions(options);
91
+ const parentRef = React$1.useRef(null);
92
+ const virtualizer = useVirtualizer({
93
+ count: flatOptions.length,
94
+ getScrollElement: () => parentRef.current,
95
+ estimateSize: () => itemSize
96
+ });
97
+ React$1.useEffect(() => {
98
+ if (selectedValue) {
99
+ const index = flatOptions.findIndex((o) => o.value === selectedValue);
100
+ if (index >= 0) virtualizer.scrollToIndex(index, { align: "center" });
101
+ }
102
+ }, [
103
+ selectedValue,
104
+ flatOptions,
105
+ virtualizer
106
+ ]);
107
+ return /* @__PURE__ */ jsx("div", {
108
+ ref: parentRef,
109
+ className: cn("max-h-[200px] overflow-auto", listClassName),
110
+ children: /* @__PURE__ */ jsx(CommandGroup, { children: /* @__PURE__ */ jsx("div", {
111
+ style: { height: `${virtualizer.getTotalSize()}px` },
112
+ className: "relative w-full",
113
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
114
+ const option = flatOptions[virtualItem.index];
115
+ const isSelected = option.value === selectedValue;
116
+ return /* @__PURE__ */ jsx(CommandItem, {
117
+ value: option.value,
118
+ keywords: [option.label, option.description ?? ""],
119
+ disabled: option.disabled,
120
+ onSelect: () => onSelect(option.value),
121
+ className: "absolute top-0 left-0 w-full cursor-pointer justify-between px-3 py-2 text-xs",
122
+ style: {
123
+ height: `${virtualItem.size}px`,
124
+ transform: `translateY(${virtualItem.start}px)`
125
+ },
126
+ children: renderOption ? renderOption(option, isSelected) : /* @__PURE__ */ jsx(DefaultOptionContent, {
127
+ option,
128
+ isSelected
129
+ })
130
+ }, option.value);
131
+ })
132
+ }) })
133
+ });
134
+ }
135
+ /**
136
+ * Autocomplete - A searchable select component
137
+ *
138
+ * Standalone, form-agnostic combobox built on Popover + Command (cmdk).
139
+ * Supports flat/grouped options, virtualization, custom rendering, and async search.
140
+ *
141
+ * @example Basic usage
142
+ * ```tsx
143
+ * <Autocomplete
144
+ * value={country}
145
+ * onValueChange={setCountry}
146
+ * options={countries}
147
+ * placeholder="Select country..."
148
+ * />
149
+ * ```
150
+ *
151
+ * @example Async search
152
+ * ```tsx
153
+ * <Autocomplete
154
+ * value={userId}
155
+ * onValueChange={setUserId}
156
+ * options={users ?? []}
157
+ * onSearchChange={setSearch}
158
+ * loading={isLoading}
159
+ * placeholder="Search users..."
160
+ * />
161
+ * ```
162
+ */
163
+ function Autocomplete({ options, value, onValueChange, onSearchChange, searchPlaceholder = "Search...", disableSearch = false, renderOption, renderValue, placeholder = "Select...", emptyContent = "No results found", footer, creatable = false, creatableLabel, virtualize = false, itemSize = 36, loading = false, disabled = false, name, id, className, triggerClassName, contentClassName, listClassName }) {
164
+ const [open, setOpen] = React$1.useState(false);
165
+ const [search, setSearch] = React$1.useState("");
166
+ const flatOptions = React$1.useMemo(() => flattenOptions(options), [options]);
167
+ const selectedOption = React$1.useMemo(() => flatOptions.find((o) => o.value === value), [flatOptions, value]);
168
+ const displayOption = React$1.useMemo(() => {
169
+ if (selectedOption) return selectedOption;
170
+ if (creatable && value) return {
171
+ value,
172
+ label: value
173
+ };
174
+ }, [
175
+ selectedOption,
176
+ creatable,
177
+ value
178
+ ]);
179
+ const isExternalSearch = !!onSearchChange;
180
+ const trimmedSearch = React$1.useMemo(() => search.trim(), [search]);
181
+ const showCreatableItem = React$1.useMemo(() => {
182
+ if (!creatable || trimmedSearch.length === 0) return false;
183
+ const needle = trimmedSearch.toLowerCase();
184
+ return !flatOptions.some((o) => o.value.toLowerCase() === needle || o.label.toLowerCase() === needle);
185
+ }, [
186
+ creatable,
187
+ trimmedSearch,
188
+ flatOptions
189
+ ]);
190
+ const handleSelect = React$1.useCallback((optionValue) => {
191
+ onValueChange?.(optionValue);
192
+ setSearch("");
193
+ setOpen(false);
194
+ }, [onValueChange]);
195
+ const handleCreatableSelect = React$1.useCallback(() => {
196
+ onValueChange?.(trimmedSearch);
197
+ setSearch("");
198
+ setOpen(false);
199
+ }, [onValueChange, trimmedSearch]);
200
+ const handleOpenChange = React$1.useCallback((nextOpen) => {
201
+ setOpen(nextOpen);
202
+ if (!nextOpen) {
203
+ setSearch("");
204
+ if (isExternalSearch) onSearchChange?.("");
205
+ }
206
+ }, [isExternalSearch, onSearchChange]);
207
+ const handleSearchChange = React$1.useCallback((val) => {
208
+ setSearch(val);
209
+ if (isExternalSearch) onSearchChange?.(val);
210
+ }, [isExternalSearch, onSearchChange]);
211
+ return /* @__PURE__ */ jsxs("div", {
212
+ className: cn("relative", className),
213
+ children: [/* @__PURE__ */ jsxs(Popover, {
214
+ open,
215
+ onOpenChange: handleOpenChange,
216
+ modal: true,
217
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
218
+ asChild: true,
219
+ children: /* @__PURE__ */ jsx(Trigger, {
220
+ selectedOption: displayOption,
221
+ renderValue,
222
+ placeholder,
223
+ loading,
224
+ disabled,
225
+ open,
226
+ id,
227
+ className: triggerClassName
228
+ })
229
+ }), /* @__PURE__ */ jsx(PopoverContent, {
230
+ className: cn("popover-content-width-full p-0", contentClassName),
231
+ align: "start",
232
+ children: /* @__PURE__ */ jsxs(Command, {
233
+ shouldFilter: !isExternalSearch && !creatable,
234
+ defaultValue: value,
235
+ children: [
236
+ !disableSearch && /* @__PURE__ */ jsx(CommandInput, {
237
+ className: "placeholder:text-secondary/60 h-7 border-none text-xs placeholder:text-xs focus-visible:ring-0",
238
+ iconClassName: "text-secondary size-3.5",
239
+ wrapperClassName: "px-3 py-2",
240
+ placeholder: searchPlaceholder,
241
+ value: search,
242
+ onValueChange: handleSearchChange
243
+ }),
244
+ /* @__PURE__ */ jsxs(CommandList, {
245
+ className: cn(!virtualize && "max-h-[300px]", listClassName),
246
+ children: [
247
+ !showCreatableItem && /* @__PURE__ */ jsx(CommandEmpty, { children: typeof emptyContent === "string" ? /* @__PURE__ */ jsx("span", {
248
+ className: "text-muted-foreground text-xs",
249
+ children: emptyContent
250
+ }) : emptyContent }),
251
+ virtualize ? /* @__PURE__ */ jsx(VirtualizedOptions, {
252
+ options,
253
+ selectedValue: value,
254
+ onSelect: handleSelect,
255
+ renderOption,
256
+ itemSize,
257
+ listClassName
258
+ }) : /* @__PURE__ */ jsx(StaticOptions, {
259
+ options,
260
+ selectedValue: value,
261
+ onSelect: handleSelect,
262
+ renderOption
263
+ }),
264
+ showCreatableItem && /* @__PURE__ */ jsx(CommandGroup, {
265
+ forceMount: true,
266
+ className: "p-0",
267
+ children: /* @__PURE__ */ jsx(CommandItem, {
268
+ forceMount: true,
269
+ value: `\0creatable:${trimmedSearch}`,
270
+ keywords: [trimmedSearch],
271
+ onSelect: handleCreatableSelect,
272
+ className: "cursor-pointer px-3 py-2 text-xs",
273
+ children: creatableLabel ? creatableLabel(trimmedSearch) : `Use "${trimmedSearch}"`
274
+ })
275
+ })
276
+ ]
277
+ }),
278
+ footer && /* @__PURE__ */ jsx("div", {
279
+ className: "border-t",
280
+ children: footer
281
+ })
282
+ ]
283
+ })
284
+ })]
285
+ }), name && /* @__PURE__ */ jsx("input", {
286
+ type: "hidden",
287
+ name,
288
+ value: value ?? ""
289
+ })]
290
+ });
291
+ }
292
+ Autocomplete.displayName = "Autocomplete";
293
+
294
+ //#endregion
295
+ export { Autocomplete as t };
@@ -0,0 +1,5 @@
1
+ import "../utils-Bfgoe-Gm.mjs";
2
+ import "../tooltip-Dd3ActSS.mjs";
3
+ import { n as avatarStackVariants, t as AvatarStack } from "../avatar-stack-JCfBlPB9.mjs";
4
+
5
+ export { AvatarStack, avatarStackVariants };
@@ -0,0 +1,80 @@
1
+ import { t as cn } from "./cn-DWCc1QRE.mjs";
2
+ import { t as cn$1 } from "./utils-Bfgoe-Gm.mjs";
3
+ import { t as Tooltip } from "./tooltip-Dd3ActSS.mjs";
4
+ import { cva } from "class-variance-authority";
5
+ import "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
8
+
9
+ //#region ../shadcn/ui/avatar.tsx
10
+ const Avatar = ({ className, ...props }) => {
11
+ return /* @__PURE__ */ jsx(AvatarPrimitive.Root, {
12
+ "data-slot": "avatar",
13
+ className: cn$1("relative flex size-8 shrink-0 overflow-hidden rounded-full", className),
14
+ ...props
15
+ });
16
+ };
17
+ const AvatarImage = ({ className, ...props }) => {
18
+ return /* @__PURE__ */ jsx(AvatarPrimitive.Image, {
19
+ "data-slot": "avatar-image",
20
+ className: cn$1("aspect-square size-full", className),
21
+ ...props
22
+ });
23
+ };
24
+ const AvatarFallback = ({ className, ...props }) => {
25
+ return /* @__PURE__ */ jsx(AvatarPrimitive.Fallback, {
26
+ "data-slot": "avatar-fallback",
27
+ className: cn$1("bg-muted flex size-full items-center justify-center rounded-full", className),
28
+ ...props
29
+ });
30
+ };
31
+
32
+ //#endregion
33
+ //#region src/components/features/avatar-stack/avatar-stack.tsx
34
+ const avatarStackVariants = cva("flex", {
35
+ variants: {
36
+ orientation: {
37
+ vertical: "flex-row",
38
+ horizontal: "flex-col"
39
+ },
40
+ spacing: {
41
+ sm: "-space-x-5 -space-y-5",
42
+ md: "-space-x-4 -space-y-4",
43
+ lg: "-space-x-3 -space-y-3",
44
+ xl: "-space-x-2 -space-y-2"
45
+ }
46
+ },
47
+ defaultVariants: {
48
+ orientation: "vertical",
49
+ spacing: "md"
50
+ }
51
+ });
52
+ function AvatarStack({ className, orientation, avatars, spacing, maxAvatarsAmount = 3, avatarClassName, ...props }) {
53
+ const shownAvatars = avatars.slice(0, maxAvatarsAmount);
54
+ const hiddenAvatars = avatars.slice(maxAvatarsAmount);
55
+ return /* @__PURE__ */ jsxs("div", {
56
+ className: cn(avatarStackVariants({
57
+ orientation,
58
+ spacing
59
+ }), className, orientation === "horizontal" ? "-space-x-0" : "-space-y-0"),
60
+ ...props,
61
+ children: [shownAvatars.map(({ name, image }, index) => /* @__PURE__ */ jsx(Tooltip, {
62
+ message: name,
63
+ delayDuration: 300,
64
+ children: /* @__PURE__ */ jsxs(Avatar, {
65
+ className: cn(avatarStackVariants(), "hover:z-10", avatarClassName),
66
+ children: [/* @__PURE__ */ jsx(AvatarImage, { src: image }), /* @__PURE__ */ jsx(AvatarFallback, { children: name?.split(" ")?.map((word) => word[0])?.join("")?.toUpperCase() })]
67
+ })
68
+ }, `${image}-${index + 1}`)), hiddenAvatars.length ? /* @__PURE__ */ jsx(Tooltip, {
69
+ message: /* @__PURE__ */ jsx(Fragment$1, { children: hiddenAvatars.map(({ name }, index) => /* @__PURE__ */ jsx("p", { children: name }, `${name}-${index + 1}`)) }),
70
+ delayDuration: 300,
71
+ children: /* @__PURE__ */ jsx(Avatar, {
72
+ className: cn(avatarClassName),
73
+ children: /* @__PURE__ */ jsxs(AvatarFallback, { children: ["+", avatars.length - shownAvatars.length] })
74
+ }, "Excesive avatars")
75
+ }) : null]
76
+ });
77
+ }
78
+
79
+ //#endregion
80
+ export { avatarStackVariants as n, AvatarStack as t };
@@ -0,0 +1,3 @@
1
+ import { n as badgeVariants, t as Badge } from "../badge-bFgeYceE.mjs";
2
+
3
+ export { Badge, badgeVariants };