@versini/ui-dropdown 1.3.5 → 1.3.7

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/dist/index.d.ts CHANGED
@@ -1,2 +1,160 @@
1
- export * from "./DropdownMenu/DropdownMenu";
2
- export * from "./DropdownMenu/DropdownMenuItem";
1
+ import { JSX } from 'react/jsx-runtime';
2
+
3
+ export declare const DropdownMenu: {
4
+ ({ trigger, children, label, defaultPlacement, onOpenChange, mode, focusMode, sideOffset, modal, }: DropdownMenuProps): JSX.Element;
5
+ displayName: string;
6
+ };
7
+
8
+ export declare const DropdownMenuGroupLabel: {
9
+ ({ className, icon, children, ...props }: DropdownMenuGroupLabelProps): JSX.Element;
10
+ displayName: string;
11
+ };
12
+
13
+ declare type DropdownMenuGroupLabelProps = {
14
+ /**
15
+ * A React component of type Icon to be placed on the left of the label.
16
+ */
17
+ icon?: React.ReactNode;
18
+ } & React.HTMLAttributes<HTMLDivElement>;
19
+
20
+ export declare const DropdownMenuItem: {
21
+ ({ label, disabled, icon, raw, children, ignoreClick, selected, onSelect, onClick, onFocus, ...props }: DropdownMenuItemProps): JSX.Element;
22
+ displayName: string;
23
+ };
24
+
25
+ declare type DropdownMenuItemProps = {
26
+ /**
27
+ * The label to use for the menu item.
28
+ */
29
+ label?: string;
30
+ /**
31
+ * Whether or not the menu item is disabled.
32
+ * @default false
33
+ */
34
+ disabled?: boolean;
35
+ /**
36
+ * A React component of type Icon to be placed on the left of the label.
37
+ */
38
+ icon?: React.ReactNode;
39
+ /**
40
+ * Disable internal menu item behavior (click, focus, etc.).
41
+ * @default false
42
+ */
43
+ raw?: boolean;
44
+ /**
45
+ * Children to render when using raw mode.
46
+ */
47
+ children?: React.ReactNode;
48
+ /**
49
+ * Whether or not the menu should close when the menu item is selected.
50
+ * @default false
51
+ */
52
+ ignoreClick?: boolean;
53
+ /**
54
+ * Whether or not the menu item is selected.
55
+ * @default undefined
56
+ */
57
+ selected?: boolean;
58
+ /**
59
+ * Callback fired when the menu item is selected.
60
+ */
61
+ onSelect?: (event: Event) => void;
62
+ /**
63
+ * Optional click handler.
64
+ */
65
+ onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
66
+ /**
67
+ * Optional focus handler.
68
+ */
69
+ onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
70
+ };
71
+
72
+ declare type DropdownMenuProps = {
73
+ /**
74
+ * The component to use to open the dropdown menu, e.g. a ButtonIcon, a Button, etc.
75
+ * Required for root menus, omit for nested sub-menus (use label instead).
76
+ */
77
+ trigger?: React.ReactNode;
78
+ /**
79
+ * The children to render (DropdownMenuItem, DropdownMenuSeparator, etc.).
80
+ */
81
+ children?: React.ReactNode;
82
+ /**
83
+ * The default location of the popup.
84
+ * @default "bottom-start"
85
+ */
86
+ defaultPlacement?: "bottom" | "bottom-start" | "bottom-end" | "top" | "top-start" | "top-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end";
87
+ /**
88
+ * The type of focus for the Button. This will change the color
89
+ * of the focus ring around the Button.
90
+ */
91
+ focusMode?: "dark" | "light" | "system" | "alt-system";
92
+ /**
93
+ * The type of Button trigger. This will change the color of the Button.
94
+ */
95
+ mode?: "dark" | "light" | "system" | "alt-system";
96
+ /**
97
+ * The label to use for the menu button (root menu) or the sub-menu trigger text (nested menu).
98
+ * When used without a trigger, this creates a nested sub-menu.
99
+ */
100
+ label?: string;
101
+ /**
102
+ * Callback fired when the component is opened or closed.
103
+ * @param open whether or not the menu is open
104
+ */
105
+ onOpenChange?: (open: boolean) => void;
106
+ /**
107
+ * The offset distance from the trigger element.
108
+ * @default 10
109
+ */
110
+ sideOffset?: number;
111
+ /**
112
+ * Whether the dropdown menu is modal (locks interaction outside).
113
+ * @default true
114
+ */
115
+ modal?: boolean;
116
+ };
117
+
118
+ export declare const DropdownMenuSeparator: {
119
+ ({ className, ...props }: DropdownMenuSeparatorProps): JSX.Element;
120
+ displayName: string;
121
+ };
122
+
123
+ declare type DropdownMenuSeparatorProps = React.HTMLAttributes<HTMLDivElement>;
124
+
125
+ export declare const DropdownMenuSub: {
126
+ ({ label, icon, children, disabled, sideOffset, alignOffset, }: DropdownMenuSubProps): JSX.Element;
127
+ displayName: string;
128
+ };
129
+
130
+ declare type DropdownMenuSubProps = {
131
+ /**
132
+ * The label for the sub-menu trigger.
133
+ */
134
+ label: string;
135
+ /**
136
+ * A React component of type Icon to be placed on the left of the label.
137
+ */
138
+ icon?: React.ReactNode;
139
+ /**
140
+ * The children to render inside the sub-menu.
141
+ */
142
+ children?: React.ReactNode;
143
+ /**
144
+ * Whether the sub-menu trigger is disabled.
145
+ * @default false
146
+ */
147
+ disabled?: boolean;
148
+ /**
149
+ * The offset distance from the sub-menu trigger.
150
+ * @default 2
151
+ */
152
+ sideOffset?: number;
153
+ /**
154
+ * The alignment offset for the sub-menu.
155
+ * @default -4
156
+ */
157
+ alignOffset?: number;
158
+ };
159
+
160
+ export { }
package/dist/index.js CHANGED
@@ -1,12 +1,266 @@
1
1
  /*!
2
- @versini/ui-dropdown v1.3.5
2
+ @versini/ui-dropdown v1.3.7
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
6
- export * from "./DropdownMenu/DropdownMenu.js";
7
- export * from "./DropdownMenu/DropdownMenuItem.js";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { Content, Item, Label, Portal, Root, Separator, Sub, SubContent, SubTrigger, Trigger } from "@radix-ui/react-dropdown-menu";
8
+ import { IconNext, IconSelected, IconUnSelected } from "@versini/ui-icons";
9
+ import clsx from "clsx";
10
+ import { cloneElement, useRef, useState } from "react";
8
11
 
9
- ;// CONCATENATED MODULE: ./src/components/index.ts
10
12
 
11
13
 
12
14
 
15
+
16
+
17
+ const getDisplayName = (element)=>{
18
+ if (typeof element === "string") {
19
+ return element;
20
+ }
21
+ if (typeof element === "function") {
22
+ return element.displayName || element.name || "Component";
23
+ }
24
+ if (typeof element === "object" && element !== null && "type" in element) {
25
+ const type = element.type;
26
+ if (typeof type === "function" || typeof type === "object") {
27
+ /* v8 ignore start */ return type.displayName || type.name || "Component";
28
+ /* v8 ignore stop */ }
29
+ }
30
+ return "Element";
31
+ };
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+ const CONTENT_CLASS = "z-100 rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2 prose prose-dark";
40
+ const SUB_CONTENT_CLASS = "z-[60] rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2 mx-3";
41
+ const SUB_TRIGGER_CLASS = clsx("flex items-center flex-row justify-between", "w-full", "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1", "rounded-md border border-transparent", "text-left text-base select-none cursor-pointer", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", "data-highlighted:bg-surface-lighter data-highlighted:border-border-medium data-highlighted:underline", "data-[state=open]:bg-surface-lighter");
42
+ /**
43
+ * Convert Radix placement format to our simplified format.
44
+ */ const getRadixSide = (placement)=>{
45
+ /* v8 ignore start */ if (!placement) {
46
+ return "bottom";
47
+ }
48
+ /* v8 ignore stop */ if (placement.startsWith("top")) {
49
+ return "top";
50
+ }
51
+ if (placement.startsWith("left")) {
52
+ return "left";
53
+ }
54
+ if (placement.startsWith("right")) {
55
+ return "right";
56
+ }
57
+ return "bottom";
58
+ };
59
+ const getRadixAlign = (placement)=>{
60
+ /* v8 ignore start */ if (!placement) {
61
+ return "start";
62
+ }
63
+ /* v8 ignore stop */ if (placement.endsWith("-start")) {
64
+ return "start";
65
+ }
66
+ if (placement.endsWith("-end")) {
67
+ return "end";
68
+ }
69
+ return "center";
70
+ };
71
+ const DropdownMenu = ({ trigger, children, label = "Open menu", defaultPlacement = "bottom-start", onOpenChange, mode = "system", focusMode = "system", sideOffset = 10, modal = true })=>{
72
+ const [isOpen, setIsOpen] = useState(false);
73
+ // Track the portal container - either explicitly passed or auto-detected dialog
74
+ const [portalContainer, setPortalContainer] = useState(undefined);
75
+ const noInternalClick = getDisplayName(trigger) === "Button" || getDisplayName(trigger) === "ButtonIcon";
76
+ const uiButtonsExtraProps = noInternalClick ? {
77
+ noInternalClick,
78
+ focusMode,
79
+ mode
80
+ } : {};
81
+ const triggerRef = useRef(null);
82
+ /* v8 ignore start - trigger is required in practice */ const triggerElement = trigger ? /*#__PURE__*/ cloneElement(trigger, {
83
+ ...uiButtonsExtraProps,
84
+ "aria-label": label,
85
+ ref: triggerRef
86
+ }) : null;
87
+ /* v8 ignore stop */ const handleOpenChange = (open)=>{
88
+ setIsOpen(open);
89
+ onOpenChange?.(open);
90
+ /**
91
+ * When the menu opens, check if we're inside a native <dialog> element
92
+ * that's shown modally. If so, we need to portal the dropdown content
93
+ * into the dialog so it appears in the browser's top layer.
94
+ * Without this, the dropdown would render to document.body which is
95
+ * behind the dialog's top layer and therefore invisible.
96
+ */ if (open && triggerRef.current) {
97
+ // Auto-detect if we're inside a native <dialog> element shown modally
98
+ const closestDialog = triggerRef.current.closest("dialog");
99
+ /* v8 ignore start - dialog detection cannot be fully tested without native showModal */ if (closestDialog?.open) {
100
+ setPortalContainer(closestDialog);
101
+ } else {
102
+ setPortalContainer(undefined);
103
+ }
104
+ /* v8 ignore stop */ /**
105
+ * Dispatch a click event to parent elements.
106
+ * This ensures that parent components like Tooltip can detect the
107
+ * interaction and respond appropriately (e.g., disable tooltip display).
108
+ */ const clickEvent = new MouseEvent("click", {
109
+ bubbles: true,
110
+ cancelable: true,
111
+ view: window
112
+ });
113
+ triggerRef.current.dispatchEvent(clickEvent);
114
+ }
115
+ };
116
+ return /*#__PURE__*/ jsxs(Root, {
117
+ onOpenChange: handleOpenChange,
118
+ modal: modal,
119
+ children: [
120
+ /*#__PURE__*/ jsx(Trigger, {
121
+ asChild: true,
122
+ "data-state": isOpen ? "open" : "closed",
123
+ children: triggerElement
124
+ }),
125
+ /*#__PURE__*/ jsx(Portal, {
126
+ container: portalContainer,
127
+ children: /*#__PURE__*/ jsx(Content, {
128
+ className: CONTENT_CLASS,
129
+ sideOffset: sideOffset,
130
+ side: getRadixSide(defaultPlacement),
131
+ align: getRadixAlign(defaultPlacement),
132
+ loop: true,
133
+ children: children
134
+ })
135
+ })
136
+ ]
137
+ });
138
+ };
139
+ DropdownMenu.displayName = "DropdownMenu";
140
+ const DropdownMenuSub = ({ label, icon, children, disabled = false, sideOffset = 2, alignOffset = -4 })=>{
141
+ const labelSpanClass = icon ? "pl-2" : "";
142
+ return /*#__PURE__*/ jsxs(Sub, {
143
+ children: [
144
+ /*#__PURE__*/ jsxs(SubTrigger, {
145
+ className: SUB_TRIGGER_CLASS,
146
+ disabled: disabled,
147
+ children: [
148
+ /*#__PURE__*/ jsxs("span", {
149
+ className: "flex items-center",
150
+ children: [
151
+ icon,
152
+ /*#__PURE__*/ jsx("span", {
153
+ className: labelSpanClass,
154
+ children: label
155
+ })
156
+ ]
157
+ }),
158
+ /*#__PURE__*/ jsx(IconNext, {
159
+ className: "ml-2",
160
+ size: "size-3",
161
+ monotone: true
162
+ })
163
+ ]
164
+ }),
165
+ /*#__PURE__*/ jsx(Portal, {
166
+ children: /*#__PURE__*/ jsx(SubContent, {
167
+ className: SUB_CONTENT_CLASS,
168
+ sideOffset: sideOffset,
169
+ alignOffset: alignOffset,
170
+ loop: true,
171
+ children: children
172
+ })
173
+ })
174
+ ]
175
+ });
176
+ };
177
+ DropdownMenuSub.displayName = "DropdownMenuSub";
178
+ const DropdownMenuSeparator = ({ className, ...props })=>{
179
+ const separatorClass = clsx(className, "my-1 border-t border-border-medium");
180
+ return /*#__PURE__*/ jsx(Separator, {
181
+ className: separatorClass,
182
+ ...props
183
+ });
184
+ };
185
+ DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
186
+ const DropdownMenuGroupLabel = ({ className, icon, children, ...props })=>{
187
+ const groupLabelClass = clsx(className, "pt-1 pb-2 mb-2", "text-sm text-copy-dark font-bold", "border-b border-border-medium", icon && "flex items-center");
188
+ const labelSpanClass = icon ? "px-2" : "";
189
+ return /*#__PURE__*/ jsxs(Label, {
190
+ className: groupLabelClass,
191
+ ...props,
192
+ children: [
193
+ icon,
194
+ /*#__PURE__*/ jsx("span", {
195
+ className: labelSpanClass,
196
+ children: children
197
+ })
198
+ ]
199
+ });
200
+ };
201
+ DropdownMenuGroupLabel.displayName = "DropdownMenuGroupLabel";
202
+
203
+
204
+
205
+
206
+
207
+ const ITEM_CLASS = clsx("flex flex-row items-center", "w-full", "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1", "rounded-md border border-transparent", "text-left text-base select-none cursor-pointer", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", "data-highlighted:bg-surface-lighter data-highlighted:border-border-medium data-highlighted:underline", "data-disabled:cursor-not-allowed data-disabled:text-copy-medium");
208
+ const DropdownMenuItem = ({ label, disabled, icon, raw = false, children, ignoreClick = false, selected, onSelect, onClick, onFocus, ...props })=>{
209
+ let buttonSpanClass = "";
210
+ if (raw && children) {
211
+ return /*#__PURE__*/ jsx(Item, {
212
+ className: "outline-hidden",
213
+ onSelect: (event)=>{
214
+ if (ignoreClick) {
215
+ event.preventDefault();
216
+ }
217
+ onSelect?.(event);
218
+ /* v8 ignore start - optional onClick may not be provided */ onClick?.(event);
219
+ /* v8 ignore stop */ },
220
+ ...props,
221
+ children: children
222
+ });
223
+ }
224
+ if (icon) {
225
+ buttonSpanClass = "pl-2";
226
+ }
227
+ const itemClass = clsx(ITEM_CLASS, {
228
+ "bg-none": !disabled && !selected
229
+ });
230
+ const handleSelect = (event)=>{
231
+ if (ignoreClick) {
232
+ event.preventDefault();
233
+ }
234
+ onSelect?.(event);
235
+ // Also call onClick for compatibility with common patterns
236
+ /* v8 ignore start - optional onClick may not be provided */ onClick?.(event);
237
+ /* v8 ignore stop */ };
238
+ return /*#__PURE__*/ jsxs(Item, {
239
+ className: itemClass,
240
+ disabled: disabled,
241
+ onSelect: handleSelect,
242
+ onFocus: onFocus,
243
+ ...props,
244
+ children: [
245
+ selected === true && /*#__PURE__*/ jsx(IconSelected, {
246
+ className: "text-copy-success mr-2",
247
+ size: "size-4"
248
+ }),
249
+ selected === false && /*#__PURE__*/ jsx(IconUnSelected, {
250
+ className: "text-copy-medium mr-2",
251
+ size: "size-4"
252
+ }),
253
+ icon,
254
+ label && /*#__PURE__*/ jsx("span", {
255
+ className: buttonSpanClass,
256
+ children: label
257
+ })
258
+ ]
259
+ });
260
+ };
261
+ DropdownMenuItem.displayName = "DropdownMenuItem";
262
+
263
+
264
+
265
+
266
+ export { DropdownMenu, DropdownMenuGroupLabel, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-dropdown",
3
- "version": "1.3.5",
3
+ "version": "1.3.7",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -43,12 +43,12 @@
43
43
  "dependencies": {
44
44
  "@radix-ui/react-dropdown-menu": "2.1.16",
45
45
  "@tailwindcss/typography": "0.5.19",
46
- "@versini/ui-icons": "4.16.0",
46
+ "@versini/ui-icons": "4.17.0",
47
47
  "clsx": "2.1.1",
48
48
  "tailwindcss": "4.1.18"
49
49
  },
50
50
  "sideEffects": [
51
51
  "**/*.css"
52
52
  ],
53
- "gitHead": "7ed95d479dbfd4ef12b1e2fe9fdb763e2e538274"
53
+ "gitHead": "95f2bef12f132245b2f2a09fc23d6500467a7251"
54
54
  }
@@ -1,17 +0,0 @@
1
- import type { DropdownMenuGroupLabelProps, DropdownMenuProps, DropdownMenuSeparatorProps, DropdownMenuSubProps } from "./DropdownMenuTypes";
2
- export declare const DropdownMenu: {
3
- ({ trigger, children, label, defaultPlacement, onOpenChange, mode, focusMode, sideOffset, modal, }: DropdownMenuProps): import("react/jsx-runtime").JSX.Element;
4
- displayName: string;
5
- };
6
- export declare const DropdownMenuSub: {
7
- ({ label, icon, children, disabled, sideOffset, alignOffset, }: DropdownMenuSubProps): import("react/jsx-runtime").JSX.Element;
8
- displayName: string;
9
- };
10
- export declare const DropdownMenuSeparator: {
11
- ({ className, ...props }: DropdownMenuSeparatorProps): import("react/jsx-runtime").JSX.Element;
12
- displayName: string;
13
- };
14
- export declare const DropdownMenuGroupLabel: {
15
- ({ className, icon, children, ...props }: DropdownMenuGroupLabelProps): import("react/jsx-runtime").JSX.Element;
16
- displayName: string;
17
- };
@@ -1,196 +0,0 @@
1
- /*!
2
- @versini/ui-dropdown v1.3.5
3
- © 2026 gizmette.com
4
- */
5
-
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- import { Content, Label, Portal, Root, Separator, Sub, SubContent, SubTrigger, Trigger } from "@radix-ui/react-dropdown-menu";
8
- import { IconNext } from "@versini/ui-icons";
9
- import clsx from "clsx";
10
- import { cloneElement, useRef, useState } from "react";
11
- import { getDisplayName } from "./utilities.js";
12
-
13
- ;// CONCATENATED MODULE: external "react/jsx-runtime"
14
-
15
- ;// CONCATENATED MODULE: external "@radix-ui/react-dropdown-menu"
16
-
17
- ;// CONCATENATED MODULE: external "@versini/ui-icons"
18
-
19
- ;// CONCATENATED MODULE: external "clsx"
20
-
21
- ;// CONCATENATED MODULE: external "react"
22
-
23
- ;// CONCATENATED MODULE: external "./utilities.js"
24
-
25
- ;// CONCATENATED MODULE: ./src/components/DropdownMenu/DropdownMenu.tsx
26
-
27
-
28
-
29
-
30
-
31
-
32
- const CONTENT_CLASS = "z-100 rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2 prose prose-dark";
33
- const SUB_CONTENT_CLASS = "z-[60] rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2 mx-3";
34
- const SUB_TRIGGER_CLASS = clsx("flex items-center flex-row justify-between", "w-full", "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1", "rounded-md border border-transparent", "text-left text-base select-none cursor-pointer", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", "data-highlighted:bg-surface-lighter data-highlighted:border-border-medium data-highlighted:underline", "data-[state=open]:bg-surface-lighter");
35
- /**
36
- * Convert Radix placement format to our simplified format.
37
- */ const getRadixSide = (placement)=>{
38
- /* v8 ignore start */ if (!placement) {
39
- return "bottom";
40
- }
41
- /* v8 ignore stop */ if (placement.startsWith("top")) {
42
- return "top";
43
- }
44
- if (placement.startsWith("left")) {
45
- return "left";
46
- }
47
- if (placement.startsWith("right")) {
48
- return "right";
49
- }
50
- return "bottom";
51
- };
52
- const getRadixAlign = (placement)=>{
53
- /* v8 ignore start */ if (!placement) {
54
- return "start";
55
- }
56
- /* v8 ignore stop */ if (placement.endsWith("-start")) {
57
- return "start";
58
- }
59
- if (placement.endsWith("-end")) {
60
- return "end";
61
- }
62
- return "center";
63
- };
64
- const DropdownMenu = ({ trigger, children, label = "Open menu", defaultPlacement = "bottom-start", onOpenChange, mode = "system", focusMode = "system", sideOffset = 10, modal = true })=>{
65
- const [isOpen, setIsOpen] = useState(false);
66
- // Track the portal container - either explicitly passed or auto-detected dialog
67
- const [portalContainer, setPortalContainer] = useState(undefined);
68
- const noInternalClick = getDisplayName(trigger) === "Button" || getDisplayName(trigger) === "ButtonIcon";
69
- const uiButtonsExtraProps = noInternalClick ? {
70
- noInternalClick,
71
- focusMode,
72
- mode
73
- } : {};
74
- const triggerRef = useRef(null);
75
- /* v8 ignore start - trigger is required in practice */ const triggerElement = trigger ? /*#__PURE__*/ cloneElement(trigger, {
76
- ...uiButtonsExtraProps,
77
- "aria-label": label,
78
- ref: triggerRef
79
- }) : null;
80
- /* v8 ignore stop */ const handleOpenChange = (open)=>{
81
- setIsOpen(open);
82
- onOpenChange?.(open);
83
- /**
84
- * When the menu opens, check if we're inside a native <dialog> element
85
- * that's shown modally. If so, we need to portal the dropdown content
86
- * into the dialog so it appears in the browser's top layer.
87
- * Without this, the dropdown would render to document.body which is
88
- * behind the dialog's top layer and therefore invisible.
89
- */ if (open && triggerRef.current) {
90
- // Auto-detect if we're inside a native <dialog> element shown modally
91
- const closestDialog = triggerRef.current.closest("dialog");
92
- /* v8 ignore start - dialog detection cannot be fully tested without native showModal */ if (closestDialog?.open) {
93
- setPortalContainer(closestDialog);
94
- } else {
95
- setPortalContainer(undefined);
96
- }
97
- /* v8 ignore stop */ /**
98
- * Dispatch a click event to parent elements.
99
- * This ensures that parent components like Tooltip can detect the
100
- * interaction and respond appropriately (e.g., disable tooltip display).
101
- */ const clickEvent = new MouseEvent("click", {
102
- bubbles: true,
103
- cancelable: true,
104
- view: window
105
- });
106
- triggerRef.current.dispatchEvent(clickEvent);
107
- }
108
- };
109
- return /*#__PURE__*/ jsxs(Root, {
110
- onOpenChange: handleOpenChange,
111
- modal: modal,
112
- children: [
113
- /*#__PURE__*/ jsx(Trigger, {
114
- asChild: true,
115
- "data-state": isOpen ? "open" : "closed",
116
- children: triggerElement
117
- }),
118
- /*#__PURE__*/ jsx(Portal, {
119
- container: portalContainer,
120
- children: /*#__PURE__*/ jsx(Content, {
121
- className: CONTENT_CLASS,
122
- sideOffset: sideOffset,
123
- side: getRadixSide(defaultPlacement),
124
- align: getRadixAlign(defaultPlacement),
125
- loop: true,
126
- children: children
127
- })
128
- })
129
- ]
130
- });
131
- };
132
- DropdownMenu.displayName = "DropdownMenu";
133
- const DropdownMenuSub = ({ label, icon, children, disabled = false, sideOffset = 2, alignOffset = -4 })=>{
134
- const labelSpanClass = icon ? "pl-2" : "";
135
- return /*#__PURE__*/ jsxs(Sub, {
136
- children: [
137
- /*#__PURE__*/ jsxs(SubTrigger, {
138
- className: SUB_TRIGGER_CLASS,
139
- disabled: disabled,
140
- children: [
141
- /*#__PURE__*/ jsxs("span", {
142
- className: "flex items-center",
143
- children: [
144
- icon,
145
- /*#__PURE__*/ jsx("span", {
146
- className: labelSpanClass,
147
- children: label
148
- })
149
- ]
150
- }),
151
- /*#__PURE__*/ jsx(IconNext, {
152
- className: "ml-2",
153
- size: "size-3",
154
- monotone: true
155
- })
156
- ]
157
- }),
158
- /*#__PURE__*/ jsx(Portal, {
159
- children: /*#__PURE__*/ jsx(SubContent, {
160
- className: SUB_CONTENT_CLASS,
161
- sideOffset: sideOffset,
162
- alignOffset: alignOffset,
163
- loop: true,
164
- children: children
165
- })
166
- })
167
- ]
168
- });
169
- };
170
- DropdownMenuSub.displayName = "DropdownMenuSub";
171
- const DropdownMenuSeparator = ({ className, ...props })=>{
172
- const separatorClass = clsx(className, "my-1 border-t border-border-medium");
173
- return /*#__PURE__*/ jsx(Separator, {
174
- className: separatorClass,
175
- ...props
176
- });
177
- };
178
- DropdownMenuSeparator.displayName = "DropdownMenuSeparator";
179
- const DropdownMenuGroupLabel = ({ className, icon, children, ...props })=>{
180
- const groupLabelClass = clsx(className, "pt-1 pb-2 mb-2", "text-sm text-copy-dark font-bold", "border-b border-border-medium", icon && "flex items-center");
181
- const labelSpanClass = icon ? "px-2" : "";
182
- return /*#__PURE__*/ jsxs(Label, {
183
- className: groupLabelClass,
184
- ...props,
185
- children: [
186
- icon,
187
- /*#__PURE__*/ jsx("span", {
188
- className: labelSpanClass,
189
- children: children
190
- })
191
- ]
192
- });
193
- };
194
- DropdownMenuGroupLabel.displayName = "DropdownMenuGroupLabel";
195
-
196
- export { DropdownMenu, DropdownMenuGroupLabel, DropdownMenuSeparator, DropdownMenuSub };
@@ -1,5 +0,0 @@
1
- import type { DropdownMenuItemProps } from "./DropdownMenuTypes";
2
- export declare const DropdownMenuItem: {
3
- ({ label, disabled, icon, raw, children, ignoreClick, selected, onSelect, onClick, onFocus, ...props }: DropdownMenuItemProps): import("react/jsx-runtime").JSX.Element;
4
- displayName: string;
5
- };
@@ -1,80 +0,0 @@
1
- /*!
2
- @versini/ui-dropdown v1.3.5
3
- © 2026 gizmette.com
4
- */
5
-
6
- import { jsx, jsxs } from "react/jsx-runtime";
7
- import { Item } from "@radix-ui/react-dropdown-menu";
8
- import { IconSelected, IconUnSelected } from "@versini/ui-icons";
9
- import clsx from "clsx";
10
-
11
- ;// CONCATENATED MODULE: external "react/jsx-runtime"
12
-
13
- ;// CONCATENATED MODULE: external "@radix-ui/react-dropdown-menu"
14
-
15
- ;// CONCATENATED MODULE: external "@versini/ui-icons"
16
-
17
- ;// CONCATENATED MODULE: external "clsx"
18
-
19
- ;// CONCATENATED MODULE: ./src/components/DropdownMenu/DropdownMenuItem.tsx
20
-
21
-
22
-
23
-
24
- const ITEM_CLASS = clsx("flex flex-row items-center", "w-full", "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1", "rounded-md border border-transparent", "text-left text-base select-none cursor-pointer", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", "data-highlighted:bg-surface-lighter data-highlighted:border-border-medium data-highlighted:underline", "data-disabled:cursor-not-allowed data-disabled:text-copy-medium");
25
- const DropdownMenuItem = ({ label, disabled, icon, raw = false, children, ignoreClick = false, selected, onSelect, onClick, onFocus, ...props })=>{
26
- let buttonSpanClass = "";
27
- if (raw && children) {
28
- return /*#__PURE__*/ jsx(Item, {
29
- className: "outline-hidden",
30
- onSelect: (event)=>{
31
- if (ignoreClick) {
32
- event.preventDefault();
33
- }
34
- onSelect?.(event);
35
- /* v8 ignore start - optional onClick may not be provided */ onClick?.(event);
36
- /* v8 ignore stop */ },
37
- ...props,
38
- children: children
39
- });
40
- }
41
- if (icon) {
42
- buttonSpanClass = "pl-2";
43
- }
44
- const itemClass = clsx(ITEM_CLASS, {
45
- "bg-none": !disabled && !selected
46
- });
47
- const handleSelect = (event)=>{
48
- if (ignoreClick) {
49
- event.preventDefault();
50
- }
51
- onSelect?.(event);
52
- // Also call onClick for compatibility with common patterns
53
- /* v8 ignore start - optional onClick may not be provided */ onClick?.(event);
54
- /* v8 ignore stop */ };
55
- return /*#__PURE__*/ jsxs(Item, {
56
- className: itemClass,
57
- disabled: disabled,
58
- onSelect: handleSelect,
59
- onFocus: onFocus,
60
- ...props,
61
- children: [
62
- selected === true && /*#__PURE__*/ jsx(IconSelected, {
63
- className: "text-copy-success mr-2",
64
- size: "size-4"
65
- }),
66
- selected === false && /*#__PURE__*/ jsx(IconUnSelected, {
67
- className: "text-copy-medium mr-2",
68
- size: "size-4"
69
- }),
70
- icon,
71
- label && /*#__PURE__*/ jsx("span", {
72
- className: buttonSpanClass,
73
- children: label
74
- })
75
- ]
76
- });
77
- };
78
- DropdownMenuItem.displayName = "DropdownMenuItem";
79
-
80
- export { DropdownMenuItem };
@@ -1,127 +0,0 @@
1
- export type DropdownMenuProps = {
2
- /**
3
- * The component to use to open the dropdown menu, e.g. a ButtonIcon, a Button, etc.
4
- * Required for root menus, omit for nested sub-menus (use label instead).
5
- */
6
- trigger?: React.ReactNode;
7
- /**
8
- * The children to render (DropdownMenuItem, DropdownMenuSeparator, etc.).
9
- */
10
- children?: React.ReactNode;
11
- /**
12
- * The default location of the popup.
13
- * @default "bottom-start"
14
- */
15
- defaultPlacement?: "bottom" | "bottom-start" | "bottom-end" | "top" | "top-start" | "top-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end";
16
- /**
17
- * The type of focus for the Button. This will change the color
18
- * of the focus ring around the Button.
19
- */
20
- focusMode?: "dark" | "light" | "system" | "alt-system";
21
- /**
22
- * The type of Button trigger. This will change the color of the Button.
23
- */
24
- mode?: "dark" | "light" | "system" | "alt-system";
25
- /**
26
- * The label to use for the menu button (root menu) or the sub-menu trigger text (nested menu).
27
- * When used without a trigger, this creates a nested sub-menu.
28
- */
29
- label?: string;
30
- /**
31
- * Callback fired when the component is opened or closed.
32
- * @param open whether or not the menu is open
33
- */
34
- onOpenChange?: (open: boolean) => void;
35
- /**
36
- * The offset distance from the trigger element.
37
- * @default 10
38
- */
39
- sideOffset?: number;
40
- /**
41
- * Whether the dropdown menu is modal (locks interaction outside).
42
- * @default true
43
- */
44
- modal?: boolean;
45
- };
46
- export type DropdownMenuItemProps = {
47
- /**
48
- * The label to use for the menu item.
49
- */
50
- label?: string;
51
- /**
52
- * Whether or not the menu item is disabled.
53
- * @default false
54
- */
55
- disabled?: boolean;
56
- /**
57
- * A React component of type Icon to be placed on the left of the label.
58
- */
59
- icon?: React.ReactNode;
60
- /**
61
- * Disable internal menu item behavior (click, focus, etc.).
62
- * @default false
63
- */
64
- raw?: boolean;
65
- /**
66
- * Children to render when using raw mode.
67
- */
68
- children?: React.ReactNode;
69
- /**
70
- * Whether or not the menu should close when the menu item is selected.
71
- * @default false
72
- */
73
- ignoreClick?: boolean;
74
- /**
75
- * Whether or not the menu item is selected.
76
- * @default undefined
77
- */
78
- selected?: boolean;
79
- /**
80
- * Callback fired when the menu item is selected.
81
- */
82
- onSelect?: (event: Event) => void;
83
- /**
84
- * Optional click handler.
85
- */
86
- onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
87
- /**
88
- * Optional focus handler.
89
- */
90
- onFocus?: (event: React.FocusEvent<HTMLDivElement>) => void;
91
- };
92
- export type DropdownMenuSeparatorProps = React.HTMLAttributes<HTMLDivElement>;
93
- export type DropdownMenuGroupLabelProps = {
94
- /**
95
- * A React component of type Icon to be placed on the left of the label.
96
- */
97
- icon?: React.ReactNode;
98
- } & React.HTMLAttributes<HTMLDivElement>;
99
- export type DropdownMenuSubProps = {
100
- /**
101
- * The label for the sub-menu trigger.
102
- */
103
- label: string;
104
- /**
105
- * A React component of type Icon to be placed on the left of the label.
106
- */
107
- icon?: React.ReactNode;
108
- /**
109
- * The children to render inside the sub-menu.
110
- */
111
- children?: React.ReactNode;
112
- /**
113
- * Whether the sub-menu trigger is disabled.
114
- * @default false
115
- */
116
- disabled?: boolean;
117
- /**
118
- * The offset distance from the sub-menu trigger.
119
- * @default 2
120
- */
121
- sideOffset?: number;
122
- /**
123
- * The alignment offset for the sub-menu.
124
- * @default -4
125
- */
126
- alignOffset?: number;
127
- };
@@ -1,9 +0,0 @@
1
- /*!
2
- @versini/ui-dropdown v1.3.5
3
- © 2026 gizmette.com
4
- */
5
-
6
-
7
- ;// CONCATENATED MODULE: ./src/components/DropdownMenu/DropdownMenuTypes.ts
8
-
9
-
@@ -1 +0,0 @@
1
- export declare const getDisplayName: (element: unknown) => string;
@@ -1,24 +0,0 @@
1
- /*!
2
- @versini/ui-dropdown v1.3.5
3
- © 2026 gizmette.com
4
- */
5
-
6
-
7
- ;// CONCATENATED MODULE: ./src/components/DropdownMenu/utilities.ts
8
- const getDisplayName = (element)=>{
9
- if (typeof element === "string") {
10
- return element;
11
- }
12
- if (typeof element === "function") {
13
- return element.displayName || element.name || "Component";
14
- }
15
- if (typeof element === "object" && element !== null && "type" in element) {
16
- const type = element.type;
17
- if (typeof type === "function" || typeof type === "object") {
18
- /* v8 ignore start */ return type.displayName || type.name || "Component";
19
- /* v8 ignore stop */ }
20
- }
21
- return "Element";
22
- };
23
-
24
- export { getDisplayName };