@graundtech/fluent2-react-kit 0.5.1
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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/components/ui/accordion.d.ts +133 -0
- package/dist/components/ui/accordion.js +80 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/alert.d.ts +106 -0
- package/dist/components/ui/alert.js +73 -0
- package/dist/components/ui/alert.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +42 -0
- package/dist/components/ui/avatar.js +55 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +36 -0
- package/dist/components/ui/badge.js +81 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +53 -0
- package/dist/components/ui/breadcrumb.js +149 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +24 -0
- package/dist/components/ui/button.js +86 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +51 -0
- package/dist/components/ui/card.js +97 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +101 -0
- package/dist/components/ui/checkbox.js +57 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +212 -0
- package/dist/components/ui/combobox.js +236 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/command.d.ts +187 -0
- package/dist/components/ui/command.js +225 -0
- package/dist/components/ui/command.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +117 -0
- package/dist/components/ui/dialog.js +165 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +155 -0
- package/dist/components/ui/dropdown-menu.js +295 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/input.d.ts +44 -0
- package/dist/components/ui/input.js +43 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +39 -0
- package/dist/components/ui/label.js +30 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/link.d.ts +62 -0
- package/dist/components/ui/link.js +61 -0
- package/dist/components/ui/link.js.map +1 -0
- package/dist/components/ui/multi-select.d.ts +115 -0
- package/dist/components/ui/multi-select.js +133 -0
- package/dist/components/ui/multi-select.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +46 -0
- package/dist/components/ui/pagination.js +164 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +117 -0
- package/dist/components/ui/popover.js +62 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/progress.d.ts +98 -0
- package/dist/components/ui/progress.js +67 -0
- package/dist/components/ui/progress.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +94 -0
- package/dist/components/ui/radio-group.js +65 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +145 -0
- package/dist/components/ui/select.js +240 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/separator.d.ts +43 -0
- package/dist/components/ui/separator.js +27 -0
- package/dist/components/ui/separator.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +81 -0
- package/dist/components/ui/skeleton.js +24 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/spinner.d.ts +56 -0
- package/dist/components/ui/spinner.js +83 -0
- package/dist/components/ui/spinner.js.map +1 -0
- package/dist/components/ui/switch.d.ts +60 -0
- package/dist/components/ui/switch.js +63 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +107 -0
- package/dist/components/ui/tabs.js +102 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +28 -0
- package/dist/components/ui/textarea.js +36 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +185 -0
- package/dist/components/ui/toast.js +181 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +115 -0
- package/dist/components/ui/tooltip.js +72 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +9 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +77 -0
- package/src/styles/tokens.css +596 -0
- package/src/styles/tokens.test.ts +216 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
|
4
|
+
import { DismissRegular } from "@fluentui/react-icons";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
function Dialog(props) {
|
|
7
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Root, { ...props });
|
|
8
|
+
}
|
|
9
|
+
function DialogTrigger({
|
|
10
|
+
className,
|
|
11
|
+
...props
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
DialogPrimitive.Trigger,
|
|
15
|
+
{
|
|
16
|
+
"data-slot": "dialog-trigger",
|
|
17
|
+
className: cn(className),
|
|
18
|
+
...props
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function DialogPortal(props) {
|
|
23
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
24
|
+
}
|
|
25
|
+
function DialogOverlay({
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}) {
|
|
29
|
+
return /* @__PURE__ */ jsx(
|
|
30
|
+
DialogPrimitive.Backdrop,
|
|
31
|
+
{
|
|
32
|
+
"data-slot": "dialog-overlay",
|
|
33
|
+
className: cn(
|
|
34
|
+
"fixed inset-0 z-50 bg-black/40",
|
|
35
|
+
// motion — fade on open (enter) / close (exit)
|
|
36
|
+
"transition-opacity duration-normal ease-decelerate-mid",
|
|
37
|
+
"data-starting-style:opacity-0",
|
|
38
|
+
"data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
...props
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function DialogContent({
|
|
46
|
+
className,
|
|
47
|
+
children,
|
|
48
|
+
showCloseButton = true,
|
|
49
|
+
...props
|
|
50
|
+
}) {
|
|
51
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { children: [
|
|
52
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
53
|
+
/* @__PURE__ */ jsxs(
|
|
54
|
+
DialogPrimitive.Popup,
|
|
55
|
+
{
|
|
56
|
+
"data-slot": "dialog-content",
|
|
57
|
+
className: cn(
|
|
58
|
+
// layout — centered fixed surface, dialog radius + elevation
|
|
59
|
+
"fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 text-foreground shadow-64 outline-none",
|
|
60
|
+
// motion — fade + slight zoom; `scale`, not `transform` (see doc note)
|
|
61
|
+
"transition-[opacity,scale] duration-normal ease-decelerate-mid",
|
|
62
|
+
"data-starting-style:scale-95 data-starting-style:opacity-0",
|
|
63
|
+
"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid",
|
|
64
|
+
className
|
|
65
|
+
),
|
|
66
|
+
...props,
|
|
67
|
+
children: [
|
|
68
|
+
children,
|
|
69
|
+
showCloseButton && /* @__PURE__ */ jsxs(
|
|
70
|
+
DialogPrimitive.Close,
|
|
71
|
+
{
|
|
72
|
+
"data-slot": "dialog-close",
|
|
73
|
+
className: cn(
|
|
74
|
+
"absolute top-4 right-4 flex size-8 items-center justify-center rounded-md text-muted-foreground",
|
|
75
|
+
"cursor-pointer outline-none transition-colors duration-fast ease-ease",
|
|
76
|
+
"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70",
|
|
77
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
78
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
79
|
+
),
|
|
80
|
+
children: [
|
|
81
|
+
/* @__PURE__ */ jsx(DismissRegular, { className: "size-4" }),
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
] });
|
|
90
|
+
}
|
|
91
|
+
function DialogHeader({ className, ...props }) {
|
|
92
|
+
return /* @__PURE__ */ jsx(
|
|
93
|
+
"div",
|
|
94
|
+
{
|
|
95
|
+
"data-slot": "dialog-header",
|
|
96
|
+
className: cn("flex flex-col gap-1.5 text-center sm:text-left", className),
|
|
97
|
+
...props
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function DialogFooter({ className, ...props }) {
|
|
102
|
+
return /* @__PURE__ */ jsx(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
"data-slot": "dialog-footer",
|
|
106
|
+
className: cn(
|
|
107
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
108
|
+
className
|
|
109
|
+
),
|
|
110
|
+
...props
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
function DialogTitle({
|
|
115
|
+
className,
|
|
116
|
+
...props
|
|
117
|
+
}) {
|
|
118
|
+
return /* @__PURE__ */ jsx(
|
|
119
|
+
DialogPrimitive.Title,
|
|
120
|
+
{
|
|
121
|
+
"data-slot": "dialog-title",
|
|
122
|
+
className: cn("text-xl font-semibold", className),
|
|
123
|
+
...props
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
function DialogDescription({
|
|
128
|
+
className,
|
|
129
|
+
...props
|
|
130
|
+
}) {
|
|
131
|
+
return /* @__PURE__ */ jsx(
|
|
132
|
+
DialogPrimitive.Description,
|
|
133
|
+
{
|
|
134
|
+
"data-slot": "dialog-description",
|
|
135
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
136
|
+
...props
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
function DialogClose({
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}) {
|
|
144
|
+
return /* @__PURE__ */ jsx(
|
|
145
|
+
DialogPrimitive.Close,
|
|
146
|
+
{
|
|
147
|
+
"data-slot": "dialog-close",
|
|
148
|
+
className: cn(className),
|
|
149
|
+
...props
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
Dialog,
|
|
155
|
+
DialogClose,
|
|
156
|
+
DialogContent,
|
|
157
|
+
DialogDescription,
|
|
158
|
+
DialogFooter,
|
|
159
|
+
DialogHeader,
|
|
160
|
+
DialogOverlay,
|
|
161
|
+
DialogPortal,
|
|
162
|
+
DialogTitle,
|
|
163
|
+
DialogTrigger
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/ui/dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\";\nimport { DismissRegular } from \"@fluentui/react-icons\";\nimport type { ComponentProps } from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\n/**\n * Dialog — Fluent 2-styled, shadcn-API modal dialog.\n *\n * Follows the overlay patterns set by `select.tsx` (portal → popup surface,\n * `data-starting-style`/`data-ending-style` motion with token durations and\n * easings) with dialog-specific elevation: `shadow-64` + `rounded-xl`\n * (conventions §3.4/§3.6 — dialogs are the kit's highest floating surface).\n *\n * ## Base UI mapping (conventions §9)\n * Behavior — focus trapping, portalling, open/close state, scroll locking,\n * outside-press and Escape dismissal — genuinely needs a primitive, so the\n * parts wrap `@base-ui/react/dialog` (namespace import, matching the actual\n * export shape in node_modules, exactly like `select.tsx`). shadcn part names\n * are mapped onto Base UI's model:\n *\n * | Exported (shadcn name) | Base UI primitive |\n * | ---------------------- | --------------------------------------------------- |\n * | `Dialog` | `Dialog.Root` |\n * | `DialogTrigger` | `Dialog.Trigger` |\n * | `DialogPortal` | `Dialog.Portal` |\n * | `DialogOverlay` | `Dialog.Backdrop` ← see divergence 1 |\n * | `DialogContent` | `Dialog.Portal` + `Dialog.Backdrop` + `Dialog.Popup` (+ built-in ✕ `Dialog.Close`) |\n * | `DialogHeader` | plain `<div>` (shadcn parity — no Base UI part) |\n * | `DialogFooter` | plain `<div>` (shadcn parity — no Base UI part) |\n * | `DialogTitle` | `Dialog.Title` (renders `<h2>`) |\n * | `DialogDescription` | `Dialog.Description` (renders `<p>`) |\n * | `DialogClose` | `Dialog.Close` |\n *\n * Base UI wires the popup's `aria-labelledby` to `DialogTitle` and\n * `aria-describedby` to `DialogDescription` automatically — no manual ids.\n *\n * ## Divergences from the shadcn/Radix Dialog API (all deliberate)\n * 1. **`DialogOverlay` wraps Base UI `Dialog.Backdrop`, not a Radix `Overlay`.**\n * Same role (the dimming layer under the popup), different primitive name.\n * Note Base UI skips rendering the backdrop for *nested* dialogs unless\n * `forceRender` is set — parent smoke already covers the viewport.\n * 2. **`modal` defaults to `true`** (focus trap + page scroll lock + outside\n * pointer interactions disabled), matching Radix. Base UI adds a third mode,\n * `modal=\"trap-focus\"` (trap focus without scroll lock), which passes\n * straight through.\n * 3. **`DialogContent` composes `Portal` + `Backdrop` + `Popup`** and appends a\n * top-right ✕ close button (shadcn parity — shadcn composes the same three\n * Radix parts). Pass `showCloseButton={false}` to omit the ✕ (matching\n * current shadcn's `DialogContent` prop).\n * 4. **No `asChild`.** Base UI composes via the `render` prop instead — e.g.\n * `<DialogTrigger render={<Button variant=\"secondary\" />}>Open</DialogTrigger>`.\n * Every part forwards it.\n * 5. **`onOpenChange(open, eventDetails)`** — Base UI passes a second\n * `eventDetails` argument (reason + native event). The first argument is the\n * boolean Radix passes, so existing shadcn call sites work unchanged.\n * 6. **`Dialog.Viewport` is not re-exported.** It's a Base UI layout helper for\n * scrollable full-viewport dialogs with no shadcn equivalent; the kit centers\n * the popup with fixed positioning like shadcn. Compose it manually from\n * `@base-ui/react/dialog` if you need it.\n *\n * ## Smoke layer (`bg-black/40`) — sanctioned hardcoded color\n * The backdrop is black at 40% opacity in *both* themes, per Fluent's smoke\n * layer (it dims light and dark surfaces alike). Conventions §3.7 bans\n * hardcoded colors so a shade can't drift from the token set, but the smoke\n * layer is intentionally theme-invariant — there is no token for it and\n * inventing one would imply it re-points in dark mode, which it must not.\n * `bg-black/40` is the sanctioned exception here.\n *\n * ## Motion note — the popup zoom animates `scale`, not `transform`\n * Tailwind v4 emits `translate-*` and `scale-*` as the independent CSS\n * `translate:`/`scale:` properties (not a shared `transform:`), so the\n * -50%/-50% centering translate and the open/close zoom coexist without\n * clobbering each other — but it also means the popup's transition must target\n * `transition-[opacity,scale]` (a `transition-[transform,opacity]` list would\n * leave the zoom snapping — every popup in the kit uses `[opacity,scale]` for\n * this reason).\n *\n * ## `\"use client\"` — required\n * Two reasons. The Base UI Dialog parts manage client state (each part module\n * carries its own `'use client'`), and — decisively — `DismissRegular` from\n * `@fluentui/react-icons` must stay inside a client boundary: the package's\n * shared icon-sizing module calls `@griffel/react`'s client-only `__styles()`\n * at module scope without its own directive, so importing an icon from a\n * Server Component breaks `next build`. See conventions §9; `select.tsx` and\n * `checkbox.tsx` are the precedents.\n *\n * ## data-slot note\n * `Dialog` (Root) renders no DOM element of its own, so it carries no\n * `data-slot`. Every part that renders an element does — `dialog-trigger`,\n * `dialog-overlay`, `dialog-content`, etc.\n */\n\nfunction Dialog<Payload>(props: DialogPrimitive.Root.Props<Payload>) {\n return <DialogPrimitive.Root {...props} />;\n}\n\nfunction DialogTrigger({\n className,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Trigger>) {\n return (\n <DialogPrimitive.Trigger\n data-slot=\"dialog-trigger\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\nfunction DialogPortal(props: ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\n/**\n * Overlay — Fluent's smoke layer: black at 40% opacity over the full viewport\n * in both themes (see the smoke-layer note above), fading in/out via Base UI's\n * `data-starting-style`/`data-ending-style` hooks with token durations/easings.\n */\nfunction DialogOverlay({\n className,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Backdrop>) {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n \"fixed inset-0 z-50 bg-black/40\",\n // motion — fade on open (enter) / close (exit)\n \"transition-opacity duration-normal ease-decelerate-mid\",\n \"data-starting-style:opacity-0\",\n \"data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n className\n )}\n {...props}\n />\n );\n}\n\n/**\n * Content — the modal surface: `Portal` → `Backdrop` → `Popup`, centered with\n * fixed positioning, on `bg-background` with `rounded-xl` + `shadow-64`\n * (dialog elevation, conventions §3.6). A fade + slight zoom rides the\n * `data-starting-style`/`data-ending-style` hooks; the zoom animates the CSS\n * `scale` property (see the motion note above). The built-in ✕ close button\n * sits top-right (`showCloseButton={false}` omits it).\n */\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Popup> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n // layout — centered fixed surface, dialog radius + elevation\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 text-foreground shadow-64 outline-none\",\n // motion — fade + slight zoom; `scale`, not `transform` (see doc note)\n \"transition-[opacity,scale] duration-normal ease-decelerate-mid\",\n \"data-starting-style:scale-95 data-starting-style:opacity-0\",\n \"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid\",\n className\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className={cn(\n \"absolute top-4 right-4 flex size-8 items-center justify-center rounded-md text-muted-foreground\",\n \"cursor-pointer outline-none transition-colors duration-fast ease-ease\",\n \"hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-accent/50 dark:active:bg-accent/70\",\n \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n \"disabled:pointer-events-none disabled:opacity-50\"\n )}\n >\n <DismissRegular className=\"size-4\" />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn(\"flex flex-col gap-1.5 text-center sm:text-left\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogFooter({ className, ...props }: ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n \"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\",\n className\n )}\n {...props}\n />\n );\n}\n\nfunction DialogTitle({\n className,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn(\"text-xl font-semibold\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn(\"text-sm text-muted-foreground\", className)}\n {...props}\n />\n );\n}\n\nfunction DialogClose({\n className,\n ...props\n}: ComponentProps<typeof DialogPrimitive.Close>) {\n return (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className={cn(className)}\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n DialogTrigger,\n DialogPortal,\n DialogOverlay,\n DialogContent,\n DialogHeader,\n DialogFooter,\n DialogTitle,\n DialogDescription,\n DialogClose,\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import { Menu } from '@base-ui/react/menu';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* DropdownMenu — Fluent 2-styled, shadcn-API dropdown menu.
|
|
7
|
+
*
|
|
8
|
+
* Second overlay component in the kit, so it deliberately reuses every pattern
|
|
9
|
+
* `select.tsx` established (portal + positioner + popup surface, `shadow-16`
|
|
10
|
+
* flyout elevation, 32px `data-highlighted` item rows, scale+fade motion via
|
|
11
|
+
* `data-starting-style`/`data-ending-style`). Its popup surface and item rows
|
|
12
|
+
* are visually identical to Select's — same tokens, same radii.
|
|
13
|
+
*
|
|
14
|
+
* ## Base UI mapping (conventions §9)
|
|
15
|
+
* Behavior — focus management, portalling, open/close state, roving tabindex,
|
|
16
|
+
* type-ahead, collision-aware positioning, submenu orchestration — genuinely
|
|
17
|
+
* needs a primitive, so the parts wrap `@base-ui/react/menu` (namespace import,
|
|
18
|
+
* matching the actual export shape in node_modules, exactly like `select.tsx`).
|
|
19
|
+
* shadcn part names are mapped onto Base UI's model:
|
|
20
|
+
*
|
|
21
|
+
* | Exported (shadcn name) | Base UI primitive |
|
|
22
|
+
* | -------------------------- | ------------------------------------------------------ |
|
|
23
|
+
* | `DropdownMenu` | `Menu.Root` |
|
|
24
|
+
* | `DropdownMenuTrigger` | `Menu.Trigger` |
|
|
25
|
+
* | `DropdownMenuContent` | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup` |
|
|
26
|
+
* | `DropdownMenuGroup` | `Menu.Group` |
|
|
27
|
+
* | `DropdownMenuLabel` | `Menu.GroupLabel` ← see divergence 1 |
|
|
28
|
+
* | `DropdownMenuItem` | `Menu.Item` |
|
|
29
|
+
* | `DropdownMenuCheckboxItem` | `Menu.CheckboxItem` (+ `Menu.CheckboxItemIndicator`) |
|
|
30
|
+
* | `DropdownMenuRadioGroup` | `Menu.RadioGroup` |
|
|
31
|
+
* | `DropdownMenuRadioItem` | `Menu.RadioItem` (+ `Menu.RadioItemIndicator`) |
|
|
32
|
+
* | `DropdownMenuSeparator` | `Menu.Separator` |
|
|
33
|
+
* | `DropdownMenuShortcut` | plain `<span>` (no Base UI part) ← see divergence 4 |
|
|
34
|
+
* | `DropdownMenuSub` | `Menu.SubmenuRoot` ← see divergence 2 |
|
|
35
|
+
* | `DropdownMenuSubTrigger` | `Menu.SubmenuTrigger` (+ `ChevronRightRegular`) |
|
|
36
|
+
* | `DropdownMenuSubContent` | `Menu.Portal` + `Menu.Positioner` + `Menu.Popup` |
|
|
37
|
+
*
|
|
38
|
+
* ## Divergences from the shadcn/Radix DropdownMenu API (all deliberate)
|
|
39
|
+
* 1. **`DropdownMenuLabel` maps to Base UI `Menu.GroupLabel`.** Like `select.tsx`,
|
|
40
|
+
* the label part is the heading *inside* a `DropdownMenuGroup` (Base UI
|
|
41
|
+
* associates it with the group for screen readers). It is styled to match
|
|
42
|
+
* `SelectLabel` (muted `text-xs` caption) rather than shadcn's darker
|
|
43
|
+
* `text-sm`, keeping every group heading in the kit visually consistent. It
|
|
44
|
+
* still honours shadcn's `inset` prop (`data-[inset]:pl-8`).
|
|
45
|
+
* 2. **`DropdownMenuSub` is Base UI `Menu.SubmenuRoot`, not a Radix `Sub`.** Base
|
|
46
|
+
* UI models a submenu as its own nested root; `DropdownMenuSubContent`
|
|
47
|
+
* therefore re-composes `Portal` → `Positioner` → `Popup` (Radix's
|
|
48
|
+
* `SubContent` is a single part). Submenus open to the side and Base UI
|
|
49
|
+
* positions them collision-aware; the trigger stays highlighted while its
|
|
50
|
+
* submenu is open via `data-[popup-open]`.
|
|
51
|
+
* 3. **Composition uses Base UI's `render` prop, not `asChild`.** shadcn's
|
|
52
|
+
* `<DropdownMenuTrigger asChild>` has no analogue here — Base UI parts take a
|
|
53
|
+
* `render` prop for polymorphism. To use the kit `Button` as the trigger,
|
|
54
|
+
* pass `render={<Button>…</Button>}`. The wrapper forwards `render` straight
|
|
55
|
+
* through, so the honest Base UI model is preserved (same reasoning as
|
|
56
|
+
* `select.tsx` keeping `SelectValue`'s render-function child).
|
|
57
|
+
* 4. **`DropdownMenuShortcut` is a plain `<span>`** (shadcn parity — it is not a
|
|
58
|
+
* Base UI part, just a right-aligned muted caption for a keyboard hint).
|
|
59
|
+
* 5. **`DropdownMenuItem` keeps shadcn's `inset` + `variant` props.** `variant`
|
|
60
|
+
* is surfaced as `data-variant` (mirrors `Button`'s `data-variant` hook); the
|
|
61
|
+
* `"destructive"` value tints the row and its icons with the `destructive`
|
|
62
|
+
* token (never a hardcoded red), and its highlight uses `destructive/10`
|
|
63
|
+
* (`/20` in dark) so the destructive intent survives the hover state.
|
|
64
|
+
*
|
|
65
|
+
* ## `"use client"` — required (same root cause as `select.tsx`)
|
|
66
|
+
* Every Base UI Menu part module carries its own `'use client'`, so on that
|
|
67
|
+
* basis this wrapper could stay server-renderable. But `CheckmarkRegular` /
|
|
68
|
+
* `ChevronRightRegular` from `@fluentui/react-icons` force the directive: the
|
|
69
|
+
* package's shared icon-sizing module (`createFluentIcon.styles.js`) calls
|
|
70
|
+
* `@griffel/react`'s `__styles()` at module scope *without* its own
|
|
71
|
+
* `'use client'`, even though `__styles` is client-only. Rendering any of these
|
|
72
|
+
* icons from a Server Component pulls that module into the server's RSC graph,
|
|
73
|
+
* and `next build` (Turbopack) fails collecting page data with "Attempted to
|
|
74
|
+
* call __styles() from the server but __styles is on the client" — reproduced
|
|
75
|
+
* against `@fluentui/react-icons@2.0.333` / `@griffel/react@1.7.5`, and it
|
|
76
|
+
* poisons every route sharing Turbopack's chunk for those icons. `"use client"`
|
|
77
|
+
* here keeps the icon imports inside a client boundary. `select.tsx` and
|
|
78
|
+
* `checkbox.tsx` carry the same fix (their doc comments explain it inline).
|
|
79
|
+
*
|
|
80
|
+
* ## data-slot note
|
|
81
|
+
* `DropdownMenu` (Root) and `DropdownMenuSub` (SubmenuRoot) render no DOM element
|
|
82
|
+
* of their own, so they carry no `data-slot`. Every part that renders an element
|
|
83
|
+
* does — `dropdown-menu-trigger`, `dropdown-menu-content`, `dropdown-menu-item`,
|
|
84
|
+
* etc. — which is what tests/consumers hook onto.
|
|
85
|
+
*/
|
|
86
|
+
declare function DropdownMenu<Payload>(props: Menu.Root.Props<Payload>): react.JSX.Element;
|
|
87
|
+
declare function DropdownMenuTrigger({ className, ...props }: ComponentProps<typeof Menu.Trigger>): react.JSX.Element;
|
|
88
|
+
declare function DropdownMenuGroup({ className, ...props }: ComponentProps<typeof Menu.Group>): react.JSX.Element;
|
|
89
|
+
/**
|
|
90
|
+
* Content — the floating flyout: `Portal` → `Positioner` → `Popup`. Surface is
|
|
91
|
+
* `bg-popover` + `border` + `shadow-16` (Fluent flyout elevation, conventions
|
|
92
|
+
* §3.6), `rounded-md`, `min-w-[8rem]` (menus size to content, unlike Select
|
|
93
|
+
* which matches the anchor width). A subtle scale+fade open/close animation
|
|
94
|
+
* rides Base UI's `data-starting-style`/`data-ending-style` hooks with token
|
|
95
|
+
* durations/easings — the exact lines from `SelectContent`.
|
|
96
|
+
*/
|
|
97
|
+
declare function DropdownMenuContent({ className, sideOffset, align, side, ...props }: ComponentProps<typeof Menu.Popup> & Pick<ComponentProps<typeof Menu.Positioner>, "side" | "align" | "sideOffset">): react.JSX.Element;
|
|
98
|
+
/**
|
|
99
|
+
* Label — a heading for a `DropdownMenuGroup` (Base UI `Menu.GroupLabel`, see
|
|
100
|
+
* divergence 1). Small muted caption, matched to `SelectLabel`. `inset` shifts
|
|
101
|
+
* it to line up with items that carry a leading indicator.
|
|
102
|
+
*/
|
|
103
|
+
declare function DropdownMenuLabel({ className, inset, ...props }: ComponentProps<typeof Menu.GroupLabel> & {
|
|
104
|
+
inset?: boolean;
|
|
105
|
+
}): react.JSX.Element;
|
|
106
|
+
/**
|
|
107
|
+
* Item — one command row. 32px tall (`h-8`) with the Fluent 4px list radius,
|
|
108
|
+
* matching `SelectItem`. Highlight (keyboard focus or hover) rides Base UI's
|
|
109
|
+
* `data-highlighted` → `bg-accent`/`text-accent-foreground`. `inset` adds the
|
|
110
|
+
* leading gutter used by checkbox/radio rows; `variant="destructive"` tints the
|
|
111
|
+
* text/icons with the `destructive` token and gives the highlight a destructive
|
|
112
|
+
* wash. Icons follow `SelectItem`'s size-only rules (no muting).
|
|
113
|
+
*/
|
|
114
|
+
declare function DropdownMenuItem({ className, inset, variant, ...props }: ComponentProps<typeof Menu.Item> & {
|
|
115
|
+
inset?: boolean;
|
|
116
|
+
variant?: "default" | "destructive";
|
|
117
|
+
}): react.JSX.Element;
|
|
118
|
+
/**
|
|
119
|
+
* CheckboxItem — a toggleable row. The check (`CheckmarkRegular`) sits in a
|
|
120
|
+
* left gutter (`pl-8`), inside `Menu.CheckboxItemIndicator`, which Base UI only
|
|
121
|
+
* mounts while checked. Base UI keeps the menu open on toggle (its
|
|
122
|
+
* `closeOnClick` defaults to `false` for checkbox items).
|
|
123
|
+
*/
|
|
124
|
+
declare function DropdownMenuCheckboxItem({ className, children, checked, ...props }: ComponentProps<typeof Menu.CheckboxItem>): react.JSX.Element;
|
|
125
|
+
declare function DropdownMenuRadioGroup({ className, ...props }: ComponentProps<typeof Menu.RadioGroup>): react.JSX.Element;
|
|
126
|
+
/**
|
|
127
|
+
* RadioItem — one option in a `DropdownMenuRadioGroup`. The selected marker is a
|
|
128
|
+
* small filled circle (an inline `<svg>` using `fill-current`, so it inherits
|
|
129
|
+
* the row's text color and needs no extra icon import — same rationale as the
|
|
130
|
+
* kit `RadioGroup` dot). `Menu.RadioItemIndicator` only mounts while selected.
|
|
131
|
+
*/
|
|
132
|
+
declare function DropdownMenuRadioItem({ className, children, ...props }: ComponentProps<typeof Menu.RadioItem>): react.JSX.Element;
|
|
133
|
+
declare function DropdownMenuSeparator({ className, ...props }: ComponentProps<typeof Menu.Separator>): react.JSX.Element;
|
|
134
|
+
/**
|
|
135
|
+
* Shortcut — a right-aligned keyboard hint caption. Plain `<span>` (divergence
|
|
136
|
+
* 4), shadcn parity: `ml-auto text-xs tracking-widest text-muted-foreground`.
|
|
137
|
+
*/
|
|
138
|
+
declare function DropdownMenuShortcut({ className, ...props }: ComponentProps<"span">): react.JSX.Element;
|
|
139
|
+
declare function DropdownMenuSub(props: Menu.SubmenuRoot.Props): react.JSX.Element;
|
|
140
|
+
/**
|
|
141
|
+
* SubTrigger — an item that opens a submenu, with a trailing `ChevronRightRegular`.
|
|
142
|
+
* Same row treatment as `DropdownMenuItem`; stays highlighted while its submenu
|
|
143
|
+
* is open via Base UI's `data-[popup-open]`. Honours `inset`.
|
|
144
|
+
*/
|
|
145
|
+
declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: ComponentProps<typeof Menu.SubmenuTrigger> & {
|
|
146
|
+
inset?: boolean;
|
|
147
|
+
}): react.JSX.Element;
|
|
148
|
+
/**
|
|
149
|
+
* SubContent — a submenu's floating surface. Same `Portal` → `Positioner` →
|
|
150
|
+
* `Popup` composition and surface as `DropdownMenuContent`, but defaults to
|
|
151
|
+
* opening on the inline-end side (Base UI still flips it collision-aware).
|
|
152
|
+
*/
|
|
153
|
+
declare function DropdownMenuSubContent({ className, sideOffset, align, ...props }: ComponentProps<typeof Menu.Popup> & Pick<ComponentProps<typeof Menu.Positioner>, "side" | "align" | "sideOffset">): react.JSX.Element;
|
|
154
|
+
|
|
155
|
+
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger };
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
|
4
|
+
import { CheckmarkRegular, ChevronRightRegular } from "@fluentui/react-icons";
|
|
5
|
+
import { cn } from "../../lib/utils.js";
|
|
6
|
+
function DropdownMenu(props) {
|
|
7
|
+
return /* @__PURE__ */ jsx(MenuPrimitive.Root, { ...props });
|
|
8
|
+
}
|
|
9
|
+
function DropdownMenuTrigger({
|
|
10
|
+
className,
|
|
11
|
+
...props
|
|
12
|
+
}) {
|
|
13
|
+
return /* @__PURE__ */ jsx(
|
|
14
|
+
MenuPrimitive.Trigger,
|
|
15
|
+
{
|
|
16
|
+
"data-slot": "dropdown-menu-trigger",
|
|
17
|
+
className: cn(className),
|
|
18
|
+
...props
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function DropdownMenuGroup({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}) {
|
|
26
|
+
return /* @__PURE__ */ jsx(
|
|
27
|
+
MenuPrimitive.Group,
|
|
28
|
+
{
|
|
29
|
+
"data-slot": "dropdown-menu-group",
|
|
30
|
+
className: cn(className),
|
|
31
|
+
...props
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function DropdownMenuContent({
|
|
36
|
+
className,
|
|
37
|
+
sideOffset = 4,
|
|
38
|
+
align = "start",
|
|
39
|
+
side = "bottom",
|
|
40
|
+
...props
|
|
41
|
+
}) {
|
|
42
|
+
return /* @__PURE__ */ jsx(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
43
|
+
MenuPrimitive.Positioner,
|
|
44
|
+
{
|
|
45
|
+
"data-slot": "dropdown-menu-positioner",
|
|
46
|
+
sideOffset,
|
|
47
|
+
align,
|
|
48
|
+
side,
|
|
49
|
+
className: "z-50 outline-none",
|
|
50
|
+
children: /* @__PURE__ */ jsx(
|
|
51
|
+
MenuPrimitive.Popup,
|
|
52
|
+
{
|
|
53
|
+
"data-slot": "dropdown-menu-content",
|
|
54
|
+
className: cn(
|
|
55
|
+
"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none",
|
|
56
|
+
"max-h-[var(--available-height)]",
|
|
57
|
+
// motion — subtle scale + fade on open (enter) / close (exit)
|
|
58
|
+
"transition-[opacity,scale] duration-fast ease-decelerate-mid",
|
|
59
|
+
"data-starting-style:scale-95 data-starting-style:opacity-0",
|
|
60
|
+
"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid",
|
|
61
|
+
className
|
|
62
|
+
),
|
|
63
|
+
...props
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
) });
|
|
68
|
+
}
|
|
69
|
+
function DropdownMenuLabel({
|
|
70
|
+
className,
|
|
71
|
+
inset,
|
|
72
|
+
...props
|
|
73
|
+
}) {
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
MenuPrimitive.GroupLabel,
|
|
76
|
+
{
|
|
77
|
+
"data-slot": "dropdown-menu-label",
|
|
78
|
+
"data-inset": inset || void 0,
|
|
79
|
+
className: cn(
|
|
80
|
+
// Fluent Menu section header — Caption 1 Stronger (12px bold) in
|
|
81
|
+
// NeutralForeground2, per the Figma "Menu section" symbol; deliberately
|
|
82
|
+
// heavier than SelectLabel's muted caption (Figma validation pass 2).
|
|
83
|
+
"px-2 py-1.5 text-xs font-bold text-foreground-2 select-none",
|
|
84
|
+
"data-[inset]:pl-8",
|
|
85
|
+
className
|
|
86
|
+
),
|
|
87
|
+
...props
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function DropdownMenuItem({
|
|
92
|
+
className,
|
|
93
|
+
inset,
|
|
94
|
+
variant = "default",
|
|
95
|
+
...props
|
|
96
|
+
}) {
|
|
97
|
+
return /* @__PURE__ */ jsx(
|
|
98
|
+
MenuPrimitive.Item,
|
|
99
|
+
{
|
|
100
|
+
"data-slot": "dropdown-menu-item",
|
|
101
|
+
"data-inset": inset || void 0,
|
|
102
|
+
"data-variant": variant,
|
|
103
|
+
className: cn(
|
|
104
|
+
// layout — Fluent 32px row, 4px list radius
|
|
105
|
+
"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none",
|
|
106
|
+
// rest text is NeutralForeground2, darkening to accent-foreground on
|
|
107
|
+
// highlight (Figma validation: Fluent list rows rest at #424242)
|
|
108
|
+
"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
109
|
+
// disabled item — muted + non-interactive
|
|
110
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
111
|
+
// inset — leading gutter to align with indicator rows
|
|
112
|
+
"data-[inset]:pl-8",
|
|
113
|
+
// destructive variant — token-tinted text, icons, and highlight wash
|
|
114
|
+
"data-[variant=destructive]:text-destructive",
|
|
115
|
+
"data-[variant=destructive]:data-[highlighted]:bg-destructive/10 data-[variant=destructive]:data-[highlighted]:text-destructive dark:data-[variant=destructive]:data-[highlighted]:bg-destructive/20",
|
|
116
|
+
"data-[variant=destructive]:[&_svg:not([class*='text-'])]:text-destructive",
|
|
117
|
+
// icons — size only (matches SelectItem)
|
|
118
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
119
|
+
className
|
|
120
|
+
),
|
|
121
|
+
...props
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function DropdownMenuCheckboxItem({
|
|
126
|
+
className,
|
|
127
|
+
children,
|
|
128
|
+
checked,
|
|
129
|
+
...props
|
|
130
|
+
}) {
|
|
131
|
+
return /* @__PURE__ */ jsxs(
|
|
132
|
+
MenuPrimitive.CheckboxItem,
|
|
133
|
+
{
|
|
134
|
+
"data-slot": "dropdown-menu-checkbox-item",
|
|
135
|
+
checked,
|
|
136
|
+
className: cn(
|
|
137
|
+
"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none",
|
|
138
|
+
"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
139
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
140
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
141
|
+
className
|
|
142
|
+
),
|
|
143
|
+
...props,
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx(MenuPrimitive.CheckboxItemIndicator, { children: /* @__PURE__ */ jsx(CheckmarkRegular, { className: "size-4" }) }) }),
|
|
146
|
+
children
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
function DropdownMenuRadioGroup({
|
|
152
|
+
className,
|
|
153
|
+
...props
|
|
154
|
+
}) {
|
|
155
|
+
return /* @__PURE__ */ jsx(
|
|
156
|
+
MenuPrimitive.RadioGroup,
|
|
157
|
+
{
|
|
158
|
+
"data-slot": "dropdown-menu-radio-group",
|
|
159
|
+
className: cn(className),
|
|
160
|
+
...props
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
function DropdownMenuRadioItem({
|
|
165
|
+
className,
|
|
166
|
+
children,
|
|
167
|
+
...props
|
|
168
|
+
}) {
|
|
169
|
+
return /* @__PURE__ */ jsxs(
|
|
170
|
+
MenuPrimitive.RadioItem,
|
|
171
|
+
{
|
|
172
|
+
"data-slot": "dropdown-menu-radio-item",
|
|
173
|
+
className: cn(
|
|
174
|
+
"relative flex h-8 cursor-default items-center gap-2 rounded-md py-1.5 pr-2 pl-8 text-sm outline-none select-none",
|
|
175
|
+
"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
176
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
177
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
178
|
+
className
|
|
179
|
+
),
|
|
180
|
+
...props,
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx(MenuPrimitive.RadioItemIndicator, { children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 8 8", className: "size-2 fill-current", "aria-hidden": true, children: /* @__PURE__ */ jsx("circle", { cx: "4", cy: "4", r: "4" }) }) }) }),
|
|
183
|
+
children
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function DropdownMenuSeparator({
|
|
189
|
+
className,
|
|
190
|
+
...props
|
|
191
|
+
}) {
|
|
192
|
+
return /* @__PURE__ */ jsx(
|
|
193
|
+
MenuPrimitive.Separator,
|
|
194
|
+
{
|
|
195
|
+
"data-slot": "dropdown-menu-separator",
|
|
196
|
+
className: cn("pointer-events-none -mx-1 my-1 h-px bg-border", className),
|
|
197
|
+
...props
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function DropdownMenuShortcut({
|
|
202
|
+
className,
|
|
203
|
+
...props
|
|
204
|
+
}) {
|
|
205
|
+
return /* @__PURE__ */ jsx(
|
|
206
|
+
"span",
|
|
207
|
+
{
|
|
208
|
+
"data-slot": "dropdown-menu-shortcut",
|
|
209
|
+
className: cn(
|
|
210
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
211
|
+
className
|
|
212
|
+
),
|
|
213
|
+
...props
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
function DropdownMenuSub(props) {
|
|
218
|
+
return /* @__PURE__ */ jsx(MenuPrimitive.SubmenuRoot, { ...props });
|
|
219
|
+
}
|
|
220
|
+
function DropdownMenuSubTrigger({
|
|
221
|
+
className,
|
|
222
|
+
inset,
|
|
223
|
+
children,
|
|
224
|
+
...props
|
|
225
|
+
}) {
|
|
226
|
+
return /* @__PURE__ */ jsxs(
|
|
227
|
+
MenuPrimitive.SubmenuTrigger,
|
|
228
|
+
{
|
|
229
|
+
"data-slot": "dropdown-menu-sub-trigger",
|
|
230
|
+
"data-inset": inset || void 0,
|
|
231
|
+
className: cn(
|
|
232
|
+
"relative flex h-8 cursor-default items-center gap-2 rounded-md px-2 text-sm outline-none select-none",
|
|
233
|
+
"text-foreground-2 data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
|
|
234
|
+
"data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground",
|
|
235
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
236
|
+
"data-[inset]:pl-8",
|
|
237
|
+
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
238
|
+
className
|
|
239
|
+
),
|
|
240
|
+
...props,
|
|
241
|
+
children: [
|
|
242
|
+
children,
|
|
243
|
+
/* @__PURE__ */ jsx(ChevronRightRegular, { className: "ml-auto size-4 text-muted-foreground" })
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function DropdownMenuSubContent({
|
|
249
|
+
className,
|
|
250
|
+
sideOffset = 4,
|
|
251
|
+
align = "start",
|
|
252
|
+
...props
|
|
253
|
+
}) {
|
|
254
|
+
return /* @__PURE__ */ jsx(MenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
255
|
+
MenuPrimitive.Positioner,
|
|
256
|
+
{
|
|
257
|
+
"data-slot": "dropdown-menu-sub-positioner",
|
|
258
|
+
sideOffset,
|
|
259
|
+
align,
|
|
260
|
+
className: "z-50 outline-none",
|
|
261
|
+
children: /* @__PURE__ */ jsx(
|
|
262
|
+
MenuPrimitive.Popup,
|
|
263
|
+
{
|
|
264
|
+
"data-slot": "dropdown-menu-sub-content",
|
|
265
|
+
className: cn(
|
|
266
|
+
"min-w-[8rem] origin-[var(--transform-origin)] overflow-y-auto overscroll-contain rounded-md border bg-popover p-1 text-popover-foreground shadow-16 outline-none",
|
|
267
|
+
"max-h-[var(--available-height)]",
|
|
268
|
+
"transition-[opacity,scale] duration-fast ease-decelerate-mid",
|
|
269
|
+
"data-starting-style:scale-95 data-starting-style:opacity-0",
|
|
270
|
+
"data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:ease-accelerate-mid",
|
|
271
|
+
className
|
|
272
|
+
),
|
|
273
|
+
...props
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
) });
|
|
278
|
+
}
|
|
279
|
+
export {
|
|
280
|
+
DropdownMenu,
|
|
281
|
+
DropdownMenuCheckboxItem,
|
|
282
|
+
DropdownMenuContent,
|
|
283
|
+
DropdownMenuGroup,
|
|
284
|
+
DropdownMenuItem,
|
|
285
|
+
DropdownMenuLabel,
|
|
286
|
+
DropdownMenuRadioGroup,
|
|
287
|
+
DropdownMenuRadioItem,
|
|
288
|
+
DropdownMenuSeparator,
|
|
289
|
+
DropdownMenuShortcut,
|
|
290
|
+
DropdownMenuSub,
|
|
291
|
+
DropdownMenuSubContent,
|
|
292
|
+
DropdownMenuSubTrigger,
|
|
293
|
+
DropdownMenuTrigger
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=dropdown-menu.js.map
|