@versini/ui-menu 5.3.3 โ 6.0.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 +91 -58
- package/dist/index.d.ts +86 -17
- package/dist/index.js +821 -348
- package/package.json +7 -8
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@versini/ui-menu)
|
|
4
4
|
>)
|
|
5
5
|
|
|
6
|
-
> Accessible and
|
|
6
|
+
> Accessible and lightweight React menu components built with TypeScript and TailwindCSS โ no external UI library required.
|
|
7
7
|
|
|
8
|
-
The Menu package provides dropdown menus
|
|
8
|
+
The Menu package provides dropdown menus with full keyboard navigation, focus management, theming for triggers, and composable items / separators. It offers the same capabilities as `@versini/ui-dropdown` but with a smaller footprint by replacing Radix UI with a custom implementation using the native Popover API.
|
|
9
9
|
|
|
10
10
|
## Table of Contents
|
|
11
11
|
|
|
@@ -17,13 +17,14 @@ The Menu package provides dropdown menus and navigation components with full key
|
|
|
17
17
|
|
|
18
18
|
## Features
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
20
|
+
- **Composable**: `Menu`, `MenuItem`, `MenuSeparator`, `MenuGroupLabel`, `MenuSub`
|
|
21
|
+
- **Nested Sub-menus**: Support for multi-level menu hierarchies with automatic positioning
|
|
22
|
+
- **Accessible**: Built with ARIA roles & WAI-ARIA menu patterns for robust a11y
|
|
23
|
+
- **Keyboard Support**: Arrow navigation, ESC / click outside to close
|
|
24
|
+
- **Theme & Focus Modes**: Trigger inherits color + separate focus styling
|
|
25
|
+
- **Smart Positioning**: Auto flip / shift to remain within viewport
|
|
26
|
+
- **Type Safe**: Strongly typed props with TypeScript
|
|
27
|
+
- **Lightweight**: No Radix UI dependency โ uses the native Popover API
|
|
27
28
|
|
|
28
29
|
## Installation
|
|
29
30
|
|
|
@@ -51,9 +52,18 @@ function App() {
|
|
|
51
52
|
</ButtonIcon>
|
|
52
53
|
}
|
|
53
54
|
>
|
|
54
|
-
<MenuItem
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
<MenuItem
|
|
56
|
+
label="Profile"
|
|
57
|
+
onSelect={() => console.info("Profile")}
|
|
58
|
+
/>
|
|
59
|
+
<MenuItem
|
|
60
|
+
label="Settings"
|
|
61
|
+
onSelect={() => console.info("Settings")}
|
|
62
|
+
/>
|
|
63
|
+
<MenuItem
|
|
64
|
+
label="Logout"
|
|
65
|
+
onSelect={() => console.info("Logout")}
|
|
66
|
+
/>
|
|
57
67
|
</Menu>
|
|
58
68
|
);
|
|
59
69
|
}
|
|
@@ -64,7 +74,11 @@ function App() {
|
|
|
64
74
|
### Menu with Icons & Selection
|
|
65
75
|
|
|
66
76
|
```tsx
|
|
67
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
Menu,
|
|
79
|
+
MenuItem,
|
|
80
|
+
MenuSeparator
|
|
81
|
+
} from "@versini/ui-menu";
|
|
68
82
|
import { ButtonIcon } from "@versini/ui-button";
|
|
69
83
|
import {
|
|
70
84
|
IconMenu,
|
|
@@ -83,23 +97,23 @@ function AccountMenu() {
|
|
|
83
97
|
<IconMenu />
|
|
84
98
|
</ButtonIcon>
|
|
85
99
|
}
|
|
86
|
-
onOpenChange={(o) => console.
|
|
100
|
+
onOpenChange={(o) => console.info("open?", o)}
|
|
87
101
|
>
|
|
88
102
|
<MenuItem
|
|
89
103
|
label="Profile"
|
|
90
104
|
icon={<IconUser />}
|
|
91
|
-
|
|
105
|
+
onSelect={() => setLast("profile")}
|
|
92
106
|
/>
|
|
93
107
|
<MenuItem
|
|
94
108
|
label="Settings"
|
|
95
109
|
icon={<IconSettings />}
|
|
96
|
-
|
|
110
|
+
onSelect={() => setLast("settings")}
|
|
97
111
|
/>
|
|
98
112
|
<MenuSeparator />
|
|
99
113
|
<MenuItem
|
|
100
114
|
label="Logout"
|
|
101
115
|
icon={<IconLogout />}
|
|
102
|
-
|
|
116
|
+
onSelect={() => setLast("logout")}
|
|
103
117
|
/>
|
|
104
118
|
</Menu>
|
|
105
119
|
);
|
|
@@ -127,10 +141,15 @@ function AccountMenu() {
|
|
|
127
141
|
|
|
128
142
|
### Nested Sub-menus
|
|
129
143
|
|
|
130
|
-
Create hierarchical menus
|
|
144
|
+
Create hierarchical menus using `MenuSub`:
|
|
131
145
|
|
|
132
146
|
```tsx
|
|
133
|
-
import {
|
|
147
|
+
import {
|
|
148
|
+
Menu,
|
|
149
|
+
MenuItem,
|
|
150
|
+
MenuSub,
|
|
151
|
+
MenuGroupLabel
|
|
152
|
+
} from "@versini/ui-menu";
|
|
134
153
|
import { ButtonIcon } from "@versini/ui-button";
|
|
135
154
|
import { IconSettings, IconOpenAI, IconAnthropic } from "@versini/ui-icons";
|
|
136
155
|
|
|
@@ -148,22 +167,24 @@ function SettingsMenu() {
|
|
|
148
167
|
<MenuItem label="Profile" />
|
|
149
168
|
<MenuItem label="Preferences" />
|
|
150
169
|
|
|
151
|
-
{/* Nested sub-menu */}
|
|
152
|
-
<
|
|
153
|
-
<MenuGroupLabel>
|
|
170
|
+
{/* Nested sub-menu with icon */}
|
|
171
|
+
<MenuSub label="AI Settings" icon={<IconSettings />}>
|
|
172
|
+
<MenuGroupLabel icon={<IconSettings />}>
|
|
173
|
+
Engines
|
|
174
|
+
</MenuGroupLabel>
|
|
154
175
|
<MenuItem
|
|
155
176
|
label="OpenAI"
|
|
156
177
|
icon={<IconOpenAI />}
|
|
157
178
|
selected={engine === "openai"}
|
|
158
|
-
|
|
179
|
+
onSelect={() => setEngine("openai")}
|
|
159
180
|
/>
|
|
160
181
|
<MenuItem
|
|
161
182
|
label="Anthropic"
|
|
162
183
|
icon={<IconAnthropic />}
|
|
163
184
|
selected={engine === "anthropic"}
|
|
164
|
-
|
|
185
|
+
onSelect={() => setEngine("anthropic")}
|
|
165
186
|
/>
|
|
166
|
-
</
|
|
187
|
+
</MenuSub>
|
|
167
188
|
|
|
168
189
|
<MenuItem label="About" />
|
|
169
190
|
</Menu>
|
|
@@ -174,7 +195,7 @@ function SettingsMenu() {
|
|
|
174
195
|
**Features of nested sub-menus:**
|
|
175
196
|
|
|
176
197
|
- Automatically positioned to the right (or left if no space)
|
|
177
|
-
- Visual chevron indicator
|
|
198
|
+
- Visual chevron indicator shows expandable items
|
|
178
199
|
- Hover or click to open sub-menus
|
|
179
200
|
- Smart positioning adjusts for viewport constraints
|
|
180
201
|
- Keyboard navigation works across all levels
|
|
@@ -184,38 +205,50 @@ function SettingsMenu() {
|
|
|
184
205
|
|
|
185
206
|
### Menu Props
|
|
186
207
|
|
|
187
|
-
| Prop | Type
|
|
188
|
-
| ------------------ |
|
|
189
|
-
| `trigger` | `React.
|
|
190
|
-
| `children` | `React.ReactNode`
|
|
191
|
-
| `label` | `string`
|
|
192
|
-
| `defaultPlacement` | `
|
|
193
|
-
| `mode` | `"dark" \| "light" \| "system" \| "alt-system"`
|
|
194
|
-
| `focusMode` | `"dark" \| "light" \| "system" \| "alt-system"`
|
|
195
|
-
| `onOpenChange` | `(open: boolean) => void`
|
|
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
|
|
208
|
+
| Prop | Type | Default | Description |
|
|
209
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ---------------------------------------------- |
|
|
210
|
+
| `trigger` | `React.ReactElement` | - | Element used to open the menu (Button, etc.). |
|
|
211
|
+
| `children` | `React.ReactNode` | - | MenuItem, MenuSeparator, etc. |
|
|
212
|
+
| `label` | `string` | `"Open menu"` | Accessible label for the trigger. |
|
|
213
|
+
| `defaultPlacement` | `"bottom"` \| `"bottom-start"` \| `"bottom-end"` \| `"top"` \| `"top-start"` \| `"top-end"` \| `"left"` \| `"left-start"` \| `"right"` \| etc. | `"bottom-start"` | Initial preferred placement. |
|
|
214
|
+
| `mode` | `"dark"` \| `"light"` \| `"system"` \| `"alt-system"` | `"system"` | Color mode of trigger (when using UI buttons). |
|
|
215
|
+
| `focusMode` | `"dark"` \| `"light"` \| `"system"` \| `"alt-system"` | `"system"` | Focus ring thematic mode. |
|
|
216
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Called when menu opens or closes. |
|
|
217
|
+
| `sideOffset` | `number` | `10` | Offset distance from the trigger element. |
|
|
203
218
|
|
|
204
219
|
### MenuItem Props
|
|
205
220
|
|
|
206
|
-
| Prop
|
|
207
|
-
|
|
|
208
|
-
| `label`
|
|
209
|
-
| `disabled`
|
|
210
|
-
| `icon`
|
|
211
|
-
| `raw`
|
|
212
|
-
| `ignoreClick`
|
|
213
|
-
| `selected`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
###
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
| Prop | Type | Default | Description |
|
|
222
|
+
| -------------- | ------------------------ | ----------- | --------------------------------------------- |
|
|
223
|
+
| `label` | `string` | - | The label to display for the menu item. |
|
|
224
|
+
| `disabled` | `boolean` | `false` | Whether the menu item is disabled. |
|
|
225
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
226
|
+
| `raw` | `boolean` | `false` | Disable internal styling for custom content. |
|
|
227
|
+
| `ignoreClick` | `boolean` | `false` | Prevent menu from closing when item selected. |
|
|
228
|
+
| `selected` | `boolean` | `undefined` | Show selected/unselected indicator. |
|
|
229
|
+
| `onSelect` | `(event: Event) => void` | - | Callback fired when the item is selected. |
|
|
230
|
+
| `onClick` | `(event) => void` | - | Optional click handler. |
|
|
231
|
+
| `onFocus` | `(event) => void` | - | Optional focus handler. |
|
|
232
|
+
| `onMouseEnter` | `(event) => void` | - | Optional mouse enter handler. |
|
|
233
|
+
|
|
234
|
+
### MenuSub Props
|
|
235
|
+
|
|
236
|
+
| Prop | Type | Default | Description |
|
|
237
|
+
| ------------ | ----------------- | ------- | ----------------------------------------- |
|
|
238
|
+
| `label` | `string` | - | The label for the sub-menu trigger. |
|
|
239
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
240
|
+
| `children` | `React.ReactNode` | - | Items to render inside sub-menu. |
|
|
241
|
+
| `disabled` | `boolean` | `false` | Whether the sub-menu is disabled. |
|
|
242
|
+
| `sideOffset` | `number` | `14` | Offset from sub-menu trigger. |
|
|
243
|
+
|
|
244
|
+
### MenuSeparator Props
|
|
245
|
+
|
|
246
|
+
Standard `React.HTMLAttributes<HTMLDivElement>` - use `className` for custom styling.
|
|
247
|
+
|
|
248
|
+
### MenuGroupLabel Props
|
|
249
|
+
|
|
250
|
+
| Prop | Type | Default | Description |
|
|
251
|
+
| ----------- | ----------------- | ------- | ----------------------------------------- |
|
|
252
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
253
|
+
| `children` | `React.ReactNode` | - | The label content. |
|
|
254
|
+
| `className` | `string` | - | Custom CSS class for styling. |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
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
2
|
|
|
6
|
-
export declare const Menu:
|
|
3
|
+
export declare const Menu: {
|
|
4
|
+
({ trigger, children, label, defaultPlacement, onOpenChange, mode, focusMode, sideOffset, }: MenuProps): JSX.Element;
|
|
5
|
+
displayName: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export declare const MenuGroupLabel: {
|
|
9
|
+
({ className, icon, children, ...props }: MenuGroupLabelProps): JSX.Element;
|
|
10
|
+
displayName: string;
|
|
11
|
+
};
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
declare type MenuGroupLabelProps = {
|
|
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>;
|
|
9
19
|
|
|
10
|
-
export declare const MenuItem:
|
|
20
|
+
export declare const MenuItem: {
|
|
21
|
+
({ label, disabled, icon, raw, children, ignoreClick, selected, onSelect, onClick, onFocus, onMouseEnter, ...props }: MenuItemProps): JSX.Element;
|
|
22
|
+
displayName: string;
|
|
23
|
+
};
|
|
11
24
|
|
|
12
25
|
declare type MenuItemProps = {
|
|
13
26
|
/**
|
|
@@ -22,12 +35,16 @@ declare type MenuItemProps = {
|
|
|
22
35
|
/**
|
|
23
36
|
* A React component of type Icon to be placed on the left of the label.
|
|
24
37
|
*/
|
|
25
|
-
icon?:
|
|
38
|
+
icon?: React.ReactNode;
|
|
26
39
|
/**
|
|
27
40
|
* Disable internal menu item behavior (click, focus, etc.).
|
|
28
41
|
* @default false
|
|
29
42
|
*/
|
|
30
43
|
raw?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Children to render when using raw mode.
|
|
46
|
+
*/
|
|
47
|
+
children?: React.ReactNode;
|
|
31
48
|
/**
|
|
32
49
|
* Whether or not the menu should close when the menu item is selected.
|
|
33
50
|
* @default false
|
|
@@ -35,26 +52,41 @@ declare type MenuItemProps = {
|
|
|
35
52
|
ignoreClick?: boolean;
|
|
36
53
|
/**
|
|
37
54
|
* Whether or not the menu item is selected.
|
|
38
|
-
* @default
|
|
55
|
+
* @default undefined
|
|
39
56
|
*/
|
|
40
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
|
+
* Optional mouse enter handler.
|
|
72
|
+
*/
|
|
73
|
+
onMouseEnter?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
|
41
74
|
};
|
|
42
75
|
|
|
43
76
|
declare type MenuProps = {
|
|
44
77
|
/**
|
|
45
78
|
* 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
79
|
*/
|
|
48
|
-
trigger?:
|
|
80
|
+
trigger?: React.ReactElement;
|
|
49
81
|
/**
|
|
50
|
-
* The children to render.
|
|
82
|
+
* The children to render (MenuItem, MenuSeparator, etc.).
|
|
51
83
|
*/
|
|
52
|
-
children?:
|
|
84
|
+
children?: React.ReactNode;
|
|
53
85
|
/**
|
|
54
86
|
* The default location of the popup.
|
|
55
87
|
* @default "bottom-start"
|
|
56
88
|
*/
|
|
57
|
-
defaultPlacement?:
|
|
89
|
+
defaultPlacement?: "bottom" | "bottom-start" | "bottom-end" | "top" | "top-start" | "top-end" | "left" | "left-start" | "left-end" | "right" | "right-start" | "right-end";
|
|
58
90
|
/**
|
|
59
91
|
* The type of focus for the Button. This will change the color
|
|
60
92
|
* of the focus ring around the Button.
|
|
@@ -65,8 +97,7 @@ declare type MenuProps = {
|
|
|
65
97
|
*/
|
|
66
98
|
mode?: "dark" | "light" | "system" | "alt-system";
|
|
67
99
|
/**
|
|
68
|
-
* The label to use for the menu button
|
|
69
|
-
* When used without a trigger, this creates a nested sub-menu.
|
|
100
|
+
* The label to use for the menu button.
|
|
70
101
|
*/
|
|
71
102
|
label?: string;
|
|
72
103
|
/**
|
|
@@ -74,10 +105,48 @@ declare type MenuProps = {
|
|
|
74
105
|
* @param open whether or not the menu is open
|
|
75
106
|
*/
|
|
76
107
|
onOpenChange?: (open: boolean) => void;
|
|
108
|
+
/**
|
|
109
|
+
* The offset distance from the trigger element.
|
|
110
|
+
* @default 10
|
|
111
|
+
*/
|
|
112
|
+
sideOffset?: number;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export declare const MenuSeparator: {
|
|
116
|
+
({ className, ...props }: MenuSeparatorProps): JSX.Element;
|
|
117
|
+
displayName: string;
|
|
77
118
|
};
|
|
78
119
|
|
|
79
|
-
|
|
120
|
+
declare type MenuSeparatorProps = React.HTMLAttributes<HTMLDivElement>;
|
|
121
|
+
|
|
122
|
+
export declare const MenuSub: {
|
|
123
|
+
({ label, icon, children, disabled, sideOffset, }: MenuSubProps): JSX.Element;
|
|
124
|
+
displayName: string;
|
|
125
|
+
};
|
|
80
126
|
|
|
81
|
-
declare type
|
|
127
|
+
declare type MenuSubProps = {
|
|
128
|
+
/**
|
|
129
|
+
* The label for the sub-menu trigger.
|
|
130
|
+
*/
|
|
131
|
+
label: string;
|
|
132
|
+
/**
|
|
133
|
+
* A React component of type Icon to be placed on the left of the label.
|
|
134
|
+
*/
|
|
135
|
+
icon?: React.ReactNode;
|
|
136
|
+
/**
|
|
137
|
+
* The children to render inside the sub-menu.
|
|
138
|
+
*/
|
|
139
|
+
children?: React.ReactNode;
|
|
140
|
+
/**
|
|
141
|
+
* Whether the sub-menu trigger is disabled.
|
|
142
|
+
* @default false
|
|
143
|
+
*/
|
|
144
|
+
disabled?: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* The offset distance from the sub-menu trigger.
|
|
147
|
+
* @default 14
|
|
148
|
+
*/
|
|
149
|
+
sideOffset?: number;
|
|
150
|
+
};
|
|
82
151
|
|
|
83
152
|
export { }
|