@versini/ui-menu 5.1.3 → 5.3.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/dist/index.d.ts CHANGED
@@ -1,81 +1,83 @@
1
- import * as React from 'react';
2
- import React__default from 'react';
3
- import { Placement } from '@floating-ui/react';
4
- import * as react_jsx_runtime from 'react/jsx-runtime';
5
-
6
- type MenuProps = {
7
- /**
8
- * The component to use to open the menu, e.g. a ButtonIcon, a Button, etc.
9
- * Required for root menus, omit for nested sub-menus (use label instead).
10
- */
11
- trigger?: React__default.ReactNode;
12
- /**
13
- * The children to render.
14
- */
15
- children?: React__default.ReactNode;
16
- /**
17
- * The default location of the popup.
18
- * @default "bottom-start"
19
- */
20
- defaultPlacement?: Placement;
21
- /**
22
- * The type of focus for the Button. This will change the color
23
- * of the focus ring around the Button.
24
- */
25
- focusMode?: "dark" | "light" | "system" | "alt-system";
26
- /**
27
- * The type of Button trigger. This will change the color of the Button.
28
- */
29
- mode?: "dark" | "light" | "system" | "alt-system";
30
- /**
31
- * The label to use for the menu button (root menu) or the sub-menu trigger text (nested menu).
32
- * When used without a trigger, this creates a nested sub-menu.
33
- */
34
- label?: string;
35
- /**
36
- * Callback fired when the component is opened or closed.
37
- * @param open whether or not the menu is open
38
- */
39
- onOpenChange?: (open: boolean) => void;
40
- };
41
-
42
- type MenuItemProps = {
43
- /**
44
- * The label to use for the menu item.
45
- */
46
- label?: string;
47
- /**
48
- * Whether or not the menu item is disabled.
49
- * @default false
50
- */
51
- disabled?: boolean;
52
- /**
53
- * A React component of type Icon to be placed on the left of the label.
54
- */
55
- icon?: React__default.ReactNode;
56
- /**
57
- * Disable internal menu item behavior (click, focus, etc.).
58
- * @default false
59
- */
60
- raw?: boolean;
61
- /**
62
- * Whether or not the menu should close when the menu item is selected.
63
- * @default false
64
- */
65
- ignoreClick?: boolean;
66
- /**
67
- * Whether or not the menu item is selected.
68
- * @default false
69
- */
70
- selected?: boolean;
71
- };
72
-
73
- type MenuSeparatorProps = React__default.HTMLAttributes<HTMLDivElement>;
74
-
75
- declare const Menu: React__default.ForwardRefExoticComponent<Omit<MenuProps & React__default.HTMLProps<HTMLButtonElement>, "ref"> & React__default.RefAttributes<HTMLButtonElement>>;
76
-
77
- declare const MenuItem: React.ForwardRefExoticComponent<MenuItemProps & React.ButtonHTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>>;
78
- declare const MenuSeparator: ({ className, ...props }: MenuSeparatorProps) => react_jsx_runtime.JSX.Element;
79
- declare const MenuGroupLabel: ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => react_jsx_runtime.JSX.Element;
80
-
81
- export { Menu, MenuGroupLabel, MenuItem, MenuSeparator };
1
+ import { JSX } from 'react/jsx-runtime';
2
+ import type { Placement } from '@floating-ui/react';
3
+ import { default as React_2 } from 'react';
4
+ import * as React_3 from 'react';
5
+
6
+ export declare const Menu: React_2.ForwardRefExoticComponent<Omit<MenuProps & React_2.HTMLProps<HTMLButtonElement>, "ref"> & React_2.RefAttributes<HTMLButtonElement>>;
7
+
8
+ export declare const MenuGroupLabel: ({ className, ...props }: React_3.HTMLAttributes<HTMLDivElement>) => JSX.Element;
9
+
10
+ export declare const MenuItem: React_3.ForwardRefExoticComponent<MenuItemProps & React_3.ButtonHTMLAttributes<HTMLButtonElement> & React_3.RefAttributes<HTMLButtonElement>>;
11
+
12
+ declare type MenuItemProps = {
13
+ /**
14
+ * The label to use for the menu item.
15
+ */
16
+ label?: string;
17
+ /**
18
+ * Whether or not the menu item is disabled.
19
+ * @default false
20
+ */
21
+ disabled?: boolean;
22
+ /**
23
+ * A React component of type Icon to be placed on the left of the label.
24
+ */
25
+ icon?: React_2.ReactNode;
26
+ /**
27
+ * Disable internal menu item behavior (click, focus, etc.).
28
+ * @default false
29
+ */
30
+ raw?: boolean;
31
+ /**
32
+ * Whether or not the menu should close when the menu item is selected.
33
+ * @default false
34
+ */
35
+ ignoreClick?: boolean;
36
+ /**
37
+ * Whether or not the menu item is selected.
38
+ * @default false
39
+ */
40
+ selected?: boolean;
41
+ };
42
+
43
+ declare type MenuProps = {
44
+ /**
45
+ * The component to use to open the menu, e.g. a ButtonIcon, a Button, etc.
46
+ * Required for root menus, omit for nested sub-menus (use label instead).
47
+ */
48
+ trigger?: React_2.ReactNode;
49
+ /**
50
+ * The children to render.
51
+ */
52
+ children?: React_2.ReactNode;
53
+ /**
54
+ * The default location of the popup.
55
+ * @default "bottom-start"
56
+ */
57
+ defaultPlacement?: Placement;
58
+ /**
59
+ * The type of focus for the Button. This will change the color
60
+ * of the focus ring around the Button.
61
+ */
62
+ focusMode?: "dark" | "light" | "system" | "alt-system";
63
+ /**
64
+ * The type of Button trigger. This will change the color of the Button.
65
+ */
66
+ mode?: "dark" | "light" | "system" | "alt-system";
67
+ /**
68
+ * The label to use for the menu button (root menu) or the sub-menu trigger text (nested menu).
69
+ * When used without a trigger, this creates a nested sub-menu.
70
+ */
71
+ label?: string;
72
+ /**
73
+ * Callback fired when the component is opened or closed.
74
+ * @param open whether or not the menu is open
75
+ */
76
+ onOpenChange?: (open: boolean) => void;
77
+ };
78
+
79
+ export declare const MenuSeparator: ({ className, ...props }: MenuSeparatorProps) => JSX.Element;
80
+
81
+ declare type MenuSeparatorProps = React_2.HTMLAttributes<HTMLDivElement>;
82
+
83
+ export { }
package/dist/index.js CHANGED
@@ -1,21 +1,464 @@
1
- import { Menu as _ } from "./components/Menu/Menu.js";
2
- import { MenuGroupLabel as n, MenuItem as t, MenuSeparator as u } from "./components/Menu/MenuItem.js";
3
1
  /*!
4
- @versini/ui-menu v5.1.3
2
+ @versini/ui-menu v5.3.0
5
3
  © 2025 gizmette.com
6
4
  */
7
5
  try {
8
- window.__VERSINI_UI_MENU__ || (window.__VERSINI_UI_MENU__ = {
9
- version: "5.1.3",
10
- buildTime: "10/24/2025 07:47 PM EDT",
11
- homepage: "https://github.com/aversini/ui-components",
12
- license: "MIT"
13
- });
14
- } catch {
6
+ if (!window.__VERSINI_UI_MENU__) {
7
+ window.__VERSINI_UI_MENU__ = {
8
+ version: "5.3.0",
9
+ buildTime: "11/04/2025 03:44 PM EST",
10
+ homepage: "https://github.com/aversini/ui-components",
11
+ license: "MIT",
12
+ };
13
+ }
14
+ } catch (error) {
15
+ // nothing to declare officer
15
16
  }
16
- export {
17
- _ as Menu,
18
- n as MenuGroupLabel,
19
- t as MenuItem,
20
- u as MenuSeparator
17
+
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ import { FloatingFocusManager, FloatingList, FloatingNode, FloatingPortal, FloatingTree, autoUpdate, flip, offset, safePolygon, shift, useClick, useDismiss, useFloating, useFloatingNodeId, useFloatingParentNodeId, useFloatingTree, useHover, useInteractions, useListItem, useListNavigation, useMergeRefs, useRole, useTypeahead } from "@floating-ui/react";
20
+ import { IconNext, IconSelected, IconUnSelected } from "@versini/ui-icons";
21
+ import clsx from "clsx";
22
+ import react, { createContext, forwardRef, useContext, useEffect, useRef, useState } from "react";
23
+
24
+ ;// CONCATENATED MODULE: external "react/jsx-runtime"
25
+
26
+ ;// CONCATENATED MODULE: external "@floating-ui/react"
27
+
28
+ ;// CONCATENATED MODULE: external "@versini/ui-icons"
29
+
30
+ ;// CONCATENATED MODULE: external "clsx"
31
+
32
+ ;// CONCATENATED MODULE: external "react"
33
+
34
+ ;// CONCATENATED MODULE: ./src/components/Menu/MenuContext.tsx
35
+
36
+ const MenuContext = /*#__PURE__*/ createContext({
37
+ getItemProps: ()=>({}),
38
+ activeIndex: null,
39
+ /* v8 ignore next 2 */ setActiveIndex: ()=>{},
40
+ setHasFocusInside: ()=>{},
41
+ isOpen: false,
42
+ allowHover: false,
43
+ parent: null
44
+ });
45
+
46
+ ;// CONCATENATED MODULE: ./src/components/Menu/utilities.ts
47
+ const getDisplayName = (element)=>{
48
+ if (typeof element === "string") {
49
+ return element;
50
+ }
51
+ if (typeof element === "function") {
52
+ return element.displayName || element.name || "Component";
53
+ }
54
+ if (typeof element === "object" && element !== null && "type" in element) {
55
+ const type = element.type;
56
+ if (typeof type === "function" || typeof type === "object") {
57
+ return type.displayName || type.name || "Component";
58
+ }
59
+ }
60
+ return "Element";
21
61
  };
62
+
63
+ ;// CONCATENATED MODULE: ./src/components/Menu/Menu.tsx
64
+
65
+
66
+
67
+
68
+
69
+
70
+
71
+ const MenuComponent = /*#__PURE__*/ forwardRef(({ trigger, children, label = "Open menu", defaultPlacement = "bottom-start", onOpenChange, mode = "system", focusMode = "system", ...props }, forwardedRef)=>{
72
+ const [isOpen, setIsOpen] = useState(false);
73
+ const [hasFocusInside, setHasFocusInside] = useState(false);
74
+ const [activeIndex, setActiveIndex] = useState(null);
75
+ const [allowHover, setAllowHover] = useState(false);
76
+ const elementsRef = useRef([]);
77
+ const labelsRef = useRef([]);
78
+ const parent = useContext(MenuContext);
79
+ const tree = useFloatingTree();
80
+ const nodeId = useFloatingNodeId();
81
+ const parentId = useFloatingParentNodeId();
82
+ const item = useListItem({
83
+ label: label !== "Open menu" ? label : null
84
+ });
85
+ const isNested = parentId != null;
86
+ const { floatingStyles, refs, context } = useFloating({
87
+ nodeId,
88
+ open: isOpen,
89
+ onOpenChange: (args)=>{
90
+ setIsOpen(args);
91
+ onOpenChange?.(args);
92
+ },
93
+ placement: isNested ? "right-start" : defaultPlacement,
94
+ strategy: "fixed",
95
+ middleware: [
96
+ offset(()=>{
97
+ /**
98
+ * For nested menus, account for responsive padding differences.
99
+ * The menu has p-4 (16px) on mobile and p-2 (8px) on sm+ breakpoints.
100
+ * At 640px (Tailwind's sm breakpoint), padding changes from 16px to 8px.
101
+ * We increase the offset at mobile sizes to compensate for the larger padding.
102
+ */ if (isNested) {
103
+ const isMobile = window.innerWidth < 640;
104
+ return {
105
+ mainAxis: isMobile ? 22 : 14,
106
+ alignmentAxis: -4
107
+ };
108
+ }
109
+ return {
110
+ mainAxis: 10,
111
+ alignmentAxis: 0
112
+ };
113
+ }),
114
+ flip(),
115
+ shift()
116
+ ],
117
+ whileElementsMounted: autoUpdate
118
+ });
119
+ const hover = useHover(context, {
120
+ enabled: isNested && allowHover,
121
+ delay: {
122
+ open: 75
123
+ },
124
+ handleClose: safePolygon({
125
+ blockPointerEvents: true
126
+ })
127
+ });
128
+ const click = useClick(context, {
129
+ event: "mousedown",
130
+ toggle: !isNested || !allowHover,
131
+ ignoreMouse: isNested && allowHover
132
+ });
133
+ const role = useRole(context, {
134
+ role: "menu"
135
+ });
136
+ const dismiss = useDismiss(context, {
137
+ bubbles: true
138
+ });
139
+ const listNavigation = useListNavigation(context, {
140
+ listRef: elementsRef,
141
+ activeIndex,
142
+ nested: isNested,
143
+ onNavigate: setActiveIndex,
144
+ loop: true
145
+ });
146
+ const typeahead = useTypeahead(context, {
147
+ listRef: labelsRef,
148
+ onMatch: isOpen ? setActiveIndex : undefined,
149
+ activeIndex
150
+ });
151
+ const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([
152
+ hover,
153
+ click,
154
+ role,
155
+ dismiss,
156
+ listNavigation,
157
+ typeahead
158
+ ]);
159
+ const mergedRef = useMergeRefs([
160
+ refs.setReference,
161
+ item.ref,
162
+ forwardedRef
163
+ ]);
164
+ // Event emitter allows you to communicate across tree components.
165
+ // This effect closes all menus when an item gets clicked anywhere in the tree.
166
+ useEffect(()=>{
167
+ /* v8 ignore next 3 */ if (!tree) {
168
+ return;
169
+ }
170
+ function handleTreeClick() {
171
+ setIsOpen(false);
172
+ onOpenChange?.(false);
173
+ }
174
+ function onSubMenuOpen(event) {
175
+ if (event.nodeId !== nodeId && event.parentId === parentId) {
176
+ setIsOpen(false);
177
+ }
178
+ }
179
+ tree.events.on("click", handleTreeClick);
180
+ tree.events.on("menuopen", onSubMenuOpen);
181
+ return ()=>{
182
+ tree.events.off("click", handleTreeClick);
183
+ tree.events.off("menuopen", onSubMenuOpen);
184
+ };
185
+ }, [
186
+ tree,
187
+ nodeId,
188
+ parentId,
189
+ onOpenChange
190
+ ]);
191
+ useEffect(()=>{
192
+ if (isOpen && tree) {
193
+ tree.events.emit("menuopen", {
194
+ parentId,
195
+ nodeId
196
+ });
197
+ }
198
+ }, [
199
+ tree,
200
+ isOpen,
201
+ nodeId,
202
+ parentId
203
+ ]);
204
+ // Determine if "hover" logic can run based on input modality.
205
+ // This prevents unwanted focus sync as menus open/close with keyboard nav.
206
+ useEffect(()=>{
207
+ function onPointerMove({ pointerType }) {
208
+ if (pointerType !== "touch") {
209
+ setAllowHover(true);
210
+ }
211
+ }
212
+ function onKeyDown() {
213
+ setAllowHover(false);
214
+ }
215
+ window.addEventListener("pointermove", onPointerMove, {
216
+ once: true,
217
+ capture: true
218
+ });
219
+ window.addEventListener("keydown", onKeyDown, true);
220
+ return ()=>{
221
+ window.removeEventListener("pointermove", onPointerMove, {
222
+ capture: true
223
+ });
224
+ window.removeEventListener("keydown", onKeyDown, true);
225
+ };
226
+ }, []);
227
+ const noInternalClick = getDisplayName(trigger) === "Button" || getDisplayName(trigger) === "ButtonIcon";
228
+ const uiButtonsExtraProps = noInternalClick ? {
229
+ noInternalClick,
230
+ focusMode,
231
+ mode
232
+ } : {};
233
+ // For nested menus (sub-menus), render as a menu item trigger
234
+ if (isNested && !trigger) {
235
+ const buttonClass = 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", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", {
236
+ "bg-surface-lighter": isOpen && !hasFocusInside
237
+ });
238
+ return /*#__PURE__*/ jsxs(FloatingNode, {
239
+ id: nodeId,
240
+ children: [
241
+ /*#__PURE__*/ jsxs("button", {
242
+ ref: mergedRef,
243
+ "data-open": isOpen ? "" : undefined,
244
+ ...getReferenceProps(parent.getItemProps({
245
+ ...props,
246
+ className: buttonClass,
247
+ onFocus (event) {
248
+ props.onFocus?.(event);
249
+ setHasFocusInside(false);
250
+ parent.setHasFocusInside(true);
251
+ },
252
+ onMouseEnter (event) {
253
+ props.onMouseEnter?.(event);
254
+ if (parent.allowHover && parent.isOpen) {
255
+ parent.setActiveIndex(item.index);
256
+ }
257
+ }
258
+ })),
259
+ children: [
260
+ /*#__PURE__*/ jsx("span", {
261
+ children: label
262
+ }),
263
+ /*#__PURE__*/ jsx(IconNext, {
264
+ className: "ml-2",
265
+ size: "size-3",
266
+ monotone: true
267
+ })
268
+ ]
269
+ }),
270
+ /*#__PURE__*/ jsx(MenuContext.Provider, {
271
+ value: {
272
+ activeIndex,
273
+ setActiveIndex,
274
+ getItemProps,
275
+ setHasFocusInside,
276
+ isOpen,
277
+ allowHover,
278
+ parent
279
+ },
280
+ children: /*#__PURE__*/ jsx(FloatingList, {
281
+ elementsRef: elementsRef,
282
+ labelsRef: labelsRef,
283
+ children: isOpen && /*#__PURE__*/ jsx(FloatingPortal, {
284
+ children: /*#__PURE__*/ jsx(FloatingFocusManager, {
285
+ context: context,
286
+ modal: false,
287
+ initialFocus: -1,
288
+ returnFocus: false,
289
+ children: /*#__PURE__*/ jsx("div", {
290
+ ref: refs.setFloating,
291
+ className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2",
292
+ style: floatingStyles,
293
+ ...getFloatingProps(),
294
+ children: children
295
+ })
296
+ })
297
+ })
298
+ })
299
+ })
300
+ ]
301
+ });
302
+ }
303
+ const triggerElement = /*#__PURE__*/ react.cloneElement(trigger, {
304
+ ...uiButtonsExtraProps,
305
+ "aria-label": label,
306
+ "data-open": isOpen ? "" : undefined,
307
+ "data-focus-inside": hasFocusInside ? "" : undefined,
308
+ ref: mergedRef,
309
+ ...getReferenceProps(parent.getItemProps({
310
+ ...props
311
+ }))
312
+ });
313
+ return /*#__PURE__*/ jsxs(FloatingNode, {
314
+ id: nodeId,
315
+ children: [
316
+ triggerElement,
317
+ /*#__PURE__*/ jsx(MenuContext.Provider, {
318
+ value: {
319
+ activeIndex,
320
+ setActiveIndex,
321
+ getItemProps,
322
+ setHasFocusInside,
323
+ isOpen,
324
+ allowHover,
325
+ parent
326
+ },
327
+ children: /*#__PURE__*/ jsx(FloatingList, {
328
+ elementsRef: elementsRef,
329
+ labelsRef: labelsRef,
330
+ children: isOpen && /*#__PURE__*/ jsx(FloatingPortal, {
331
+ children: /*#__PURE__*/ jsx(FloatingFocusManager, {
332
+ context: context,
333
+ modal: false,
334
+ initialFocus: 0,
335
+ returnFocus: true,
336
+ children: /*#__PURE__*/ jsx("div", {
337
+ ref: refs.setFloating,
338
+ className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2",
339
+ style: floatingStyles,
340
+ ...getFloatingProps(),
341
+ children: children
342
+ })
343
+ })
344
+ })
345
+ })
346
+ })
347
+ ]
348
+ });
349
+ });
350
+ const Menu = /*#__PURE__*/ forwardRef((props, ref)=>{
351
+ const parentId = useFloatingParentNodeId();
352
+ // If parentId is null, this is a root menu - wrap in FloatingTree
353
+ if (parentId === null) {
354
+ return /*#__PURE__*/ jsx(FloatingTree, {
355
+ children: /*#__PURE__*/ jsx(MenuComponent, {
356
+ ...props,
357
+ ref: ref
358
+ })
359
+ });
360
+ }
361
+ // This is a nested sub-menu - don't wrap in FloatingTree
362
+ return /*#__PURE__*/ jsx(MenuComponent, {
363
+ ...props,
364
+ ref: ref
365
+ });
366
+ });
367
+ Menu.displayName = "Menu";
368
+ MenuComponent.displayName = "MenuComponent";
369
+
370
+ ;// CONCATENATED MODULE: ./src/components/Menu/MenuItem.tsx
371
+
372
+
373
+
374
+
375
+
376
+
377
+ const MenuItem = /*#__PURE__*/ forwardRef(({ label, disabled, icon, raw = false, children, ignoreClick = false, selected, ...props }, forwardedRef)=>{
378
+ let buttonSpanClass = "";
379
+ const menu = useContext(MenuContext);
380
+ const item = useListItem({
381
+ label: disabled ? null : label
382
+ });
383
+ const tree = useFloatingTree();
384
+ const mergedRef = useMergeRefs([
385
+ item.ref,
386
+ forwardedRef
387
+ ]);
388
+ if (raw && children) {
389
+ return /*#__PURE__*/ jsx("div", {
390
+ role: "menuitem",
391
+ ...menu.getItemProps({
392
+ onClick (event) {
393
+ if (!ignoreClick) {
394
+ props.onClick?.(event);
395
+ tree?.events.emit("click");
396
+ }
397
+ }
398
+ }),
399
+ children: children
400
+ });
401
+ }
402
+ if (icon) {
403
+ buttonSpanClass = "pl-2";
404
+ }
405
+ const buttonClass = 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", "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline", "disabled:cursor-not-allowed disabled:text-copy-medium", {
406
+ "bg-none": !disabled && !selected
407
+ });
408
+ return /*#__PURE__*/ jsxs("button", {
409
+ ...props,
410
+ ref: mergedRef,
411
+ role: "menuitem",
412
+ className: buttonClass,
413
+ tabIndex: 0,
414
+ disabled: disabled,
415
+ ...menu.getItemProps({
416
+ onClick (event) {
417
+ if (!ignoreClick) {
418
+ props.onClick?.(event);
419
+ tree?.events.emit("click");
420
+ }
421
+ },
422
+ onFocus (event) {
423
+ props.onFocus?.(event);
424
+ menu.setHasFocusInside(true);
425
+ }
426
+ }),
427
+ children: [
428
+ selected === true && /*#__PURE__*/ jsx(IconSelected, {
429
+ className: "text-copy-success mr-2",
430
+ size: "size-4"
431
+ }),
432
+ selected === false && /*#__PURE__*/ jsx(IconUnSelected, {
433
+ className: "text-copy-medium mr-2",
434
+ size: "size-4"
435
+ }),
436
+ icon,
437
+ label && /*#__PURE__*/ jsx("span", {
438
+ className: buttonSpanClass,
439
+ children: label
440
+ })
441
+ ]
442
+ });
443
+ });
444
+ MenuItem.displayName = "MenuItem";
445
+ const MenuSeparator = ({ className, ...props })=>{
446
+ const separatorClass = clsx(className, "my-1 border-t border-border-medium");
447
+ return /*#__PURE__*/ jsx("div", {
448
+ className: separatorClass,
449
+ ...props
450
+ });
451
+ };
452
+ const MenuGroupLabel = ({ className, ...props })=>{
453
+ const groupLabelClass = clsx(className, "pt-1 mb-2", "text-sm text-copy-dark font-bold", "border-b border-border-medium");
454
+ return /*#__PURE__*/ jsx("div", {
455
+ className: groupLabelClass,
456
+ ...props
457
+ });
458
+ };
459
+
460
+ ;// CONCATENATED MODULE: ./src/components/index.ts
461
+
462
+
463
+
464
+ export { Menu, MenuGroupLabel, MenuItem, MenuSeparator };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-menu",
3
- "version": "5.1.3",
3
+ "version": "5.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -20,13 +20,13 @@
20
20
  ],
21
21
  "scripts": {
22
22
  "build:check": "tsc",
23
- "build:js": "vite build",
24
- "build:types": "tsup",
25
- "build": "npm-run-all --serial clean build:check build:js build:types",
23
+ "build:js": "rslib build",
24
+ "build:types": "echo 'Types now built with rslib'",
25
+ "build": "npm-run-all --serial clean build:check build:js",
26
26
  "clean": "rimraf dist tmp",
27
- "dev:js": "vite build --watch --mode development",
28
- "dev:types": "tsup --watch src",
29
- "dev": "npm-run-all clean --parallel dev:js dev:types",
27
+ "dev:js": "rslib build --watch",
28
+ "dev:types": "echo 'Types now watched with rslib'",
29
+ "dev": "rslib build --watch",
30
30
  "lint": "biome lint src",
31
31
  "lint:fix": "biome check src --write --no-errors-on-unmatched",
32
32
  "prettier": "biome check --write --no-errors-on-unmatched",
@@ -43,12 +43,12 @@
43
43
  "dependencies": {
44
44
  "@floating-ui/react": "0.27.16",
45
45
  "@tailwindcss/typography": "0.5.19",
46
- "@versini/ui-icons": "4.14.1",
46
+ "@versini/ui-icons": "4.15.1",
47
47
  "clsx": "2.1.1",
48
48
  "tailwindcss": "4.1.16"
49
49
  },
50
50
  "sideEffects": [
51
51
  "**/*.css"
52
52
  ],
53
- "gitHead": "26763e1e738bcdcb0806c39612161666e4bc4459"
53
+ "gitHead": "7484ad443b77ef31e52ae3a7d88b8129bc6cdf1d"
54
54
  }
@@ -1,61 +0,0 @@
1
- import { jsxs as I, jsx as a } from "react/jsx-runtime";
2
- import m from "clsx";
3
- const d = ({
4
- children: o,
5
- fill: s,
6
- viewBox: t,
7
- className: n,
8
- defaultViewBox: _,
9
- size: l,
10
- title: e,
11
- semantic: i = !1,
12
- ...r
13
- }) => {
14
- const c = m(l, n);
15
- return /* @__PURE__ */ I(
16
- "svg",
17
- {
18
- xmlns: "http://www.w3.org/2000/svg",
19
- className: c,
20
- viewBox: t || _,
21
- fill: s || "currentColor",
22
- role: "img",
23
- "aria-hidden": !i,
24
- focusable: !1,
25
- ...r,
26
- children: [
27
- e && i && /* @__PURE__ */ a("title", { children: e }),
28
- o
29
- ]
30
- }
31
- );
32
- };
33
- /*!
34
- @versini/ui-svgicon v4.2.2
35
- © 2025 gizmette.com
36
- */
37
- try {
38
- window.__VERSINI_UI_SVGICON__ || (window.__VERSINI_UI_SVGICON__ = {
39
- version: "4.2.2",
40
- buildTime: "10/24/2025 06:42 PM EDT",
41
- homepage: "https://github.com/aversini/ui-components",
42
- license: "MIT"
43
- });
44
- } catch {
45
- }
46
- /*!
47
- @versini/ui-icons v4.14.1
48
- © 2025 gizmette.com
49
- */
50
- try {
51
- window.__VERSINI_UI_ICONS__ || (window.__VERSINI_UI_ICONS__ = {
52
- version: "4.14.1",
53
- buildTime: "10/24/2025 06:42 PM EDT",
54
- homepage: "https://github.com/aversini/ui-components",
55
- license: "MIT"
56
- });
57
- } catch {
58
- }
59
- export {
60
- d as I
61
- };
@@ -1,258 +0,0 @@
1
- import { jsxs as b, jsx as t } from "react/jsx-runtime";
2
- import { useFloatingTree as se, useFloatingNodeId as ie, useFloatingParentNodeId as W, useListItem as re, useFloating as ae, autoUpdate as le, offset as de, flip as ue, shift as ce, useHover as fe, safePolygon as me, useClick as pe, useRole as he, useDismiss as ve, useListNavigation as ge, useTypeahead as we, useInteractions as ye, useMergeRefs as be, FloatingNode as D, FloatingList as S, FloatingPortal as _, FloatingFocusManager as K, FloatingTree as xe } from "@floating-ui/react";
3
- import { I as Ie } from "../../chunks/index.Benv90bx.js";
4
- import Fe from "clsx";
5
- import Ne, { forwardRef as q, useState as y, useRef as U, useContext as Me, useEffect as P } from "react";
6
- import { MenuContext as R } from "./MenuContext.js";
7
- const Ee = ({
8
- className: e,
9
- viewBox: n,
10
- title: d,
11
- monotone: x,
12
- ...f
13
- }) => /* @__PURE__ */ b(
14
- Ie,
15
- {
16
- defaultViewBox: "0 0 448 512",
17
- size: "size-5",
18
- viewBox: n,
19
- className: e,
20
- title: d || "Next",
21
- ...f,
22
- children: [
23
- /* @__PURE__ */ t(
24
- "path",
25
- {
26
- d: "M0 256c0 17.7 14.3 32 32 32h306.7l32-32-32-32H32c-17.7 0-32 14.3-32 32",
27
- opacity: x ? "1" : "0.4"
28
- }
29
- ),
30
- /* @__PURE__ */ t("path", { d: "M438.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L370.7 256 233.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z" })
31
- ]
32
- }
33
- ), V = (e) => {
34
- if (typeof e == "string")
35
- return e;
36
- if (typeof e == "function")
37
- return e.displayName || e.name || "Component";
38
- if (typeof e == "object" && e !== null && "type" in e) {
39
- const n = e.type;
40
- if (typeof n == "function" || typeof n == "object")
41
- return n.displayName || n.name || "Component";
42
- }
43
- return "Element";
44
- }, k = q(
45
- ({
46
- trigger: e,
47
- children: n,
48
- label: d = "Open menu",
49
- defaultPlacement: x = "bottom-start",
50
- onOpenChange: f,
51
- mode: G = "system",
52
- focusMode: J = "system",
53
- ...h
54
- }, Q) => {
55
- const [o, I] = y(!1), [C, F] = y(!1), [v, g] = y(null), [m, L] = y(!1), N = U([]), M = U([]), a = Me(R), i = se(), u = ie(), p = W(), A = re({ label: d !== "Open menu" ? d : null }), c = p != null, { floatingStyles: H, refs: E, context: l } = ae({
56
- nodeId: u,
57
- open: o,
58
- onOpenChange: (s) => {
59
- I(s), f?.(s);
60
- },
61
- placement: c ? "right-start" : x,
62
- strategy: "fixed",
63
- middleware: [
64
- de(() => c ? {
65
- mainAxis: window.innerWidth < 640 ? 22 : 14,
66
- alignmentAxis: -4
67
- } : {
68
- mainAxis: 10,
69
- alignmentAxis: 0
70
- }),
71
- ue(),
72
- ce()
73
- ],
74
- whileElementsMounted: le
75
- }), X = fe(l, {
76
- enabled: c && m,
77
- delay: { open: 75 },
78
- handleClose: me({ blockPointerEvents: !0 })
79
- }), Y = pe(l, {
80
- event: "mousedown",
81
- toggle: !c || !m,
82
- ignoreMouse: c && m
83
- }), Z = he(l, { role: "menu" }), $ = ve(l, { bubbles: !0 }), ee = ge(l, {
84
- listRef: N,
85
- activeIndex: v,
86
- nested: c,
87
- onNavigate: g,
88
- loop: !0
89
- }), te = we(l, {
90
- listRef: M,
91
- onMatch: o ? g : void 0,
92
- activeIndex: v
93
- }), { getReferenceProps: B, getFloatingProps: j, getItemProps: z } = ye([X, Y, Z, $, ee, te]), O = be([E.setReference, A.ref, Q]);
94
- P(() => {
95
- if (!i)
96
- return;
97
- function s() {
98
- I(!1), f?.(!1);
99
- }
100
- function r(w) {
101
- w.nodeId !== u && w.parentId === p && I(!1);
102
- }
103
- return i.events.on("click", s), i.events.on("menuopen", r), () => {
104
- i.events.off("click", s), i.events.off("menuopen", r);
105
- };
106
- }, [i, u, p, f]), P(() => {
107
- o && i && i.events.emit("menuopen", { parentId: p, nodeId: u });
108
- }, [i, o, u, p]), P(() => {
109
- function s({ pointerType: w }) {
110
- w !== "touch" && L(!0);
111
- }
112
- function r() {
113
- L(!1);
114
- }
115
- return window.addEventListener("pointermove", s, {
116
- once: !0,
117
- capture: !0
118
- }), window.addEventListener("keydown", r, !0), () => {
119
- window.removeEventListener("pointermove", s, {
120
- capture: !0
121
- }), window.removeEventListener("keydown", r, !0);
122
- };
123
- }, []);
124
- const T = V(e) === "Button" || V(e) === "ButtonIcon", ne = T ? {
125
- noInternalClick: T,
126
- focusMode: J,
127
- mode: G
128
- } : {};
129
- if (c && !e) {
130
- const s = Fe(
131
- "flex items-center flex-row justify-between",
132
- "w-full",
133
- "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1",
134
- "rounded-md border border-transparent",
135
- "text-left text-base",
136
- "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline",
137
- "disabled:cursor-not-allowed disabled:text-copy-medium",
138
- {
139
- "bg-surface-lighter": o && !C
140
- }
141
- );
142
- return /* @__PURE__ */ b(D, { id: u, children: [
143
- /* @__PURE__ */ b(
144
- "button",
145
- {
146
- ref: O,
147
- "data-open": o ? "" : void 0,
148
- ...B(
149
- a.getItemProps({
150
- ...h,
151
- className: s,
152
- onFocus(r) {
153
- h.onFocus?.(r), F(!1), a.setHasFocusInside(!0);
154
- },
155
- onMouseEnter(r) {
156
- h.onMouseEnter?.(r), a.allowHover && a.isOpen && a.setActiveIndex(A.index);
157
- }
158
- })
159
- ),
160
- children: [
161
- /* @__PURE__ */ t("span", { children: d }),
162
- /* @__PURE__ */ t(Ee, { className: "ml-2", size: "size-3", monotone: !0 })
163
- ]
164
- }
165
- ),
166
- /* @__PURE__ */ t(
167
- R.Provider,
168
- {
169
- value: {
170
- activeIndex: v,
171
- setActiveIndex: g,
172
- getItemProps: z,
173
- setHasFocusInside: F,
174
- isOpen: o,
175
- allowHover: m,
176
- parent: a
177
- },
178
- children: /* @__PURE__ */ t(S, { elementsRef: N, labelsRef: M, children: o && /* @__PURE__ */ t(_, { children: /* @__PURE__ */ t(
179
- K,
180
- {
181
- context: l,
182
- modal: !1,
183
- initialFocus: -1,
184
- returnFocus: !1,
185
- children: /* @__PURE__ */ t(
186
- "div",
187
- {
188
- ref: E.setFloating,
189
- className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2",
190
- style: H,
191
- ...j(),
192
- children: n
193
- }
194
- )
195
- }
196
- ) }) })
197
- }
198
- )
199
- ] });
200
- }
201
- const oe = Ne.cloneElement(
202
- e,
203
- {
204
- ...ne,
205
- "aria-label": d,
206
- "data-open": o ? "" : void 0,
207
- "data-focus-inside": C ? "" : void 0,
208
- ref: O,
209
- ...B(
210
- a.getItemProps({
211
- ...h
212
- })
213
- )
214
- }
215
- );
216
- return /* @__PURE__ */ b(D, { id: u, children: [
217
- oe,
218
- /* @__PURE__ */ t(
219
- R.Provider,
220
- {
221
- value: {
222
- activeIndex: v,
223
- setActiveIndex: g,
224
- getItemProps: z,
225
- setHasFocusInside: F,
226
- isOpen: o,
227
- allowHover: m,
228
- parent: a
229
- },
230
- children: /* @__PURE__ */ t(S, { elementsRef: N, labelsRef: M, children: o && /* @__PURE__ */ t(_, { children: /* @__PURE__ */ t(
231
- K,
232
- {
233
- context: l,
234
- modal: !1,
235
- initialFocus: 0,
236
- returnFocus: !0,
237
- children: /* @__PURE__ */ t(
238
- "div",
239
- {
240
- ref: E.setFloating,
241
- className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-3 sm:p-2",
242
- style: H,
243
- ...j(),
244
- children: n
245
- }
246
- )
247
- }
248
- ) }) })
249
- }
250
- )
251
- ] });
252
- }
253
- ), Pe = q((e, n) => W() === null ? /* @__PURE__ */ t(xe, { children: /* @__PURE__ */ t(k, { ...e, ref: n }) }) : /* @__PURE__ */ t(k, { ...e, ref: n }));
254
- Pe.displayName = "Menu";
255
- k.displayName = "MenuComponent";
256
- export {
257
- Pe as Menu
258
- };
@@ -1,16 +0,0 @@
1
- import * as e from "react";
2
- const t = e.createContext({
3
- getItemProps: () => ({}),
4
- activeIndex: null,
5
- /* v8 ignore next 2 */
6
- setActiveIndex: () => {
7
- },
8
- setHasFocusInside: () => {
9
- },
10
- isOpen: !1,
11
- allowHover: !1,
12
- parent: null
13
- });
14
- export {
15
- t as MenuContext
16
- };
@@ -1,136 +0,0 @@
1
- import { jsxs as x, jsx as s } from "react/jsx-runtime";
2
- import { useListItem as C, useFloatingTree as M, useMergeRefs as g } from "@floating-ui/react";
3
- import { I as b } from "../../chunks/index.Benv90bx.js";
4
- import l from "clsx";
5
- import * as p from "react";
6
- import { MenuContext as h } from "./MenuContext.js";
7
- const z = ({
8
- className: e,
9
- viewBox: t,
10
- title: o,
11
- monotone: a,
12
- ...r
13
- }) => /* @__PURE__ */ x(
14
- b,
15
- {
16
- defaultViewBox: "0 0 512 512",
17
- size: "size-5",
18
- viewBox: t,
19
- className: e,
20
- title: o || "Selected",
21
- ...r,
22
- children: [
23
- /* @__PURE__ */ s(
24
- "path",
25
- {
26
- d: "M0 256a256 256 0 1 0 512 0 256 256 0 1 0-512 0m352 0a96 96 0 1 1-192 0 96 96 0 1 1 192 0",
27
- opacity: ".4"
28
- }
29
- ),
30
- /* @__PURE__ */ s("path", { d: "M256 160a96 96 0 1 0 0 192 96 96 0 1 0 0-192" })
31
- ]
32
- }
33
- ), I = ({
34
- className: e,
35
- viewBox: t,
36
- title: o,
37
- monotone: a,
38
- ...r
39
- }) => /* @__PURE__ */ s(
40
- b,
41
- {
42
- defaultViewBox: "0 0 512 512",
43
- size: "size-5",
44
- viewBox: t,
45
- className: e,
46
- title: o || "UnSelected",
47
- ...r,
48
- children: /* @__PURE__ */ s("path", { d: "M256 512a256 256 0 1 0 0-512 256 256 0 1 0 0 512", opacity: ".4" })
49
- }
50
- ), k = p.forwardRef(
51
- ({
52
- label: e,
53
- disabled: t,
54
- icon: o,
55
- raw: a = !1,
56
- children: r,
57
- ignoreClick: u = !1,
58
- selected: i,
59
- ...n
60
- }, N) => {
61
- let d = "";
62
- const c = p.useContext(h), w = C({ label: t ? null : e }), f = M(), v = g([w.ref, N]);
63
- if (a && r)
64
- return /* @__PURE__ */ s(
65
- "div",
66
- {
67
- role: "menuitem",
68
- ...c.getItemProps({
69
- onClick(m) {
70
- u || (n.onClick?.(m), f?.events.emit("click"));
71
- }
72
- }),
73
- children: r
74
- }
75
- );
76
- o && (d = "pl-2");
77
- const y = l(
78
- "flex flex-row items-center",
79
- "w-full",
80
- "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1",
81
- "rounded-md border border-transparent",
82
- "text-left text-base",
83
- "outline-hidden focus:border focus:border-border-medium focus:bg-surface-lighter focus:underline",
84
- "disabled:cursor-not-allowed disabled:text-copy-medium",
85
- {
86
- "bg-none": !t && !i
87
- }
88
- );
89
- return /* @__PURE__ */ x(
90
- "button",
91
- {
92
- ...n,
93
- ref: v,
94
- role: "menuitem",
95
- className: y,
96
- tabIndex: 0,
97
- disabled: t,
98
- ...c.getItemProps({
99
- onClick(m) {
100
- u || (n.onClick?.(m), f?.events.emit("click"));
101
- },
102
- onFocus(m) {
103
- n.onFocus?.(m), c.setHasFocusInside(!0);
104
- }
105
- }),
106
- children: [
107
- i === !0 && /* @__PURE__ */ s(z, { className: "text-copy-success mr-2", size: "size-4" }),
108
- i === !1 && /* @__PURE__ */ s(I, { className: "text-copy-medium mr-2", size: "size-4" }),
109
- o,
110
- e && /* @__PURE__ */ s("span", { className: d, children: e })
111
- ]
112
- }
113
- );
114
- }
115
- );
116
- k.displayName = "MenuItem";
117
- const j = ({ className: e, ...t }) => {
118
- const o = l(e, "my-1 border-t border-border-medium");
119
- return /* @__PURE__ */ s("div", { className: o, ...t });
120
- }, P = ({
121
- className: e,
122
- ...t
123
- }) => {
124
- const o = l(
125
- e,
126
- "pt-1 mb-2",
127
- "text-sm text-copy-dark font-bold",
128
- "border-b border-border-medium"
129
- );
130
- return /* @__PURE__ */ s("div", { className: o, ...t });
131
- };
132
- export {
133
- P as MenuGroupLabel,
134
- k as MenuItem,
135
- j as MenuSeparator
136
- };