@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.
- package/README.md +46 -21
- package/dist/alert/index.mjs +3 -0
- package/dist/alert-BC2Mccfo.mjs +95 -0
- package/dist/autocomplete/index.mjs +7 -0
- package/dist/autocomplete-DZtI97HP.mjs +295 -0
- package/dist/avatar-stack/index.mjs +5 -0
- package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
- package/dist/badge/index.mjs +3 -0
- package/dist/badge-bFgeYceE.mjs +185 -0
- package/dist/breadcrumb/index.mjs +4 -0
- package/dist/breadcrumb-BGYJgom_.mjs +71 -0
- package/dist/button/index.mjs +4 -0
- package/dist/button-AzpnV-WB.mjs +49 -0
- package/dist/button-C1wRfGtT.mjs +230 -0
- package/dist/button-group/index.mjs +5 -0
- package/dist/button-group-C1IB2K5s.mjs +40 -0
- package/dist/calendar/index.mjs +5 -0
- package/dist/calendar-DlIHeWb0.mjs +113 -0
- package/dist/card/index.mjs +4 -0
- package/dist/card-3Kd0VdNf.mjs +63 -0
- package/dist/chart/index.mjs +4 -0
- package/dist/chart-BZqUKpkh.mjs +143 -0
- package/dist/checkbox/index.mjs +4 -0
- package/dist/checkbox-LG1OKTpG.mjs +34 -0
- package/dist/col-lrLMZaTJ.mjs +184 -0
- package/dist/collapsible/index.mjs +3 -0
- package/dist/collapsible-Bt9UYfv3.mjs +9 -0
- package/dist/command/index.mjs +5 -0
- package/dist/command-s0Yv3abE.mjs +86 -0
- package/dist/components/features/date-picker/index.d.ts +3 -0
- package/dist/components/features/date-picker/index.d.ts.map +1 -0
- package/dist/components/features/dropzone/index.d.ts +1 -0
- package/dist/components/features/dropzone/index.d.ts.map +1 -1
- package/dist/date-picker/index.mjs +9 -0
- package/dist/{datum.provider-D6VMjSV0.mjs → datum.provider-B77goJgl.mjs} +1 -1
- package/dist/dialog/index.mjs +5 -0
- package/dist/dialog-DXBaT9gA.mjs +86 -0
- package/dist/dialog-bnMMf9GD.mjs +73 -0
- package/dist/dropdown/index.mjs +3 -0
- package/dist/dropdown-DtSa_lqc.mjs +112 -0
- package/dist/dropzone/index.mjs +5 -0
- package/dist/dropzone-BkOnwrS4.mjs +221 -0
- package/dist/empty-content/index.mjs +3 -0
- package/dist/empty-content-BM9rzI13.mjs +196 -0
- package/dist/exports/map.d.ts +3 -0
- package/dist/exports/map.d.ts.map +1 -0
- package/dist/form/index.mjs +146 -0
- package/dist/grid/index.mjs +3 -0
- package/dist/hooks/index.mjs +2 -3
- package/dist/hover-card/index.mjs +4 -0
- package/dist/hover-card-CUPfFUqE.mjs +33 -0
- package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
- package/dist/icons/index.mjs +3 -3
- package/dist/index.mjs +66 -8
- package/dist/input/index.mjs +5 -0
- package/dist/input-DuyjEKEW.mjs +17 -0
- package/dist/input-fzXBheCN.mjs +17 -0
- package/dist/input-group/index.mjs +7 -0
- package/dist/input-group-CPaFSTEV.mjs +80 -0
- package/dist/input-number/index.mjs +6 -0
- package/dist/input-number-9o62JHRl.mjs +106 -0
- package/dist/input-with-addons/index.mjs +3 -0
- package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
- package/dist/label/index.mjs +4 -0
- package/dist/label-_ste_Re3.mjs +44 -0
- package/dist/link-button-TIF2Zdrk.mjs +36 -0
- package/dist/loader-overlay/index.mjs +3 -0
- package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
- package/dist/map/index.mjs +13 -0
- package/dist/map-Df8QMcX0.mjs +1094 -0
- package/dist/more-actions/index.mjs +5 -0
- package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
- package/dist/nprogress/index.mjs +32 -0
- package/dist/page-title/index.mjs +3 -0
- package/dist/page-title-BJuo81rT.mjs +26 -0
- package/dist/popover/index.mjs +4 -0
- package/dist/popover-SQlKSz6L.mjs +36 -0
- package/dist/provider/index.mjs +4 -0
- package/dist/radio-group/index.mjs +4 -0
- package/dist/radio-group-Oshv0b-U.mjs +49 -0
- package/dist/select/index.mjs +4 -0
- package/dist/select-DVlEzD2W.mjs +166 -0
- package/dist/separator/index.mjs +4 -0
- package/dist/separator-T2ppyD-8.mjs +18 -0
- package/dist/sheet/index.mjs +5 -0
- package/dist/sheet-BKiCwtNO.mjs +45 -0
- package/dist/sheet-CtnP6gTD.mjs +77 -0
- package/dist/sidebar/index.mjs +11 -0
- package/dist/sidebar-DfqezV8t.mjs +945 -0
- package/dist/skeleton/index.mjs +4 -0
- package/dist/skeleton-vzbxA-DQ.mjs +13 -0
- package/dist/spinner/index.mjs +4 -0
- package/dist/spinner-BE7k2bAD.mjs +16 -0
- package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
- package/dist/stepper/index.mjs +5 -0
- package/dist/stepper-SWB-u_nM.mjs +323 -0
- package/dist/switch/index.mjs +4 -0
- package/dist/switch-Calk7Gyw.mjs +32 -0
- package/dist/table/index.mjs +4 -0
- package/dist/table-CsXBcQLI.mjs +68 -0
- package/dist/tabs/index.mjs +3 -0
- package/dist/tabs-D8n-dqnw.mjs +52 -0
- package/dist/tag-input/index.mjs +5 -0
- package/dist/tag-input-Di7SDNbK.mjs +284 -0
- package/dist/task-queue/index.mjs +7 -0
- package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
- package/dist/textarea/index.mjs +5 -0
- package/dist/textarea-CxE3YbC7.mjs +17 -0
- package/dist/textarea-QYRcDEpK.mjs +15 -0
- package/dist/theme/index.mjs +4 -0
- package/dist/theme-script-XBouzsNR.mjs +66 -0
- package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
- package/dist/toast/index.mjs +3 -0
- package/dist/tooltip/index.mjs +4 -0
- package/dist/tooltip-Dd3ActSS.mjs +74 -0
- package/dist/typography/index.mjs +3 -0
- package/dist/typography-UA7ZZvgJ.mjs +200 -0
- package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
- package/dist/use-stepper-BaToCYMs.mjs +2017 -0
- package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
- package/dist/utils/index.mjs +0 -1
- package/dist/utils-Bfgoe-Gm.mjs +20 -0
- package/dist/visually-hidden/index.mjs +3 -0
- package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
- package/package.json +208 -8
- package/dist/components/index.mjs +0 -8
- package/dist/providers/index.mjs +0 -4
- package/dist/theme-script-DHyLk25i.mjs +0 -11128
- /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
- /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-CdzvEnzY.mjs} +0 -0
- /package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-DgGshapa.mjs} +0 -0
- /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
|
-
|
|
33
|
+
`DatumProvider` automatically loads all design tokens, theme variables, and component styles — no extra CSS import needed.
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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,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,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,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 };
|