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

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 (132) hide show
  1. package/README.md +46 -21
  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/date-picker/index.mjs +9 -0
  35. package/dist/{datum.provider-D6VMjSV0.mjs → datum.provider-B77goJgl.mjs} +1 -1
  36. package/dist/dialog/index.mjs +5 -0
  37. package/dist/dialog-DXBaT9gA.mjs +86 -0
  38. package/dist/dialog-bnMMf9GD.mjs +73 -0
  39. package/dist/dropdown/index.mjs +3 -0
  40. package/dist/dropdown-DtSa_lqc.mjs +112 -0
  41. package/dist/dropzone/index.mjs +5 -0
  42. package/dist/dropzone-BkOnwrS4.mjs +221 -0
  43. package/dist/empty-content/index.mjs +3 -0
  44. package/dist/empty-content-BM9rzI13.mjs +196 -0
  45. package/dist/exports/map.d.ts +3 -0
  46. package/dist/exports/map.d.ts.map +1 -0
  47. package/dist/form/index.mjs +146 -0
  48. package/dist/grid/index.mjs +3 -0
  49. package/dist/hooks/index.mjs +2 -3
  50. package/dist/hover-card/index.mjs +4 -0
  51. package/dist/hover-card-CUPfFUqE.mjs +33 -0
  52. package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
  53. package/dist/icons/index.mjs +3 -3
  54. package/dist/index.mjs +66 -8
  55. package/dist/input/index.mjs +5 -0
  56. package/dist/input-DuyjEKEW.mjs +17 -0
  57. package/dist/input-fzXBheCN.mjs +17 -0
  58. package/dist/input-group/index.mjs +7 -0
  59. package/dist/input-group-CPaFSTEV.mjs +80 -0
  60. package/dist/input-number/index.mjs +6 -0
  61. package/dist/input-number-9o62JHRl.mjs +106 -0
  62. package/dist/input-with-addons/index.mjs +3 -0
  63. package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
  64. package/dist/label/index.mjs +4 -0
  65. package/dist/label-_ste_Re3.mjs +44 -0
  66. package/dist/link-button-TIF2Zdrk.mjs +36 -0
  67. package/dist/loader-overlay/index.mjs +3 -0
  68. package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
  69. package/dist/map/index.mjs +13 -0
  70. package/dist/map-Df8QMcX0.mjs +1094 -0
  71. package/dist/more-actions/index.mjs +5 -0
  72. package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
  73. package/dist/nprogress/index.mjs +32 -0
  74. package/dist/page-title/index.mjs +3 -0
  75. package/dist/page-title-BJuo81rT.mjs +26 -0
  76. package/dist/popover/index.mjs +4 -0
  77. package/dist/popover-SQlKSz6L.mjs +36 -0
  78. package/dist/provider/index.mjs +4 -0
  79. package/dist/radio-group/index.mjs +4 -0
  80. package/dist/radio-group-Oshv0b-U.mjs +49 -0
  81. package/dist/select/index.mjs +4 -0
  82. package/dist/select-DVlEzD2W.mjs +166 -0
  83. package/dist/separator/index.mjs +4 -0
  84. package/dist/separator-T2ppyD-8.mjs +18 -0
  85. package/dist/sheet/index.mjs +5 -0
  86. package/dist/sheet-BKiCwtNO.mjs +45 -0
  87. package/dist/sheet-CtnP6gTD.mjs +77 -0
  88. package/dist/sidebar/index.mjs +11 -0
  89. package/dist/sidebar-DfqezV8t.mjs +945 -0
  90. package/dist/skeleton/index.mjs +4 -0
  91. package/dist/skeleton-vzbxA-DQ.mjs +13 -0
  92. package/dist/spinner/index.mjs +4 -0
  93. package/dist/spinner-BE7k2bAD.mjs +16 -0
  94. package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
  95. package/dist/stepper/index.mjs +5 -0
  96. package/dist/stepper-SWB-u_nM.mjs +323 -0
  97. package/dist/switch/index.mjs +4 -0
  98. package/dist/switch-Calk7Gyw.mjs +32 -0
  99. package/dist/table/index.mjs +4 -0
  100. package/dist/table-CsXBcQLI.mjs +68 -0
  101. package/dist/tabs/index.mjs +3 -0
  102. package/dist/tabs-D8n-dqnw.mjs +52 -0
  103. package/dist/tag-input/index.mjs +5 -0
  104. package/dist/tag-input-Di7SDNbK.mjs +284 -0
  105. package/dist/task-queue/index.mjs +7 -0
  106. package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
  107. package/dist/textarea/index.mjs +5 -0
  108. package/dist/textarea-CxE3YbC7.mjs +17 -0
  109. package/dist/textarea-QYRcDEpK.mjs +15 -0
  110. package/dist/theme/index.mjs +4 -0
  111. package/dist/theme-script-XBouzsNR.mjs +66 -0
  112. package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
  113. package/dist/toast/index.mjs +3 -0
  114. package/dist/tooltip/index.mjs +4 -0
  115. package/dist/tooltip-Dd3ActSS.mjs +74 -0
  116. package/dist/typography/index.mjs +3 -0
  117. package/dist/typography-UA7ZZvgJ.mjs +200 -0
  118. package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
  119. package/dist/use-stepper-BaToCYMs.mjs +2017 -0
  120. package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
  121. package/dist/utils/index.mjs +0 -1
  122. package/dist/utils-Bfgoe-Gm.mjs +20 -0
  123. package/dist/visually-hidden/index.mjs +3 -0
  124. package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
  125. package/package.json +208 -8
  126. package/dist/components/index.mjs +0 -8
  127. package/dist/providers/index.mjs +0 -4
  128. package/dist/theme-script-DHyLk25i.mjs +0 -11128
  129. /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
  130. /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-CdzvEnzY.mjs} +0 -0
  131. /package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-DgGshapa.mjs} +0 -0
  132. /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-DQ1tmxOL.mjs} +0 -0
package/README.md CHANGED
@@ -19,8 +19,7 @@ bun add @datum-cloud/datum-ui
19
19
  Wrap your app with `DatumProvider` to enable theming and styles:
20
20
 
21
21
  ```tsx
22
- import { DatumProvider } from '@datum-cloud/datum-ui'
23
- import '@datum-cloud/datum-ui/styles'
22
+ import { DatumProvider } from '@datum-cloud/datum-ui/provider'
24
23
 
25
24
  function App() {
26
25
  return (
@@ -31,17 +30,43 @@ function App() {
31
30
  }
32
31
  ```
33
32
 
34
- ## Entry Points
33
+ `DatumProvider` automatically loads all design tokens, theme variables, and component styles — no extra CSS import needed.
35
34
 
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) |
35
+ ## Imports
36
+
37
+ Each component has its own subpath export. Import only what you need:
38
+
39
+ ```tsx
40
+ import { Button } from '@datum-cloud/datum-ui/button'
41
+ import { Dialog } from '@datum-cloud/datum-ui/dialog'
42
+ import { Form, FormField, FormInput } from '@datum-cloud/datum-ui/form'
43
+ ```
44
+
45
+ This keeps your bundle small and means you only install peer dependencies for the components you actually use.
46
+
47
+ ### Shared Exports
48
+
49
+ | Import Path | Description |
50
+ | -------------------------------- | ----------------------------------------------------- |
51
+ | `@datum-cloud/datum-ui` | Root barrel — all components (requires all peer deps) |
52
+ | `@datum-cloud/datum-ui/provider` | `DatumProvider` (loads CSS + theme) |
53
+ | `@datum-cloud/datum-ui/theme` | Theme utilities and types |
54
+ | `@datum-cloud/datum-ui/hooks` | `useCopyToClipboard`, `useDebounce` |
55
+ | `@datum-cloud/datum-ui/icons` | `CloseIcon`, `IconWrapper`, `SpinnerIcon` |
56
+ | `@datum-cloud/datum-ui/utils` | `cn` (className merge utility) |
57
+ | `@datum-cloud/datum-ui/styles` | Global CSS (fonts, tokens, component styles) |
58
+
59
+ ### Grouped Exports
60
+
61
+ Some components with shared heavy dependencies are grouped under a single subpath:
62
+
63
+ | Import Path | Includes | Peer Dependencies |
64
+ | ----------------------------------- | ------------------------------------------ | --------------------------------------------- |
65
+ | `@datum-cloud/datum-ui/date-picker` | `CalendarDatePicker`, `TimeRangePicker` | `react-day-picker`, `date-fns`, `date-fns-tz` |
66
+ | `@datum-cloud/datum-ui/map` | `Map`, `PlaceAutocomplete`, + map controls | `leaflet`, `react-leaflet`, + leaflet plugins |
67
+ | `@datum-cloud/datum-ui/dropzone` | `Dropzone`, `FileInputButton` | `react-dropzone` |
68
+ | `@datum-cloud/datum-ui/chart` | `ChartContainer`, `ChartTooltip`, etc. | `recharts` |
69
+ | `@datum-cloud/datum-ui/form` | `Form`, `FormField`, `FormInput`, etc. | `@conform-to/react`, `@conform-to/zod`, `zod` |
45
70
 
46
71
  ## Components
47
72
 
@@ -117,7 +142,7 @@ Complex, fully-customized components with significant business logic.
117
142
  ### Button
118
143
 
119
144
  ```tsx
120
- import { Button } from '@datum-cloud/datum-ui'
145
+ import { Button } from '@datum-cloud/datum-ui/button'
121
146
 
122
147
  // Variants: type × theme
123
148
  <Button type="primary" theme="solid">Save</Button>
@@ -128,7 +153,7 @@ import { Button } from '@datum-cloud/datum-ui'
128
153
  ### Typography
129
154
 
130
155
  ```tsx
131
- import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui'
156
+ import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui/typography'
132
157
 
133
158
  <Title level={1}>Page Title</Title>
134
159
  <Title level={3} color="primary">Section</Title>
@@ -140,7 +165,7 @@ import { Title, Text, Paragraph, Code } from '@datum-cloud/datum-ui'
140
165
  ### Form with Validation
141
166
 
142
167
  ```tsx
143
- import { Form, FormField, FormInput, FormSelect } from '@datum-cloud/datum-ui'
168
+ import { Form, FormField, FormInput, FormSelect } from '@datum-cloud/datum-ui/form'
144
169
  import { z } from 'zod'
145
170
 
146
171
  const schema = z.object({
@@ -166,7 +191,7 @@ const schema = z.object({
166
191
  ```tsx
167
192
  import {
168
193
  Map, MapTileLayer, MapMarker, MapPopup, MapZoomControl,
169
- } from '@datum-cloud/datum-ui'
194
+ } from '@datum-cloud/datum-ui/map'
170
195
 
171
196
  <div className="h-[500px] w-full">
172
197
  <Map center={[51.505, -0.09]} zoom={13}>
@@ -185,7 +210,7 @@ import {
185
210
  import {
186
211
  Dialog, DialogTrigger, DialogContent,
187
212
  DialogHeader, DialogTitle, DialogDescription,
188
- } from '@datum-cloud/datum-ui'
213
+ } from '@datum-cloud/datum-ui/dialog'
189
214
 
190
215
  <Dialog>
191
216
  <DialogTrigger asChild>
@@ -203,7 +228,7 @@ import {
203
228
  ### Toast
204
229
 
205
230
  ```tsx
206
- import { Toaster, useToast } from '@datum-cloud/datum-ui'
231
+ import { Toaster, useToast } from '@datum-cloud/datum-ui/toast'
207
232
 
208
233
  function App() {
209
234
  return (
@@ -225,7 +250,7 @@ function MyComponent() {
225
250
  ```tsx
226
251
  import {
227
252
  TaskQueueProvider, TaskQueueDropdown, useTaskQueue,
228
- } from '@datum-cloud/datum-ui'
253
+ } from '@datum-cloud/datum-ui/task-queue'
229
254
 
230
255
  <TaskQueueProvider config={{ concurrency: 3 }}>
231
256
  <TaskQueueDropdown />
@@ -252,7 +277,7 @@ function MyComponent() {
252
277
  `DatumProvider` includes a theme provider with dark mode support:
253
278
 
254
279
  ```tsx
255
- import { DatumProvider } from '@datum-cloud/datum-ui'
280
+ import { DatumProvider } from '@datum-cloud/datum-ui/provider'
256
281
 
257
282
  // Props: defaultTheme, storageKey, disableTransitionOnChange
258
283
  <DatumProvider defaultTheme="system">
@@ -265,7 +290,7 @@ Theme values: `'light'`, `'dark'`, `'system'`.
265
290
  Access the current theme:
266
291
 
267
292
  ```tsx
268
- import { useTheme } from '@datum-cloud/datum-ui'
293
+ import { useTheme } from '@datum-cloud/datum-ui/theme'
269
294
 
270
295
  const { theme, setTheme, resolvedTheme } = useTheme()
271
296
  ```
@@ -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 };