@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.
- package/README.md +66 -32
- 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/components/themes/index.d.ts +1 -1
- package/dist/components/themes/index.d.ts.map +1 -1
- package/dist/components/themes/types.d.ts +0 -2
- package/dist/components/themes/types.d.ts.map +1 -1
- package/dist/date-picker/index.mjs +9 -0
- 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/fonts/AllianceNo1-Medium.ttf +0 -0
- package/dist/fonts/AllianceNo1-Regular.ttf +0 -0
- package/dist/fonts/AllianceNo1-SemiBold.ttf +0 -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.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +65 -9
- 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-WL6jhkSM.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/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/{style.css → styles.css} +317 -575
- package/dist/styles.mjs +1 -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 +3 -0
- package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-CzCxEFFh.mjs} +63 -1
- 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 +223 -24
- package/dist/components/index.mjs +0 -8
- package/dist/datum.provider-D6VMjSV0.mjs +0 -37
- package/dist/providers/datum.provider.d.ts +0 -20
- package/dist/providers/datum.provider.d.ts.map +0 -1
- package/dist/providers/index.d.ts +0 -3
- package/dist/providers/index.d.ts.map +0 -1
- 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-C4JYls8q.mjs} +0 -0
- /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
|
-
|
|
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 {
|
|
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
|
-
<
|
|
40
|
+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
|
28
41
|
{/* your app */}
|
|
29
|
-
</
|
|
42
|
+
</ThemeProvider>
|
|
30
43
|
)
|
|
31
44
|
}
|
|
32
45
|
```
|
|
33
46
|
|
|
34
|
-
##
|
|
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
|
|
37
|
-
|
|
|
38
|
-
| `@datum-cloud/datum-ui`
|
|
39
|
-
| `@datum-cloud/datum-ui/
|
|
40
|
-
| `@datum-cloud/datum-ui/
|
|
41
|
-
| `@datum-cloud/datum-ui/hooks`
|
|
42
|
-
| `@datum-cloud/datum-ui/icons`
|
|
43
|
-
| `@datum-cloud/datum-ui/utils`
|
|
44
|
-
|
|
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 {
|
|
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
|
-
<
|
|
247
|
+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
|
211
248
|
<Toaster />
|
|
212
249
|
<MyComponent />
|
|
213
|
-
</
|
|
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
|
-
`
|
|
289
|
+
`ThemeProvider` supports light, dark, and system theme modes:
|
|
253
290
|
|
|
254
291
|
```tsx
|
|
255
|
-
import {
|
|
292
|
+
import { ThemeProvider } from '@datum-cloud/datum-ui/theme'
|
|
256
293
|
|
|
257
|
-
|
|
258
|
-
<DatumProvider defaultTheme="system">
|
|
294
|
+
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
|
259
295
|
{children}
|
|
260
|
-
</
|
|
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,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 };
|