@inui/design-system 0.0.0-stage → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -2
- package/dist/components/ui/accordion.d.ts +44 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +86 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/ai-avatar.d.ts +46 -0
- package/dist/components/ui/ai-avatar.d.ts.map +1 -0
- package/dist/components/ui/ai-avatar.js +43 -0
- package/dist/components/ui/ai-avatar.js.map +1 -0
- package/dist/components/ui/ai-label.d.ts +118 -0
- package/dist/components/ui/ai-label.d.ts.map +1 -0
- package/dist/components/ui/ai-label.js +184 -0
- package/dist/components/ui/ai-label.js.map +1 -0
- package/dist/components/ui/ai-sparkle.d.ts +91 -0
- package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
- package/dist/components/ui/ai-sparkle.js +200 -0
- package/dist/components/ui/ai-sparkle.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +79 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +147 -0
- package/dist/components/ui/alert-dialog.js.map +1 -0
- package/dist/components/ui/alert.d.ts +71 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +108 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/aspect-ratio.d.ts +30 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +40 -0
- package/dist/components/ui/aspect-ratio.js.map +1 -0
- package/dist/components/ui/attachment.d.ts +74 -0
- package/dist/components/ui/attachment.d.ts.map +1 -0
- package/dist/components/ui/attachment.js +142 -0
- package/dist/components/ui/attachment.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +82 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +123 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +130 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +226 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +43 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +98 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/browser-tabs.d.ts +218 -0
- package/dist/components/ui/browser-tabs.d.ts.map +1 -0
- package/dist/components/ui/browser-tabs.js +528 -0
- package/dist/components/ui/browser-tabs.js.map +1 -0
- package/dist/components/ui/bubble.d.ts +80 -0
- package/dist/components/ui/bubble.d.ts.map +1 -0
- package/dist/components/ui/bubble.js +235 -0
- package/dist/components/ui/bubble.js.map +1 -0
- package/dist/components/ui/button-group.d.ts +60 -0
- package/dist/components/ui/button-group.d.ts.map +1 -0
- package/dist/components/ui/button-group.js +89 -0
- package/dist/components/ui/button-group.js.map +1 -0
- package/dist/components/ui/button.d.ts +157 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +144 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/calendar.d.ts +57 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +163 -0
- package/dist/components/ui/calendar.js.map +1 -0
- package/dist/components/ui/card.d.ts +96 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +131 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/carousel.d.ts +86 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +207 -0
- package/dist/components/ui/carousel.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +54 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +78 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/collapsible.d.ts +45 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +65 -0
- package/dist/components/ui/collapsible.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +111 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +264 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +71 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +133 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +70 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +163 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/count-badge.d.ts +40 -0
- package/dist/components/ui/count-badge.d.ts.map +1 -0
- package/dist/components/ui/count-badge.js +43 -0
- package/dist/components/ui/count-badge.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +1026 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +3423 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +73 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +145 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/direction.d.ts +35 -0
- package/dist/components/ui/direction.d.ts.map +1 -0
- package/dist/components/ui/direction.js +35 -0
- package/dist/components/ui/direction.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +53 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +115 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +75 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +173 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/dropzone.d.ts +141 -0
- package/dist/components/ui/dropzone.d.ts.map +1 -0
- package/dist/components/ui/dropzone.js +257 -0
- package/dist/components/ui/dropzone.js.map +1 -0
- package/dist/components/ui/empty.d.ts +94 -0
- package/dist/components/ui/empty.d.ts.map +1 -0
- package/dist/components/ui/empty.js +149 -0
- package/dist/components/ui/empty.js.map +1 -0
- package/dist/components/ui/field.d.ts +122 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/components/ui/field.js +254 -0
- package/dist/components/ui/field.js.map +1 -0
- package/dist/components/ui/hover-card.d.ts +51 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +101 -0
- package/dist/components/ui/hover-card.js.map +1 -0
- package/dist/components/ui/input-group.d.ts +62 -0
- package/dist/components/ui/input-group.d.ts.map +1 -0
- package/dist/components/ui/input-group.js +115 -0
- package/dist/components/ui/input-group.js.map +1 -0
- package/dist/components/ui/input-otp.d.ts +52 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +89 -0
- package/dist/components/ui/input-otp.js.map +1 -0
- package/dist/components/ui/input.d.ts +47 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +47 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/item.d.ts +74 -0
- package/dist/components/ui/item.d.ts.map +1 -0
- package/dist/components/ui/item.js +176 -0
- package/dist/components/ui/item.js.map +1 -0
- package/dist/components/ui/kbd.d.ts +41 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +58 -0
- package/dist/components/ui/kbd.js.map +1 -0
- package/dist/components/ui/key-value-pair.d.ts +56 -0
- package/dist/components/ui/key-value-pair.d.ts.map +1 -0
- package/dist/components/ui/key-value-pair.js +110 -0
- package/dist/components/ui/key-value-pair.js.map +1 -0
- package/dist/components/ui/label.d.ts +83 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +85 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/marker.d.ts +54 -0
- package/dist/components/ui/marker.d.ts.map +1 -0
- package/dist/components/ui/marker.js +79 -0
- package/dist/components/ui/marker.js.map +1 -0
- package/dist/components/ui/menubar.d.ts +72 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +174 -0
- package/dist/components/ui/menubar.js.map +1 -0
- package/dist/components/ui/message-scroller.d.ts +53 -0
- package/dist/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/components/ui/message-scroller.js +95 -0
- package/dist/components/ui/message-scroller.js.map +1 -0
- package/dist/components/ui/message.d.ts +63 -0
- package/dist/components/ui/message.d.ts.map +1 -0
- package/dist/components/ui/message.js +92 -0
- package/dist/components/ui/message.js.map +1 -0
- package/dist/components/ui/meta-list.d.ts +55 -0
- package/dist/components/ui/meta-list.d.ts.map +1 -0
- package/dist/components/ui/meta-list.js +75 -0
- package/dist/components/ui/meta-list.js.map +1 -0
- package/dist/components/ui/native-select.d.ts +51 -0
- package/dist/components/ui/native-select.d.ts.map +1 -0
- package/dist/components/ui/native-select.js +75 -0
- package/dist/components/ui/native-select.js.map +1 -0
- package/dist/components/ui/navigation-menu.d.ts +56 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +116 -0
- package/dist/components/ui/navigation-menu.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +223 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +233 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +68 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +145 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +61 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +144 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +35 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +46 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/questionnaire.d.ts +92 -0
- package/dist/components/ui/questionnaire.d.ts.map +1 -0
- package/dist/components/ui/questionnaire.js +277 -0
- package/dist/components/ui/questionnaire.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +50 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/resizable.d.ts +44 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +73 -0
- package/dist/components/ui/resizable.js.map +1 -0
- package/dist/components/ui/scroll-area.d.ts +55 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +72 -0
- package/dist/components/ui/scroll-area.js.map +1 -0
- package/dist/components/ui/select.d.ts +274 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +609 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +31 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +38 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/sheet.d.ts +59 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +128 -0
- package/dist/components/ui/sheet.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +166 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +512 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +35 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +46 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/slider.d.ts +39 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +75 -0
- package/dist/components/ui/slider.js.map +1 -0
- package/dist/components/ui/sonner.d.ts +56 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +96 -0
- package/dist/components/ui/sonner.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +37 -0
- package/dist/components/ui/spinner.d.ts.map +1 -0
- package/dist/components/ui/spinner.js +51 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/stepper.d.ts +84 -0
- package/dist/components/ui/stepper.d.ts.map +1 -0
- package/dist/components/ui/stepper.js +145 -0
- package/dist/components/ui/stepper.js.map +1 -0
- package/dist/components/ui/switch.d.ts +36 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +44 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +80 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +130 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +87 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +128 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +35 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +38 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/time-picker.d.ts +58 -0
- package/dist/components/ui/time-picker.d.ts.map +1 -0
- package/dist/components/ui/time-picker.js +188 -0
- package/dist/components/ui/time-picker.js.map +1 -0
- package/dist/components/ui/toggle-group.d.ts +52 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +94 -0
- package/dist/components/ui/toggle-group.js.map +1 -0
- package/dist/components/ui/toggle.d.ts +50 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +70 -0
- package/dist/components/ui/toggle.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +51 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +78 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/hooks/use-dark-mode.d.ts +2 -0
- package/dist/hooks/use-dark-mode.d.ts.map +1 -0
- package/dist/hooks/use-dark-mode.js +36 -0
- package/dist/hooks/use-dark-mode.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +20 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/lib/scope.d.ts +64 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +74 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +11 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/theme-tokens.css +537 -0
- package/dist/theme.css +417 -0
- package/package.json +103 -3
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { getPortalContainer } from "../../lib/scope.js";
|
|
4
|
+
import "react";
|
|
5
|
+
import { DropdownMenu as DropdownMenu$1 } from "radix-ui";
|
|
6
|
+
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/components/ui/dropdown-menu.tsx
|
|
9
|
+
function DropdownMenu({ ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Root, {
|
|
11
|
+
"data-slot": "dropdown-menu",
|
|
12
|
+
...props
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
function DropdownMenuPortal({ ...props }) {
|
|
16
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, {
|
|
17
|
+
"data-slot": "dropdown-menu-portal",
|
|
18
|
+
container: getPortalContainer(),
|
|
19
|
+
...props
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function DropdownMenuTrigger({ ...props }) {
|
|
23
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Trigger, {
|
|
24
|
+
"data-slot": "dropdown-menu-trigger",
|
|
25
|
+
...props
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function DropdownMenuContent({ className, align = "start", sideOffset = 4, ...props }) {
|
|
29
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, {
|
|
30
|
+
container: getPortalContainer(),
|
|
31
|
+
children: /* @__PURE__ */ jsx(DropdownMenu$1.Content, {
|
|
32
|
+
"data-slot": "dropdown-menu-content",
|
|
33
|
+
sideOffset,
|
|
34
|
+
align,
|
|
35
|
+
className: cn("z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-popover ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className),
|
|
36
|
+
...props
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function DropdownMenuGroup({ ...props }) {
|
|
41
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Group, {
|
|
42
|
+
"data-slot": "dropdown-menu-group",
|
|
43
|
+
...props
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
47
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Item, {
|
|
48
|
+
"data-slot": "dropdown-menu-item",
|
|
49
|
+
"data-inset": inset,
|
|
50
|
+
"data-variant": variant,
|
|
51
|
+
className: cn("group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive", className),
|
|
52
|
+
...props
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props }) {
|
|
56
|
+
return /* @__PURE__ */ jsxs(DropdownMenu$1.CheckboxItem, {
|
|
57
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
58
|
+
"data-inset": inset,
|
|
59
|
+
className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
60
|
+
checked,
|
|
61
|
+
...props,
|
|
62
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
63
|
+
className: "pointer-events-none absolute right-2 flex items-center justify-center",
|
|
64
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
65
|
+
children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
66
|
+
}), children]
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function DropdownMenuRadioGroup({ ...props }) {
|
|
70
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.RadioGroup, {
|
|
71
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
72
|
+
...props
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function DropdownMenuRadioItem({ className, children, inset, ...props }) {
|
|
76
|
+
return /* @__PURE__ */ jsxs(DropdownMenu$1.RadioItem, {
|
|
77
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
78
|
+
"data-inset": inset,
|
|
79
|
+
className: cn("relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
80
|
+
...props,
|
|
81
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
82
|
+
className: "pointer-events-none absolute right-2 flex items-center justify-center",
|
|
83
|
+
"data-slot": "dropdown-menu-radio-item-indicator",
|
|
84
|
+
children: /* @__PURE__ */ jsx(DropdownMenu$1.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, {}) })
|
|
85
|
+
}), children]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
89
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Label, {
|
|
90
|
+
"data-slot": "dropdown-menu-label",
|
|
91
|
+
"data-inset": inset,
|
|
92
|
+
className: cn("px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7", className),
|
|
93
|
+
...props
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
97
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Separator, {
|
|
98
|
+
"data-slot": "dropdown-menu-separator",
|
|
99
|
+
className: cn("-mx-1 my-1 h-px bg-border", className),
|
|
100
|
+
...props
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function DropdownMenuShortcut({ className, ...props }) {
|
|
104
|
+
return /* @__PURE__ */ jsx("span", {
|
|
105
|
+
"data-slot": "dropdown-menu-shortcut",
|
|
106
|
+
className: cn("ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground", className),
|
|
107
|
+
...props
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function DropdownMenuSub({ ...props }) {
|
|
111
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.Sub, {
|
|
112
|
+
"data-slot": "dropdown-menu-sub",
|
|
113
|
+
...props
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
117
|
+
return /* @__PURE__ */ jsxs(DropdownMenu$1.SubTrigger, {
|
|
118
|
+
"data-slot": "dropdown-menu-sub-trigger",
|
|
119
|
+
"data-inset": inset,
|
|
120
|
+
className: cn("flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className),
|
|
121
|
+
...props,
|
|
122
|
+
children: [children, /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto" })]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function DropdownMenuSubContent({ className, ...props }) {
|
|
126
|
+
return /* @__PURE__ */ jsx(DropdownMenu$1.SubContent, {
|
|
127
|
+
"data-slot": "dropdown-menu-sub-content",
|
|
128
|
+
className: cn("z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-popover ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className),
|
|
129
|
+
...props
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* WHEN TO USE
|
|
134
|
+
* - Secondary or tertiary actions on a row or entity, behind a "..." overflow trigger
|
|
135
|
+
* - View/appearance toggles via DropdownMenuCheckboxItem, or a single-select list via
|
|
136
|
+
* DropdownMenuRadioGroup + DropdownMenuRadioItem
|
|
137
|
+
* - Actions that benefit from grouping (DropdownMenuGroup/Label/Separator), keyboard shortcut
|
|
138
|
+
* hints (DropdownMenuShortcut), or nested submenus (DropdownMenuSub)
|
|
139
|
+
*
|
|
140
|
+
* NOT WHEN
|
|
141
|
+
* - The action is a page's primary action — use Button directly
|
|
142
|
+
* - The user is picking a value for a form field — use Select
|
|
143
|
+
* - The menu opens on right-click rather than a click trigger — use ContextMenu
|
|
144
|
+
*
|
|
145
|
+
* KEY RULES
|
|
146
|
+
* - DropdownMenuItem accepts variant="default" | "destructive"; use "destructive" for
|
|
147
|
+
* irreversible entries (delete, log out, remove) instead of custom text-color classes
|
|
148
|
+
* - Wrap DropdownMenuTrigger's child in asChild so the trigger renders as that element
|
|
149
|
+
* (e.g. a Button) rather than being wrapped in an extra <button>
|
|
150
|
+
* - DropdownMenuContent defaults to align="start" and sideOffset={4}; width is controlled
|
|
151
|
+
* via className (e.g. "w-56"), not a prop
|
|
152
|
+
* - DropdownMenuCheckboxItem/RadioItem are controlled — checked/value and their onChange
|
|
153
|
+
* handlers must be wired by the caller, they hold no internal state
|
|
154
|
+
*
|
|
155
|
+
* USAGE
|
|
156
|
+
* <DropdownMenu>
|
|
157
|
+
* <DropdownMenuTrigger asChild><Button variant="outline">Open menu</Button></DropdownMenuTrigger>
|
|
158
|
+
* <DropdownMenuContent className="w-56">
|
|
159
|
+
* <DropdownMenuLabel>My account</DropdownMenuLabel>
|
|
160
|
+
* <DropdownMenuSeparator />
|
|
161
|
+
* <DropdownMenuItem><UserIcon />Profile<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut></DropdownMenuItem>
|
|
162
|
+
* <DropdownMenuItem variant="destructive"><LogOutIcon />Log out</DropdownMenuItem>
|
|
163
|
+
* </DropdownMenuContent>
|
|
164
|
+
* </DropdownMenu>
|
|
165
|
+
*
|
|
166
|
+
* <DropdownMenuCheckboxItem checked={showStatus} onCheckedChange={setShowStatus}>
|
|
167
|
+
* Status bar
|
|
168
|
+
* </DropdownMenuCheckboxItem>
|
|
169
|
+
*/
|
|
170
|
+
//#endregion
|
|
171
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
|
|
172
|
+
|
|
173
|
+
//# sourceMappingURL=dropdown-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown-menu.js","names":[],"sources":["../../../src/components/ui/dropdown-menu.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * DropdownMenu — menu of actions or options opened from a trigger element\n *\n * Built on the Radix DropdownMenu primitive (Root/Trigger/Content/Item/...),\n * styled with MMDS popover/accent tokens, with a `variant=\"destructive\"`\n * styling hook on DropdownMenuItem for dangerous entries.\n */\n\nimport * as React from \"react\"\nimport { DropdownMenu as DropdownMenuPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { getPortalContainer } from \"@/lib/scope\"\nimport { CheckIcon, ChevronRightIcon } from \"lucide-react\"\n\nfunction DropdownMenu({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {\n return <DropdownMenuPrimitive.Root data-slot=\"dropdown-menu\" {...props} />\n}\n\nfunction DropdownMenuPortal({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {\n return (\n <DropdownMenuPrimitive.Portal\n data-slot=\"dropdown-menu-portal\"\n container={getPortalContainer()}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuTrigger({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {\n return (\n <DropdownMenuPrimitive.Trigger\n data-slot=\"dropdown-menu-trigger\"\n {...props}\n />\n )\n}\n\nfunction DropdownMenuContent({\n className,\n align = \"start\",\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {\n return (\n <DropdownMenuPrimitive.Portal container={getPortalContainer()}>\n <DropdownMenuPrimitive.Content\n data-slot=\"dropdown-menu-content\"\n sideOffset={sideOffset}\n align={align}\n className={cn(\n \"z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-popover ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:overflow-hidden data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n className\n )}\n {...props}\n />\n </DropdownMenuPrimitive.Portal>\n )\n}\n\nfunction DropdownMenuGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {\n return (\n <DropdownMenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />\n )\n}\n\nfunction DropdownMenuItem({\n className,\n inset,\n variant = \"default\",\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {\n inset?: boolean\n variant?: \"default\" | \"destructive\"\n}) {\n return (\n <DropdownMenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"group/dropdown-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuCheckboxItem({\n className,\n children,\n checked,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n checked={checked}\n {...props}\n >\n <span\n className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n data-slot=\"dropdown-menu-checkbox-item-indicator\"\n >\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.CheckboxItem>\n )\n}\n\nfunction DropdownMenuRadioGroup({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {\n return (\n <DropdownMenuPrimitive.RadioGroup\n data-slot=\"dropdown-menu-radio-group\"\n {...props}\n />\n )\n}\n\nfunction DropdownMenuRadioItem({\n className,\n children,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n <span\n className=\"pointer-events-none absolute right-2 flex items-center justify-center\"\n data-slot=\"dropdown-menu-radio-item-indicator\"\n >\n <DropdownMenuPrimitive.ItemIndicator>\n <CheckIcon />\n </DropdownMenuPrimitive.ItemIndicator>\n </span>\n {children}\n </DropdownMenuPrimitive.RadioItem>\n )\n}\n\nfunction DropdownMenuLabel({\n className,\n inset,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.Label\n data-slot=\"dropdown-menu-label\"\n data-inset={inset}\n className={cn(\n \"px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {\n return (\n <DropdownMenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-border\", className)}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuShortcut({\n className,\n ...props\n}: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\n \"ml-auto text-xs tracking-widest text-muted-foreground group-focus/dropdown-menu-item:text-accent-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction DropdownMenuSub({\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {\n return <DropdownMenuPrimitive.Sub data-slot=\"dropdown-menu-sub\" {...props} />\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {\n inset?: boolean\n}) {\n return (\n <DropdownMenuPrimitive.SubTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-7 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className\n )}\n {...props}\n >\n {children}\n <ChevronRightIcon className=\"ml-auto\" />\n </DropdownMenuPrimitive.SubTrigger>\n )\n}\n\nfunction DropdownMenuSubContent({\n className,\n ...props\n}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {\n return (\n <DropdownMenuPrimitive.SubContent\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(\n \"z-50 min-w-[96px] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-lg bg-popover p-1 text-popover-foreground shadow-popover ring-1 ring-foreground/10 duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95\",\n className\n )}\n {...props}\n />\n )\n}\n\nexport {\n DropdownMenu,\n DropdownMenuPortal,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuLabel,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubTrigger,\n DropdownMenuSubContent,\n}\n\n/**\n * WHEN TO USE\n * - Secondary or tertiary actions on a row or entity, behind a \"...\" overflow trigger\n * - View/appearance toggles via DropdownMenuCheckboxItem, or a single-select list via\n * DropdownMenuRadioGroup + DropdownMenuRadioItem\n * - Actions that benefit from grouping (DropdownMenuGroup/Label/Separator), keyboard shortcut\n * hints (DropdownMenuShortcut), or nested submenus (DropdownMenuSub)\n *\n * NOT WHEN\n * - The action is a page's primary action — use Button directly\n * - The user is picking a value for a form field — use Select\n * - The menu opens on right-click rather than a click trigger — use ContextMenu\n *\n * KEY RULES\n * - DropdownMenuItem accepts variant=\"default\" | \"destructive\"; use \"destructive\" for\n * irreversible entries (delete, log out, remove) instead of custom text-color classes\n * - Wrap DropdownMenuTrigger's child in asChild so the trigger renders as that element\n * (e.g. a Button) rather than being wrapped in an extra <button>\n * - DropdownMenuContent defaults to align=\"start\" and sideOffset={4}; width is controlled\n * via className (e.g. \"w-56\"), not a prop\n * - DropdownMenuCheckboxItem/RadioItem are controlled — checked/value and their onChange\n * handlers must be wired by the caller, they hold no internal state\n *\n * USAGE\n * <DropdownMenu>\n * <DropdownMenuTrigger asChild><Button variant=\"outline\">Open menu</Button></DropdownMenuTrigger>\n * <DropdownMenuContent className=\"w-56\">\n * <DropdownMenuLabel>My account</DropdownMenuLabel>\n * <DropdownMenuSeparator />\n * <DropdownMenuItem><UserIcon />Profile<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut></DropdownMenuItem>\n * <DropdownMenuItem variant=\"destructive\"><LogOutIcon />Log out</DropdownMenuItem>\n * </DropdownMenuContent>\n * </DropdownMenu>\n *\n * <DropdownMenuCheckboxItem checked={showStatus} onCheckedChange={setShowStatus}>\n * Status bar\n * </DropdownMenuCheckboxItem>\n */\n"],"mappings":";;;;;;;;AAiBA,SAAS,aAAa,EACpB,GAAG,SACuD;CAC1D,OAAO,oBAAC,eAAsB,MAAvB;EAA4B,aAAU;EAAgB,GAAI;CAAQ,CAAA;AAC3E;AAEA,SAAS,mBAAmB,EAC1B,GAAG,SACyD;CAC5D,OACE,oBAAC,eAAsB,QAAvB;EACE,aAAU;EACV,WAAW,mBAAmB;EAC9B,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,oBAAoB,EAC3B,GAAG,SAC0D;CAC7D,OACE,oBAAC,eAAsB,SAAvB;EACE,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,oBAAoB,EAC3B,WACA,QAAQ,SACR,aAAa,GACb,GAAG,SAC0D;CAC7D,OACE,oBAAC,eAAsB,QAAvB;EAA8B,WAAW,mBAAmB;EAC1D,UAAA,oBAAC,eAAsB,SAAvB;GACE,aAAU;GACE;GACL;GACP,WAAW,GACT,uqBACA,SACF;GACA,GAAI;EACL,CAAA;CAC2B,CAAA;AAElC;AAEA,SAAS,kBAAkB,EACzB,GAAG,SACwD;CAC3D,OACE,oBAAC,eAAsB,OAAvB;EAA6B,aAAU;EAAsB,GAAI;CAAQ,CAAA;AAE7E;AAEA,SAAS,iBAAiB,EACxB,WACA,OACA,UAAU,WACV,GAAG,SAIF;CACD,OACE,oBAAC,eAAsB,MAAvB;EACE,aAAU;EACV,cAAY;EACZ,gBAAc;EACd,WAAW,GACT,8oBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,yBAAyB,EAChC,WACA,UACA,SACA,OACA,GAAG,SAGF;CACD,OACE,qBAAC,eAAsB,cAAvB;EACE,aAAU;EACV,cAAY;EACZ,WAAW,GACT,8VACA,SACF;EACS;EACT,GAAI;EARN,UAAA,CAUE,oBAAC,QAAD;GACE,WAAU;GACV,aAAU;GAEV,UAAA,oBAAC,eAAsB,eAAvB,EAAA,UACE,oBAAC,WAAD,CAAY,CAAA,EACuB,CAAA;EACjC,CAAA,GACL,QACiC;;AAExC;AAEA,SAAS,uBAAuB,EAC9B,GAAG,SAC6D;CAChE,OACE,oBAAC,eAAsB,YAAvB;EACE,aAAU;EACV,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,sBAAsB,EAC7B,WACA,UACA,OACA,GAAG,SAGF;CACD,OACE,qBAAC,eAAsB,WAAvB;EACE,aAAU;EACV,cAAY;EACZ,WAAW,GACT,8VACA,SACF;EACA,GAAI;EAPN,UAAA,CASE,oBAAC,QAAD;GACE,WAAU;GACV,aAAU;GAEV,UAAA,oBAAC,eAAsB,eAAvB,EAAA,UACE,oBAAC,WAAD,CAAY,CAAA,EACuB,CAAA;EACjC,CAAA,GACL,QAC8B;;AAErC;AAEA,SAAS,kBAAkB,EACzB,WACA,OACA,GAAG,SAGF;CACD,OACE,oBAAC,eAAsB,OAAvB;EACE,aAAU;EACV,cAAY;EACZ,WAAW,GACT,yEACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,sBAAsB,EAC7B,WACA,GAAG,SAC4D;CAC/D,OACE,oBAAC,eAAsB,WAAvB;EACE,aAAU;EACV,WAAW,GAAG,6BAA6B,SAAS;EACpD,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,qBAAqB,EAC5B,WACA,GAAG,SAC4B;CAC/B,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,+GACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,gBAAgB,EACvB,GAAG,SACsD;CACzD,OAAO,oBAAC,eAAsB,KAAvB;EAA2B,aAAU;EAAoB,GAAI;CAAQ,CAAA;AAC9E;AAEA,SAAS,uBAAuB,EAC9B,WACA,OACA,UACA,GAAG,SAGF;CACD,OACE,qBAAC,eAAsB,YAAvB;EACE,aAAU;EACV,cAAY;EACZ,WAAW,GACT,yXACA,SACF;EACA,GAAI;EAPN,UAAA,CASG,UACD,oBAAC,kBAAD,EAAkB,WAAU,UAAW,CAAA,CACP;;AAEtC;AAEA,SAAS,uBAAuB,EAC9B,WACA,GAAG,SAC6D;CAChE,OACE,oBAAC,eAAsB,YAAvB;EACE,aAAU;EACV,WAAW,GACT,shBACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropzone — click-or-drag target that hands files to the caller.
|
|
3
|
+
*
|
|
4
|
+
* A composite over Attachment, not a new primitive: it's Attachment's
|
|
5
|
+
* `state="idle"` dashed-border look wired up with a native hidden file
|
|
6
|
+
* input plus drag-and-drop handlers. Selected files themselves aren't
|
|
7
|
+
* rendered here — map them to a stack of Attachment rows below the
|
|
8
|
+
* dropzone (state="uploading"/"error"/"done" per file, AttachmentAction
|
|
9
|
+
* for retry/remove) the same way any other Attachment list works.
|
|
10
|
+
* FileTypeIcon (below) gives each row a colored icon circle per file type,
|
|
11
|
+
* as an AttachmentMedia composition.
|
|
12
|
+
*/
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import { AttachmentMedia } from "../../components/ui/attachment";
|
|
15
|
+
type FileTypeFamily = "video" | "image" | "document" | "audio" | "other";
|
|
16
|
+
/** Derives a file-type family from a File or a plain MIME-type string. */
|
|
17
|
+
declare function getFileTypeFamily(file: File | string): FileTypeFamily;
|
|
18
|
+
/**
|
|
19
|
+
* FileTypeIcon — colored icon-circle, per file type.
|
|
20
|
+
*
|
|
21
|
+
* A thin AttachmentMedia composition, not a new primitive: swaps its default
|
|
22
|
+
* square muted-bg look for a `rounded-full` circle tinted per file-type
|
|
23
|
+
* family (derived from `file`'s MIME type, or pass `family` directly).
|
|
24
|
+
*/
|
|
25
|
+
declare function FileTypeIcon({ file, family, className, ...props }: Omit<React.ComponentProps<typeof AttachmentMedia>, "variant" | "children"> & {
|
|
26
|
+
file?: File | string;
|
|
27
|
+
family?: FileTypeFamily;
|
|
28
|
+
}): React.JSX.Element;
|
|
29
|
+
declare function Dropzone({ className, onFilesSelected, accept, multiple, disabled, size, state, activeContent, errorContent, children, id, "aria-describedby": ariaDescribedby, "aria-invalid": ariaInvalid, inputRef, ...props }: Omit<React.ComponentProps<"label">, "onDrop" | "onDragOver" | "onDragLeave"> & {
|
|
30
|
+
onFilesSelected: (files: File[]) => void;
|
|
31
|
+
accept?: string;
|
|
32
|
+
multiple?: boolean;
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
size?: "default" | "sm" | "xs";
|
|
35
|
+
/** Overrides the drag-derived visual state — mainly for demos; leave unset for real drag-and-drop. */
|
|
36
|
+
state?: "default" | "active" | "error";
|
|
37
|
+
/** Overrides the built-in "active" feedback content (icon + "Drag your files here"). */
|
|
38
|
+
activeContent?: React.ReactNode;
|
|
39
|
+
/** Overrides the built-in "error" feedback content (icon + "File format not accepted"). */
|
|
40
|
+
errorContent?: React.ReactNode;
|
|
41
|
+
/** Ref to the underlying (visually-hidden) `<input type="file">` — the actual control `id`/`aria-describedby`/`aria-invalid` land on, not the outer `<label>`. */
|
|
42
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
43
|
+
}): React.JSX.Element;
|
|
44
|
+
export { Dropzone, FileTypeIcon, getFileTypeFamily };
|
|
45
|
+
/**
|
|
46
|
+
* WHEN TO USE
|
|
47
|
+
* - A drag-and-drop or click-to-browse target for picking one or more files
|
|
48
|
+
* - Paired with a list of Attachment rows below it, one per selected file,
|
|
49
|
+
* for per-file progress/error/remove — this is Dropzone's job, not the
|
|
50
|
+
* list's; there's no dedicated list component, just Attachment rows
|
|
51
|
+
* - FileTypeIcon on each row's AttachmentMedia slot for colored, per-type
|
|
52
|
+
* icon circles (video/image/document/audio/other)
|
|
53
|
+
*
|
|
54
|
+
* NOT WHEN
|
|
55
|
+
* - A plain, unstyled file input with no drop target or preview — use Input type="file"
|
|
56
|
+
* - Files are already attached and just need to be displayed — use Attachment/AttachmentGroup directly
|
|
57
|
+
*
|
|
58
|
+
* KEY RULES
|
|
59
|
+
* - `onFilesSelected(files: File[])` fires from both the native picker (click) and drag-and-drop —
|
|
60
|
+
* handle it once, not twice
|
|
61
|
+
* - `size`: "default" | "sm" | "xs" — controls LAYOUT (Dropzone owns this via `dropzoneVariants`,
|
|
62
|
+
* not just padding/icon-size): "default" stacks the icon above the text, "sm" puts the icon on
|
|
63
|
+
* the left instead (row, not stacked — the format/size hint stays, it's not optional info, just
|
|
64
|
+
* repositioned), "xs" drops the icon entirely down to one compact text line — same rule every
|
|
65
|
+
* other xs UI state in this library follows (see Empty). The children themselves (icon, hint
|
|
66
|
+
* text) are still fully custom — Dropzone only dictates the flex direction and whether there's
|
|
67
|
+
* room to show a leading icon; get the actual "which size shows what" split from a real example
|
|
68
|
+
* like the Sizes story, don't just eyeball it from this list.
|
|
69
|
+
* - `state` is normally left unset — Dropzone derives "active" (valid drag-over) and "error"
|
|
70
|
+
* (drag-over with a file type `accept` would reject) from real drag events by inspecting
|
|
71
|
+
* `dataTransfer.items[].type`, which browsers expose pre-drop. Pass `state` explicitly only
|
|
72
|
+
* to force a look for a demo; a real Dropzone should let this stay derived.
|
|
73
|
+
* - Unlike the idle look, "active"/"error" REPLACE `children` with built-in feedback (a filled
|
|
74
|
+
* icon + a short line — "Drag your files here" / "File format not accepted", no icon at
|
|
75
|
+
* `size="xs"` same as everywhere else) — override via `activeContent`/`errorContent` if the
|
|
76
|
+
* defaults don't fit; this is deliberate, not something to fight with children-based tricks.
|
|
77
|
+
* - The drag-over "error" look is a file-type warning ONLY, and it does not block the drop —
|
|
78
|
+
* the file still reaches `onFilesSelected`. File size can't be checked before drop (browsers
|
|
79
|
+
* don't expose it until then), so "file too large" is always a file-list-level error — render
|
|
80
|
+
* that file's Attachment with `state="error"` and the reason in AttachmentDescription, not
|
|
81
|
+
* as a Dropzone state.
|
|
82
|
+
* - The dropzone renders as a `label` wrapping a visually-hidden `<input type="file">`, so
|
|
83
|
+
* clicking anywhere in it opens the OS file picker with zero extra JS
|
|
84
|
+
* - `accept`/`multiple`/`disabled` map straight to the underlying input's attributes
|
|
85
|
+
* - `id`/`aria-describedby`/`aria-invalid` land on the underlying `<input type="file">`, not the
|
|
86
|
+
* outer `<label>` — a label only passes its accessible *name* through, not these relationships —
|
|
87
|
+
* so wire hint/error text (e.g. a rejection reason) through `aria-describedby`/`aria-invalid`
|
|
88
|
+
* rather than assuming label-wrapping is enough. `id` falls back to a generated one if unset.
|
|
89
|
+
* `inputRef` reaches the same underlying input if you need the ref itself.
|
|
90
|
+
* - Idle-state content (icon, "Drag files here or browse", format/size hints, an optional sample-
|
|
91
|
+
* file link) is fully custom via children — nothing is hardcoded, this component only owns the
|
|
92
|
+
* interaction wiring, the idle-state look, and the active/error feedback swap above
|
|
93
|
+
* - FileTypeIcon derives its color+icon from `file`'s MIME type (or pass `family` directly) — it's
|
|
94
|
+
* an AttachmentMedia composition (`rounded-full` + a per-family tint), not a new component tree
|
|
95
|
+
* - Attachment defaults to `w-fit` (sized to its own content, right for a horizontal chip strip) —
|
|
96
|
+
* for a vertical file list where every row should line up at the same width, pass
|
|
97
|
+
* `className="w-full"` on each Attachment row
|
|
98
|
+
* - AttachmentAction covers both retry and remove — render two of them (retry then remove) on an
|
|
99
|
+
* error row, one (remove) otherwise
|
|
100
|
+
*
|
|
101
|
+
* USAGE
|
|
102
|
+
* const [files, setFiles] = React.useState<Array<{ file: File; state: "uploading" | "error" | "done" }>>([])
|
|
103
|
+
* <div className="grid gap-3">
|
|
104
|
+
* <Dropzone
|
|
105
|
+
* accept="image/png,image/jpeg"
|
|
106
|
+
* onFilesSelected={(picked) =>
|
|
107
|
+
* setFiles((prev) => [...prev, ...picked.map((file) => ({ file, state: "uploading" as const }))])
|
|
108
|
+
* }
|
|
109
|
+
* >
|
|
110
|
+
* <UploadIcon className="text-muted-foreground" />
|
|
111
|
+
* <p className="text-sm font-medium">Drag your files here or <span className="text-primary">browse</span></p>
|
|
112
|
+
* <p className="text-xs text-muted-foreground">PNG or JPEG, up to 25MB</p>
|
|
113
|
+
* </Dropzone>
|
|
114
|
+
*
|
|
115
|
+
* {files.map(({ file, state }) => (
|
|
116
|
+
* <Attachment key={file.name} state={state} className="w-full">
|
|
117
|
+
* <FileTypeIcon file={file} />
|
|
118
|
+
* <AttachmentContent>
|
|
119
|
+
* <AttachmentTitle>{file.name}</AttachmentTitle>
|
|
120
|
+
* <AttachmentDescription>
|
|
121
|
+
* {state === "error" ? "File exceeds the 25MB limit" : `${(file.size / 1024).toFixed(1)} KB`}
|
|
122
|
+
* </AttachmentDescription>
|
|
123
|
+
* </AttachmentContent>
|
|
124
|
+
* <AttachmentActions>
|
|
125
|
+
* {state === "error" && (
|
|
126
|
+
* <AttachmentAction aria-label={`Retry ${file.name}`} onClick={() => retry(file)}>
|
|
127
|
+
* <RefreshCwIcon />
|
|
128
|
+
* </AttachmentAction>
|
|
129
|
+
* )}
|
|
130
|
+
* <AttachmentAction
|
|
131
|
+
* aria-label={`Remove ${file.name}`}
|
|
132
|
+
* onClick={() => setFiles((prev) => prev.filter((f) => f.file !== file))}
|
|
133
|
+
* >
|
|
134
|
+
* <XIcon />
|
|
135
|
+
* </AttachmentAction>
|
|
136
|
+
* </AttachmentActions>
|
|
137
|
+
* </Attachment>
|
|
138
|
+
* ))}
|
|
139
|
+
* </div>
|
|
140
|
+
*/
|
|
141
|
+
//# sourceMappingURL=dropzone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropzone.d.ts","sourceRoot":"","sources":["../../../src/components/ui/dropzone.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,OAAO,EAAc,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAExE,KAAK,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAA;AAExE,0EAA0E;AAC1E,iBAAS,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,cAAc,CAO9D;AAkDD;;;;;;GAMG;AACH,iBAAS,YAAY,CAAC,EACpB,IAAI,EACJ,MAAM,EACN,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CACL,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,EAC5C,SAAS,GAAG,UAAU,CACvB,GAAG;IACF,IAAI,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,cAAc,CAAA;CACxB,qBAaA;AA4ED,iBAAS,QAAQ,CAAC,EAChB,SAAS,EACT,eAAe,EACf,MAAM,EACN,QAAe,EACf,QAAQ,EACR,IAAgB,EAChB,KAAK,EACL,aAAa,EACb,YAAY,EACZ,QAAQ,EACR,EAAE,EACF,kBAAkB,EAAE,eAAe,EACnC,cAAc,EAAE,WAAW,EAC3B,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,IAAI,CACL,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAC7B,QAAQ,GAAG,YAAY,GAAG,aAAa,CACxC,GAAG;IACF,eAAe,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAA;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAA;IAC9B,sGAAsG;IACtG,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAA;IACtC,wFAAwF;IACxF,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC/B,2FAA2F;IAC3F,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,kKAAkK;IAClK,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAA;CACvC,qBAyEA;AAED,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAA;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+FG"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils.js";
|
|
2
|
+
import { Attachment, AttachmentMedia } from "./attachment.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ArrowDownToLineIcon, FileAudioIcon, FileIcon, FileTextIcon, FileVideoIcon, ImageIcon } from "lucide-react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { cva } from "class-variance-authority";
|
|
7
|
+
//#region src/components/ui/dropzone.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Dropzone — click-or-drag target that hands files to the caller.
|
|
10
|
+
*
|
|
11
|
+
* A composite over Attachment, not a new primitive: it's Attachment's
|
|
12
|
+
* `state="idle"` dashed-border look wired up with a native hidden file
|
|
13
|
+
* input plus drag-and-drop handlers. Selected files themselves aren't
|
|
14
|
+
* rendered here — map them to a stack of Attachment rows below the
|
|
15
|
+
* dropzone (state="uploading"/"error"/"done" per file, AttachmentAction
|
|
16
|
+
* for retry/remove) the same way any other Attachment list works.
|
|
17
|
+
* FileTypeIcon (below) gives each row a colored icon circle per file type,
|
|
18
|
+
* as an AttachmentMedia composition.
|
|
19
|
+
*/
|
|
20
|
+
/** Derives a file-type family from a File or a plain MIME-type string. */
|
|
21
|
+
function getFileTypeFamily(file) {
|
|
22
|
+
const type = typeof file === "string" ? file : file.type;
|
|
23
|
+
if (type.startsWith("video/")) return "video";
|
|
24
|
+
if (type.startsWith("image/")) return "image";
|
|
25
|
+
if (type.startsWith("audio/")) return "audio";
|
|
26
|
+
if (type.startsWith("application/")) return "document";
|
|
27
|
+
return "other";
|
|
28
|
+
}
|
|
29
|
+
/** Whether a dragged item's MIME type matches an `accept` list (comma-separated, wildcards like "image/*" supported). */
|
|
30
|
+
function matchesAccept(type, accept) {
|
|
31
|
+
if (!type) return true;
|
|
32
|
+
return accept.split(",").map((entry) => entry.trim()).filter(Boolean).some((entry) => entry.endsWith("/*") ? type.startsWith(entry.slice(0, -1)) : type === entry);
|
|
33
|
+
}
|
|
34
|
+
var fileTypeIcons = {
|
|
35
|
+
video: FileVideoIcon,
|
|
36
|
+
image: ImageIcon,
|
|
37
|
+
document: FileTextIcon,
|
|
38
|
+
audio: FileAudioIcon,
|
|
39
|
+
other: FileIcon
|
|
40
|
+
};
|
|
41
|
+
var fileTypeAccents = {
|
|
42
|
+
video: "!bg-chart-2/15 !text-chart-2",
|
|
43
|
+
image: "!bg-chart-6/15 !text-chart-6",
|
|
44
|
+
document: "!bg-chart-3/15 !text-chart-3",
|
|
45
|
+
audio: "!bg-chart-1/15 !text-chart-1",
|
|
46
|
+
other: "!bg-chart-4/15 !text-chart-4"
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* FileTypeIcon — colored icon-circle, per file type.
|
|
50
|
+
*
|
|
51
|
+
* A thin AttachmentMedia composition, not a new primitive: swaps its default
|
|
52
|
+
* square muted-bg look for a `rounded-full` circle tinted per file-type
|
|
53
|
+
* family (derived from `file`'s MIME type, or pass `family` directly).
|
|
54
|
+
*/
|
|
55
|
+
function FileTypeIcon({ file, family, className, ...props }) {
|
|
56
|
+
const resolvedFamily = family ?? (file ? getFileTypeFamily(file) : "other");
|
|
57
|
+
const Icon = fileTypeIcons[resolvedFamily];
|
|
58
|
+
return /* @__PURE__ */ jsx(AttachmentMedia, {
|
|
59
|
+
variant: "icon",
|
|
60
|
+
className: cn("rounded-full", fileTypeAccents[resolvedFamily], className),
|
|
61
|
+
...props,
|
|
62
|
+
children: /* @__PURE__ */ jsx(Icon, {})
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
var dropzoneVariants = cva("group/dropzone w-full cursor-pointer items-center justify-center transition-colors", {
|
|
66
|
+
variants: { size: {
|
|
67
|
+
default: "flex-col gap-1.5 p-6 text-center [&_svg:not([class*='size-'])]:size-8",
|
|
68
|
+
sm: "flex-row gap-3 p-4 text-left [&_svg:not([class*='size-'])]:size-6",
|
|
69
|
+
xs: "flex-row gap-2 p-2.5 text-left [&_svg:not([class*='size-'])]:size-5"
|
|
70
|
+
} },
|
|
71
|
+
defaultVariants: { size: "default" }
|
|
72
|
+
});
|
|
73
|
+
var dropzoneFeedbackIconVariants = cva("flex shrink-0 items-center justify-center [&_svg]:pointer-events-none", {
|
|
74
|
+
variants: { size: {
|
|
75
|
+
default: "size-10 [&_svg:not([class*='size-'])]:size-5",
|
|
76
|
+
sm: "size-9 [&_svg:not([class*='size-'])]:size-4",
|
|
77
|
+
xs: "size-7 [&_svg:not([class*='size-'])]:size-4"
|
|
78
|
+
} },
|
|
79
|
+
defaultVariants: { size: "default" }
|
|
80
|
+
});
|
|
81
|
+
/** Default "active" (valid drag-over) feedback — a filled icon plus a short instruction, replacing `children` entirely while dragging. No icon at xs, same as everywhere else in this file. */
|
|
82
|
+
function DropzoneActiveContent({ size }) {
|
|
83
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [size !== "xs" && /* @__PURE__ */ jsx("div", {
|
|
84
|
+
className: cn(dropzoneFeedbackIconVariants({ size }), "rounded-lg bg-primary text-primary-foreground"),
|
|
85
|
+
children: /* @__PURE__ */ jsx(ArrowDownToLineIcon, {})
|
|
86
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
87
|
+
className: "font-medium text-primary",
|
|
88
|
+
children: "Drag your files here"
|
|
89
|
+
})] });
|
|
90
|
+
}
|
|
91
|
+
/** Default "error" (rejected file type on drag-over) feedback — a filled "!" plus a short reason, replacing `children` entirely while dragging. No icon at xs, same as everywhere else in this file. */
|
|
92
|
+
function DropzoneErrorContent({ size }) {
|
|
93
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [size !== "xs" && /* @__PURE__ */ jsx("div", {
|
|
94
|
+
className: cn(dropzoneFeedbackIconVariants({ size }), "rounded-full bg-destructive text-destructive-foreground"),
|
|
95
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
96
|
+
className: "text-base leading-none font-bold",
|
|
97
|
+
children: "!"
|
|
98
|
+
})
|
|
99
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
100
|
+
className: "font-medium text-destructive",
|
|
101
|
+
children: "File format not accepted"
|
|
102
|
+
})] });
|
|
103
|
+
}
|
|
104
|
+
function Dropzone({ className, onFilesSelected, accept, multiple = true, disabled, size = "default", state, activeContent, errorContent, children, id, "aria-describedby": ariaDescribedby, "aria-invalid": ariaInvalid, inputRef, ...props }) {
|
|
105
|
+
const [dragState, setDragState] = React.useState("default");
|
|
106
|
+
const resolvedState = state ?? dragState;
|
|
107
|
+
const generatedId = React.useId();
|
|
108
|
+
const inputId = id ?? generatedId;
|
|
109
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
110
|
+
"data-slot": "dropzone",
|
|
111
|
+
"data-state": resolvedState,
|
|
112
|
+
className: cn("block", disabled && "cursor-not-allowed"),
|
|
113
|
+
...props,
|
|
114
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
115
|
+
ref: inputRef,
|
|
116
|
+
type: "file",
|
|
117
|
+
id: inputId,
|
|
118
|
+
"data-slot": "dropzone-input",
|
|
119
|
+
className: "sr-only",
|
|
120
|
+
accept,
|
|
121
|
+
multiple,
|
|
122
|
+
disabled,
|
|
123
|
+
"aria-describedby": ariaDescribedby,
|
|
124
|
+
"aria-invalid": ariaInvalid,
|
|
125
|
+
onChange: (e) => {
|
|
126
|
+
const files = Array.from(e.target.files ?? []);
|
|
127
|
+
e.target.value = "";
|
|
128
|
+
if (files.length) onFilesSelected(files);
|
|
129
|
+
}
|
|
130
|
+
}), /* @__PURE__ */ jsxs(Attachment, {
|
|
131
|
+
"data-slot": "dropzone-target",
|
|
132
|
+
state: "idle",
|
|
133
|
+
orientation: "vertical",
|
|
134
|
+
onDragOver: (e) => {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
if (disabled) return;
|
|
137
|
+
const items = Array.from(e.dataTransfer.items ?? []);
|
|
138
|
+
const allAccepted = !accept || items.every((item) => matchesAccept(item.type, accept));
|
|
139
|
+
setDragState(allAccepted ? "active" : "error");
|
|
140
|
+
},
|
|
141
|
+
onDragLeave: () => setDragState("default"),
|
|
142
|
+
onDrop: (e) => {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
setDragState("default");
|
|
145
|
+
if (disabled) return;
|
|
146
|
+
const files = Array.from(e.dataTransfer.files ?? []);
|
|
147
|
+
if (files.length) onFilesSelected(files);
|
|
148
|
+
},
|
|
149
|
+
className: cn(dropzoneVariants({ size }), resolvedState === "active" && "border-primary bg-primary/10", resolvedState === "error" && "border-destructive bg-destructive/10", disabled && "pointer-events-none opacity-50", className),
|
|
150
|
+
children: [
|
|
151
|
+
resolvedState === "default" && children,
|
|
152
|
+
resolvedState === "active" && (activeContent ?? /* @__PURE__ */ jsx(DropzoneActiveContent, { size })),
|
|
153
|
+
resolvedState === "error" && (errorContent ?? /* @__PURE__ */ jsx(DropzoneErrorContent, { size }))
|
|
154
|
+
]
|
|
155
|
+
})]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* WHEN TO USE
|
|
160
|
+
* - A drag-and-drop or click-to-browse target for picking one or more files
|
|
161
|
+
* - Paired with a list of Attachment rows below it, one per selected file,
|
|
162
|
+
* for per-file progress/error/remove — this is Dropzone's job, not the
|
|
163
|
+
* list's; there's no dedicated list component, just Attachment rows
|
|
164
|
+
* - FileTypeIcon on each row's AttachmentMedia slot for colored, per-type
|
|
165
|
+
* icon circles (video/image/document/audio/other)
|
|
166
|
+
*
|
|
167
|
+
* NOT WHEN
|
|
168
|
+
* - A plain, unstyled file input with no drop target or preview — use Input type="file"
|
|
169
|
+
* - Files are already attached and just need to be displayed — use Attachment/AttachmentGroup directly
|
|
170
|
+
*
|
|
171
|
+
* KEY RULES
|
|
172
|
+
* - `onFilesSelected(files: File[])` fires from both the native picker (click) and drag-and-drop —
|
|
173
|
+
* handle it once, not twice
|
|
174
|
+
* - `size`: "default" | "sm" | "xs" — controls LAYOUT (Dropzone owns this via `dropzoneVariants`,
|
|
175
|
+
* not just padding/icon-size): "default" stacks the icon above the text, "sm" puts the icon on
|
|
176
|
+
* the left instead (row, not stacked — the format/size hint stays, it's not optional info, just
|
|
177
|
+
* repositioned), "xs" drops the icon entirely down to one compact text line — same rule every
|
|
178
|
+
* other xs UI state in this library follows (see Empty). The children themselves (icon, hint
|
|
179
|
+
* text) are still fully custom — Dropzone only dictates the flex direction and whether there's
|
|
180
|
+
* room to show a leading icon; get the actual "which size shows what" split from a real example
|
|
181
|
+
* like the Sizes story, don't just eyeball it from this list.
|
|
182
|
+
* - `state` is normally left unset — Dropzone derives "active" (valid drag-over) and "error"
|
|
183
|
+
* (drag-over with a file type `accept` would reject) from real drag events by inspecting
|
|
184
|
+
* `dataTransfer.items[].type`, which browsers expose pre-drop. Pass `state` explicitly only
|
|
185
|
+
* to force a look for a demo; a real Dropzone should let this stay derived.
|
|
186
|
+
* - Unlike the idle look, "active"/"error" REPLACE `children` with built-in feedback (a filled
|
|
187
|
+
* icon + a short line — "Drag your files here" / "File format not accepted", no icon at
|
|
188
|
+
* `size="xs"` same as everywhere else) — override via `activeContent`/`errorContent` if the
|
|
189
|
+
* defaults don't fit; this is deliberate, not something to fight with children-based tricks.
|
|
190
|
+
* - The drag-over "error" look is a file-type warning ONLY, and it does not block the drop —
|
|
191
|
+
* the file still reaches `onFilesSelected`. File size can't be checked before drop (browsers
|
|
192
|
+
* don't expose it until then), so "file too large" is always a file-list-level error — render
|
|
193
|
+
* that file's Attachment with `state="error"` and the reason in AttachmentDescription, not
|
|
194
|
+
* as a Dropzone state.
|
|
195
|
+
* - The dropzone renders as a `label` wrapping a visually-hidden `<input type="file">`, so
|
|
196
|
+
* clicking anywhere in it opens the OS file picker with zero extra JS
|
|
197
|
+
* - `accept`/`multiple`/`disabled` map straight to the underlying input's attributes
|
|
198
|
+
* - `id`/`aria-describedby`/`aria-invalid` land on the underlying `<input type="file">`, not the
|
|
199
|
+
* outer `<label>` — a label only passes its accessible *name* through, not these relationships —
|
|
200
|
+
* so wire hint/error text (e.g. a rejection reason) through `aria-describedby`/`aria-invalid`
|
|
201
|
+
* rather than assuming label-wrapping is enough. `id` falls back to a generated one if unset.
|
|
202
|
+
* `inputRef` reaches the same underlying input if you need the ref itself.
|
|
203
|
+
* - Idle-state content (icon, "Drag files here or browse", format/size hints, an optional sample-
|
|
204
|
+
* file link) is fully custom via children — nothing is hardcoded, this component only owns the
|
|
205
|
+
* interaction wiring, the idle-state look, and the active/error feedback swap above
|
|
206
|
+
* - FileTypeIcon derives its color+icon from `file`'s MIME type (or pass `family` directly) — it's
|
|
207
|
+
* an AttachmentMedia composition (`rounded-full` + a per-family tint), not a new component tree
|
|
208
|
+
* - Attachment defaults to `w-fit` (sized to its own content, right for a horizontal chip strip) —
|
|
209
|
+
* for a vertical file list where every row should line up at the same width, pass
|
|
210
|
+
* `className="w-full"` on each Attachment row
|
|
211
|
+
* - AttachmentAction covers both retry and remove — render two of them (retry then remove) on an
|
|
212
|
+
* error row, one (remove) otherwise
|
|
213
|
+
*
|
|
214
|
+
* USAGE
|
|
215
|
+
* const [files, setFiles] = React.useState<Array<{ file: File; state: "uploading" | "error" | "done" }>>([])
|
|
216
|
+
* <div className="grid gap-3">
|
|
217
|
+
* <Dropzone
|
|
218
|
+
* accept="image/png,image/jpeg"
|
|
219
|
+
* onFilesSelected={(picked) =>
|
|
220
|
+
* setFiles((prev) => [...prev, ...picked.map((file) => ({ file, state: "uploading" as const }))])
|
|
221
|
+
* }
|
|
222
|
+
* >
|
|
223
|
+
* <UploadIcon className="text-muted-foreground" />
|
|
224
|
+
* <p className="text-sm font-medium">Drag your files here or <span className="text-primary">browse</span></p>
|
|
225
|
+
* <p className="text-xs text-muted-foreground">PNG or JPEG, up to 25MB</p>
|
|
226
|
+
* </Dropzone>
|
|
227
|
+
*
|
|
228
|
+
* {files.map(({ file, state }) => (
|
|
229
|
+
* <Attachment key={file.name} state={state} className="w-full">
|
|
230
|
+
* <FileTypeIcon file={file} />
|
|
231
|
+
* <AttachmentContent>
|
|
232
|
+
* <AttachmentTitle>{file.name}</AttachmentTitle>
|
|
233
|
+
* <AttachmentDescription>
|
|
234
|
+
* {state === "error" ? "File exceeds the 25MB limit" : `${(file.size / 1024).toFixed(1)} KB`}
|
|
235
|
+
* </AttachmentDescription>
|
|
236
|
+
* </AttachmentContent>
|
|
237
|
+
* <AttachmentActions>
|
|
238
|
+
* {state === "error" && (
|
|
239
|
+
* <AttachmentAction aria-label={`Retry ${file.name}`} onClick={() => retry(file)}>
|
|
240
|
+
* <RefreshCwIcon />
|
|
241
|
+
* </AttachmentAction>
|
|
242
|
+
* )}
|
|
243
|
+
* <AttachmentAction
|
|
244
|
+
* aria-label={`Remove ${file.name}`}
|
|
245
|
+
* onClick={() => setFiles((prev) => prev.filter((f) => f.file !== file))}
|
|
246
|
+
* >
|
|
247
|
+
* <XIcon />
|
|
248
|
+
* </AttachmentAction>
|
|
249
|
+
* </AttachmentActions>
|
|
250
|
+
* </Attachment>
|
|
251
|
+
* ))}
|
|
252
|
+
* </div>
|
|
253
|
+
*/
|
|
254
|
+
//#endregion
|
|
255
|
+
export { Dropzone, FileTypeIcon, getFileTypeFamily };
|
|
256
|
+
|
|
257
|
+
//# sourceMappingURL=dropzone.js.map
|