cortena-ui 1.3.0 → 1.4.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 +74 -3
- package/dist/a2ui/catalogue-doc.d.ts +44 -0
- package/dist/a2ui/catalogue-doc.js +169 -0
- package/dist/a2ui/catalogue-doc.js.map +1 -0
- package/dist/a2ui/catalogue.d.ts +44 -0
- package/dist/a2ui/catalogue.js +942 -0
- package/dist/a2ui/catalogue.js.map +1 -0
- package/dist/a2ui/index.d.ts +7 -0
- package/dist/a2ui/message.d.ts +232 -0
- package/dist/a2ui/message.js +365 -0
- package/dist/a2ui/message.js.map +1 -0
- package/dist/a2ui/renderer.d.ts +46 -0
- package/dist/a2ui/renderer.js +252 -0
- package/dist/a2ui/renderer.js.map +1 -0
- package/dist/a2ui/views.d.ts +80 -0
- package/dist/a2ui/views.js +596 -0
- package/dist/a2ui/views.js.map +1 -0
- package/dist/a2ui.d.ts +8 -0
- package/dist/a2ui.js +7 -0
- package/dist/chart.d.ts +9 -0
- package/dist/chart.js +9 -0
- package/dist/components/accordion.d.ts +22 -0
- package/dist/components/accordion.js +63 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert.d.ts +15 -0
- package/dist/components/alert.js +64 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +18 -0
- package/dist/components/avatar.js +40 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +12 -0
- package/dist/components/badge.js +47 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.d.ts +71 -0
- package/dist/components/breadcrumb.js +123 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button-link.d.ts +28 -0
- package/dist/components/button-link.js +24 -0
- package/dist/components/button-link.js.map +1 -0
- package/dist/components/button.d.ts +24 -0
- package/dist/components/button.js +65 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +28 -0
- package/dist/components/calendar.js +123 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +32 -0
- package/dist/components/card.js +57 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/chart/chart.d.ts +71 -0
- package/dist/components/chart/chart.js +167 -0
- package/dist/components/chart/chart.js.map +1 -0
- package/dist/components/chart/container.d.ts +88 -0
- package/dist/components/chart/container.js +189 -0
- package/dist/components/chart/container.js.map +1 -0
- package/dist/components/chart/data.js +128 -0
- package/dist/components/chart/data.js.map +1 -0
- package/dist/components/chart/index.d.ts +9 -0
- package/dist/components/chart/index.js +10 -0
- package/dist/components/chart/nivo-charts.js +225 -0
- package/dist/components/chart/nivo-charts.js.map +1 -0
- package/dist/components/chart/nivo-lazy.js +50 -0
- package/dist/components/chart/nivo-lazy.js.map +1 -0
- package/dist/components/chart/nivo-theme.d.ts +156 -0
- package/dist/components/chart/nivo-theme.js +161 -0
- package/dist/components/chart/nivo-theme.js.map +1 -0
- package/dist/components/chart/recharts-charts.js +276 -0
- package/dist/components/chart/recharts-charts.js.map +1 -0
- package/dist/components/chart/recharts-lazy.js +58 -0
- package/dist/components/chart/recharts-lazy.js.map +1 -0
- package/dist/components/chart/recharts-parts.d.ts +10 -0
- package/dist/components/chart/recharts-parts.js +25 -0
- package/dist/components/chart/recharts-parts.js.map +1 -0
- package/dist/components/chart/tokens.d.ts +62 -0
- package/dist/components/chart/tokens.js +192 -0
- package/dist/components/chart/tokens.js.map +1 -0
- package/dist/components/chart/types.d.ts +114 -0
- package/dist/components/chart/types.js +27 -0
- package/dist/components/chart/types.js.map +1 -0
- package/dist/components/checkbox.d.ts +20 -0
- package/dist/components/checkbox.js +24 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/chip.d.ts +17 -0
- package/dist/components/chip.js +47 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/combobox.d.ts +76 -0
- package/dist/components/combobox.js +201 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/command.d.ts +50 -0
- package/dist/components/command.js +117 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/data-table/data-table.d.ts +65 -0
- package/dist/components/data-table/data-table.js +588 -0
- package/dist/components/data-table/data-table.js.map +1 -0
- package/dist/components/data-table/export.d.ts +23 -0
- package/dist/components/data-table/export.js +95 -0
- package/dist/components/data-table/export.js.map +1 -0
- package/dist/components/data-table/features.d.ts +66 -0
- package/dist/components/data-table/features.js +103 -0
- package/dist/components/data-table/features.js.map +1 -0
- package/dist/components/data-table/index.d.ts +8 -0
- package/dist/components/data-table/parts.d.ts +79 -0
- package/dist/components/data-table/parts.js +400 -0
- package/dist/components/data-table/parts.js.map +1 -0
- package/dist/components/data-table/system-columns.js +84 -0
- package/dist/components/data-table/system-columns.js.map +1 -0
- package/dist/components/data-table/types.d.ts +172 -0
- package/dist/components/data-table/use-data-table.d.ts +19 -0
- package/dist/components/data-table/use-data-table.js +171 -0
- package/dist/components/data-table/use-data-table.js.map +1 -0
- package/dist/components/data-table/use-server-source.js +200 -0
- package/dist/components/data-table/use-server-source.js.map +1 -0
- package/dist/components/date-field.d.ts +54 -0
- package/dist/components/date-field.js +235 -0
- package/dist/components/date-field.js.map +1 -0
- package/dist/components/date-picker.d.ts +42 -0
- package/dist/components/date-picker.js +125 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +36 -0
- package/dist/components/dialog.js +97 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +54 -0
- package/dist/components/dropdown-menu.js +191 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/dropzone.d.ts +48 -0
- package/dist/components/dropzone.js +185 -0
- package/dist/components/dropzone.js.map +1 -0
- package/dist/components/empty-state.d.ts +22 -0
- package/dist/components/empty-state.js +46 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/error-banner.d.ts +17 -0
- package/dist/components/error-banner.js +52 -0
- package/dist/components/error-banner.js.map +1 -0
- package/dist/components/field.d.ts +64 -0
- package/dist/components/field.js +133 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/form.d.ts +37 -0
- package/dist/components/form.js +103 -0
- package/dist/components/form.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.js +26 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +11 -0
- package/dist/components/kbd.js +20 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +14 -0
- package/dist/components/label.js +28 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/loading-skeleton.d.ts +22 -0
- package/dist/components/loading-skeleton.js +123 -0
- package/dist/components/loading-skeleton.js.map +1 -0
- package/dist/components/markdown.d.ts +44 -0
- package/dist/components/markdown.js +231 -0
- package/dist/components/markdown.js.map +1 -0
- package/dist/components/page-header.d.ts +26 -0
- package/dist/components/page-header.js +53 -0
- package/dist/components/page-header.js.map +1 -0
- package/dist/components/popover.d.ts +29 -0
- package/dist/components/popover.js +74 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +22 -0
- package/dist/components/progress.js +49 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +20 -0
- package/dist/components/radio-group.js +29 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts +25 -0
- package/dist/components/scroll-area.js +39 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/section-card.d.ts +29 -0
- package/dist/components/section-card.js +55 -0
- package/dist/components/section-card.js.map +1 -0
- package/dist/components/segmented.d.ts +27 -0
- package/dist/components/segmented.js +32 -0
- package/dist/components/segmented.js.map +1 -0
- package/dist/components/select.d.ts +62 -0
- package/dist/components/select.js +97 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +19 -0
- package/dist/components/separator.js +23 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +35 -0
- package/dist/components/sheet.js +131 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +12 -0
- package/dist/components/skeleton.js +22 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sortable-list.d.ts +67 -0
- package/dist/components/sortable-list.js +206 -0
- package/dist/components/sortable-list.js.map +1 -0
- package/dist/components/spinner.d.ts +29 -0
- package/dist/components/spinner.js +34 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/status-dot.d.ts +26 -0
- package/dist/components/status-dot.js +47 -0
- package/dist/components/status-dot.js.map +1 -0
- package/dist/components/switch.d.ts +21 -0
- package/dist/components/switch.js +21 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +24 -0
- package/dist/components/tabs.js +60 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +19 -0
- package/dist/components/textarea.js +47 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toast.d.ts +47 -0
- package/dist/components/toast.js +106 -0
- package/dist/components/toast.js.map +1 -0
- package/dist/components/toolbar.d.ts +19 -0
- package/dist/components/toolbar.js +45 -0
- package/dist/components/toolbar.js.map +1 -0
- package/dist/components/tooltip.d.ts +28 -0
- package/dist/components/tooltip.js +59 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/core.d.ts +44 -0
- package/dist/core.js +44 -0
- package/dist/data-table.d.ts +9 -0
- package/dist/data-table.js +7 -0
- package/dist/form.d.ts +7 -0
- package/dist/form.js +7 -0
- package/dist/hooks/use-cortena-theme.d.ts +98 -0
- package/dist/hooks/use-cortena-theme.js +145 -0
- package/dist/hooks/use-cortena-theme.js.map +1 -0
- package/dist/index.d.ts +75 -2678
- package/dist/index.js +66 -8598
- package/dist/lib/cn.d.ts +10 -0
- package/dist/lib/cn.js +15 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/render.d.ts +23 -0
- package/dist/lib/render.js +20 -0
- package/dist/lib/render.js.map +1 -0
- package/dist/lib/shakeable.js +39 -0
- package/dist/lib/shakeable.js.map +1 -0
- package/dist/markdown.d.ts +3 -0
- package/dist/markdown.js +3 -0
- package/dist/sortable-list.d.ts +3 -0
- package/dist/sortable-list.js +3 -0
- package/package.json +37 -14
- package/src/a2ui/catalogue.ts +140 -103
- package/src/a2ui/views.tsx +5 -2
- package/src/components/alert.tsx +1 -1
- package/src/components/badge.tsx +1 -1
- package/src/components/button.tsx +1 -1
- package/src/components/chart/chart.tsx +72 -25
- package/src/components/chart/container.tsx +24 -39
- package/src/components/chart/index.tsx +4 -2
- package/src/components/chart/nivo-charts.tsx +41 -13
- package/src/components/chart/nivo-lazy.tsx +52 -0
- package/src/components/chart/recharts-charts.tsx +98 -3
- package/src/components/chart/recharts-lazy.tsx +72 -0
- package/src/components/chart/recharts-parts.tsx +34 -0
- package/src/components/chart/tokens.ts +1 -1
- package/src/components/chart/types.ts +14 -0
- package/src/components/chip.tsx +1 -1
- package/src/components/combobox.tsx +2 -2
- package/src/components/data-table/export.ts +18 -2
- package/src/components/data-table/features.ts +77 -45
- package/src/components/data-table/parts.tsx +15 -0
- package/src/components/data-table/use-data-table.ts +7 -2
- package/src/components/data-table/use-server-source.ts +17 -4
- package/src/components/date-field.tsx +1 -1
- package/src/components/error-banner.tsx +1 -1
- package/src/components/field.tsx +2 -2
- package/src/components/form.tsx +2 -2
- package/src/components/input.tsx +1 -1
- package/src/components/label.tsx +1 -1
- package/src/components/markdown.tsx +5 -4
- package/src/components/sheet.tsx +2 -2
- package/src/components/sortable-list.tsx +2 -2
- package/src/components/textarea.tsx +1 -1
- package/src/components/toast.tsx +1 -1
- package/src/entries/a2ui.ts +11 -0
- package/src/entries/chart.ts +10 -0
- package/src/entries/core.ts +91 -0
- package/src/entries/data-table.ts +9 -0
- package/src/entries/form.ts +24 -0
- package/src/entries/markdown.ts +9 -0
- package/src/entries/sortable-list.ts +13 -0
- package/src/index.ts +32 -98
- package/src/lib/shakeable.ts +33 -0
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Menu } from "@base-ui/react/menu";
|
|
4
|
+
//#region src/components/dropdown-menu.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* DropdownMenu over Base UI's Menu.
|
|
7
|
+
*
|
|
8
|
+
* `DropdownMenuContent` and `DropdownMenuSubContent` compose Portal +
|
|
9
|
+
* Positioner + Popup, so consumers keep writing one element; `side`, `align`
|
|
10
|
+
* and `sideOffset` are forwarded to the Positioner where Base UI reads them.
|
|
11
|
+
*
|
|
12
|
+
* Radix users: `asChild` on the trigger becomes `render={<Button />}`;
|
|
13
|
+
* `onSelect` on items is `onClick`; `DropdownMenuLabel` is Base UI's
|
|
14
|
+
* `Menu.GroupLabel` (place it inside a `DropdownMenuGroup` for the
|
|
15
|
+
* `aria-labelledby` wiring); `DropdownMenuCheckboxItem` reports through
|
|
16
|
+
* `onCheckedChange(checked, eventDetails)`; `DropdownMenuRadioGroup` through
|
|
17
|
+
* `onValueChange(value, eventDetails)`. Highlight state is `data-highlighted`,
|
|
18
|
+
* not `:focus`.
|
|
19
|
+
*/
|
|
20
|
+
declare function DropdownMenu(props: Menu.Root.Props): React.JSX.Element;
|
|
21
|
+
declare function DropdownMenuTrigger(props: Menu.Trigger.Props): React.JSX.Element;
|
|
22
|
+
declare function DropdownMenuPortal(props: Menu.Portal.Props): React.JSX.Element;
|
|
23
|
+
declare function DropdownMenuGroup(props: Menu.Group.Props): React.JSX.Element;
|
|
24
|
+
declare function DropdownMenuSub(props: Menu.SubmenuRoot.Props): React.JSX.Element;
|
|
25
|
+
declare function DropdownMenuRadioGroup(props: Menu.RadioGroup.Props): React.JSX.Element;
|
|
26
|
+
type PositionerProps = Pick<Menu.Positioner.Props, "side" | "align" | "sideOffset" | "alignOffset" | "anchor" | "collisionBoundary" | "collisionPadding" | "collisionAvoidance" | "sticky" | "positionMethod">;
|
|
27
|
+
export interface DropdownMenuContentProps extends Menu.Popup.Props, PositionerProps {
|
|
28
|
+
/** Keep the menu mounted in the DOM while hidden. */
|
|
29
|
+
keepMounted?: boolean;
|
|
30
|
+
/** Where to portal the menu; defaults to `<body>`. */
|
|
31
|
+
container?: Menu.Portal.Props["container"];
|
|
32
|
+
}
|
|
33
|
+
declare function DropdownMenuContent({ className, side, align, sideOffset, alignOffset, anchor, collisionBoundary, collisionPadding, collisionAvoidance, sticky, positionMethod, keepMounted, container, ...props }: DropdownMenuContentProps): React.JSX.Element;
|
|
34
|
+
declare function DropdownMenuSubContent({ className, side, align, sideOffset, alignOffset, anchor, collisionBoundary, collisionPadding, collisionAvoidance, sticky, positionMethod, keepMounted, container, ...props }: DropdownMenuContentProps): React.JSX.Element;
|
|
35
|
+
export interface DropdownMenuItemProps extends Menu.Item.Props {
|
|
36
|
+
inset?: boolean;
|
|
37
|
+
variant?: "default" | "destructive";
|
|
38
|
+
}
|
|
39
|
+
declare function DropdownMenuItem({ className, inset, variant, ...props }: DropdownMenuItemProps): React.JSX.Element;
|
|
40
|
+
declare function DropdownMenuCheckboxItem({ className, children, ...props }: Menu.CheckboxItem.Props): React.JSX.Element;
|
|
41
|
+
declare function DropdownMenuRadioItem({ className, children, ...props }: Menu.RadioItem.Props): React.JSX.Element;
|
|
42
|
+
export interface DropdownMenuLabelProps extends Menu.GroupLabel.Props {
|
|
43
|
+
inset?: boolean;
|
|
44
|
+
}
|
|
45
|
+
declare function DropdownMenuLabel({ className, inset, ...props }: DropdownMenuLabelProps): React.JSX.Element;
|
|
46
|
+
declare function DropdownMenuSeparator({ className, ...props }: Menu.Separator.Props): React.JSX.Element;
|
|
47
|
+
export interface DropdownMenuSubTriggerProps extends Menu.SubmenuTrigger.Props {
|
|
48
|
+
inset?: boolean;
|
|
49
|
+
}
|
|
50
|
+
declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: DropdownMenuSubTriggerProps): React.JSX.Element;
|
|
51
|
+
declare function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
|
|
54
|
+
//# sourceMappingURL=dropdown-menu.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { Check, ChevronRight, Circle } from "lucide-react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Menu } from "@base-ui/react/menu";
|
|
7
|
+
//#region src/components/dropdown-menu.tsx
|
|
8
|
+
/**
|
|
9
|
+
* DropdownMenu over Base UI's Menu.
|
|
10
|
+
*
|
|
11
|
+
* `DropdownMenuContent` and `DropdownMenuSubContent` compose Portal +
|
|
12
|
+
* Positioner + Popup, so consumers keep writing one element; `side`, `align`
|
|
13
|
+
* and `sideOffset` are forwarded to the Positioner where Base UI reads them.
|
|
14
|
+
*
|
|
15
|
+
* Radix users: `asChild` on the trigger becomes `render={<Button />}`;
|
|
16
|
+
* `onSelect` on items is `onClick`; `DropdownMenuLabel` is Base UI's
|
|
17
|
+
* `Menu.GroupLabel` (place it inside a `DropdownMenuGroup` for the
|
|
18
|
+
* `aria-labelledby` wiring); `DropdownMenuCheckboxItem` reports through
|
|
19
|
+
* `onCheckedChange(checked, eventDetails)`; `DropdownMenuRadioGroup` through
|
|
20
|
+
* `onValueChange(value, eventDetails)`. Highlight state is `data-highlighted`,
|
|
21
|
+
* not `:focus`.
|
|
22
|
+
*/
|
|
23
|
+
function DropdownMenu(props) {
|
|
24
|
+
return /* @__PURE__ */ jsx(Menu.Root, { ...props });
|
|
25
|
+
}
|
|
26
|
+
function DropdownMenuTrigger(props) {
|
|
27
|
+
return /* @__PURE__ */ jsx(Menu.Trigger, {
|
|
28
|
+
"data-slot": "dropdown-menu-trigger",
|
|
29
|
+
...props
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function DropdownMenuPortal(props) {
|
|
33
|
+
return /* @__PURE__ */ jsx(Menu.Portal, {
|
|
34
|
+
"data-slot": "dropdown-menu-portal",
|
|
35
|
+
...props
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function DropdownMenuGroup(props) {
|
|
39
|
+
return /* @__PURE__ */ jsx(Menu.Group, {
|
|
40
|
+
"data-slot": "dropdown-menu-group",
|
|
41
|
+
...props
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function DropdownMenuSub(props) {
|
|
45
|
+
return /* @__PURE__ */ jsx(Menu.SubmenuRoot, { ...props });
|
|
46
|
+
}
|
|
47
|
+
function DropdownMenuRadioGroup(props) {
|
|
48
|
+
return /* @__PURE__ */ jsx(Menu.RadioGroup, {
|
|
49
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
50
|
+
...props
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const popupClassName = [
|
|
54
|
+
"min-w-[8rem] overflow-hidden p-1 outline-none",
|
|
55
|
+
"rounded-[var(--ds-radius-md)] border border-[var(--ds-border)]",
|
|
56
|
+
"bg-[var(--ds-popover)] text-[color:var(--ds-popover-foreground)] shadow-[var(--ds-shadow-lg)]",
|
|
57
|
+
"open:animate-in open:fade-in-0 open:zoom-in-95",
|
|
58
|
+
"closed:animate-out closed:fade-out-0 closed:zoom-out-95",
|
|
59
|
+
"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-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2",
|
|
60
|
+
"duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]"
|
|
61
|
+
];
|
|
62
|
+
function DropdownMenuContent({ className, side, align, sideOffset = 4, alignOffset, anchor, collisionBoundary, collisionPadding, collisionAvoidance, sticky, positionMethod, keepMounted, container, ...props }) {
|
|
63
|
+
return /* @__PURE__ */ jsx(Menu.Portal, {
|
|
64
|
+
keepMounted,
|
|
65
|
+
container,
|
|
66
|
+
children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
67
|
+
"data-slot": "dropdown-menu-positioner",
|
|
68
|
+
side,
|
|
69
|
+
align,
|
|
70
|
+
sideOffset,
|
|
71
|
+
alignOffset,
|
|
72
|
+
anchor,
|
|
73
|
+
collisionBoundary,
|
|
74
|
+
collisionPadding,
|
|
75
|
+
collisionAvoidance,
|
|
76
|
+
sticky,
|
|
77
|
+
positionMethod,
|
|
78
|
+
className: "isolate z-50",
|
|
79
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
80
|
+
"data-slot": "dropdown-menu-content",
|
|
81
|
+
className: cn(popupClassName, className),
|
|
82
|
+
...props
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function DropdownMenuSubContent({ className, side, align, sideOffset, alignOffset, anchor, collisionBoundary, collisionPadding, collisionAvoidance, sticky, positionMethod, keepMounted, container, ...props }) {
|
|
88
|
+
return /* @__PURE__ */ jsx(Menu.Portal, {
|
|
89
|
+
keepMounted,
|
|
90
|
+
container,
|
|
91
|
+
children: /* @__PURE__ */ jsx(Menu.Positioner, {
|
|
92
|
+
"data-slot": "dropdown-menu-sub-positioner",
|
|
93
|
+
side,
|
|
94
|
+
align,
|
|
95
|
+
sideOffset,
|
|
96
|
+
alignOffset,
|
|
97
|
+
anchor,
|
|
98
|
+
collisionBoundary,
|
|
99
|
+
collisionPadding,
|
|
100
|
+
collisionAvoidance,
|
|
101
|
+
sticky,
|
|
102
|
+
positionMethod,
|
|
103
|
+
className: "isolate z-50",
|
|
104
|
+
children: /* @__PURE__ */ jsx(Menu.Popup, {
|
|
105
|
+
"data-slot": "dropdown-menu-sub-content",
|
|
106
|
+
className: cn(popupClassName, className),
|
|
107
|
+
...props
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const itemClassName = [
|
|
113
|
+
"relative flex cursor-default select-none items-center gap-2 px-2 py-1.5 outline-none",
|
|
114
|
+
"rounded-[var(--ds-radius-sm)] text-[length:var(--ds-text-body-sm)]",
|
|
115
|
+
"transition-colors duration-[var(--ds-duration-fast)]",
|
|
116
|
+
"data-[highlighted]:bg-[var(--ds-hover)] data-[highlighted]:text-[color:var(--ds-foreground)]",
|
|
117
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
118
|
+
"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0"
|
|
119
|
+
];
|
|
120
|
+
function DropdownMenuItem({ className, inset, variant = "default", ...props }) {
|
|
121
|
+
return /* @__PURE__ */ jsx(Menu.Item, {
|
|
122
|
+
"data-slot": "dropdown-menu-item",
|
|
123
|
+
"data-inset": inset ? "" : void 0,
|
|
124
|
+
"data-variant": variant,
|
|
125
|
+
className: cn(itemClassName, inset && "pl-8", variant === "destructive" && "text-[color:var(--ds-destructive)] data-[highlighted]:bg-[var(--ds-destructive-soft)] data-[highlighted]:text-[color:var(--ds-destructive)]", className),
|
|
126
|
+
...props
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function DropdownMenuCheckboxItem({ className, children, ...props }) {
|
|
130
|
+
return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
|
|
131
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
132
|
+
className: cn(itemClassName, "pl-8 pr-2", className),
|
|
133
|
+
...props,
|
|
134
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
135
|
+
className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center",
|
|
136
|
+
children: /* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, {
|
|
137
|
+
"data-slot": "dropdown-menu-checkbox-item-indicator",
|
|
138
|
+
children: /* @__PURE__ */ jsx(Check, { className: "size-3.5" })
|
|
139
|
+
})
|
|
140
|
+
}), children]
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function DropdownMenuRadioItem({ className, children, ...props }) {
|
|
144
|
+
return /* @__PURE__ */ jsxs(Menu.RadioItem, {
|
|
145
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
146
|
+
className: cn(itemClassName, "pl-8 pr-2", className),
|
|
147
|
+
...props,
|
|
148
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
149
|
+
className: "pointer-events-none absolute left-2 flex size-3.5 items-center justify-center",
|
|
150
|
+
children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, {
|
|
151
|
+
"data-slot": "dropdown-menu-radio-item-indicator",
|
|
152
|
+
children: /* @__PURE__ */ jsx(Circle, { className: "size-2 fill-current" })
|
|
153
|
+
})
|
|
154
|
+
}), children]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function DropdownMenuLabel({ className, inset, ...props }) {
|
|
158
|
+
return /* @__PURE__ */ jsx(Menu.GroupLabel, {
|
|
159
|
+
"data-slot": "dropdown-menu-label",
|
|
160
|
+
"data-inset": inset ? "" : void 0,
|
|
161
|
+
className: cn("px-2 py-1.5 text-[length:var(--ds-text-caption-lg)] font-semibold text-[color:var(--ds-muted-foreground)]", inset && "pl-8", className),
|
|
162
|
+
...props
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
166
|
+
return /* @__PURE__ */ jsx(Menu.Separator, {
|
|
167
|
+
"data-slot": "dropdown-menu-separator",
|
|
168
|
+
className: cn("-mx-1 my-1 h-px bg-[var(--ds-border-subtle)]", className),
|
|
169
|
+
...props
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
function DropdownMenuSubTrigger({ className, inset, children, ...props }) {
|
|
173
|
+
return /* @__PURE__ */ jsxs(Menu.SubmenuTrigger, {
|
|
174
|
+
"data-slot": "dropdown-menu-sub-trigger",
|
|
175
|
+
"data-inset": inset ? "" : void 0,
|
|
176
|
+
className: cn(itemClassName, "popup-open:bg-[var(--ds-hover)] popup-open:text-[color:var(--ds-foreground)]", inset && "pl-8", className),
|
|
177
|
+
...props,
|
|
178
|
+
children: [children, /* @__PURE__ */ jsx(ChevronRight, { className: "ml-auto" })]
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function DropdownMenuShortcut({ className, ...props }) {
|
|
182
|
+
return /* @__PURE__ */ jsx("span", {
|
|
183
|
+
"data-slot": "dropdown-menu-shortcut",
|
|
184
|
+
className: cn("ml-auto text-[length:var(--ds-text-caption)] tracking-widest text-[color:var(--ds-muted-foreground)]", className),
|
|
185
|
+
...props
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
//#endregion
|
|
189
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=dropdown-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropdown-menu.js","names":["MenuPrimitive"],"sources":["../../src/components/dropdown-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\";\nimport { Check, ChevronRight, Circle } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * DropdownMenu over Base UI's Menu.\n *\n * `DropdownMenuContent` and `DropdownMenuSubContent` compose Portal +\n * Positioner + Popup, so consumers keep writing one element; `side`, `align`\n * and `sideOffset` are forwarded to the Positioner where Base UI reads them.\n *\n * Radix users: `asChild` on the trigger becomes `render={<Button />}`;\n * `onSelect` on items is `onClick`; `DropdownMenuLabel` is Base UI's\n * `Menu.GroupLabel` (place it inside a `DropdownMenuGroup` for the\n * `aria-labelledby` wiring); `DropdownMenuCheckboxItem` reports through\n * `onCheckedChange(checked, eventDetails)`; `DropdownMenuRadioGroup` through\n * `onValueChange(value, eventDetails)`. Highlight state is `data-highlighted`,\n * not `:focus`.\n */\nfunction DropdownMenu(props: MenuPrimitive.Root.Props) {\n return <MenuPrimitive.Root {...props} />;\n}\n\nfunction DropdownMenuTrigger(props: MenuPrimitive.Trigger.Props) {\n return <MenuPrimitive.Trigger data-slot=\"dropdown-menu-trigger\" {...props} />;\n}\n\nfunction DropdownMenuPortal(props: MenuPrimitive.Portal.Props) {\n return <MenuPrimitive.Portal data-slot=\"dropdown-menu-portal\" {...props} />;\n}\n\nfunction DropdownMenuGroup(props: MenuPrimitive.Group.Props) {\n return <MenuPrimitive.Group data-slot=\"dropdown-menu-group\" {...props} />;\n}\n\nfunction DropdownMenuSub(props: MenuPrimitive.SubmenuRoot.Props) {\n return <MenuPrimitive.SubmenuRoot {...props} />;\n}\n\nfunction DropdownMenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {\n return <MenuPrimitive.RadioGroup data-slot=\"dropdown-menu-radio-group\" {...props} />;\n}\n\ntype PositionerProps = Pick<\n MenuPrimitive.Positioner.Props,\n | \"side\"\n | \"align\"\n | \"sideOffset\"\n | \"alignOffset\"\n | \"anchor\"\n | \"collisionBoundary\"\n | \"collisionPadding\"\n | \"collisionAvoidance\"\n | \"sticky\"\n | \"positionMethod\"\n>;\n\nexport interface DropdownMenuContentProps extends MenuPrimitive.Popup.Props, PositionerProps {\n /** Keep the menu mounted in the DOM while hidden. */\n keepMounted?: boolean;\n /** Where to portal the menu; defaults to `<body>`. */\n container?: MenuPrimitive.Portal.Props[\"container\"];\n}\n\nconst popupClassName = [\n \"min-w-[8rem] overflow-hidden p-1 outline-none\",\n \"rounded-[var(--ds-radius-md)] border border-[var(--ds-border)]\",\n \"bg-[var(--ds-popover)] text-[color:var(--ds-popover-foreground)] shadow-[var(--ds-shadow-lg)]\",\n \"open:animate-in open:fade-in-0 open:zoom-in-95\",\n \"closed:animate-out closed:fade-out-0 closed:zoom-out-95\",\n \"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-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2\",\n \"duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n];\n\nfunction DropdownMenuContent({\n className,\n side,\n align,\n sideOffset = 4,\n alignOffset,\n anchor,\n collisionBoundary,\n collisionPadding,\n collisionAvoidance,\n sticky,\n positionMethod,\n keepMounted,\n container,\n ...props\n}: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal keepMounted={keepMounted} container={container}>\n <MenuPrimitive.Positioner\n data-slot=\"dropdown-menu-positioner\"\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n anchor={anchor}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n collisionAvoidance={collisionAvoidance}\n sticky={sticky}\n positionMethod={positionMethod}\n className=\"isolate z-50\"\n >\n <MenuPrimitive.Popup\n data-slot=\"dropdown-menu-content\"\n className={cn(popupClassName, className)}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n );\n}\n\nfunction DropdownMenuSubContent({\n className,\n side,\n align,\n sideOffset,\n alignOffset,\n anchor,\n collisionBoundary,\n collisionPadding,\n collisionAvoidance,\n sticky,\n positionMethod,\n keepMounted,\n container,\n ...props\n}: DropdownMenuContentProps) {\n return (\n <MenuPrimitive.Portal keepMounted={keepMounted} container={container}>\n <MenuPrimitive.Positioner\n data-slot=\"dropdown-menu-sub-positioner\"\n side={side}\n align={align}\n sideOffset={sideOffset}\n alignOffset={alignOffset}\n anchor={anchor}\n collisionBoundary={collisionBoundary}\n collisionPadding={collisionPadding}\n collisionAvoidance={collisionAvoidance}\n sticky={sticky}\n positionMethod={positionMethod}\n className=\"isolate z-50\"\n >\n <MenuPrimitive.Popup\n data-slot=\"dropdown-menu-sub-content\"\n className={cn(popupClassName, className)}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n );\n}\n\nconst itemClassName = [\n \"relative flex cursor-default select-none items-center gap-2 px-2 py-1.5 outline-none\",\n \"rounded-[var(--ds-radius-sm)] text-[length:var(--ds-text-body-sm)]\",\n \"transition-colors duration-[var(--ds-duration-fast)]\",\n \"data-[highlighted]:bg-[var(--ds-hover)] data-[highlighted]:text-[color:var(--ds-foreground)]\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n \"[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n];\n\nexport interface DropdownMenuItemProps extends MenuPrimitive.Item.Props {\n inset?: boolean;\n variant?: \"default\" | \"destructive\";\n}\n\nfunction DropdownMenuItem({ className, inset, variant = \"default\", ...props }: DropdownMenuItemProps) {\n return (\n <MenuPrimitive.Item\n data-slot=\"dropdown-menu-item\"\n data-inset={inset ? \"\" : undefined}\n data-variant={variant}\n className={cn(\n itemClassName,\n inset && \"pl-8\",\n variant === \"destructive\" &&\n \"text-[color:var(--ds-destructive)] data-[highlighted]:bg-[var(--ds-destructive-soft)] data-[highlighted]:text-[color:var(--ds-destructive)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuCheckboxItem({ className, children, ...props }: MenuPrimitive.CheckboxItem.Props) {\n return (\n <MenuPrimitive.CheckboxItem\n data-slot=\"dropdown-menu-checkbox-item\"\n className={cn(itemClassName, \"pl-8 pr-2\", className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <MenuPrimitive.CheckboxItemIndicator data-slot=\"dropdown-menu-checkbox-item-indicator\">\n <Check className=\"size-3.5\" />\n </MenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </MenuPrimitive.CheckboxItem>\n );\n}\n\nfunction DropdownMenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {\n return (\n <MenuPrimitive.RadioItem\n data-slot=\"dropdown-menu-radio-item\"\n className={cn(itemClassName, \"pl-8 pr-2\", className)}\n {...props}\n >\n <span className=\"pointer-events-none absolute left-2 flex size-3.5 items-center justify-center\">\n <MenuPrimitive.RadioItemIndicator data-slot=\"dropdown-menu-radio-item-indicator\">\n <Circle className=\"size-2 fill-current\" />\n </MenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </MenuPrimitive.RadioItem>\n );\n}\n\nexport interface DropdownMenuLabelProps extends MenuPrimitive.GroupLabel.Props {\n inset?: boolean;\n}\n\nfunction DropdownMenuLabel({ className, inset, ...props }: DropdownMenuLabelProps) {\n return (\n <MenuPrimitive.GroupLabel\n data-slot=\"dropdown-menu-label\"\n data-inset={inset ? \"\" : undefined}\n className={cn(\n \"px-2 py-1.5 text-[length:var(--ds-text-caption-lg)] font-semibold text-[color:var(--ds-muted-foreground)]\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction DropdownMenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {\n return (\n <MenuPrimitive.Separator\n data-slot=\"dropdown-menu-separator\"\n className={cn(\"-mx-1 my-1 h-px bg-[var(--ds-border-subtle)]\", className)}\n {...props}\n />\n );\n}\n\nexport interface DropdownMenuSubTriggerProps extends MenuPrimitive.SubmenuTrigger.Props {\n inset?: boolean;\n}\n\nfunction DropdownMenuSubTrigger({\n className,\n inset,\n children,\n ...props\n}: DropdownMenuSubTriggerProps) {\n return (\n <MenuPrimitive.SubmenuTrigger\n data-slot=\"dropdown-menu-sub-trigger\"\n data-inset={inset ? \"\" : undefined}\n className={cn(\n itemClassName,\n \"popup-open:bg-[var(--ds-hover)] popup-open:text-[color:var(--ds-foreground)]\",\n inset && \"pl-8\",\n className,\n )}\n {...props}\n >\n {children}\n <ChevronRight className=\"ml-auto\" />\n </MenuPrimitive.SubmenuTrigger>\n );\n}\n\nfunction DropdownMenuShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"dropdown-menu-shortcut\"\n className={cn(\n \"ml-auto text-[length:var(--ds-text-caption)] tracking-widest text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nexport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuCheckboxItem,\n DropdownMenuRadioItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuGroup,\n DropdownMenuPortal,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuRadioGroup,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAS,aAAa,OAAiC;CACrD,OAAO,oBAACA,KAAc,MAAf,EAAoB,GAAI,MAAQ,CAAA;AACzC;AAEA,SAAS,oBAAoB,OAAoC;CAC/D,OAAO,oBAACA,KAAc,SAAf;EAAuB,aAAU;EAAwB,GAAI;CAAQ,CAAA;AAC9E;AAEA,SAAS,mBAAmB,OAAmC;CAC7D,OAAO,oBAACA,KAAc,QAAf;EAAsB,aAAU;EAAuB,GAAI;CAAQ,CAAA;AAC5E;AAEA,SAAS,kBAAkB,OAAkC;CAC3D,OAAO,oBAACA,KAAc,OAAf;EAAqB,aAAU;EAAsB,GAAI;CAAQ,CAAA;AAC1E;AAEA,SAAS,gBAAgB,OAAwC;CAC/D,OAAO,oBAACA,KAAc,aAAf,EAA2B,GAAI,MAAQ,CAAA;AAChD;AAEA,SAAS,uBAAuB,OAAuC;CACrE,OAAO,oBAACA,KAAc,YAAf;EAA0B,aAAU;EAA4B,GAAI;CAAQ,CAAA;AACrF;AAuBA,MAAM,iBAAiB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,oBAAoB,EAC3B,WACA,MACA,OACA,aAAa,GACb,aACA,QACA,mBACA,kBACA,oBACA,QACA,gBACA,aACA,WACA,GAAG,SACwB;CAC3B,OACE,oBAACA,KAAc,QAAf;EAAmC;EAAwB;EACzD,UAAA,oBAACA,KAAc,YAAf;GACE,aAAU;GACJ;GACC;GACK;GACC;GACL;GACW;GACD;GACE;GACZ;GACQ;GAChB,WAAU;GAEV,UAAA,oBAACA,KAAc,OAAf;IACE,aAAU;IACV,WAAW,GAAG,gBAAgB,SAAS;IACvC,GAAI;GACL,CAAA;EACuB,CAAA;CACN,CAAA;AAE1B;AAEA,SAAS,uBAAuB,EAC9B,WACA,MACA,OACA,YACA,aACA,QACA,mBACA,kBACA,oBACA,QACA,gBACA,aACA,WACA,GAAG,SACwB;CAC3B,OACE,oBAACA,KAAc,QAAf;EAAmC;EAAwB;EACzD,UAAA,oBAACA,KAAc,YAAf;GACE,aAAU;GACJ;GACC;GACK;GACC;GACL;GACW;GACD;GACE;GACZ;GACQ;GAChB,WAAU;GAEV,UAAA,oBAACA,KAAc,OAAf;IACE,aAAU;IACV,WAAW,GAAG,gBAAgB,SAAS;IACvC,GAAI;GACL,CAAA;EACuB,CAAA;CACN,CAAA;AAE1B;AAEA,MAAM,gBAAgB;CACpB;CACA;CACA;CACA;CACA;CACA;AACF;AAOA,SAAS,iBAAiB,EAAE,WAAW,OAAO,UAAU,WAAW,GAAG,SAAgC;CACpG,OACE,oBAACA,KAAc,MAAf;EACE,aAAU;EACV,cAAY,QAAQ,KAAK,KAAA;EACzB,gBAAc;EACd,WAAW,GACT,eACA,SAAS,QACT,YAAY,iBACV,+IACF,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,yBAAyB,EAAE,WAAW,UAAU,GAAG,SAA2C;CACrG,OACE,qBAACA,KAAc,cAAf;EACE,aAAU;EACV,WAAW,GAAG,eAAe,aAAa,SAAS;EACnD,GAAI;EAHN,UAAA,CAKE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,KAAc,uBAAf;IAAqC,aAAU;IAC7C,UAAA,oBAAC,OAAD,EAAO,WAAU,WAAY,CAAA;GACM,CAAA;EACjC,CAAA,GACL,QACyB;;AAEhC;AAEA,SAAS,sBAAsB,EAAE,WAAW,UAAU,GAAG,SAAwC;CAC/F,OACE,qBAACA,KAAc,WAAf;EACE,aAAU;EACV,WAAW,GAAG,eAAe,aAAa,SAAS;EACnD,GAAI;EAHN,UAAA,CAKE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,KAAc,oBAAf;IAAkC,aAAU;IAC1C,UAAA,oBAAC,QAAD,EAAQ,WAAU,sBAAuB,CAAA;GACT,CAAA;EAC9B,CAAA,GACL,QACsB;;AAE7B;AAMA,SAAS,kBAAkB,EAAE,WAAW,OAAO,GAAG,SAAiC;CACjF,OACE,oBAACA,KAAc,YAAf;EACE,aAAU;EACV,cAAY,QAAQ,KAAK,KAAA;EACzB,WAAW,GACT,6GACA,SAAS,QACT,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,sBAAsB,EAAE,WAAW,GAAG,SAAwC;CACrF,OACE,oBAACA,KAAc,WAAf;EACE,aAAU;EACV,WAAW,GAAG,gDAAgD,SAAS;EACvE,GAAI;CACL,CAAA;AAEL;AAMA,SAAS,uBAAuB,EAC9B,WACA,OACA,UACA,GAAG,SAC2B;CAC9B,OACE,qBAACA,KAAc,gBAAf;EACE,aAAU;EACV,cAAY,QAAQ,KAAK,KAAA;EACzB,WAAW,GACT,eACA,gFACA,SAAS,QACT,SACF;EACA,GAAI;EATN,UAAA,CAWG,UACD,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA,CACP;;AAElC;AAEA,SAAS,qBAAqB,EAAE,WAAW,GAAG,SAAuC;CACnF,OACE,oBAAC,QAAD;EACE,aAAU;EACV,WAAW,GACT,wGACA,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Accept, DropzoneState, FileRejection } from "react-dropzone";
|
|
4
|
+
//#region src/components/dropzone.d.ts
|
|
5
|
+
/** `1536` → `1.5 KB`, `0` → `0 B`; base 1024, one decimal above bytes. */
|
|
6
|
+
declare function formatFileSize(bytes: number): string;
|
|
7
|
+
export interface DropzoneProps extends Omit<React.ComponentProps<"div">, "children" | "onDrop" | "onDragEnter" | "onDragOver" | "onDragLeave"> {
|
|
8
|
+
/** Called with the accepted files after a drop, a paste or the file dialog. */
|
|
9
|
+
onFiles: (files: File[]) => void;
|
|
10
|
+
/** Called with the rejected files and their reasons. */
|
|
11
|
+
onReject?: (rejections: FileRejection[]) => void;
|
|
12
|
+
/** MIME type to extension map, e.g. `{ "image/png": [".png"] }`. */
|
|
13
|
+
accept?: Accept;
|
|
14
|
+
/** Allow more than one file; defaults to true. */
|
|
15
|
+
multiple?: boolean;
|
|
16
|
+
/** Largest accepted file in bytes. */
|
|
17
|
+
maxSize?: number;
|
|
18
|
+
/** Most files accepted per drop; `0` means unlimited. */
|
|
19
|
+
maxFiles?: number;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Do not open the file dialog on click; for a zone that wraps clickable content. @default false */
|
|
22
|
+
noClick?: boolean;
|
|
23
|
+
/** Do not open the file dialog on Enter or Space; pair with `noClick`. @default false */
|
|
24
|
+
noKeyboard?: boolean;
|
|
25
|
+
/** Primary line of the default body. */
|
|
26
|
+
label?: React.ReactNode;
|
|
27
|
+
/** Muted second line of the default body; derived from `accept` and `maxSize` when omitted. */
|
|
28
|
+
hint?: React.ReactNode;
|
|
29
|
+
/** Replace the default body. A function receives react-dropzone's state. */
|
|
30
|
+
children?: React.ReactNode | ((state: DropzoneState) => React.ReactNode);
|
|
31
|
+
}
|
|
32
|
+
declare function Dropzone({ onFiles, onReject, accept, multiple, maxSize, maxFiles, disabled, noClick, noKeyboard, label, hint, children, className, ref, ...props }: DropzoneProps): React.JSX.Element;
|
|
33
|
+
export interface FileListProps extends Omit<React.ComponentProps<"ul">, "children"> {
|
|
34
|
+
files: readonly File[];
|
|
35
|
+
/** Called with the file and its index when its remove button is pressed; the button is hidden when omitted. */
|
|
36
|
+
onRemove?: (file: File, index: number) => void;
|
|
37
|
+
/** Formats the size column; defaults to `formatFileSize`. */
|
|
38
|
+
formatSize?: (bytes: number) => string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The files a Dropzone picked: name, size and a remove button per row. Rows
|
|
42
|
+
* are keyed by name, size and last-modified time so re-picking the same file
|
|
43
|
+
* keeps its row stable.
|
|
44
|
+
*/
|
|
45
|
+
declare function FileList({ files, onRemove, formatSize, className, ...props }: FileListProps): React.JSX.Element | null;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { type Accept, Dropzone, type DropzoneState, FileList, type FileRejection, formatFileSize };
|
|
48
|
+
//# sourceMappingURL=dropzone.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { FileIcon, Upload, X } from "lucide-react";
|
|
5
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useDropzone } from "react-dropzone";
|
|
7
|
+
//#region src/components/dropzone.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Dropzone.
|
|
10
|
+
*
|
|
11
|
+
* A drop target over react-dropzone's `useDropzone`. The root is a focusable
|
|
12
|
+
* region that opens the file dialog on click or Enter; the hidden `<input>` is
|
|
13
|
+
* what the dialog fills. Every state is exposed as a data attribute so the
|
|
14
|
+
* classes below (and a consumer's) can paint it:
|
|
15
|
+
*
|
|
16
|
+
* - `data-active` a drag is over the zone
|
|
17
|
+
* - `data-accept` every dragged file passes `accept` / `maxSize`
|
|
18
|
+
* - `data-reject` at least one dragged file fails
|
|
19
|
+
* - `data-disabled`
|
|
20
|
+
*
|
|
21
|
+
* `onFiles` receives only the accepted files; rejections go to `onReject`.
|
|
22
|
+
* `FileList` and `formatFileSize` are separate parts for listing what was
|
|
23
|
+
* picked, so a form can keep the zone and the list in different places.
|
|
24
|
+
*
|
|
25
|
+
* ## Wrapping a whole page as a drop target
|
|
26
|
+
*
|
|
27
|
+
* `noClick` and `noKeyboard` turn off the two activators that only make sense
|
|
28
|
+
* on a dashed box: clicking anywhere inside opens the file dialog, and Enter /
|
|
29
|
+
* Space on the focused root does the same. A page-sized zone wraps content
|
|
30
|
+
* that has its own buttons and inputs, so both have to go — otherwise every
|
|
31
|
+
* click in the page opens a file picker.
|
|
32
|
+
*
|
|
33
|
+
* ```tsx
|
|
34
|
+
* <Dropzone noClick noKeyboard onFiles={upload} className="min-h-dvh border-0 bg-transparent p-0">
|
|
35
|
+
* {({ isDragActive, open }) => (
|
|
36
|
+
* <>
|
|
37
|
+
* <PageContent />
|
|
38
|
+
* <Button onClick={open}>Browse…</Button>
|
|
39
|
+
* {isDragActive ? <DropHint /> : null}
|
|
40
|
+
* </>
|
|
41
|
+
* )}
|
|
42
|
+
* </Dropzone>
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* The render-prop form gets react-dropzone's `open()`, which is the way back
|
|
46
|
+
* to the file dialog from a deliberate control. With `noClick` the root also
|
|
47
|
+
* drops its `cursor-pointer`, since it is no longer clickable.
|
|
48
|
+
*/
|
|
49
|
+
const UNITS = [
|
|
50
|
+
"B",
|
|
51
|
+
"KB",
|
|
52
|
+
"MB",
|
|
53
|
+
"GB",
|
|
54
|
+
"TB"
|
|
55
|
+
];
|
|
56
|
+
/** `1536` → `1.5 KB`, `0` → `0 B`; base 1024, one decimal above bytes. */
|
|
57
|
+
function formatFileSize(bytes) {
|
|
58
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
|
|
59
|
+
let value = bytes;
|
|
60
|
+
let unit = 0;
|
|
61
|
+
while (value >= 1024 && unit < UNITS.length - 1) {
|
|
62
|
+
value /= 1024;
|
|
63
|
+
unit += 1;
|
|
64
|
+
}
|
|
65
|
+
return `${unit === 0 ? Math.round(value) : Math.round(value * 10) / 10} ${UNITS[unit]}`;
|
|
66
|
+
}
|
|
67
|
+
/** Turns an `accept` map into a hint such as `PNG, JPG, PDF`. */
|
|
68
|
+
function describeAccept(accept) {
|
|
69
|
+
if (!accept) return void 0;
|
|
70
|
+
const exts = /* @__PURE__ */ new Set();
|
|
71
|
+
for (const [mime, list] of Object.entries(accept)) {
|
|
72
|
+
const items = typeof list === "string" ? [list] : list;
|
|
73
|
+
if (items.length > 0) for (const ext of items) exts.add(ext.replace(/^\./, "").toUpperCase());
|
|
74
|
+
else {
|
|
75
|
+
const sub = mime.split("/")[1];
|
|
76
|
+
if (sub && sub !== "*") exts.add(sub.toUpperCase());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return exts.size > 0 ? [...exts].join(", ") : void 0;
|
|
80
|
+
}
|
|
81
|
+
function Dropzone({ onFiles, onReject, accept, multiple = true, maxSize, maxFiles, disabled = false, noClick = false, noKeyboard = false, label, hint, children, className, ref, ...props }) {
|
|
82
|
+
const state = useDropzone({
|
|
83
|
+
accept,
|
|
84
|
+
multiple,
|
|
85
|
+
maxSize,
|
|
86
|
+
maxFiles,
|
|
87
|
+
disabled,
|
|
88
|
+
noClick,
|
|
89
|
+
noKeyboard,
|
|
90
|
+
onDropAccepted: (files) => onFiles(files),
|
|
91
|
+
onDropRejected: (rejections) => onReject?.(rejections)
|
|
92
|
+
});
|
|
93
|
+
const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, isFocused, rootRef } = state;
|
|
94
|
+
const setRoot = (node) => {
|
|
95
|
+
rootRef.current = node;
|
|
96
|
+
if (typeof ref === "function") ref(node);
|
|
97
|
+
else if (ref) ref.current = node;
|
|
98
|
+
};
|
|
99
|
+
const derivedHint = hint ?? [describeAccept(accept), maxSize ? `up to ${formatFileSize(maxSize)}` : void 0].filter(Boolean).join(" ");
|
|
100
|
+
const body = typeof children === "function" ? children(state) : children !== void 0 ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
101
|
+
/* @__PURE__ */ jsx("span", {
|
|
102
|
+
"data-slot": "dropzone-icon",
|
|
103
|
+
className: cn("flex size-10 items-center justify-center rounded-[var(--ds-radius-full)]", "bg-[var(--ds-muted)] text-[color:var(--ds-muted-foreground)]", "group-data-[active]/dropzone:bg-[var(--ds-primary-soft)] group-data-[active]/dropzone:text-[color:var(--ds-primary)]", "group-data-[reject]/dropzone:bg-[var(--ds-destructive-soft)] group-data-[reject]/dropzone:text-[color:var(--ds-destructive)]"),
|
|
104
|
+
children: /* @__PURE__ */ jsx(Upload, {
|
|
105
|
+
"aria-hidden": true,
|
|
106
|
+
className: "size-5"
|
|
107
|
+
})
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ jsx("span", {
|
|
110
|
+
"data-slot": "dropzone-label",
|
|
111
|
+
className: "text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]",
|
|
112
|
+
children: label ?? (isDragReject ? "Some of these files are not allowed" : isDragActive ? "Drop to upload" : multiple ? "Drop files here, or click to browse" : "Drop a file here, or click to browse")
|
|
113
|
+
}),
|
|
114
|
+
derivedHint ? /* @__PURE__ */ jsx("span", {
|
|
115
|
+
"data-slot": "dropzone-hint",
|
|
116
|
+
className: "text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
117
|
+
children: derivedHint
|
|
118
|
+
}) : null
|
|
119
|
+
] });
|
|
120
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
121
|
+
...getRootProps({
|
|
122
|
+
...props,
|
|
123
|
+
ref: setRoot,
|
|
124
|
+
className: cn("group/dropzone flex flex-col items-center justify-center gap-2 p-6 text-center", "rounded-[var(--ds-radius-lg)] border border-dashed border-[var(--ds-border)] bg-[var(--ds-card)]", noClick ? "select-none outline-none" : "cursor-pointer select-none outline-none", "transition-[border-color,background-color] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]", "hover:border-[var(--ds-border-strong)] hover:bg-[var(--ds-hover)]", "focus-visible:border-[var(--ds-ring)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "data-[active]:border-[var(--ds-primary)] data-[active]:bg-[var(--ds-primary-soft)]", "data-[reject]:border-[var(--ds-destructive)] data-[reject]:bg-[var(--ds-destructive-soft)]", "data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className)
|
|
125
|
+
}),
|
|
126
|
+
"data-slot": "dropzone",
|
|
127
|
+
"data-active": isDragActive || void 0,
|
|
128
|
+
"data-accept": isDragActive && isDragAccept || void 0,
|
|
129
|
+
"data-reject": isDragReject || void 0,
|
|
130
|
+
"data-focused": isFocused || void 0,
|
|
131
|
+
"data-disabled": disabled || void 0,
|
|
132
|
+
"data-no-click": noClick || void 0,
|
|
133
|
+
"data-no-keyboard": noKeyboard || void 0,
|
|
134
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
135
|
+
"data-slot": "dropzone-input",
|
|
136
|
+
...getInputProps()
|
|
137
|
+
}), body]
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The files a Dropzone picked: name, size and a remove button per row. Rows
|
|
142
|
+
* are keyed by name, size and last-modified time so re-picking the same file
|
|
143
|
+
* keeps its row stable.
|
|
144
|
+
*/
|
|
145
|
+
function FileList({ files, onRemove, formatSize = formatFileSize, className, ...props }) {
|
|
146
|
+
if (files.length === 0) return null;
|
|
147
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
148
|
+
"data-slot": "file-list",
|
|
149
|
+
className: cn("flex list-none flex-col gap-1.5 p-0", className),
|
|
150
|
+
...props,
|
|
151
|
+
children: files.map((file, index) => /* @__PURE__ */ jsxs("li", {
|
|
152
|
+
"data-slot": "file-list-item",
|
|
153
|
+
className: cn("flex items-center gap-3 px-3 py-2", "rounded-[var(--ds-radius-md)] border border-[var(--ds-border-subtle)] bg-[var(--ds-card)]"),
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx(FileIcon, {
|
|
156
|
+
"aria-hidden": true,
|
|
157
|
+
className: "size-4 shrink-0 text-[color:var(--ds-muted-foreground)]"
|
|
158
|
+
}),
|
|
159
|
+
/* @__PURE__ */ jsx("span", {
|
|
160
|
+
"data-slot": "file-list-name",
|
|
161
|
+
className: "min-w-0 flex-1 truncate text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]",
|
|
162
|
+
title: file.name,
|
|
163
|
+
children: file.name
|
|
164
|
+
}),
|
|
165
|
+
/* @__PURE__ */ jsx("span", {
|
|
166
|
+
"data-slot": "file-list-size",
|
|
167
|
+
className: "shrink-0 font-[family-name:var(--ds-font-mono)] text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]",
|
|
168
|
+
children: formatSize(file.size)
|
|
169
|
+
}),
|
|
170
|
+
onRemove ? /* @__PURE__ */ jsx("button", {
|
|
171
|
+
type: "button",
|
|
172
|
+
"data-slot": "file-list-remove",
|
|
173
|
+
"aria-label": `Remove ${file.name}`,
|
|
174
|
+
onClick: () => onRemove(file, index),
|
|
175
|
+
className: cn("inline-flex size-6 shrink-0 items-center justify-center rounded-[var(--ds-radius-sm)]", "text-[color:var(--ds-muted-foreground)] hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]", "outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40", "[&_svg]:size-3.5"),
|
|
176
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": true })
|
|
177
|
+
}) : null
|
|
178
|
+
]
|
|
179
|
+
}, `${file.name}-${file.size}-${file.lastModified}`))
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
export { Dropzone, FileList, formatFileSize };
|
|
184
|
+
|
|
185
|
+
//# sourceMappingURL=dropzone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dropzone.js","names":[],"sources":["../../src/components/dropzone.tsx"],"sourcesContent":["\"use client\";\n\nimport { FileIcon, Upload, X } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { useDropzone, type Accept, type DropzoneState, type FileRejection } from \"react-dropzone\";\nimport { cn } from \"@/lib/cn\";\n\n/**\n * Dropzone.\n *\n * A drop target over react-dropzone's `useDropzone`. The root is a focusable\n * region that opens the file dialog on click or Enter; the hidden `<input>` is\n * what the dialog fills. Every state is exposed as a data attribute so the\n * classes below (and a consumer's) can paint it:\n *\n * - `data-active` a drag is over the zone\n * - `data-accept` every dragged file passes `accept` / `maxSize`\n * - `data-reject` at least one dragged file fails\n * - `data-disabled`\n *\n * `onFiles` receives only the accepted files; rejections go to `onReject`.\n * `FileList` and `formatFileSize` are separate parts for listing what was\n * picked, so a form can keep the zone and the list in different places.\n *\n * ## Wrapping a whole page as a drop target\n *\n * `noClick` and `noKeyboard` turn off the two activators that only make sense\n * on a dashed box: clicking anywhere inside opens the file dialog, and Enter /\n * Space on the focused root does the same. A page-sized zone wraps content\n * that has its own buttons and inputs, so both have to go — otherwise every\n * click in the page opens a file picker.\n *\n * ```tsx\n * <Dropzone noClick noKeyboard onFiles={upload} className=\"min-h-dvh border-0 bg-transparent p-0\">\n * {({ isDragActive, open }) => (\n * <>\n * <PageContent />\n * <Button onClick={open}>Browse…</Button>\n * {isDragActive ? <DropHint /> : null}\n * </>\n * )}\n * </Dropzone>\n * ```\n *\n * The render-prop form gets react-dropzone's `open()`, which is the way back\n * to the file dialog from a deliberate control. With `noClick` the root also\n * drops its `cursor-pointer`, since it is no longer clickable.\n */\n\nconst UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"] as const;\n\n/** `1536` → `1.5 KB`, `0` → `0 B`; base 1024, one decimal above bytes. */\nfunction formatFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return \"0 B\";\n let value = bytes;\n let unit = 0;\n while (value >= 1024 && unit < UNITS.length - 1) {\n value /= 1024;\n unit += 1;\n }\n const rounded = unit === 0 ? Math.round(value) : Math.round(value * 10) / 10;\n return `${rounded} ${UNITS[unit]}`;\n}\n\n/** Turns an `accept` map into a hint such as `PNG, JPG, PDF`. */\nfunction describeAccept(accept: Accept | undefined): string | undefined {\n if (!accept) return undefined;\n const exts = new Set<string>();\n for (const [mime, list] of Object.entries(accept)) {\n const items = typeof list === \"string\" ? [list] : list;\n if (items.length > 0) {\n for (const ext of items) exts.add(ext.replace(/^\\./, \"\").toUpperCase());\n } else {\n const sub = mime.split(\"/\")[1];\n if (sub && sub !== \"*\") exts.add(sub.toUpperCase());\n }\n }\n return exts.size > 0 ? [...exts].join(\", \") : undefined;\n}\n\nexport interface DropzoneProps extends Omit<\n React.ComponentProps<\"div\">,\n \"children\" | \"onDrop\" | \"onDragEnter\" | \"onDragOver\" | \"onDragLeave\"\n> {\n /** Called with the accepted files after a drop, a paste or the file dialog. */\n onFiles: (files: File[]) => void;\n /** Called with the rejected files and their reasons. */\n onReject?: (rejections: FileRejection[]) => void;\n /** MIME type to extension map, e.g. `{ \"image/png\": [\".png\"] }`. */\n accept?: Accept;\n /** Allow more than one file; defaults to true. */\n multiple?: boolean;\n /** Largest accepted file in bytes. */\n maxSize?: number;\n /** Most files accepted per drop; `0` means unlimited. */\n maxFiles?: number;\n disabled?: boolean;\n /** Do not open the file dialog on click; for a zone that wraps clickable content. @default false */\n noClick?: boolean;\n /** Do not open the file dialog on Enter or Space; pair with `noClick`. @default false */\n noKeyboard?: boolean;\n /** Primary line of the default body. */\n label?: React.ReactNode;\n /** Muted second line of the default body; derived from `accept` and `maxSize` when omitted. */\n hint?: React.ReactNode;\n /** Replace the default body. A function receives react-dropzone's state. */\n children?: React.ReactNode | ((state: DropzoneState) => React.ReactNode);\n}\n\nfunction Dropzone({\n onFiles,\n onReject,\n accept,\n multiple = true,\n maxSize,\n maxFiles,\n disabled = false,\n noClick = false,\n noKeyboard = false,\n label,\n hint,\n children,\n className,\n ref,\n ...props\n}: DropzoneProps) {\n const state = useDropzone({\n accept,\n multiple,\n maxSize,\n maxFiles,\n disabled,\n noClick,\n noKeyboard,\n onDropAccepted: (files) => onFiles(files),\n onDropRejected: (rejections) => onReject?.(rejections),\n });\n const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, isFocused, rootRef } =\n state;\n // getRootProps would let a consumer `ref` replace its own; hold both.\n const setRoot = (node: HTMLDivElement | null) => {\n (rootRef as React.MutableRefObject<HTMLElement | null>).current = node;\n if (typeof ref === \"function\") ref(node);\n else if (ref) ref.current = node;\n };\n\n const derivedHint =\n hint ??\n [describeAccept(accept), maxSize ? `up to ${formatFileSize(maxSize)}` : undefined]\n .filter(Boolean)\n .join(\" \");\n\n const body =\n typeof children === \"function\" ? (\n children(state)\n ) : children !== undefined ? (\n children\n ) : (\n <>\n <span\n data-slot=\"dropzone-icon\"\n className={cn(\n \"flex size-10 items-center justify-center rounded-[var(--ds-radius-full)]\",\n \"bg-[var(--ds-muted)] text-[color:var(--ds-muted-foreground)]\",\n \"group-data-[active]/dropzone:bg-[var(--ds-primary-soft)] group-data-[active]/dropzone:text-[color:var(--ds-primary)]\",\n \"group-data-[reject]/dropzone:bg-[var(--ds-destructive-soft)] group-data-[reject]/dropzone:text-[color:var(--ds-destructive)]\",\n )}\n >\n <Upload aria-hidden className=\"size-5\" />\n </span>\n <span\n data-slot=\"dropzone-label\"\n className=\"text-[length:var(--ds-text-body-sm)] font-medium text-[color:var(--ds-foreground)]\"\n >\n {label ??\n (isDragReject\n ? \"Some of these files are not allowed\"\n : isDragActive\n ? \"Drop to upload\"\n : multiple\n ? \"Drop files here, or click to browse\"\n : \"Drop a file here, or click to browse\")}\n </span>\n {derivedHint ? (\n <span\n data-slot=\"dropzone-hint\"\n className=\"text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {derivedHint}\n </span>\n ) : null}\n </>\n );\n\n return (\n <div\n {...getRootProps({\n ...props,\n ref: setRoot,\n className: cn(\n \"group/dropzone flex flex-col items-center justify-center gap-2 p-6 text-center\",\n \"rounded-[var(--ds-radius-lg)] border border-dashed border-[var(--ds-border)] bg-[var(--ds-card)]\",\n noClick ? \"select-none outline-none\" : \"cursor-pointer select-none outline-none\",\n \"transition-[border-color,background-color] duration-[var(--ds-duration-fast)] ease-[var(--ds-ease-out)]\",\n \"hover:border-[var(--ds-border-strong)] hover:bg-[var(--ds-hover)]\",\n \"focus-visible:border-[var(--ds-ring)] focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"data-[active]:border-[var(--ds-primary)] data-[active]:bg-[var(--ds-primary-soft)]\",\n \"data-[reject]:border-[var(--ds-destructive)] data-[reject]:bg-[var(--ds-destructive-soft)]\",\n \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n className,\n ),\n })}\n data-slot=\"dropzone\"\n data-active={isDragActive || undefined}\n data-accept={(isDragActive && isDragAccept) || undefined}\n data-reject={isDragReject || undefined}\n data-focused={isFocused || undefined}\n data-disabled={disabled || undefined}\n data-no-click={noClick || undefined}\n data-no-keyboard={noKeyboard || undefined}\n >\n <input data-slot=\"dropzone-input\" {...getInputProps()} />\n {body}\n </div>\n );\n}\n\nexport interface FileListProps extends Omit<React.ComponentProps<\"ul\">, \"children\"> {\n files: readonly File[];\n /** Called with the file and its index when its remove button is pressed; the button is hidden when omitted. */\n onRemove?: (file: File, index: number) => void;\n /** Formats the size column; defaults to `formatFileSize`. */\n formatSize?: (bytes: number) => string;\n}\n\n/**\n * The files a Dropzone picked: name, size and a remove button per row. Rows\n * are keyed by name, size and last-modified time so re-picking the same file\n * keeps its row stable.\n */\nfunction FileList({ files, onRemove, formatSize = formatFileSize, className, ...props }: FileListProps) {\n if (files.length === 0) return null;\n return (\n <ul data-slot=\"file-list\" className={cn(\"flex list-none flex-col gap-1.5 p-0\", className)} {...props}>\n {files.map((file, index) => (\n <li\n key={`${file.name}-${file.size}-${file.lastModified}`}\n data-slot=\"file-list-item\"\n className={cn(\n \"flex items-center gap-3 px-3 py-2\",\n \"rounded-[var(--ds-radius-md)] border border-[var(--ds-border-subtle)] bg-[var(--ds-card)]\",\n )}\n >\n <FileIcon\n aria-hidden\n className=\"size-4 shrink-0 text-[color:var(--ds-muted-foreground)]\"\n />\n <span\n data-slot=\"file-list-name\"\n className=\"min-w-0 flex-1 truncate text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-foreground)]\"\n title={file.name}\n >\n {file.name}\n </span>\n <span\n data-slot=\"file-list-size\"\n className=\"shrink-0 font-[family-name:var(--ds-font-mono)] text-[length:var(--ds-text-caption)] text-[color:var(--ds-muted-foreground)]\"\n >\n {formatSize(file.size)}\n </span>\n {onRemove ? (\n <button\n type=\"button\"\n data-slot=\"file-list-remove\"\n aria-label={`Remove ${file.name}`}\n onClick={() => onRemove(file, index)}\n className={cn(\n \"inline-flex size-6 shrink-0 items-center justify-center rounded-[var(--ds-radius-sm)]\",\n \"text-[color:var(--ds-muted-foreground)] hover:bg-[var(--ds-hover)] hover:text-[color:var(--ds-foreground)]\",\n \"outline-none focus-visible:ring-[3px] focus-visible:ring-[var(--ds-ring)]/40\",\n \"[&_svg]:size-3.5\",\n )}\n >\n <X aria-hidden />\n </button>\n ) : null}\n </li>\n ))}\n </ul>\n );\n}\n\nexport { Dropzone, FileList, formatFileSize };\nexport type { Accept, FileRejection, DropzoneState };\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
//#region src/components/empty-state.d.ts
|
|
4
|
+
export interface EmptyStateProps extends Omit<React.ComponentProps<"div">, "title"> {
|
|
5
|
+
/** A lucide icon; sized and coloured by the state. */
|
|
6
|
+
icon?: React.ReactNode;
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
description?: React.ReactNode;
|
|
9
|
+
/** One or two buttons offering the next step. */
|
|
10
|
+
action?: React.ReactNode;
|
|
11
|
+
/** `compact` for an empty panel inside a card; `default` for an empty page. */
|
|
12
|
+
size?: "default" | "compact";
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* EmptyState — the centred, muted "nothing here yet" surface. The icon sits
|
|
16
|
+
* in a soft disc on the muted token; the title stays in the foreground so the
|
|
17
|
+
* eye lands on it, and everything else recedes.
|
|
18
|
+
*/
|
|
19
|
+
declare function EmptyState({ className, icon, title, description, action, size, children, ...props }: EmptyStateProps): React.JSX.Element;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { EmptyState };
|
|
22
|
+
//# sourceMappingURL=empty-state.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use client";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/empty-state.tsx
|
|
6
|
+
/**
|
|
7
|
+
* EmptyState — the centred, muted "nothing here yet" surface. The icon sits
|
|
8
|
+
* in a soft disc on the muted token; the title stays in the foreground so the
|
|
9
|
+
* eye lands on it, and everything else recedes.
|
|
10
|
+
*/
|
|
11
|
+
function EmptyState({ className, icon, title, description, action, size = "default", children, ...props }) {
|
|
12
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
+
"data-slot": "empty-state",
|
|
14
|
+
"data-size": size,
|
|
15
|
+
className: cn("flex flex-col items-center justify-center text-center", size === "compact" ? "gap-2 px-4 py-8" : "gap-3 px-6 py-16", "text-[color:var(--ds-muted-foreground)]", className),
|
|
16
|
+
...props,
|
|
17
|
+
children: [
|
|
18
|
+
icon ? /* @__PURE__ */ jsx("div", {
|
|
19
|
+
"data-slot": "empty-state-icon",
|
|
20
|
+
"aria-hidden": true,
|
|
21
|
+
className: cn("mb-1 flex items-center justify-center rounded-[var(--ds-radius-full)]", "bg-[var(--ds-muted)] text-[color:var(--ds-text-tertiary)]", size === "compact" ? "size-10 [&_svg]:size-5" : "size-12 [&_svg]:size-6"),
|
|
22
|
+
children: icon
|
|
23
|
+
}) : null,
|
|
24
|
+
/* @__PURE__ */ jsx("p", {
|
|
25
|
+
"data-slot": "empty-state-title",
|
|
26
|
+
className: cn("font-semibold text-[color:var(--ds-foreground)]", size === "compact" ? "text-[length:var(--ds-text-body-sm)] leading-[var(--ds-text-body-sm--line-height)]" : "text-[length:var(--ds-text-body-md)] leading-[var(--ds-text-body-md--line-height)]"),
|
|
27
|
+
children: title
|
|
28
|
+
}),
|
|
29
|
+
description ? /* @__PURE__ */ jsx("p", {
|
|
30
|
+
"data-slot": "empty-state-description",
|
|
31
|
+
className: cn("max-w-sm text-balance", size === "compact" ? "text-[length:var(--ds-text-caption-lg)] leading-[var(--ds-text-caption-lg--line-height)]" : "text-[length:var(--ds-text-body-sm)] leading-[var(--ds-text-body-sm--line-height)]"),
|
|
32
|
+
children: description
|
|
33
|
+
}) : null,
|
|
34
|
+
children,
|
|
35
|
+
action ? /* @__PURE__ */ jsx("div", {
|
|
36
|
+
"data-slot": "empty-state-action",
|
|
37
|
+
className: "mt-2 flex flex-wrap items-center justify-center gap-2",
|
|
38
|
+
children: action
|
|
39
|
+
}) : null
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { EmptyState };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=empty-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"empty-state.js","names":[],"sources":["../../src/components/empty-state.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { cn } from \"@/lib/cn\";\n\nexport interface EmptyStateProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n /** A lucide icon; sized and coloured by the state. */\n icon?: React.ReactNode;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** One or two buttons offering the next step. */\n action?: React.ReactNode;\n /** `compact` for an empty panel inside a card; `default` for an empty page. */\n size?: \"default\" | \"compact\";\n}\n\n/**\n * EmptyState — the centred, muted \"nothing here yet\" surface. The icon sits\n * in a soft disc on the muted token; the title stays in the foreground so the\n * eye lands on it, and everything else recedes.\n */\nfunction EmptyState({\n className,\n icon,\n title,\n description,\n action,\n size = \"default\",\n children,\n ...props\n}: EmptyStateProps) {\n return (\n <div\n data-slot=\"empty-state\"\n data-size={size}\n className={cn(\n \"flex flex-col items-center justify-center text-center\",\n size === \"compact\" ? \"gap-2 px-4 py-8\" : \"gap-3 px-6 py-16\",\n \"text-[color:var(--ds-muted-foreground)]\",\n className,\n )}\n {...props}\n >\n {icon ? (\n <div\n data-slot=\"empty-state-icon\"\n aria-hidden\n className={cn(\n \"mb-1 flex items-center justify-center rounded-[var(--ds-radius-full)]\",\n \"bg-[var(--ds-muted)] text-[color:var(--ds-text-tertiary)]\",\n size === \"compact\" ? \"size-10 [&_svg]:size-5\" : \"size-12 [&_svg]:size-6\",\n )}\n >\n {icon}\n </div>\n ) : null}\n <p\n data-slot=\"empty-state-title\"\n className={cn(\n \"font-semibold text-[color:var(--ds-foreground)]\",\n size === \"compact\"\n ? \"text-[length:var(--ds-text-body-sm)] leading-[var(--ds-text-body-sm--line-height)]\"\n : \"text-[length:var(--ds-text-body-md)] leading-[var(--ds-text-body-md--line-height)]\",\n )}\n >\n {title}\n </p>\n {description ? (\n <p\n data-slot=\"empty-state-description\"\n className={cn(\n \"max-w-sm text-balance\",\n size === \"compact\"\n ? \"text-[length:var(--ds-text-caption-lg)] leading-[var(--ds-text-caption-lg--line-height)]\"\n : \"text-[length:var(--ds-text-body-sm)] leading-[var(--ds-text-body-sm--line-height)]\",\n )}\n >\n {description}\n </p>\n ) : null}\n {children}\n {action ? (\n <div data-slot=\"empty-state-action\" className=\"mt-2 flex flex-wrap items-center justify-center gap-2\">\n {action}\n </div>\n ) : null}\n </div>\n );\n}\n\nexport { EmptyState };\n"],"mappings":";;;;;;;;;;AAqBA,SAAS,WAAW,EAClB,WACA,MACA,OACA,aACA,QACA,OAAO,WACP,UACA,GAAG,SACe;CAClB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,yDACA,SAAS,YAAY,oBAAoB,oBACzC,2CACA,SACF;EACA,GAAI;EATN,UAAA;GAWG,OACC,oBAAC,OAAD;IACE,aAAU;IACV,eAAA;IACA,WAAW,GACT,yEACA,6DACA,SAAS,YAAY,2BAA2B,wBAClD;IAEC,UAAA;GACE,CAAA,IACH;GACJ,oBAAC,KAAD;IACE,aAAU;IACV,WAAW,GACT,mDACA,SAAS,YACL,uFACA,oFACN;IAEC,UAAA;GACA,CAAA;GACF,cACC,oBAAC,KAAD;IACE,aAAU;IACV,WAAW,GACT,yBACA,SAAS,YACL,6FACA,oFACN;IAEC,UAAA;GACA,CAAA,IACD;GACH;GACA,SACC,oBAAC,OAAD;IAAK,aAAU;IAAqB,WAAU;IAC3C,UAAA;GACE,CAAA,IACH;EACD;;AAET"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
//#region src/components/error-banner.d.ts
|
|
4
|
+
export interface ErrorBannerProps extends Omit<React.ComponentProps<"div">, "children"> {
|
|
5
|
+
message: string;
|
|
6
|
+
onDismiss?: () => void;
|
|
7
|
+
onRetry?: () => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* ErrorBanner — the one error surface every screen uses: a warning icon, the
|
|
11
|
+
* message, and optional Retry / Dismiss actions. Renders nothing for an empty
|
|
12
|
+
* message so callers can pass their error state straight through.
|
|
13
|
+
*/
|
|
14
|
+
declare function ErrorBanner({ message, onDismiss, onRetry, className, ...props }: ErrorBannerProps): React.JSX.Element | null;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ErrorBanner };
|
|
17
|
+
//# sourceMappingURL=error-banner.d.ts.map
|