@versini/ui-menu 5.3.4 โ 6.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 +71 -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 the now deprecated `@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,9 @@ function App() {
|
|
|
51
52
|
</ButtonIcon>
|
|
52
53
|
}
|
|
53
54
|
>
|
|
54
|
-
<MenuItem label="Profile"
|
|
55
|
-
<MenuItem label="Settings"
|
|
56
|
-
<MenuItem label="Logout"
|
|
55
|
+
<MenuItem label="Profile" onSelect={() => console.info("Profile")} />
|
|
56
|
+
<MenuItem label="Settings" onSelect={() => console.info("Settings")} />
|
|
57
|
+
<MenuItem label="Logout" onSelect={() => console.info("Logout")} />
|
|
57
58
|
</Menu>
|
|
58
59
|
);
|
|
59
60
|
}
|
|
@@ -64,7 +65,7 @@ function App() {
|
|
|
64
65
|
### Menu with Icons & Selection
|
|
65
66
|
|
|
66
67
|
```tsx
|
|
67
|
-
import { Menu, MenuItem } from "@versini/ui-menu";
|
|
68
|
+
import { Menu, MenuItem, MenuSeparator } from "@versini/ui-menu";
|
|
68
69
|
import { ButtonIcon } from "@versini/ui-button";
|
|
69
70
|
import {
|
|
70
71
|
IconMenu,
|
|
@@ -83,23 +84,23 @@ function AccountMenu() {
|
|
|
83
84
|
<IconMenu />
|
|
84
85
|
</ButtonIcon>
|
|
85
86
|
}
|
|
86
|
-
onOpenChange={(o) => console.
|
|
87
|
+
onOpenChange={(o) => console.info("open?", o)}
|
|
87
88
|
>
|
|
88
89
|
<MenuItem
|
|
89
90
|
label="Profile"
|
|
90
91
|
icon={<IconUser />}
|
|
91
|
-
|
|
92
|
+
onSelect={() => setLast("profile")}
|
|
92
93
|
/>
|
|
93
94
|
<MenuItem
|
|
94
95
|
label="Settings"
|
|
95
96
|
icon={<IconSettings />}
|
|
96
|
-
|
|
97
|
+
onSelect={() => setLast("settings")}
|
|
97
98
|
/>
|
|
98
99
|
<MenuSeparator />
|
|
99
100
|
<MenuItem
|
|
100
101
|
label="Logout"
|
|
101
102
|
icon={<IconLogout />}
|
|
102
|
-
|
|
103
|
+
onSelect={() => setLast("logout")}
|
|
103
104
|
/>
|
|
104
105
|
</Menu>
|
|
105
106
|
);
|
|
@@ -127,10 +128,10 @@ function AccountMenu() {
|
|
|
127
128
|
|
|
128
129
|
### Nested Sub-menus
|
|
129
130
|
|
|
130
|
-
Create hierarchical menus
|
|
131
|
+
Create hierarchical menus using `MenuSub`:
|
|
131
132
|
|
|
132
133
|
```tsx
|
|
133
|
-
import { Menu, MenuItem, MenuGroupLabel } from "@versini/ui-menu";
|
|
134
|
+
import { Menu, MenuItem, MenuSub, MenuGroupLabel } from "@versini/ui-menu";
|
|
134
135
|
import { ButtonIcon } from "@versini/ui-button";
|
|
135
136
|
import { IconSettings, IconOpenAI, IconAnthropic } from "@versini/ui-icons";
|
|
136
137
|
|
|
@@ -148,22 +149,22 @@ function SettingsMenu() {
|
|
|
148
149
|
<MenuItem label="Profile" />
|
|
149
150
|
<MenuItem label="Preferences" />
|
|
150
151
|
|
|
151
|
-
{/* Nested sub-menu */}
|
|
152
|
-
<
|
|
153
|
-
<MenuGroupLabel>Engines</MenuGroupLabel>
|
|
152
|
+
{/* Nested sub-menu with icon */}
|
|
153
|
+
<MenuSub label="AI Settings" icon={<IconSettings />}>
|
|
154
|
+
<MenuGroupLabel icon={<IconSettings />}>Engines</MenuGroupLabel>
|
|
154
155
|
<MenuItem
|
|
155
156
|
label="OpenAI"
|
|
156
157
|
icon={<IconOpenAI />}
|
|
157
158
|
selected={engine === "openai"}
|
|
158
|
-
|
|
159
|
+
onSelect={() => setEngine("openai")}
|
|
159
160
|
/>
|
|
160
161
|
<MenuItem
|
|
161
162
|
label="Anthropic"
|
|
162
163
|
icon={<IconAnthropic />}
|
|
163
164
|
selected={engine === "anthropic"}
|
|
164
|
-
|
|
165
|
+
onSelect={() => setEngine("anthropic")}
|
|
165
166
|
/>
|
|
166
|
-
</
|
|
167
|
+
</MenuSub>
|
|
167
168
|
|
|
168
169
|
<MenuItem label="About" />
|
|
169
170
|
</Menu>
|
|
@@ -174,7 +175,7 @@ function SettingsMenu() {
|
|
|
174
175
|
**Features of nested sub-menus:**
|
|
175
176
|
|
|
176
177
|
- Automatically positioned to the right (or left if no space)
|
|
177
|
-
- Visual chevron indicator
|
|
178
|
+
- Visual chevron indicator shows expandable items
|
|
178
179
|
- Hover or click to open sub-menus
|
|
179
180
|
- Smart positioning adjusts for viewport constraints
|
|
180
181
|
- Keyboard navigation works across all levels
|
|
@@ -184,38 +185,50 @@ function SettingsMenu() {
|
|
|
184
185
|
|
|
185
186
|
### Menu Props
|
|
186
187
|
|
|
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
|
|
188
|
+
| Prop | Type | Default | Description |
|
|
189
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ---------------------------------------------- |
|
|
190
|
+
| `trigger` | `React.ReactElement` | - | Element used to open the menu (Button, etc.). |
|
|
191
|
+
| `children` | `React.ReactNode` | - | MenuItem, MenuSeparator, etc. |
|
|
192
|
+
| `label` | `string` | `"Open menu"` | Accessible label for the trigger. |
|
|
193
|
+
| `defaultPlacement` | `"bottom"` \| `"bottom-start"` \| `"bottom-end"` \| `"top"` \| `"top-start"` \| `"top-end"` \| `"left"` \| `"left-start"` \| `"right"` \| etc. | `"bottom-start"` | Initial preferred placement. |
|
|
194
|
+
| `mode` | `"dark"` \| `"light"` \| `"system"` \| `"alt-system"` | `"system"` | Color mode of trigger (when using UI buttons). |
|
|
195
|
+
| `focusMode` | `"dark"` \| `"light"` \| `"system"` \| `"alt-system"` | `"system"` | Focus ring thematic mode. |
|
|
196
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Called when menu opens or closes. |
|
|
197
|
+
| `sideOffset` | `number` | `10` | Offset distance from the trigger element. |
|
|
203
198
|
|
|
204
199
|
### MenuItem Props
|
|
205
200
|
|
|
206
|
-
| Prop
|
|
207
|
-
|
|
|
208
|
-
| `label`
|
|
209
|
-
| `disabled`
|
|
210
|
-
| `icon`
|
|
211
|
-
| `raw`
|
|
212
|
-
| `ignoreClick`
|
|
213
|
-
| `selected`
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
###
|
|
220
|
-
|
|
221
|
-
|
|
201
|
+
| Prop | Type | Default | Description |
|
|
202
|
+
| -------------- | ------------------------ | ----------- | --------------------------------------------- |
|
|
203
|
+
| `label` | `string` | - | The label to display for the menu item. |
|
|
204
|
+
| `disabled` | `boolean` | `false` | Whether the menu item is disabled. |
|
|
205
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
206
|
+
| `raw` | `boolean` | `false` | Disable internal styling for custom content. |
|
|
207
|
+
| `ignoreClick` | `boolean` | `false` | Prevent menu from closing when item selected. |
|
|
208
|
+
| `selected` | `boolean` | `undefined` | Show selected/unselected indicator. |
|
|
209
|
+
| `onSelect` | `(event: Event) => void` | - | Callback fired when the item is selected. |
|
|
210
|
+
| `onClick` | `(event) => void` | - | Optional click handler. |
|
|
211
|
+
| `onFocus` | `(event) => void` | - | Optional focus handler. |
|
|
212
|
+
| `onMouseEnter` | `(event) => void` | - | Optional mouse enter handler. |
|
|
213
|
+
|
|
214
|
+
### MenuSub Props
|
|
215
|
+
|
|
216
|
+
| Prop | Type | Default | Description |
|
|
217
|
+
| ------------ | ----------------- | ------- | ----------------------------------------- |
|
|
218
|
+
| `label` | `string` | - | The label for the sub-menu trigger. |
|
|
219
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
220
|
+
| `children` | `React.ReactNode` | - | Items to render inside sub-menu. |
|
|
221
|
+
| `disabled` | `boolean` | `false` | Whether the sub-menu is disabled. |
|
|
222
|
+
| `sideOffset` | `number` | `14` | Offset from sub-menu trigger. |
|
|
223
|
+
|
|
224
|
+
### MenuSeparator Props
|
|
225
|
+
|
|
226
|
+
Standard `React.HTMLAttributes<HTMLDivElement>` - use `className` for custom styling.
|
|
227
|
+
|
|
228
|
+
### MenuGroupLabel Props
|
|
229
|
+
|
|
230
|
+
| Prop | Type | Default | Description |
|
|
231
|
+
| ----------- | ----------------- | ------- | ----------------------------------------- |
|
|
232
|
+
| `icon` | `React.ReactNode` | - | Icon to display on the left of the label. |
|
|
233
|
+
| `children` | `React.ReactNode` | - | The label content. |
|
|
234
|
+
| `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 { }
|