@fragments-sdk/ui 0.12.0 → 0.13.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/components/Accordion/index.cjs +11 -4
- package/dist/components/Accordion/index.cjs.map +1 -1
- package/dist/components/Accordion/index.d.ts +3 -3
- package/dist/components/Accordion/index.d.ts.map +1 -1
- package/dist/components/Accordion/index.js +11 -4
- package/dist/components/Accordion/index.js.map +1 -1
- package/dist/components/Collapsible/index.cjs +45 -10
- package/dist/components/Collapsible/index.cjs.map +1 -1
- package/dist/components/Collapsible/index.d.ts +6 -12
- package/dist/components/Collapsible/index.d.ts.map +1 -1
- package/dist/components/Collapsible/index.js +45 -10
- package/dist/components/Collapsible/index.js.map +1 -1
- package/dist/components/Combobox/index.cjs +18 -9
- package/dist/components/Combobox/index.cjs.map +1 -1
- package/dist/components/Combobox/index.d.ts +8 -12
- package/dist/components/Combobox/index.d.ts.map +1 -1
- package/dist/components/Combobox/index.js +18 -9
- package/dist/components/Combobox/index.js.map +1 -1
- package/dist/components/Command/index.cjs +54 -21
- package/dist/components/Command/index.cjs.map +1 -1
- package/dist/components/Command/index.d.ts +2 -2
- package/dist/components/Command/index.d.ts.map +1 -1
- package/dist/components/Command/index.js +54 -21
- package/dist/components/Command/index.js.map +1 -1
- package/dist/components/DataTable/index.cjs +13 -1
- package/dist/components/DataTable/index.cjs.map +1 -1
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/index.js +13 -1
- package/dist/components/DataTable/index.js.map +1 -1
- package/dist/components/DatePicker/index.d.ts +2 -3
- package/dist/components/DatePicker/index.d.ts.map +1 -1
- package/dist/components/Dialog/index.cjs +12 -9
- package/dist/components/Dialog/index.cjs.map +1 -1
- package/dist/components/Dialog/index.d.ts +8 -12
- package/dist/components/Dialog/index.d.ts.map +1 -1
- package/dist/components/Dialog/index.js +12 -9
- package/dist/components/Dialog/index.js.map +1 -1
- package/dist/components/Drawer/index.cjs +12 -9
- package/dist/components/Drawer/index.cjs.map +1 -1
- package/dist/components/Drawer/index.d.ts +8 -12
- package/dist/components/Drawer/index.d.ts.map +1 -1
- package/dist/components/Drawer/index.js +12 -9
- package/dist/components/Drawer/index.js.map +1 -1
- package/dist/components/Menu/index.cjs +30 -16
- package/dist/components/Menu/index.cjs.map +1 -1
- package/dist/components/Menu/index.d.ts +17 -25
- package/dist/components/Menu/index.d.ts.map +1 -1
- package/dist/components/Menu/index.js +30 -16
- package/dist/components/Menu/index.js.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenuContext.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenuContext.d.ts +1 -0
- package/dist/components/NavigationMenu/NavigationMenuContext.d.ts.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenuContext.js.map +1 -1
- package/dist/components/NavigationMenu/index.cjs +43 -11
- package/dist/components/NavigationMenu/index.cjs.map +1 -1
- package/dist/components/NavigationMenu/index.d.ts.map +1 -1
- package/dist/components/NavigationMenu/index.js +43 -11
- package/dist/components/NavigationMenu/index.js.map +1 -1
- package/dist/components/NavigationMenu/useNavigationMenu.cjs +2 -0
- package/dist/components/NavigationMenu/useNavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/useNavigationMenu.d.ts +1 -0
- package/dist/components/NavigationMenu/useNavigationMenu.d.ts.map +1 -1
- package/dist/components/NavigationMenu/useNavigationMenu.js +2 -0
- package/dist/components/NavigationMenu/useNavigationMenu.js.map +1 -1
- package/dist/components/Popover/index.cjs +11 -10
- package/dist/components/Popover/index.cjs.map +1 -1
- package/dist/components/Popover/index.d.ts +8 -12
- package/dist/components/Popover/index.d.ts.map +1 -1
- package/dist/components/Popover/index.js +11 -10
- package/dist/components/Popover/index.js.map +1 -1
- package/dist/components/Select/index.cjs +7 -6
- package/dist/components/Select/index.cjs.map +1 -1
- package/dist/components/Select/index.d.ts +6 -9
- package/dist/components/Select/index.d.ts.map +1 -1
- package/dist/components/Select/index.js +7 -6
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/Sidebar/index.cjs +71 -24
- package/dist/components/Sidebar/index.cjs.map +1 -1
- package/dist/components/Sidebar/index.d.ts +21 -33
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +71 -24
- package/dist/components/Sidebar/index.js.map +1 -1
- package/dist/components/Tooltip/index.cjs +12 -6
- package/dist/components/Tooltip/index.cjs.map +1 -1
- package/dist/components/Tooltip/index.d.ts.map +1 -1
- package/dist/components/Tooltip/index.js +12 -6
- package/dist/components/Tooltip/index.js.map +1 -1
- package/dist/datepicker.cjs +24 -10
- package/dist/datepicker.cjs.map +1 -1
- package/dist/datepicker.js +24 -10
- package/dist/datepicker.js.map +1 -1
- package/fragments.json +1 -1
- package/package.json +2 -2
- package/src/components/Accordion/Accordion.test.tsx +33 -0
- package/src/components/Accordion/index.tsx +10 -3
- package/src/components/Collapsible/Collapsible.test.tsx +41 -0
- package/src/components/Collapsible/index.tsx +53 -16
- package/src/components/Combobox/Combobox.test.tsx +55 -0
- package/src/components/Combobox/index.tsx +23 -17
- package/src/components/Command/Command.test.tsx +93 -0
- package/src/components/Command/index.tsx +61 -18
- package/src/components/DataTable/DataTable.test.tsx +11 -2
- package/src/components/DataTable/index.tsx +22 -2
- package/src/components/DatePicker/DatePicker.test.tsx +79 -0
- package/src/components/DatePicker/index.tsx +29 -14
- package/src/components/Dialog/Dialog.test.tsx +23 -0
- package/src/components/Dialog/index.tsx +15 -16
- package/src/components/Drawer/Drawer.test.tsx +27 -0
- package/src/components/Drawer/index.tsx +15 -16
- package/src/components/Menu/index.tsx +35 -30
- package/src/components/NavigationMenu/NavigationMenu.fragment.tsx +1 -1
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +40 -4
- package/src/components/NavigationMenu/NavigationMenuContext.ts +3 -0
- package/src/components/NavigationMenu/index.tsx +49 -13
- package/src/components/NavigationMenu/useNavigationMenu.ts +4 -0
- package/src/components/Popover/Popover.test.tsx +23 -0
- package/src/components/Popover/index.tsx +15 -18
- package/src/components/Select/Select.test.tsx +41 -0
- package/src/components/Select/index.tsx +10 -12
- package/src/components/Sidebar/Sidebar.test.tsx +83 -4
- package/src/components/Sidebar/index.tsx +87 -45
- package/src/components/Tooltip/Tooltip.test.tsx +17 -0
- package/src/components/Tooltip/index.tsx +46 -32
|
@@ -38,24 +38,21 @@ export interface DrawerContentProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
38
38
|
backdrop?: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
export interface DrawerTriggerProps {
|
|
41
|
+
export interface DrawerTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
42
42
|
children: React.ReactNode;
|
|
43
43
|
asChild?: boolean;
|
|
44
|
-
className?: string;
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
export interface DrawerHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
48
47
|
children: React.ReactNode;
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
export interface DrawerTitleProps {
|
|
50
|
+
export interface DrawerTitleProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
|
|
52
51
|
children: React.ReactNode;
|
|
53
|
-
className?: string;
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
export interface DrawerDescriptionProps {
|
|
54
|
+
export interface DrawerDescriptionProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
|
|
57
55
|
children: React.ReactNode;
|
|
58
|
-
className?: string;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
export interface DrawerBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -66,10 +63,9 @@ export interface DrawerFooterProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
66
63
|
children: React.ReactNode;
|
|
67
64
|
}
|
|
68
65
|
|
|
69
|
-
export interface DrawerCloseProps {
|
|
66
|
+
export interface DrawerCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
70
67
|
children?: React.ReactNode;
|
|
71
68
|
asChild?: boolean;
|
|
72
|
-
className?: string;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
71
|
// ============================================
|
|
@@ -123,17 +119,18 @@ function DrawerTrigger({
|
|
|
123
119
|
children,
|
|
124
120
|
asChild,
|
|
125
121
|
className,
|
|
122
|
+
...htmlProps
|
|
126
123
|
}: DrawerTriggerProps) {
|
|
127
124
|
if (asChild) {
|
|
128
125
|
return (
|
|
129
|
-
<BaseDialog.Trigger className={className} render={children as React.ReactElement}>
|
|
126
|
+
<BaseDialog.Trigger {...htmlProps} className={className} render={children as React.ReactElement}>
|
|
130
127
|
{null}
|
|
131
128
|
</BaseDialog.Trigger>
|
|
132
129
|
);
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
return (
|
|
136
|
-
<BaseDialog.Trigger className={className}>
|
|
133
|
+
<BaseDialog.Trigger {...htmlProps} className={className}>
|
|
137
134
|
{children}
|
|
138
135
|
</BaseDialog.Trigger>
|
|
139
136
|
);
|
|
@@ -171,15 +168,15 @@ function DrawerHeader({ children, className, ...htmlProps }: DrawerHeaderProps)
|
|
|
171
168
|
return <div {...htmlProps} className={classes}>{children}</div>;
|
|
172
169
|
}
|
|
173
170
|
|
|
174
|
-
function DrawerTitle({ children, className }: DrawerTitleProps) {
|
|
171
|
+
function DrawerTitle({ children, className, ...htmlProps }: DrawerTitleProps) {
|
|
175
172
|
const classes = [styles.title, className].filter(Boolean).join(' ');
|
|
176
|
-
return <BaseDialog.Title className={classes}>{children}</BaseDialog.Title>;
|
|
173
|
+
return <BaseDialog.Title {...htmlProps} className={classes}>{children}</BaseDialog.Title>;
|
|
177
174
|
}
|
|
178
175
|
|
|
179
|
-
function DrawerDescription({ children, className }: DrawerDescriptionProps) {
|
|
176
|
+
function DrawerDescription({ children, className, ...htmlProps }: DrawerDescriptionProps) {
|
|
180
177
|
const classes = [styles.description, className].filter(Boolean).join(' ');
|
|
181
178
|
return (
|
|
182
|
-
<BaseDialog.Description className={classes}>
|
|
179
|
+
<BaseDialog.Description {...htmlProps} className={classes}>
|
|
183
180
|
{children}
|
|
184
181
|
</BaseDialog.Description>
|
|
185
182
|
);
|
|
@@ -195,10 +192,11 @@ function DrawerFooter({ children, className, ...htmlProps }: DrawerFooterProps)
|
|
|
195
192
|
return <div {...htmlProps} className={classes}>{children}</div>;
|
|
196
193
|
}
|
|
197
194
|
|
|
198
|
-
function DrawerClose({ children, asChild, className }: DrawerCloseProps) {
|
|
195
|
+
function DrawerClose({ children, asChild, className, ...htmlProps }: DrawerCloseProps) {
|
|
199
196
|
if (!children) {
|
|
200
197
|
return (
|
|
201
198
|
<BaseDialog.Close
|
|
199
|
+
{...htmlProps}
|
|
202
200
|
data-drawer-close
|
|
203
201
|
aria-label="Close drawer"
|
|
204
202
|
className={[styles.close, className].filter(Boolean).join(' ')}
|
|
@@ -211,6 +209,7 @@ function DrawerClose({ children, asChild, className }: DrawerCloseProps) {
|
|
|
211
209
|
if (asChild) {
|
|
212
210
|
return (
|
|
213
211
|
<BaseDialog.Close
|
|
212
|
+
{...htmlProps}
|
|
214
213
|
data-drawer-close
|
|
215
214
|
className={className}
|
|
216
215
|
render={children as React.ReactElement}
|
|
@@ -221,7 +220,7 @@ function DrawerClose({ children, asChild, className }: DrawerCloseProps) {
|
|
|
221
220
|
}
|
|
222
221
|
|
|
223
222
|
return (
|
|
224
|
-
<BaseDialog.Close data-drawer-close className={className}>
|
|
223
|
+
<BaseDialog.Close {...htmlProps} data-drawer-close className={className}>
|
|
225
224
|
{children}
|
|
226
225
|
</BaseDialog.Close>
|
|
227
226
|
);
|
|
@@ -16,10 +16,9 @@ export interface MenuProps {
|
|
|
16
16
|
modal?: boolean;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface MenuTriggerProps {
|
|
19
|
+
export interface MenuTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
20
20
|
children: React.ReactNode;
|
|
21
21
|
asChild?: boolean;
|
|
22
|
-
className?: string;
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
export interface MenuContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -29,25 +28,23 @@ export interface MenuContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
29
28
|
sideOffset?: number;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
export interface MenuItemProps {
|
|
31
|
+
export interface MenuItemProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children' | 'onSelect'> {
|
|
33
32
|
children: React.ReactNode;
|
|
34
33
|
disabled?: boolean;
|
|
35
34
|
danger?: boolean;
|
|
36
|
-
onSelect?: () => void;
|
|
37
|
-
className?: string;
|
|
35
|
+
onSelect?: (...args: any[]) => void;
|
|
38
36
|
icon?: React.ReactNode;
|
|
39
37
|
shortcut?: string;
|
|
40
38
|
/** When passed, renders a check indicator. `true` shows a checkmark, `false` reserves space. */
|
|
41
39
|
checked?: boolean;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
export interface MenuCheckboxItemProps {
|
|
42
|
+
export interface MenuCheckboxItemProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children' | 'onChange'> {
|
|
45
43
|
children: React.ReactNode;
|
|
46
44
|
checked?: boolean;
|
|
47
45
|
defaultChecked?: boolean;
|
|
48
46
|
onCheckedChange?: (checked: boolean) => void;
|
|
49
47
|
disabled?: boolean;
|
|
50
|
-
className?: string;
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
export interface MenuRadioGroupProps {
|
|
@@ -57,26 +54,21 @@ export interface MenuRadioGroupProps {
|
|
|
57
54
|
onValueChange?: (value: string) => void;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
export interface MenuRadioItemProps {
|
|
57
|
+
export interface MenuRadioItemProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
|
|
61
58
|
children: React.ReactNode;
|
|
62
59
|
value: string;
|
|
63
60
|
disabled?: boolean;
|
|
64
|
-
className?: string;
|
|
65
61
|
}
|
|
66
62
|
|
|
67
|
-
export interface MenuGroupProps {
|
|
63
|
+
export interface MenuGroupProps extends React.HTMLAttributes<HTMLElement> {
|
|
68
64
|
children: React.ReactNode;
|
|
69
|
-
className?: string;
|
|
70
65
|
}
|
|
71
66
|
|
|
72
|
-
export interface MenuGroupLabelProps {
|
|
67
|
+
export interface MenuGroupLabelProps extends React.HTMLAttributes<HTMLElement> {
|
|
73
68
|
children: React.ReactNode;
|
|
74
|
-
className?: string;
|
|
75
69
|
}
|
|
76
70
|
|
|
77
|
-
export interface MenuSeparatorProps {
|
|
78
|
-
className?: string;
|
|
79
|
-
}
|
|
71
|
+
export interface MenuSeparatorProps extends React.HTMLAttributes<HTMLElement> {}
|
|
80
72
|
|
|
81
73
|
export interface MenuSubmenuProps {
|
|
82
74
|
children: React.ReactNode;
|
|
@@ -85,10 +77,9 @@ export interface MenuSubmenuProps {
|
|
|
85
77
|
onOpenChange?: (open: boolean) => void;
|
|
86
78
|
}
|
|
87
79
|
|
|
88
|
-
export interface MenuSubmenuTriggerProps {
|
|
80
|
+
export interface MenuSubmenuTriggerProps extends React.HTMLAttributes<HTMLElement> {
|
|
89
81
|
children: React.ReactNode;
|
|
90
82
|
disabled?: boolean;
|
|
91
|
-
className?: string;
|
|
92
83
|
icon?: React.ReactNode;
|
|
93
84
|
}
|
|
94
85
|
|
|
@@ -153,17 +144,17 @@ function MenuRoot({
|
|
|
153
144
|
);
|
|
154
145
|
}
|
|
155
146
|
|
|
156
|
-
function MenuTrigger({ children, asChild, className }: MenuTriggerProps) {
|
|
147
|
+
function MenuTrigger({ children, asChild, className, ...htmlProps }: MenuTriggerProps) {
|
|
157
148
|
if (asChild) {
|
|
158
149
|
return (
|
|
159
|
-
<BaseMenu.Trigger className={className} render={children as React.ReactElement}>
|
|
150
|
+
<BaseMenu.Trigger {...htmlProps} className={className} render={children as React.ReactElement}>
|
|
160
151
|
{null}
|
|
161
152
|
</BaseMenu.Trigger>
|
|
162
153
|
);
|
|
163
154
|
}
|
|
164
155
|
|
|
165
156
|
return (
|
|
166
|
-
<BaseMenu.Trigger className={className}>
|
|
157
|
+
<BaseMenu.Trigger {...htmlProps} className={className}>
|
|
167
158
|
{children}
|
|
168
159
|
</BaseMenu.Trigger>
|
|
169
160
|
);
|
|
@@ -204,7 +195,16 @@ function MenuItem({
|
|
|
204
195
|
icon,
|
|
205
196
|
shortcut,
|
|
206
197
|
checked,
|
|
198
|
+
...htmlProps
|
|
207
199
|
}: MenuItemProps) {
|
|
200
|
+
const handleClick = React.useCallback(
|
|
201
|
+
(event: React.MouseEvent<HTMLElement>) => {
|
|
202
|
+
(htmlProps.onClick as React.MouseEventHandler<HTMLElement> | undefined)?.(event);
|
|
203
|
+
onSelect?.(event);
|
|
204
|
+
},
|
|
205
|
+
[htmlProps, onSelect],
|
|
206
|
+
);
|
|
207
|
+
|
|
208
208
|
const hasChecked = checked !== undefined;
|
|
209
209
|
const classes = [
|
|
210
210
|
styles.item,
|
|
@@ -214,8 +214,9 @@ function MenuItem({
|
|
|
214
214
|
|
|
215
215
|
return (
|
|
216
216
|
<BaseMenu.Item
|
|
217
|
+
{...htmlProps}
|
|
217
218
|
disabled={disabled}
|
|
218
|
-
onClick={
|
|
219
|
+
onClick={handleClick as any}
|
|
219
220
|
className={classes}
|
|
220
221
|
>
|
|
221
222
|
{hasChecked && (
|
|
@@ -237,6 +238,7 @@ function MenuCheckboxItem({
|
|
|
237
238
|
onCheckedChange,
|
|
238
239
|
disabled,
|
|
239
240
|
className,
|
|
241
|
+
...htmlProps
|
|
240
242
|
}: MenuCheckboxItemProps) {
|
|
241
243
|
const isControlled = checkedProp !== undefined;
|
|
242
244
|
const [internalChecked, setInternalChecked] = React.useState(defaultChecked ?? false);
|
|
@@ -256,6 +258,7 @@ function MenuCheckboxItem({
|
|
|
256
258
|
|
|
257
259
|
return (
|
|
258
260
|
<BaseMenu.CheckboxItem
|
|
261
|
+
{...htmlProps}
|
|
259
262
|
checked={checkedProp}
|
|
260
263
|
defaultChecked={defaultChecked}
|
|
261
264
|
onCheckedChange={handleCheckedChange}
|
|
@@ -292,13 +295,14 @@ function MenuRadioItem({
|
|
|
292
295
|
value,
|
|
293
296
|
disabled,
|
|
294
297
|
className,
|
|
298
|
+
...htmlProps
|
|
295
299
|
}: MenuRadioItemProps) {
|
|
296
300
|
const classes = [styles.item, styles.radioItem, className]
|
|
297
301
|
.filter(Boolean)
|
|
298
302
|
.join(' ');
|
|
299
303
|
|
|
300
304
|
return (
|
|
301
|
-
<BaseMenu.RadioItem value={value} disabled={disabled} className={classes}>
|
|
305
|
+
<BaseMenu.RadioItem {...htmlProps} value={value} disabled={disabled} className={classes}>
|
|
302
306
|
<span className={styles.radioIndicator}>
|
|
303
307
|
<DotIcon />
|
|
304
308
|
</span>
|
|
@@ -307,19 +311,19 @@ function MenuRadioItem({
|
|
|
307
311
|
);
|
|
308
312
|
}
|
|
309
313
|
|
|
310
|
-
function MenuGroup({ children, className }: MenuGroupProps) {
|
|
314
|
+
function MenuGroup({ children, className, ...htmlProps }: MenuGroupProps) {
|
|
311
315
|
const classes = [styles.group, className].filter(Boolean).join(' ');
|
|
312
|
-
return <BaseMenu.Group className={classes}>{children}</BaseMenu.Group>;
|
|
316
|
+
return <BaseMenu.Group {...htmlProps} className={classes}>{children}</BaseMenu.Group>;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
function MenuGroupLabel({ children, className }: MenuGroupLabelProps) {
|
|
319
|
+
function MenuGroupLabel({ children, className, ...htmlProps }: MenuGroupLabelProps) {
|
|
316
320
|
const classes = [styles.groupLabel, className].filter(Boolean).join(' ');
|
|
317
|
-
return <BaseMenu.GroupLabel className={classes}>{children}</BaseMenu.GroupLabel>;
|
|
321
|
+
return <BaseMenu.GroupLabel {...htmlProps} className={classes}>{children}</BaseMenu.GroupLabel>;
|
|
318
322
|
}
|
|
319
323
|
|
|
320
|
-
function MenuSeparator({ className }: MenuSeparatorProps) {
|
|
324
|
+
function MenuSeparator({ className, ...htmlProps }: MenuSeparatorProps) {
|
|
321
325
|
const classes = [styles.separator, className].filter(Boolean).join(' ');
|
|
322
|
-
return <BaseMenu.Separator className={classes} />;
|
|
326
|
+
return <BaseMenu.Separator {...htmlProps} className={classes} />;
|
|
323
327
|
}
|
|
324
328
|
|
|
325
329
|
function MenuSubmenu({
|
|
@@ -344,13 +348,14 @@ function MenuSubmenuTrigger({
|
|
|
344
348
|
disabled,
|
|
345
349
|
className,
|
|
346
350
|
icon,
|
|
351
|
+
...htmlProps
|
|
347
352
|
}: MenuSubmenuTriggerProps) {
|
|
348
353
|
const classes = [styles.item, styles.submenuTrigger, className]
|
|
349
354
|
.filter(Boolean)
|
|
350
355
|
.join(' ');
|
|
351
356
|
|
|
352
357
|
return (
|
|
353
|
-
<BaseMenu.SubmenuTrigger disabled={disabled} className={classes}>
|
|
358
|
+
<BaseMenu.SubmenuTrigger {...htmlProps} disabled={disabled} className={classes}>
|
|
354
359
|
{icon && <span className={styles.itemIcon}>{icon}</span>}
|
|
355
360
|
<span className={styles.itemLabel}>{children}</span>
|
|
356
361
|
</BaseMenu.SubmenuTrigger>
|
|
@@ -107,7 +107,7 @@ export default defineFragment({
|
|
|
107
107
|
|
|
108
108
|
ai: {
|
|
109
109
|
compositionPattern: 'compound',
|
|
110
|
-
subComponents: ['List', 'Item', 'Trigger', 'Content', 'Link', 'Indicator', 'Viewport', 'MobileContent', 'MobileSection'],
|
|
110
|
+
subComponents: ['List', 'Item', 'Trigger', 'Content', 'Link', 'Indicator', 'Viewport', 'MobileBrand', 'MobileContent', 'MobileSection'],
|
|
111
111
|
requiredChildren: ['List'],
|
|
112
112
|
commonPatterns: [
|
|
113
113
|
'<NavigationMenu><NavigationMenu.List><NavigationMenu.Item value="docs"><NavigationMenu.Trigger>Docs</NavigationMenu.Trigger><NavigationMenu.Content><NavigationMenu.Link href="/guides" title="Guides" description="Learn the basics" /></NavigationMenu.Content></NavigationMenu.Item></NavigationMenu.List><NavigationMenu.Viewport /></NavigationMenu>',
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, screen, userEvent, expectNoA11yViolations } from '../../test/utils';
|
|
3
|
-
import { fireEvent, act } from '@testing-library/react';
|
|
3
|
+
import { fireEvent, act, within } from '@testing-library/react';
|
|
4
4
|
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest';
|
|
5
5
|
import { NavigationMenu } from '.';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
beforeAll(() => {
|
|
7
|
+
function setMatchMedia(matches: boolean) {
|
|
9
8
|
Object.defineProperty(window, 'matchMedia', {
|
|
10
9
|
writable: true,
|
|
11
10
|
value: vi.fn().mockImplementation((query: string) => ({
|
|
12
|
-
matches
|
|
11
|
+
matches,
|
|
13
12
|
media: query,
|
|
14
13
|
onchange: null,
|
|
15
14
|
addListener: vi.fn(),
|
|
@@ -19,6 +18,11 @@ beforeAll(() => {
|
|
|
19
18
|
dispatchEvent: vi.fn(),
|
|
20
19
|
})),
|
|
21
20
|
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Mock matchMedia for jsdom
|
|
24
|
+
beforeAll(() => {
|
|
25
|
+
setMatchMedia(false);
|
|
22
26
|
});
|
|
23
27
|
|
|
24
28
|
// ============================================
|
|
@@ -254,6 +258,17 @@ describe('NavigationMenu', () => {
|
|
|
254
258
|
expect(document.activeElement).toBe(communityTrigger);
|
|
255
259
|
});
|
|
256
260
|
|
|
261
|
+
it('navigates based on focused trigger even when another item is open', async () => {
|
|
262
|
+
renderBasicMenu();
|
|
263
|
+
const learnTrigger = screen.getByText('Learn');
|
|
264
|
+
const communityTrigger = screen.getByText('Community');
|
|
265
|
+
|
|
266
|
+
await userEvent.click(learnTrigger);
|
|
267
|
+
communityTrigger.focus();
|
|
268
|
+
fireEvent.keyDown(communityTrigger.closest('ul')!, { key: 'ArrowRight' });
|
|
269
|
+
expect(document.activeElement).toBe(learnTrigger);
|
|
270
|
+
});
|
|
271
|
+
|
|
257
272
|
it('navigates to first trigger on Home', () => {
|
|
258
273
|
renderBasicMenu();
|
|
259
274
|
const communityTrigger = screen.getByText('Community');
|
|
@@ -454,4 +469,25 @@ describe('NavigationMenu', () => {
|
|
|
454
469
|
expect(screen.queryByText('Extra')).not.toBeInTheDocument();
|
|
455
470
|
});
|
|
456
471
|
});
|
|
472
|
+
|
|
473
|
+
describe('Mobile drawer', () => {
|
|
474
|
+
beforeEach(() => {
|
|
475
|
+
setMatchMedia(true);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
afterEach(() => {
|
|
479
|
+
setMatchMedia(false);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('includes direct link items in auto-converted drawer navigation', async () => {
|
|
483
|
+
renderBasicMenu();
|
|
484
|
+
|
|
485
|
+
const toggle = await screen.findByLabelText('Toggle navigation');
|
|
486
|
+
await userEvent.click(toggle);
|
|
487
|
+
|
|
488
|
+
const drawer = await screen.findByRole('dialog', { name: 'Navigation' });
|
|
489
|
+
const blogLink = within(drawer).getByRole('link', { name: 'Blog' });
|
|
490
|
+
expect(blogLink).toHaveAttribute('href', '/blog');
|
|
491
|
+
});
|
|
492
|
+
});
|
|
457
493
|
});
|
|
@@ -34,6 +34,9 @@ export interface NavigationMenuContextValue {
|
|
|
34
34
|
triggerRefs: React.MutableRefObject<Map<string, HTMLButtonElement>>;
|
|
35
35
|
triggerOrder: React.MutableRefObject<string[]>;
|
|
36
36
|
|
|
37
|
+
// Full item order (includes items without triggers)
|
|
38
|
+
itemOrder: React.MutableRefObject<string[]>;
|
|
39
|
+
|
|
37
40
|
// Item info registry (for mobile drawer)
|
|
38
41
|
itemInfoMap: React.MutableRefObject<Map<string, NavigationMenuItemInfo>>;
|
|
39
42
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
-
import { CaretDown,
|
|
5
|
+
import { CaretDown, List, X } from '@phosphor-icons/react';
|
|
6
6
|
import { handleArrowNavigation, useFocusTrap } from '../../utils/a11y';
|
|
7
7
|
import { Collapsible } from '../Collapsible';
|
|
8
8
|
import { ScrollArea } from '../ScrollArea';
|
|
@@ -183,7 +183,9 @@ function NavigationMenuList({ children, className }: NavigationMenuListProps) {
|
|
|
183
183
|
|
|
184
184
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
185
185
|
const order = triggerOrder.current;
|
|
186
|
-
const
|
|
186
|
+
const focusedValue = (document.activeElement as HTMLElement | null)?.getAttribute('data-navmenu-value');
|
|
187
|
+
const currentValue = focusedValue || value;
|
|
188
|
+
const currentIdx = order.indexOf(currentValue);
|
|
187
189
|
|
|
188
190
|
const newIdx = handleArrowNavigation(e, order, currentIdx >= 0 ? currentIdx : 0, {
|
|
189
191
|
orientation: orientation === 'horizontal' ? 'horizontal' : 'vertical',
|
|
@@ -220,14 +222,33 @@ function NavigationMenuList({ children, className }: NavigationMenuListProps) {
|
|
|
220
222
|
// Item
|
|
221
223
|
// ============================================
|
|
222
224
|
|
|
223
|
-
let itemCounter = 0;
|
|
224
|
-
|
|
225
225
|
function NavigationMenuItem({ children, value: valueProp, className }: NavigationMenuItemProps) {
|
|
226
226
|
const rootCtx = useNavigationMenuContext();
|
|
227
|
-
const
|
|
227
|
+
const generatedValue = React.useId();
|
|
228
|
+
const autoValue = valueProp || `navmenu-item-${generatedValue}`;
|
|
228
229
|
const triggerId = `${rootCtx.rootId}-trigger-${autoValue}`;
|
|
229
230
|
const contentId = `${rootCtx.rootId}-content-${autoValue}`;
|
|
230
231
|
|
|
232
|
+
React.useEffect(() => {
|
|
233
|
+
if (!rootCtx.itemOrder.current.includes(autoValue)) {
|
|
234
|
+
rootCtx.itemOrder.current.push(autoValue);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const existing = rootCtx.itemInfoMap.current.get(autoValue);
|
|
238
|
+
if (!existing) {
|
|
239
|
+
rootCtx.itemInfoMap.current.set(autoValue, {
|
|
240
|
+
value: autoValue,
|
|
241
|
+
triggerLabel: '',
|
|
242
|
+
contentChildren: null,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return () => {
|
|
247
|
+
rootCtx.itemOrder.current = rootCtx.itemOrder.current.filter(v => v !== autoValue);
|
|
248
|
+
rootCtx.itemInfoMap.current.delete(autoValue);
|
|
249
|
+
};
|
|
250
|
+
}, [autoValue, rootCtx.itemInfoMap, rootCtx.itemOrder]);
|
|
251
|
+
|
|
231
252
|
const itemCtx = React.useMemo(
|
|
232
253
|
() => ({
|
|
233
254
|
value: autoValue,
|
|
@@ -335,6 +356,7 @@ function NavigationMenuTrigger({ children, className }: NavigationMenuTriggerPro
|
|
|
335
356
|
type="button"
|
|
336
357
|
id={itemCtx.triggerId}
|
|
337
358
|
className={classes}
|
|
359
|
+
data-navmenu-value={itemCtx.value}
|
|
338
360
|
aria-expanded={isOpen}
|
|
339
361
|
aria-controls={itemCtx.contentId}
|
|
340
362
|
data-state={isOpen ? 'open' : 'closed'}
|
|
@@ -472,8 +494,25 @@ function NavigationMenuLink({
|
|
|
472
494
|
...htmlProps
|
|
473
495
|
}: NavigationMenuLinkProps) {
|
|
474
496
|
const ctx = React.useContext(NavigationMenuContext);
|
|
497
|
+
const itemCtx = React.useContext(NavigationMenuItemContext);
|
|
475
498
|
const isStructured = !!(title || description || icon);
|
|
476
499
|
|
|
500
|
+
React.useEffect(() => {
|
|
501
|
+
if (!ctx || !itemCtx) return;
|
|
502
|
+
|
|
503
|
+
const existing = ctx.itemInfoMap.current.get(itemCtx.value);
|
|
504
|
+
const fallbackLabel = typeof children === 'string' ? children : title || '';
|
|
505
|
+
const resolvedHref = typeof href === 'string' ? href : existing?.linkHref;
|
|
506
|
+
|
|
507
|
+
ctx.itemInfoMap.current.set(itemCtx.value, {
|
|
508
|
+
...existing,
|
|
509
|
+
value: itemCtx.value,
|
|
510
|
+
triggerLabel: existing?.triggerLabel || fallbackLabel || '',
|
|
511
|
+
contentChildren: existing?.contentChildren ?? null,
|
|
512
|
+
linkHref: resolvedHref,
|
|
513
|
+
});
|
|
514
|
+
}, [ctx, itemCtx, children, title, href]);
|
|
515
|
+
|
|
477
516
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
478
517
|
onClick?.(e);
|
|
479
518
|
// Close mobile drawer on link click
|
|
@@ -745,14 +784,11 @@ function MobileDrawer() {
|
|
|
745
784
|
}, [ctx]);
|
|
746
785
|
|
|
747
786
|
// Build auto-converted nav items from item info registry
|
|
748
|
-
const autoItems =
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
}
|
|
754
|
-
return items;
|
|
755
|
-
}, [ctx.triggerOrder, ctx.itemInfoMap]);
|
|
787
|
+
const autoItems: NavigationMenuItemInfo[] = [];
|
|
788
|
+
for (const value of ctx.itemOrder.current) {
|
|
789
|
+
const info = ctx.itemInfoMap.current.get(value);
|
|
790
|
+
if (info) autoItems.push(info);
|
|
791
|
+
}
|
|
756
792
|
|
|
757
793
|
const handleLinkClick = () => {
|
|
758
794
|
ctx.setMobileOpen(false);
|
|
@@ -44,6 +44,9 @@ export function useNavigationMenu({
|
|
|
44
44
|
const triggerRefs = React.useRef<Map<string, HTMLButtonElement>>(new Map());
|
|
45
45
|
const triggerOrder = React.useRef<string[]>([]);
|
|
46
46
|
|
|
47
|
+
// Full item order registry (includes link-only items)
|
|
48
|
+
const itemOrder = React.useRef<string[]>([]);
|
|
49
|
+
|
|
47
50
|
// Item info registry for mobile drawer
|
|
48
51
|
const itemInfoMap = React.useRef<Map<string, NavigationMenuItemInfo>>(new Map());
|
|
49
52
|
|
|
@@ -79,6 +82,7 @@ export function useNavigationMenu({
|
|
|
79
82
|
skipDelayTimerRef,
|
|
80
83
|
triggerRefs,
|
|
81
84
|
triggerOrder,
|
|
85
|
+
itemOrder,
|
|
82
86
|
itemInfoMap,
|
|
83
87
|
viewportSize,
|
|
84
88
|
setViewportSize,
|
|
@@ -43,6 +43,29 @@ describe('Popover', () => {
|
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
it('forwards html props to trigger, title, description, and close', async () => {
|
|
47
|
+
const user = userEvent.setup();
|
|
48
|
+
render(
|
|
49
|
+
<Popover>
|
|
50
|
+
<Popover.Trigger id="popover-trigger">Open</Popover.Trigger>
|
|
51
|
+
<Popover.Content>
|
|
52
|
+
<Popover.Title id="popover-title">Popover Title</Popover.Title>
|
|
53
|
+
<Popover.Description id="popover-description">Popover Description</Popover.Description>
|
|
54
|
+
<Popover.Close data-testid="popover-close" />
|
|
55
|
+
</Popover.Content>
|
|
56
|
+
</Popover>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByRole('button', { name: /open/i })).toHaveAttribute('id', 'popover-trigger');
|
|
60
|
+
await user.click(screen.getByRole('button', { name: /open/i }));
|
|
61
|
+
|
|
62
|
+
await waitFor(() => {
|
|
63
|
+
expect(screen.getByText('Popover Title')).toHaveAttribute('id', 'popover-title');
|
|
64
|
+
expect(screen.getByText('Popover Description')).toHaveAttribute('id', 'popover-description');
|
|
65
|
+
expect(screen.getByTestId('popover-close')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
46
69
|
it('has a close button with aria-label', async () => {
|
|
47
70
|
const user = userEvent.setup();
|
|
48
71
|
renderPopover();
|
|
@@ -25,10 +25,9 @@ export interface PopoverProps {
|
|
|
25
25
|
modal?: boolean;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export interface PopoverTriggerProps {
|
|
28
|
+
export interface PopoverTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
29
29
|
children: React.ReactNode;
|
|
30
30
|
asChild?: boolean;
|
|
31
|
-
className?: string;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -40,14 +39,12 @@ export interface PopoverContentProps extends React.HTMLAttributes<HTMLDivElement
|
|
|
40
39
|
arrow?: boolean;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
export interface PopoverTitleProps {
|
|
42
|
+
export interface PopoverTitleProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
|
|
44
43
|
children: React.ReactNode;
|
|
45
|
-
className?: string;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
export interface PopoverDescriptionProps {
|
|
46
|
+
export interface PopoverDescriptionProps extends Omit<React.HTMLAttributes<HTMLElement>, 'children'> {
|
|
49
47
|
children: React.ReactNode;
|
|
50
|
-
className?: string;
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
export interface PopoverBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -58,10 +55,9 @@ export interface PopoverFooterProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
58
55
|
children: React.ReactNode;
|
|
59
56
|
}
|
|
60
57
|
|
|
61
|
-
export interface PopoverCloseProps {
|
|
58
|
+
export interface PopoverCloseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
62
59
|
children?: React.ReactNode;
|
|
63
60
|
asChild?: boolean;
|
|
64
|
-
className?: string;
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
// ============================================
|
|
@@ -111,17 +107,17 @@ function PopoverRoot({
|
|
|
111
107
|
);
|
|
112
108
|
}
|
|
113
109
|
|
|
114
|
-
function PopoverTrigger({ children, asChild, className }: PopoverTriggerProps) {
|
|
110
|
+
function PopoverTrigger({ children, asChild, className, ...htmlProps }: PopoverTriggerProps) {
|
|
115
111
|
if (asChild) {
|
|
116
112
|
return (
|
|
117
|
-
<BasePopover.Trigger className={className} render={children as React.ReactElement}>
|
|
113
|
+
<BasePopover.Trigger {...htmlProps} className={className} render={children as React.ReactElement}>
|
|
118
114
|
{null}
|
|
119
115
|
</BasePopover.Trigger>
|
|
120
116
|
);
|
|
121
117
|
}
|
|
122
118
|
|
|
123
119
|
return (
|
|
124
|
-
<BasePopover.Trigger className={className}>
|
|
120
|
+
<BasePopover.Trigger {...htmlProps} className={className}>
|
|
125
121
|
{children}
|
|
126
122
|
</BasePopover.Trigger>
|
|
127
123
|
);
|
|
@@ -160,15 +156,15 @@ function PopoverContent({
|
|
|
160
156
|
);
|
|
161
157
|
}
|
|
162
158
|
|
|
163
|
-
function PopoverTitle({ children, className }: PopoverTitleProps) {
|
|
159
|
+
function PopoverTitle({ children, className, ...htmlProps }: PopoverTitleProps) {
|
|
164
160
|
const classes = [styles.title, className].filter(Boolean).join(' ');
|
|
165
|
-
return <BasePopover.Title className={classes}>{children}</BasePopover.Title>;
|
|
161
|
+
return <BasePopover.Title {...htmlProps} className={classes}>{children}</BasePopover.Title>;
|
|
166
162
|
}
|
|
167
163
|
|
|
168
|
-
function PopoverDescription({ children, className }: PopoverDescriptionProps) {
|
|
164
|
+
function PopoverDescription({ children, className, ...htmlProps }: PopoverDescriptionProps) {
|
|
169
165
|
const classes = [styles.description, className].filter(Boolean).join(' ');
|
|
170
166
|
return (
|
|
171
|
-
<BasePopover.Description className={classes}>
|
|
167
|
+
<BasePopover.Description {...htmlProps} className={classes}>
|
|
172
168
|
{children}
|
|
173
169
|
</BasePopover.Description>
|
|
174
170
|
);
|
|
@@ -184,11 +180,12 @@ function PopoverFooter({ children, className, ...htmlProps }: PopoverFooterProps
|
|
|
184
180
|
return <div {...htmlProps} className={classes}>{children}</div>;
|
|
185
181
|
}
|
|
186
182
|
|
|
187
|
-
function PopoverClose({ children, asChild, className }: PopoverCloseProps) {
|
|
183
|
+
function PopoverClose({ children, asChild, className, ...htmlProps }: PopoverCloseProps) {
|
|
188
184
|
// Default close button (X icon)
|
|
189
185
|
if (!children) {
|
|
190
186
|
return (
|
|
191
187
|
<BasePopover.Close
|
|
188
|
+
{...htmlProps}
|
|
192
189
|
aria-label="Close popover"
|
|
193
190
|
className={[styles.close, className].filter(Boolean).join(' ')}
|
|
194
191
|
>
|
|
@@ -199,14 +196,14 @@ function PopoverClose({ children, asChild, className }: PopoverCloseProps) {
|
|
|
199
196
|
|
|
200
197
|
if (asChild) {
|
|
201
198
|
return (
|
|
202
|
-
<BasePopover.Close className={className} render={children as React.ReactElement}>
|
|
199
|
+
<BasePopover.Close {...htmlProps} className={className} render={children as React.ReactElement}>
|
|
203
200
|
{null}
|
|
204
201
|
</BasePopover.Close>
|
|
205
202
|
);
|
|
206
203
|
}
|
|
207
204
|
|
|
208
205
|
return (
|
|
209
|
-
<BasePopover.Close className={className}>
|
|
206
|
+
<BasePopover.Close {...htmlProps} className={className}>
|
|
210
207
|
{children}
|
|
211
208
|
</BasePopover.Close>
|
|
212
209
|
);
|