@versini/ui-menu 5.0.1 โ†’ 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,10 +18,11 @@ The Menu package provides dropdown menus and navigation components with full key
18
18
  ## Features
19
19
 
20
20
  - **๐Ÿ“‹ Composable**: `Menu`, `MenuItem`, `MenuSeparator`, `MenuGroupLabel`
21
+ - **๐Ÿ”„ Nested Sub-menus**: Support for multi-level menu hierarchies with automatic positioning
21
22
  - **โ™ฟ Accessible**: Built with Floating UI & ARIA roles for robust a11y
22
23
  - **โŒจ๏ธ Keyboard Support**: Arrow navigation, typeahead matching, ESC / click outside close
23
24
  - **๐ŸŽจ Theme & Focus Modes**: Trigger inherits color + separate focus styling
24
- - **๐Ÿงญ Smart Positioning**: Auto flip / shift to remain within viewport
25
+ - **๐Ÿงญ Smart Positioning**: Auto flip / shift to remain within viewport, responsive spacing
25
26
  - **๐Ÿงช Type Safe**: Strongly typed props & label-based typeahead
26
27
 
27
28
  ## Installation
@@ -124,19 +125,81 @@ function AccountMenu() {
124
125
  </Menu>
125
126
  ```
126
127
 
128
+ ### Nested Sub-menus
129
+
130
+ Create hierarchical menus by nesting `Menu` components. Simply use a `Menu` with a `label` prop but no `trigger` to create a sub-menu:
131
+
132
+ ```tsx
133
+ import { Menu, MenuItem, MenuGroupLabel } from "@versini/ui-menu";
134
+ import { ButtonIcon } from "@versini/ui-button";
135
+ import { IconSettings, IconOpenAI, IconAnthropic } from "@versini/ui-icons";
136
+
137
+ function SettingsMenu() {
138
+ const [engine, setEngine] = useState("openai");
139
+
140
+ return (
141
+ <Menu
142
+ trigger={
143
+ <ButtonIcon label="Settings">
144
+ <IconSettings />
145
+ </ButtonIcon>
146
+ }
147
+ >
148
+ <MenuItem label="Profile" />
149
+ <MenuItem label="Preferences" />
150
+
151
+ {/* Nested sub-menu */}
152
+ <Menu label="AI Settings">
153
+ <MenuGroupLabel>Engines</MenuGroupLabel>
154
+ <MenuItem
155
+ label="OpenAI"
156
+ icon={<IconOpenAI />}
157
+ selected={engine === "openai"}
158
+ onClick={() => setEngine("openai")}
159
+ />
160
+ <MenuItem
161
+ label="Anthropic"
162
+ icon={<IconAnthropic />}
163
+ selected={engine === "anthropic"}
164
+ onClick={() => setEngine("anthropic")}
165
+ />
166
+ </Menu>
167
+
168
+ <MenuItem label="About" />
169
+ </Menu>
170
+ );
171
+ }
172
+ ```
173
+
174
+ **Features of nested sub-menus:**
175
+
176
+ - Automatically positioned to the right (or left if no space)
177
+ - Visual chevron indicator (`โ†’`) shows expandable items
178
+ - Hover or click to open sub-menus
179
+ - Smart positioning adjusts for viewport constraints
180
+ - Keyboard navigation works across all levels
181
+ - Sibling sub-menus auto-close when opening another
182
+
127
183
  ## API
128
184
 
129
185
  ### Menu Props
130
186
 
131
- | Prop | Type | Default | Description |
132
- | ------------------ | ------------------------- | ---------------- | ------------------------------------------------------------- | ------------- | ---------- | ------------------------------------------------- |
133
- | `trigger` | `React.ReactNode` | - | Element used to open the menu (Button / ButtonIcon / custom). |
134
- | `children` | `React.ReactNode` | - | One or more `MenuItem` / `MenuSeparator` / custom nodes. |
135
- | `defaultPlacement` | `Placement` (Floating UI) | `"bottom-start"` | Initial preferred placement. |
136
- | `mode` | `"dark" | "light" | "system" | "alt-system"` | `"system"` | Color mode of trigger (when using UI buttons). |
137
- | `focusMode` | `"dark" | "light" | "system" | "alt-system"` | `"system"` | Focus ring thematic mode (when using UI buttons). |
138
- | `label` | `string` | `"Open menu"` | Accessible label for the trigger if none present. |
139
- | `onOpenChange` | `(open: boolean) => void` | - | Called when menu opens or closes. |
187
+ | Prop | Type | Default | Description |
188
+ | ------------------ | ----------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------- |
189
+ | `trigger` | `React.ReactNode` | - | Element used to open the menu (Button / ButtonIcon / custom). Optional for nested sub-menus. |
190
+ | `children` | `React.ReactNode` | - | One or more `MenuItem` / `MenuSeparator` / `Menu` (for sub-menus) / custom nodes. |
191
+ | `label` | `string` | `"Open menu"` | Accessible label for the trigger. When used without `trigger`, creates a nested sub-menu item. |
192
+ | `defaultPlacement` | `Placement` (Floating UI) | `"bottom-start"` | Initial preferred placement (only applies to root menu). |
193
+ | `mode` | `"dark" \| "light" \| "system" \| "alt-system"` | `"system"` | Color mode of trigger (when using UI buttons). |
194
+ | `focusMode` | `"dark" \| "light" \| "system" \| "alt-system"` | `"system"` | Focus ring thematic mode (when using UI buttons). |
195
+ | `onOpenChange` | `(open: boolean) => void` | - | Called when menu opens or closes. |
196
+
197
+ **Creating nested sub-menus:**
198
+
199
+ - Use `Menu` with `label` but without `trigger` to create a sub-menu item
200
+ - Sub-menus automatically show a chevron (`โ†’`) indicator
201
+ - Positioning is automatically handled (right-start, flips to left if needed)
202
+ - Hover or click to open nested menus
140
203
 
141
204
  ### MenuItem Props
142
205
 
@@ -0,0 +1,61 @@
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/19/2025 01:03 PM EDT",
41
+ homepage: "https://github.com/aversini/ui-components",
42
+ license: "MIT"
43
+ });
44
+ } catch {
45
+ }
46
+ /*!
47
+ @versini/ui-icons v4.13.0
48
+ ยฉ 2025 gizmette.com
49
+ */
50
+ try {
51
+ window.__VERSINI_UI_ICONS__ || (window.__VERSINI_UI_ICONS__ = {
52
+ version: "4.13.0",
53
+ buildTime: "10/19/2025 01:03 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,110 +1,242 @@
1
- import { jsxs as G, jsx as s } from "react/jsx-runtime";
2
- import { useFloatingTree as J, useFloatingNodeId as K, useListItem as Q, useFloating as V, autoUpdate as W, offset as X, flip as Y, shift as Z, useClick as $, useRole as ee, useDismiss as te, useListNavigation as ne, useTypeahead as oe, useInteractions as se, useMergeRefs as ie, FloatingNode as ae, FloatingList as re, FloatingPortal as le, FloatingFocusManager as ce, FloatingTree as ue } from "@floating-ui/react";
3
- import fe, { forwardRef as x, useState as f, useRef as h, useContext as de, useEffect as v } from "react";
4
- import { MenuContext as F } from "./MenuContext.js";
5
- const I = (e) => {
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.dzHBmDC4.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) => {
6
34
  if (typeof e == "string")
7
35
  return e;
8
36
  if (typeof e == "function")
9
37
  return e.displayName || e.name || "Component";
10
38
  if (typeof e == "object" && e !== null && "type" in e) {
11
- const t = e.type;
12
- if (typeof t == "function" || typeof t == "object")
13
- return t.displayName || t.name || "Component";
39
+ const n = e.type;
40
+ if (typeof n == "function" || typeof n == "object")
41
+ return n.displayName || n.name || "Component";
14
42
  }
15
43
  return "Element";
16
- }, N = x(
44
+ }, k = q(
17
45
  ({
18
46
  trigger: e,
19
- children: t,
20
- label: R = "Open menu",
21
- defaultPlacement: b = "bottom-start",
22
- onOpenChange: l,
23
- mode: M = "system",
24
- focusMode: C = "system",
25
- ...k
26
- }, P) => {
27
- const [n, d] = f(!1), [w, E] = f(!1), [c, u] = f(null), p = h([]), m = h([]), j = de(F), o = J(), r = K(), T = Q(), { floatingStyles: B, refs: g, context: i } = V({
28
- nodeId: r,
29
- open: n,
30
- onOpenChange: (a) => {
31
- d(a), l?.(a);
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);
32
60
  },
33
- placement: b,
61
+ placement: c ? "right-start" : x,
34
62
  strategy: "fixed",
35
- middleware: [X({ mainAxis: 10 }), Y(), Z()],
36
- whileElementsMounted: W
37
- }), L = $(i, {
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, {
38
80
  event: "mousedown",
39
- toggle: !0,
40
- ignoreMouse: !1
41
- }), A = ee(i, { role: "menu" }), D = te(i, { bubbles: !0 }), O = ne(i, {
42
- listRef: p,
43
- activeIndex: c,
44
- nested: !1,
45
- onNavigate: u,
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,
46
88
  loop: !0
47
- }), S = oe(i, {
48
- listRef: m,
49
- onMatch: n ? u : void 0,
50
- activeIndex: c
51
- }), { getReferenceProps: _, getFloatingProps: H, getItemProps: U } = se([L, A, D, O, S]), y = I(e) === "Button" || I(e) === "ButtonIcon", q = y ? {
52
- noInternalClick: y,
53
- focusMode: C,
54
- mode: M
55
- } : {}, z = fe.cloneElement(
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
+ "items-center flex-row justify-between",
132
+ "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1 flex w-full 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",
133
+ {
134
+ "bg-surface-lighter": o && !C
135
+ }
136
+ );
137
+ return /* @__PURE__ */ b(D, { id: u, children: [
138
+ /* @__PURE__ */ b(
139
+ "button",
140
+ {
141
+ ref: O,
142
+ "data-open": o ? "" : void 0,
143
+ ...B(
144
+ a.getItemProps({
145
+ ...h,
146
+ className: s,
147
+ onFocus(r) {
148
+ h.onFocus?.(r), F(!1), a.setHasFocusInside(!0);
149
+ },
150
+ onMouseEnter(r) {
151
+ h.onMouseEnter?.(r), a.allowHover && a.isOpen && a.setActiveIndex(A.index);
152
+ }
153
+ })
154
+ ),
155
+ children: [
156
+ /* @__PURE__ */ t("span", { children: d }),
157
+ /* @__PURE__ */ t(Ee, { className: "ml-2", size: "size-3", monotone: !0 })
158
+ ]
159
+ }
160
+ ),
161
+ /* @__PURE__ */ t(
162
+ R.Provider,
163
+ {
164
+ value: {
165
+ activeIndex: v,
166
+ setActiveIndex: g,
167
+ getItemProps: z,
168
+ setHasFocusInside: F,
169
+ isOpen: o,
170
+ allowHover: m,
171
+ parent: a
172
+ },
173
+ children: /* @__PURE__ */ t(S, { elementsRef: N, labelsRef: M, children: o && /* @__PURE__ */ t(_, { children: /* @__PURE__ */ t(
174
+ K,
175
+ {
176
+ context: l,
177
+ modal: !1,
178
+ initialFocus: -1,
179
+ returnFocus: !1,
180
+ children: /* @__PURE__ */ t(
181
+ "div",
182
+ {
183
+ ref: E.setFloating,
184
+ className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-4 sm:p-2",
185
+ style: H,
186
+ ...j(),
187
+ children: n
188
+ }
189
+ )
190
+ }
191
+ ) }) })
192
+ }
193
+ )
194
+ ] });
195
+ }
196
+ const oe = Ne.cloneElement(
56
197
  e,
57
198
  {
58
- ...q,
59
- "aria-label": R,
60
- "data-open": n ? "" : void 0,
61
- "data-focus-inside": w ? "" : void 0,
62
- ref: ie([g.setReference, T.ref, P]),
63
- ..._(
64
- j.getItemProps({
65
- ...k
199
+ ...ne,
200
+ "aria-label": d,
201
+ "data-open": o ? "" : void 0,
202
+ "data-focus-inside": C ? "" : void 0,
203
+ ref: O,
204
+ ...B(
205
+ a.getItemProps({
206
+ ...h
66
207
  })
67
208
  )
68
209
  }
69
210
  );
70
- return v(() => {
71
- if (!o)
72
- return;
73
- function a() {
74
- d(!1), l?.(!1);
75
- }
76
- return o.events.on("click", a), () => {
77
- o.events.off("click", a);
78
- };
79
- }, [o, l]), v(() => {
80
- n && o && o.events.emit("menuopen", { nodeId: r });
81
- }, [o, n, r]), /* @__PURE__ */ G(ae, { id: r, children: [
82
- z,
83
- /* @__PURE__ */ s(
84
- F.Provider,
211
+ return /* @__PURE__ */ b(D, { id: u, children: [
212
+ oe,
213
+ /* @__PURE__ */ t(
214
+ R.Provider,
85
215
  {
86
216
  value: {
87
- activeIndex: c,
88
- setActiveIndex: u,
89
- getItemProps: U,
90
- setHasFocusInside: E,
91
- isOpen: n
217
+ activeIndex: v,
218
+ setActiveIndex: g,
219
+ getItemProps: z,
220
+ setHasFocusInside: F,
221
+ isOpen: o,
222
+ allowHover: m,
223
+ parent: a
92
224
  },
93
- children: /* @__PURE__ */ s(re, { elementsRef: p, labelsRef: m, children: n && /* @__PURE__ */ s(le, { children: /* @__PURE__ */ s(
94
- ce,
225
+ children: /* @__PURE__ */ t(S, { elementsRef: N, labelsRef: M, children: o && /* @__PURE__ */ t(_, { children: /* @__PURE__ */ t(
226
+ K,
95
227
  {
96
- context: i,
228
+ context: l,
97
229
  modal: !1,
98
230
  initialFocus: 0,
99
231
  returnFocus: !0,
100
- children: /* @__PURE__ */ s(
232
+ children: /* @__PURE__ */ t(
101
233
  "div",
102
234
  {
103
- ref: g.setFloating,
235
+ ref: E.setFloating,
104
236
  className: "rounded-md bg-surface-light shadow-sm shadow-border-dark outline-hidden p-4 sm:p-2",
105
- style: B,
106
- ...H(),
107
- children: t
237
+ style: H,
238
+ ...j(),
239
+ children: n
108
240
  }
109
241
  )
110
242
  }
@@ -113,9 +245,9 @@ const I = (e) => {
113
245
  )
114
246
  ] });
115
247
  }
116
- ), pe = x((e, t) => /* @__PURE__ */ s(ue, { children: /* @__PURE__ */ s(N, { ...e, ref: t }) }));
117
- pe.displayName = "Menu";
118
- N.displayName = "MenuComponent";
248
+ ), Pe = q((e, n) => W() === null ? /* @__PURE__ */ t(xe, { children: /* @__PURE__ */ t(k, { ...e, ref: n }) }) : /* @__PURE__ */ t(k, { ...e, ref: n }));
249
+ Pe.displayName = "Menu";
250
+ k.displayName = "MenuComponent";
119
251
  export {
120
- pe as Menu
252
+ Pe as Menu
121
253
  };
@@ -7,7 +7,9 @@ const t = e.createContext({
7
7
  },
8
8
  setHasFocusInside: () => {
9
9
  },
10
- isOpen: !1
10
+ isOpen: !1,
11
+ allowHover: !1,
12
+ parent: null
11
13
  });
12
14
  export {
13
15
  t as MenuContext
@@ -1,66 +1,24 @@
1
- import { jsxs as p, jsx as o } from "react/jsx-runtime";
2
- import { useListItem as h, useFloatingTree as N, useMergeRefs as g } from "@floating-ui/react";
3
- import u from "clsx";
4
- import * as I from "react";
5
- import { MenuContext as v } from "./MenuContext.js";
6
- const C = ({
7
- children: e,
8
- fill: t,
9
- viewBox: s,
10
- className: l,
11
- defaultViewBox: i,
12
- size: m,
13
- title: n,
14
- semantic: r = !1,
15
- ...d
16
- }) => {
17
- const a = u(m, l);
18
- return /* @__PURE__ */ p(
19
- "svg",
20
- {
21
- xmlns: "http://www.w3.org/2000/svg",
22
- className: a,
23
- viewBox: s || i,
24
- fill: t || "currentColor",
25
- role: "img",
26
- "aria-hidden": !r,
27
- focusable: !1,
28
- ...d,
29
- children: [
30
- n && r && /* @__PURE__ */ o("title", { children: n }),
31
- e
32
- ]
33
- }
34
- );
35
- };
36
- /*!
37
- @versini/ui-svgicon v4.2.1
38
- ยฉ 2025 gizmette.com
39
- */
40
- try {
41
- window.__VERSINI_UI_SVGICON__ || (window.__VERSINI_UI_SVGICON__ = {
42
- version: "4.2.1",
43
- buildTime: "08/27/2025 08:27 AM EDT",
44
- homepage: "https://github.com/aversini/ui-components",
45
- license: "MIT"
46
- });
47
- } catch {
48
- }
49
- const M = ({
1
+ import { jsxs as x, jsx as o } from "react/jsx-runtime";
2
+ import { useListItem as N, useFloatingTree as w, useMergeRefs as y } from "@floating-ui/react";
3
+ import { I as b } from "../../chunks/index.dzHBmDC4.js";
4
+ import l from "clsx";
5
+ import * as p from "react";
6
+ import { MenuContext as M } from "./MenuContext.js";
7
+ const g = ({
50
8
  className: e,
51
9
  viewBox: t,
52
10
  title: s,
53
- monotone: l,
54
- ...i
55
- }) => /* @__PURE__ */ p(
56
- C,
11
+ monotone: n,
12
+ ...r
13
+ }) => /* @__PURE__ */ x(
14
+ b,
57
15
  {
58
16
  defaultViewBox: "0 0 448 512",
59
17
  size: "size-5",
60
18
  viewBox: t,
61
19
  className: e,
62
- title: s || "Copied",
63
- ...i,
20
+ title: s || "Checked",
21
+ ...r,
64
22
  children: [
65
23
  /* @__PURE__ */ o(
66
24
  "path",
@@ -72,96 +30,106 @@ const M = ({
72
30
  /* @__PURE__ */ o("path", { d: "M337 175c9.4 9.4 9.4 24.6 0 33.9L209 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L303 175c9.4-9.4 24.6-9.4 33.9 0z" })
73
31
  ]
74
32
  }
75
- );
76
- /*!
77
- @versini/ui-icons v4.11.0
78
- ยฉ 2025 gizmette.com
79
- */
80
- try {
81
- window.__VERSINI_UI_ICONS__ || (window.__VERSINI_UI_ICONS__ = {
82
- version: "4.11.0",
83
- buildTime: "08/27/2025 08:28 AM EDT",
84
- homepage: "https://github.com/aversini/ui-components",
85
- license: "MIT"
86
- });
87
- } catch {
88
- }
89
- const S = I.forwardRef(
33
+ ), k = ({
34
+ className: e,
35
+ viewBox: t,
36
+ title: s,
37
+ monotone: n,
38
+ ...r
39
+ }) => /* @__PURE__ */ o(
40
+ b,
41
+ {
42
+ defaultViewBox: "0 0 448 512",
43
+ size: "size-5",
44
+ viewBox: t,
45
+ className: e,
46
+ title: s || "UnChecked",
47
+ ...r,
48
+ children: /* @__PURE__ */ o(
49
+ "path",
50
+ {
51
+ d: "M0 96c0-35.3 28.7-64 64-64h320c35.3 0 64 28.7 64 64v320c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64z",
52
+ opacity: ".4"
53
+ }
54
+ )
55
+ }
56
+ ), I = p.forwardRef(
90
57
  ({
91
58
  label: e,
92
59
  disabled: t,
93
60
  icon: s,
94
- raw: l = !1,
95
- children: i,
96
- ignoreClick: m = !1,
97
- selected: n = !1,
98
- ...r
99
- }, d) => {
100
- let a = "";
101
- const f = I.useContext(v), _ = h({ label: t ? null : e }), b = N(), w = g([_.ref, d]);
102
- if (l && i)
61
+ raw: n = !1,
62
+ children: r,
63
+ ignoreClick: u = !1,
64
+ selected: i,
65
+ ...m
66
+ }, h) => {
67
+ let d = "";
68
+ const a = p.useContext(M), C = N({ label: t ? null : e }), f = w(), v = y([C.ref, h]);
69
+ if (n && r)
103
70
  return /* @__PURE__ */ o(
104
71
  "div",
105
72
  {
106
73
  role: "menuitem",
107
- ...f.getItemProps({
74
+ ...a.getItemProps({
108
75
  onClick(c) {
109
- m || (r.onClick?.(c), b?.events.emit("click"));
76
+ u || (m.onClick?.(c), f?.events.emit("click"));
110
77
  }
111
78
  }),
112
- children: i
79
+ children: r
113
80
  }
114
81
  );
115
- s && (a = "pl-2");
116
- const x = u(
82
+ s && (d = "pl-2");
83
+ const z = l(
117
84
  "items-center flex-row",
118
85
  "m-0 first:mt-0 mt-2 sm:mt-1 px-2 py-1 flex w-full 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",
119
86
  {
120
- "bg-none": !t && !n
87
+ "bg-none": !t && !i
121
88
  }
122
89
  );
123
- return /* @__PURE__ */ p(
90
+ return /* @__PURE__ */ x(
124
91
  "button",
125
92
  {
126
- ...r,
127
- ref: w,
93
+ ...m,
94
+ ref: v,
128
95
  role: "menuitem",
129
- className: x,
96
+ className: z,
130
97
  tabIndex: 0,
131
98
  disabled: t,
132
- ...f.getItemProps({
99
+ ...a.getItemProps({
133
100
  onClick(c) {
134
- m || (r.onClick?.(c), b?.events.emit("click"));
101
+ u || (m.onClick?.(c), f?.events.emit("click"));
135
102
  },
136
103
  onFocus(c) {
137
- r.onFocus?.(c), f.setHasFocusInside(!0);
104
+ m.onFocus?.(c), a.setHasFocusInside(!0);
138
105
  }
139
106
  }),
140
107
  children: [
141
- n && /* @__PURE__ */ o(M, { className: "mr-2", size: "size-4" }),
108
+ i === !0 && /* @__PURE__ */ o(g, { className: "text-copy-success mr-2", size: "size-4" }),
109
+ i === !1 && /* @__PURE__ */ o(k, { className: "text-copy-medium mr-2", size: "size-4" }),
142
110
  s,
143
- e && /* @__PURE__ */ o("span", { className: a, children: e })
111
+ e && /* @__PURE__ */ o("span", { className: d, children: e })
144
112
  ]
145
113
  }
146
114
  );
147
115
  }
148
116
  );
149
- S.displayName = "MenuItem";
150
- const T = ({ className: e, ...t }) => {
151
- const s = u(e, "my-1 border-t border-border-medium");
117
+ I.displayName = "MenuItem";
118
+ const V = ({ className: e, ...t }) => {
119
+ const s = l(e, "my-1 border-t border-border-medium");
152
120
  return /* @__PURE__ */ o("div", { className: s, ...t });
153
- }, k = ({
121
+ }, j = ({
154
122
  className: e,
155
123
  ...t
156
124
  }) => {
157
- const s = u(
125
+ const s = l(
158
126
  e,
159
127
  "pt-1 mb-2 text-sm text-copy-dark border-b border-border-medium"
160
128
  );
161
129
  return /* @__PURE__ */ o("div", { className: s, ...t });
162
130
  };
163
131
  export {
164
- k as MenuGroupLabel,
165
- S as MenuItem,
166
- T as MenuSeparator
132
+ j as MenuGroupLabel,
133
+ I as MenuItem,
134
+ V as MenuSeparator
167
135
  };
package/dist/index.d.ts CHANGED
@@ -6,8 +6,9 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
6
6
  type MenuProps = {
7
7
  /**
8
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).
9
10
  */
10
- trigger: React__default.ReactNode;
11
+ trigger?: React__default.ReactNode;
11
12
  /**
12
13
  * The children to render.
13
14
  */
@@ -27,7 +28,8 @@ type MenuProps = {
27
28
  */
28
29
  mode?: "dark" | "light" | "system" | "alt-system";
29
30
  /**
30
- * The label to use for the menu button.
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.
31
33
  */
32
34
  label?: string;
33
35
  /**
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { Menu as _ } from "./components/Menu/Menu.js";
2
2
  import { MenuGroupLabel as n, MenuItem as t, MenuSeparator as u } from "./components/Menu/MenuItem.js";
3
3
  /*!
4
- @versini/ui-menu v5.0.1
4
+ @versini/ui-menu v5.1.0
5
5
  ยฉ 2025 gizmette.com
6
6
  */
7
7
  try {
8
8
  window.__VERSINI_UI_MENU__ || (window.__VERSINI_UI_MENU__ = {
9
- version: "5.0.1",
10
- buildTime: "09/01/2025 04:02 PM EDT",
9
+ version: "5.1.0",
10
+ buildTime: "10/19/2025 04:18 PM EDT",
11
11
  homepage: "https://github.com/aversini/ui-components",
12
12
  license: "MIT"
13
13
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@versini/ui-menu",
3
- "version": "5.0.1",
3
+ "version": "5.1.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "publishConfig": {
@@ -36,23 +36,19 @@
36
36
  "test:watch": "vitest",
37
37
  "test": "vitest run"
38
38
  },
39
- "peerDependencies": {
40
- "react": "^19.1.0",
41
- "react-dom": "^19.1.0"
42
- },
43
39
  "devDependencies": {
44
- "@testing-library/jest-dom": "6.8.0",
45
- "@versini/ui-types": "6.0.1"
40
+ "@testing-library/jest-dom": "6.9.1",
41
+ "@versini/ui-types": "6.0.2"
46
42
  },
47
43
  "dependencies": {
48
44
  "@floating-ui/react": "0.27.16",
49
- "@tailwindcss/typography": "0.5.16",
50
- "@versini/ui-icons": "4.11.0",
45
+ "@tailwindcss/typography": "0.5.19",
46
+ "@versini/ui-icons": "4.13.0",
51
47
  "clsx": "2.1.1",
52
- "tailwindcss": "4.1.12"
48
+ "tailwindcss": "4.1.14"
53
49
  },
54
50
  "sideEffects": [
55
51
  "**/*.css"
56
52
  ],
57
- "gitHead": "dcc216644c8c3e7d43a49ea655a22aed21fa4b83"
53
+ "gitHead": "5e38069da8dfd7a151bb67950a96d193b50a0b00"
58
54
  }