@box/blueprint-web 9.4.1 → 9.5.1
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/lib-esm/index.css +54 -10
- package/lib-esm/primitives/chips/filter-chip/filter-combo-chip.d.ts +2 -0
- package/lib-esm/primitives/chips/filter-chip/index.d.ts +2 -0
- package/lib-esm/primitives/dropdown-menu/FullScreenMenuContext.d.ts +16 -0
- package/lib-esm/primitives/dropdown-menu/FullScreenMenuContext.js +36 -0
- package/lib-esm/primitives/dropdown-menu/SubMenuContext.d.ts +13 -0
- package/lib-esm/primitives/dropdown-menu/SubMenuContext.js +27 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-content.d.ts +5 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-content.js +43 -3
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-header.d.ts +15 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-header.js +93 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-item.js +4 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-root.d.ts +3 -1
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-root.js +21 -3
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-content.js +30 -2
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-root.d.ts +6 -0
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-root.js +32 -4
- package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-trigger.js +22 -2
- package/lib-esm/primitives/dropdown-menu/dropdown-menu.module.js +1 -1
- package/lib-esm/primitives/dropdown-menu/index.d.ts +8 -0
- package/lib-esm/primitives/dropdown-menu/index.js +3 -1
- package/lib-esm/utils/filterNonHtmlProps.js +10 -5
- package/package.json +2 -2
package/lib-esm/index.css
CHANGED
|
@@ -4036,7 +4036,7 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
4036
4036
|
}
|
|
4037
4037
|
}
|
|
4038
4038
|
|
|
4039
|
-
.bp_dropdown_menu_module_content--
|
|
4039
|
+
.bp_dropdown_menu_module_content--00a5d{
|
|
4040
4040
|
background-color:var(--surface-menu-surface);
|
|
4041
4041
|
border:var(--border-1) solid var(--border-card-border);
|
|
4042
4042
|
border-radius:var(--radius-3);
|
|
@@ -4057,14 +4057,31 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
4057
4057
|
text-transform:none;
|
|
4058
4058
|
z-index:2147483647;
|
|
4059
4059
|
}
|
|
4060
|
+
.bp_dropdown_menu_module_content--00a5d[data-menu-fullscreen=true]{
|
|
4061
|
+
border:unset;
|
|
4062
|
+
border-radius:unset;
|
|
4063
|
+
display:flex;
|
|
4064
|
+
flex-direction:column;
|
|
4065
|
+
height:100vh;
|
|
4066
|
+
max-height:none;
|
|
4067
|
+
max-width:none;
|
|
4068
|
+
overflow:hidden;
|
|
4069
|
+
padding:unset;
|
|
4070
|
+
position:relative;
|
|
4071
|
+
width:100vw;
|
|
4072
|
+
}
|
|
4073
|
+
.bp_dropdown_menu_module_content--00a5d[data-menu-fullscreen=true] .bp_dropdown_menu_module_fullScreenContent--00a5d{
|
|
4074
|
+
overflow-y:auto;
|
|
4075
|
+
padding:var(--space-3);
|
|
4076
|
+
}
|
|
4060
4077
|
|
|
4061
|
-
.bp_dropdown_menu_module_ellipsis--
|
|
4078
|
+
.bp_dropdown_menu_module_ellipsis--00a5d{
|
|
4062
4079
|
overflow:hidden;
|
|
4063
4080
|
text-overflow:ellipsis;
|
|
4064
4081
|
white-space:nowrap;
|
|
4065
4082
|
}
|
|
4066
4083
|
|
|
4067
|
-
.bp_dropdown_menu_module_checkmark--
|
|
4084
|
+
.bp_dropdown_menu_module_checkmark--00a5d{
|
|
4068
4085
|
align-items:center;
|
|
4069
4086
|
display:inline-flex;
|
|
4070
4087
|
justify-content:center;
|
|
@@ -4072,36 +4089,63 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
|
|
|
4072
4089
|
position:absolute;
|
|
4073
4090
|
}
|
|
4074
4091
|
|
|
4075
|
-
.bp_dropdown_menu_module_item--
|
|
4092
|
+
.bp_dropdown_menu_module_item--00a5d,.bp_dropdown_menu_module_subMenuTrigger--00a5d{
|
|
4076
4093
|
align-items:center;
|
|
4077
4094
|
border:var(--border-2) solid #0000;
|
|
4078
4095
|
border-radius:var(--radius-3);
|
|
4079
4096
|
cursor:pointer;
|
|
4080
4097
|
display:flex;
|
|
4081
4098
|
outline:none;
|
|
4082
|
-
padding:var(--border-8);
|
|
4099
|
+
padding:calc(var(--border-8) - var(--border-2));
|
|
4083
4100
|
position:relative;
|
|
4084
4101
|
-webkit-user-select:none;
|
|
4085
4102
|
user-select:none;
|
|
4086
4103
|
}
|
|
4087
|
-
.bp_dropdown_menu_module_item--
|
|
4104
|
+
.bp_dropdown_menu_module_item--00a5d[data-disabled],.bp_dropdown_menu_module_subMenuTrigger--00a5d[data-disabled]{
|
|
4088
4105
|
opacity:60%;
|
|
4089
4106
|
pointer-events:none;
|
|
4090
4107
|
}
|
|
4091
|
-
.bp_dropdown_menu_module_item--
|
|
4108
|
+
.bp_dropdown_menu_module_item--00a5d[data-highlighted],.bp_dropdown_menu_module_subMenuTrigger--00a5d[data-highlighted]{
|
|
4092
4109
|
background-color:var(--surface-menu-surface-hover);
|
|
4093
4110
|
border:var(--border-2) solid var(--outline-focus-on-light);
|
|
4094
4111
|
}
|
|
4095
|
-
.bp_dropdown_menu_module_item--
|
|
4112
|
+
.bp_dropdown_menu_module_item--00a5d:hover,.bp_dropdown_menu_module_item--00a5d[data-state=open],.bp_dropdown_menu_module_subMenuTrigger--00a5d:hover,.bp_dropdown_menu_module_subMenuTrigger--00a5d[data-state=open]{
|
|
4096
4113
|
background-color:var(--surface-menu-surface-hover);
|
|
4097
4114
|
}
|
|
4098
|
-
.bp_dropdown_menu_module_item--
|
|
4115
|
+
.bp_dropdown_menu_module_item--00a5d.bp_dropdown_menu_module_checkboxItem--00a5d,.bp_dropdown_menu_module_item--00a5d.bp_dropdown_menu_module_radioItem--00a5d,.bp_dropdown_menu_module_subMenuTrigger--00a5d.bp_dropdown_menu_module_checkboxItem--00a5d,.bp_dropdown_menu_module_subMenuTrigger--00a5d.bp_dropdown_menu_module_radioItem--00a5d{
|
|
4099
4116
|
padding:.4375rem .5rem .4375rem calc(var(--space-2) + var(--space-4) + var(--space-4));
|
|
4100
4117
|
}
|
|
4101
4118
|
|
|
4102
|
-
.bp_dropdown_menu_module_dropdownMenuItemSeparator--
|
|
4119
|
+
.bp_dropdown_menu_module_dropdownMenuItemSeparator--00a5d{
|
|
4103
4120
|
margin-block:var(--space-2);
|
|
4104
4121
|
}
|
|
4122
|
+
div[data-radix-popper-content-wrapper]:has([role=menu]):has([data-state=open]):has([data-menu-fullscreen=true]){
|
|
4123
|
+
height:100%;
|
|
4124
|
+
transform:none !important;
|
|
4125
|
+
width:100%;
|
|
4126
|
+
}
|
|
4127
|
+
|
|
4128
|
+
.bp_dropdown_menu_module_menuHeader--00a5d{
|
|
4129
|
+
align-items:center;
|
|
4130
|
+
box-shadow:var(--dropshadow-1);
|
|
4131
|
+
display:grid;
|
|
4132
|
+
gap:var(--space-2);
|
|
4133
|
+
grid-template-areas:"submenu-close content close";
|
|
4134
|
+
grid-template-columns:auto minmax(0, 1fr) auto;
|
|
4135
|
+
padding:var(--space-3);
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
.bp_dropdown_menu_module_headerTextContent--00a5d{
|
|
4139
|
+
display:grid;
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4142
|
+
.bp_dropdown_menu_module_submenuCloseButton--00a5d{
|
|
4143
|
+
grid-area:submenu-close;
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
.bp_dropdown_menu_module_menuCloseButton--00a5d{
|
|
4147
|
+
grid-area:close;
|
|
4148
|
+
}
|
|
4105
4149
|
|
|
4106
4150
|
.bp_menu_item_sections_module_menuItemMainContent--c557e{
|
|
4107
4151
|
display:flex;
|
|
@@ -20,6 +20,8 @@ export type FilterComboChipProps = DropdownMenuRootProps & {
|
|
|
20
20
|
children: React.ReactNode;
|
|
21
21
|
};
|
|
22
22
|
export declare const FilterComboChip: React.ForwardRefExoticComponent<import("@radix-ui/react-dropdown-menu").DropdownMenuProps & {
|
|
23
|
+
isFullScreenEnabled?: boolean;
|
|
24
|
+
} & {
|
|
23
25
|
/** FilterChip.Chip component used as Dropdown menu trigger. Trigger chip value is ignored from selection */
|
|
24
26
|
chip: React.ReactElement;
|
|
25
27
|
/** List of FilterChip.ComboOption elements displayed in the dropdown menu */
|
|
@@ -7,6 +7,8 @@ export declare const FilterChip: {
|
|
|
7
7
|
Status: import("react").ForwardRefExoticComponent<Omit<import("../types").ChipStatusProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
8
8
|
DropdownIndicator: import("react").ForwardRefExoticComponent<import("react").RefAttributes<SVGSVGElement>>;
|
|
9
9
|
ComboChip: import("react").ForwardRefExoticComponent<import("@radix-ui/react-dropdown-menu").DropdownMenuProps & {
|
|
10
|
+
isFullScreenEnabled?: boolean;
|
|
11
|
+
} & {
|
|
10
12
|
chip: React.ReactElement;
|
|
11
13
|
children: React.ReactNode;
|
|
12
14
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
interface FullScreenMenuContextProps {
|
|
3
|
+
isMenuFullScreenEnabled: boolean;
|
|
4
|
+
setIsMenuFullScreenEnabled: React.Dispatch<React.SetStateAction<boolean>>;
|
|
5
|
+
isMenuOpen: boolean;
|
|
6
|
+
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
7
|
+
}
|
|
8
|
+
interface FullScreenProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
isFullScreenEnabled?: boolean;
|
|
11
|
+
isMenuOpen: boolean;
|
|
12
|
+
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
13
|
+
}
|
|
14
|
+
export declare const FullScreenMenuProvider: ({ children, isFullScreenEnabled, isMenuOpen, setIsMenuOpen, }: FullScreenProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
export declare const useFullScreenMenu: () => FullScreenMenuContextProps;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext, createContext, useState, useEffect, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
const FullScreenMenuContext = /*#__PURE__*/createContext(null);
|
|
5
|
+
const FullScreenMenuProvider = ({
|
|
6
|
+
children,
|
|
7
|
+
isFullScreenEnabled,
|
|
8
|
+
isMenuOpen,
|
|
9
|
+
setIsMenuOpen
|
|
10
|
+
}) => {
|
|
11
|
+
const [isMenuFullScreenEnabled, setIsMenuFullScreenEnabled] = useState(isFullScreenEnabled || false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (isFullScreenEnabled !== undefined) {
|
|
14
|
+
setIsMenuFullScreenEnabled(isFullScreenEnabled);
|
|
15
|
+
}
|
|
16
|
+
}, [isFullScreenEnabled]);
|
|
17
|
+
const value = useMemo(() => ({
|
|
18
|
+
isMenuFullScreenEnabled,
|
|
19
|
+
setIsMenuFullScreenEnabled,
|
|
20
|
+
isMenuOpen,
|
|
21
|
+
setIsMenuOpen
|
|
22
|
+
}), [isMenuFullScreenEnabled, setIsMenuFullScreenEnabled, isMenuOpen, setIsMenuOpen]);
|
|
23
|
+
return jsx(FullScreenMenuContext.Provider, {
|
|
24
|
+
value: value,
|
|
25
|
+
children: children
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const useFullScreenMenu = () => {
|
|
29
|
+
const context = useContext(FullScreenMenuContext);
|
|
30
|
+
if (context === null) {
|
|
31
|
+
throw new Error('useFullScreenMenu must be used within a DropdownMenu.Root');
|
|
32
|
+
}
|
|
33
|
+
return context;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { FullScreenMenuProvider, useFullScreenMenu };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
interface SubMenuContextProps {
|
|
3
|
+
isSubMenuOpen: boolean;
|
|
4
|
+
setIsSubMenuOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
5
|
+
}
|
|
6
|
+
interface SubMenuProviderProps {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
isSubMenuOpen: boolean;
|
|
9
|
+
setIsSubMenuOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
|
|
10
|
+
}
|
|
11
|
+
export declare const SubMenuMenuProvider: ({ children, isSubMenuOpen, setIsSubMenuOpen }: SubMenuProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export declare const useSubMenu: () => SubMenuContextProps;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useContext, createContext, useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
const SubMenuContext = /*#__PURE__*/createContext(null);
|
|
5
|
+
const SubMenuMenuProvider = ({
|
|
6
|
+
children,
|
|
7
|
+
isSubMenuOpen,
|
|
8
|
+
setIsSubMenuOpen
|
|
9
|
+
}) => {
|
|
10
|
+
const value = useMemo(() => ({
|
|
11
|
+
isSubMenuOpen,
|
|
12
|
+
setIsSubMenuOpen
|
|
13
|
+
}), [isSubMenuOpen, setIsSubMenuOpen]);
|
|
14
|
+
return jsx(SubMenuContext.Provider, {
|
|
15
|
+
value: value,
|
|
16
|
+
children: children
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
const useSubMenu = () => {
|
|
20
|
+
const context = useContext(SubMenuContext);
|
|
21
|
+
if (context === null) {
|
|
22
|
+
throw new Error('useSubMenu must be used within a DropdownMenu.Root');
|
|
23
|
+
}
|
|
24
|
+
return context;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { SubMenuMenuProvider, useSubMenu };
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
2
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
3
|
+
export declare const sortMenuChildren: (children: ReactNode) => {
|
|
4
|
+
Header: ReactElement | null;
|
|
5
|
+
OtherChildren: Array<ReactElement>;
|
|
6
|
+
};
|
|
2
7
|
export interface DropdownMenuContentProps extends DropdownMenuPrimitive.DropdownMenuContentProps {
|
|
3
8
|
/** Specify a container element to portal the content into. */
|
|
4
9
|
/** @default document.body */
|
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { forwardRef } from 'react';
|
|
4
|
+
import { forwardRef, Children, isValidElement } from 'react';
|
|
5
|
+
import { DropdownMenuHeader } from './dropdown-menu-header.js';
|
|
5
6
|
import styles from './dropdown-menu.module.js';
|
|
7
|
+
import { useFullScreenMenu } from './FullScreenMenuContext.js';
|
|
6
8
|
|
|
9
|
+
const sortMenuChildren = children => {
|
|
10
|
+
return Children.toArray(children).reduce((acc, child) => {
|
|
11
|
+
if (! /*#__PURE__*/isValidElement(child)) {
|
|
12
|
+
return acc;
|
|
13
|
+
}
|
|
14
|
+
if (child.type === DropdownMenuHeader) {
|
|
15
|
+
acc.Header = child;
|
|
16
|
+
} else {
|
|
17
|
+
acc.OtherChildren.push(child);
|
|
18
|
+
}
|
|
19
|
+
return acc;
|
|
20
|
+
}, {
|
|
21
|
+
Header: null,
|
|
22
|
+
OtherChildren: []
|
|
23
|
+
});
|
|
24
|
+
};
|
|
7
25
|
/**
|
|
8
26
|
* Based on Radix-UI Content
|
|
9
27
|
* @see https://www.radix-ui.com/docs/primitives/components/dropdown-menu#content
|
|
@@ -15,6 +33,28 @@ const DropdownMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
15
33
|
className,
|
|
16
34
|
...rest
|
|
17
35
|
} = props;
|
|
36
|
+
const {
|
|
37
|
+
isMenuFullScreenEnabled
|
|
38
|
+
} = useFullScreenMenu();
|
|
39
|
+
if (isMenuFullScreenEnabled) {
|
|
40
|
+
const {
|
|
41
|
+
Header,
|
|
42
|
+
OtherChildren
|
|
43
|
+
} = sortMenuChildren(children);
|
|
44
|
+
return jsx(DropdownMenuPrimitive.Portal, {
|
|
45
|
+
container: container,
|
|
46
|
+
children: jsxs(DropdownMenuPrimitive.Content, {
|
|
47
|
+
...rest,
|
|
48
|
+
ref: forwardedRef,
|
|
49
|
+
className: clsx(styles.content, className),
|
|
50
|
+
"data-menu-fullscreen": true,
|
|
51
|
+
children: [Header, jsx("div", {
|
|
52
|
+
className: styles.fullScreenContent,
|
|
53
|
+
children: OtherChildren
|
|
54
|
+
})]
|
|
55
|
+
})
|
|
56
|
+
});
|
|
57
|
+
}
|
|
18
58
|
return (
|
|
19
59
|
// TODO(DSYS-440): Unify all Portals (Tooltip, Select, DropdownMenu, etc...), Expose a Provider which would tell where to mount (body? other elem?)
|
|
20
60
|
jsx(DropdownMenuPrimitive.Portal, {
|
|
@@ -32,4 +72,4 @@ const DropdownMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
32
72
|
});
|
|
33
73
|
DropdownMenuContent.displayName = 'DropdownMenuContent';
|
|
34
74
|
|
|
35
|
-
export { DropdownMenuContent };
|
|
75
|
+
export { DropdownMenuContent, sortMenuChildren };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type HTMLAttributes } from 'react';
|
|
2
|
+
import { type IconButtonProps } from '../icon-button';
|
|
3
|
+
export type MenuCloseButtonProps = Omit<IconButtonProps, 'icon' | 'variant' | 'size'>;
|
|
4
|
+
export type TextContentProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const DropdownMenuHeader: (({ children, className, ...rest }: HTMLAttributes<HTMLDivElement>) => import("react/jsx-runtime").JSX.Element) & {
|
|
9
|
+
MenuCloseButton: import("react").ForwardRefExoticComponent<Omit<MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
SubMenuBackButton: import("react").ForwardRefExoticComponent<Omit<MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
11
|
+
TextContent: import("react").ForwardRefExoticComponent<Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
12
|
+
title: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
15
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { XMark, PointerChevronLeft } from '@box/blueprint-web-assets/icons/Fill';
|
|
3
|
+
import clsx from 'clsx';
|
|
4
|
+
import { forwardRef, useCallback } from 'react';
|
|
5
|
+
import { Text } from '../../text/text.js';
|
|
6
|
+
import { composeEventHandlers } from '../../utils/composeEventHandlers.js';
|
|
7
|
+
import { IconButton } from '../icon-button/icon-button.js';
|
|
8
|
+
import styles from './dropdown-menu.module.js';
|
|
9
|
+
import { useFullScreenMenu } from './FullScreenMenuContext.js';
|
|
10
|
+
import { useSubMenu } from './SubMenuContext.js';
|
|
11
|
+
|
|
12
|
+
const MenuCloseButton = /*#__PURE__*/forwardRef(({
|
|
13
|
+
onClick,
|
|
14
|
+
className,
|
|
15
|
+
...rest
|
|
16
|
+
}, ref) => {
|
|
17
|
+
const {
|
|
18
|
+
setIsMenuOpen
|
|
19
|
+
} = useFullScreenMenu();
|
|
20
|
+
const handleCloseClick = useCallback(() => {
|
|
21
|
+
setIsMenuOpen(false);
|
|
22
|
+
}, [setIsMenuOpen]);
|
|
23
|
+
return jsx(IconButton, {
|
|
24
|
+
ref: ref,
|
|
25
|
+
...rest,
|
|
26
|
+
className: clsx(styles.menuCloseButton, className),
|
|
27
|
+
icon: XMark,
|
|
28
|
+
onClick: composeEventHandlers(handleCloseClick, onClick),
|
|
29
|
+
variant: "small-utility"
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
const SubMenuBackButton = /*#__PURE__*/forwardRef(({
|
|
33
|
+
onClick,
|
|
34
|
+
className,
|
|
35
|
+
...rest
|
|
36
|
+
}, ref) => {
|
|
37
|
+
const {
|
|
38
|
+
setIsSubMenuOpen
|
|
39
|
+
} = useSubMenu();
|
|
40
|
+
const handleCloseClick = useCallback(() => {
|
|
41
|
+
setIsSubMenuOpen(false);
|
|
42
|
+
}, [setIsSubMenuOpen]);
|
|
43
|
+
return jsx(IconButton, {
|
|
44
|
+
ref: ref,
|
|
45
|
+
...rest,
|
|
46
|
+
className: clsx(styles.submenuCloseButton, className),
|
|
47
|
+
icon: PointerChevronLeft,
|
|
48
|
+
onClick: composeEventHandlers(handleCloseClick, onClick)
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
const MenuHeaderPrimitive = ({
|
|
52
|
+
children,
|
|
53
|
+
className,
|
|
54
|
+
...rest
|
|
55
|
+
}) => {
|
|
56
|
+
return jsx("div", {
|
|
57
|
+
...rest,
|
|
58
|
+
className: clsx(styles.menuHeader, className),
|
|
59
|
+
role: "presentation",
|
|
60
|
+
children: children
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const TextContent = /*#__PURE__*/forwardRef(({
|
|
64
|
+
title,
|
|
65
|
+
subtitle,
|
|
66
|
+
className,
|
|
67
|
+
...rest
|
|
68
|
+
}, ref) => {
|
|
69
|
+
return jsxs("div", {
|
|
70
|
+
ref: ref,
|
|
71
|
+
...rest,
|
|
72
|
+
className: clsx(className, styles.headerTextContent),
|
|
73
|
+
children: [jsx(Text, {
|
|
74
|
+
as: "span",
|
|
75
|
+
className: styles.ellipsis,
|
|
76
|
+
variant: "bodyLargeBold",
|
|
77
|
+
children: title
|
|
78
|
+
}), subtitle && jsx(Text, {
|
|
79
|
+
as: "span",
|
|
80
|
+
className: styles.ellipsis,
|
|
81
|
+
color: "textOnLightSecondary",
|
|
82
|
+
variant: "caption",
|
|
83
|
+
children: subtitle
|
|
84
|
+
})]
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
const DropdownMenuHeader = Object.assign(MenuHeaderPrimitive, {
|
|
88
|
+
MenuCloseButton,
|
|
89
|
+
SubMenuBackButton,
|
|
90
|
+
TextContent
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export { DropdownMenuHeader };
|
|
@@ -18,6 +18,10 @@ const DropdownMenuItem = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
|
18
18
|
className: styles.item,
|
|
19
19
|
onPointerLeave: preventDefault,
|
|
20
20
|
onPointerMove: preventDefault,
|
|
21
|
+
// If click starts at trigger button, and ends on the item, it should not trigger the item.
|
|
22
|
+
// Note: this also has a side effect of not allowing to start click and end on a different time.
|
|
23
|
+
// TODO: see if possible to make https://github.com/radix-ui/primitives/blob/b32a93318cdfce383c2eec095710d35ffbd33a1c/packages/react/menu/src/Menu.tsx#L646
|
|
24
|
+
onPointerUp: preventDefault,
|
|
21
25
|
children: jsx("span", {
|
|
22
26
|
className: styles.ellipsis,
|
|
23
27
|
children: children
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
2
|
-
export type DropdownMenuRootProps = DropdownMenuPrimitive.DropdownMenuProps
|
|
2
|
+
export type DropdownMenuRootProps = DropdownMenuPrimitive.DropdownMenuProps & {
|
|
3
|
+
isFullScreenEnabled?: boolean;
|
|
4
|
+
};
|
|
3
5
|
/**
|
|
4
6
|
* The Dropdown-menu component displays a menu to the user, such as a set of actions or functions—triggered by a button.
|
|
5
7
|
*/
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
+
import { useControllableState } from '../../utils/useControllableState.js';
|
|
4
|
+
import { FullScreenMenuProvider } from './FullScreenMenuContext.js';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* The Dropdown-menu component displays a menu to the user, such as a set of actions or functions—triggered by a button.
|
|
@@ -7,11 +9,27 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
|
7
9
|
const DropdownMenuRoot = props => {
|
|
8
10
|
const {
|
|
9
11
|
children,
|
|
12
|
+
isFullScreenEnabled,
|
|
13
|
+
open,
|
|
14
|
+
onOpenChange,
|
|
15
|
+
defaultOpen,
|
|
10
16
|
...rest
|
|
11
17
|
} = props;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
const [isMenuOpen = false, setIsMenuOpen] = useControllableState({
|
|
19
|
+
prop: open,
|
|
20
|
+
onChange: onOpenChange,
|
|
21
|
+
defaultProp: defaultOpen
|
|
22
|
+
});
|
|
23
|
+
return jsx(FullScreenMenuProvider, {
|
|
24
|
+
isFullScreenEnabled: isFullScreenEnabled,
|
|
25
|
+
isMenuOpen: isMenuOpen,
|
|
26
|
+
setIsMenuOpen: setIsMenuOpen,
|
|
27
|
+
children: jsx(DropdownMenuPrimitive.Root, {
|
|
28
|
+
...rest,
|
|
29
|
+
onOpenChange: setIsMenuOpen,
|
|
30
|
+
open: isMenuOpen,
|
|
31
|
+
children: children
|
|
32
|
+
})
|
|
15
33
|
});
|
|
16
34
|
};
|
|
17
35
|
DropdownMenuRoot.displayName = 'DropdownMenuRoot';
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
3
|
import clsx from 'clsx';
|
|
4
|
-
import { forwardRef } from 'react';
|
|
4
|
+
import { forwardRef, useCallback } from 'react';
|
|
5
|
+
import { sortMenuChildren } from './dropdown-menu-content.js';
|
|
5
6
|
import styles from './dropdown-menu.module.js';
|
|
7
|
+
import { useFullScreenMenu } from './FullScreenMenuContext.js';
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Based on Radix-UI Sub Content
|
|
@@ -15,6 +17,32 @@ const DropdownMenuSubMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef)
|
|
|
15
17
|
className,
|
|
16
18
|
...rest
|
|
17
19
|
} = props;
|
|
20
|
+
const {
|
|
21
|
+
isMenuFullScreenEnabled
|
|
22
|
+
} = useFullScreenMenu();
|
|
23
|
+
const preventDefault = useCallback(event => event.preventDefault(), []);
|
|
24
|
+
if (isMenuFullScreenEnabled) {
|
|
25
|
+
const {
|
|
26
|
+
Header,
|
|
27
|
+
OtherChildren
|
|
28
|
+
} = sortMenuChildren(children);
|
|
29
|
+
return jsx(DropdownMenuPrimitive.Portal, {
|
|
30
|
+
container: container,
|
|
31
|
+
children: jsxs(DropdownMenuPrimitive.SubContent, {
|
|
32
|
+
...rest,
|
|
33
|
+
ref: forwardedRef,
|
|
34
|
+
className: clsx(styles.content, className),
|
|
35
|
+
"data-menu-fullscreen": true,
|
|
36
|
+
// Prevents submenu from closing, when detects focus outside submenu in fullscreen mode,
|
|
37
|
+
// while mouse is moving over submenu.
|
|
38
|
+
onFocusOutside: preventDefault,
|
|
39
|
+
children: [Header, jsx("div", {
|
|
40
|
+
className: styles.fullScreenContent,
|
|
41
|
+
children: OtherChildren
|
|
42
|
+
})]
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
}
|
|
18
46
|
return (
|
|
19
47
|
// TODO(DSYS-440): Unify all Portals (Tooltip, Select, DropdownMenu, etc...), Expose a Provider which would tell where to mount (body? other elem?)
|
|
20
48
|
jsx(DropdownMenuPrimitive.Portal, {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
2
2
|
export type DropdownMenuSubMenuRootProps = DropdownMenuPrimitive.DropdownMenuSubProps;
|
|
3
|
+
interface ResponsiveSubmenuContextValue {
|
|
4
|
+
open: boolean;
|
|
5
|
+
setOpen: (open: boolean) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const useResponsiveSubmenuContext: () => ResponsiveSubmenuContextValue;
|
|
3
8
|
/**
|
|
4
9
|
* Based on Radix-UI Sub
|
|
5
10
|
* @see https://www.radix-ui.com/docs/primitives/components/dropdown-menu#sub
|
|
@@ -8,3 +13,4 @@ export declare const DropdownMenuSubMenuRoot: {
|
|
|
8
13
|
(props: DropdownMenuSubMenuRootProps): import("react/jsx-runtime").JSX.Element;
|
|
9
14
|
displayName: string;
|
|
10
15
|
};
|
|
16
|
+
export {};
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
+
import noop from 'lodash/noop';
|
|
4
|
+
import { useContext, useCallback, createContext } from 'react';
|
|
5
|
+
import { useControllableState } from '../../utils/useControllableState.js';
|
|
6
|
+
import { SubMenuMenuProvider } from './SubMenuContext.js';
|
|
3
7
|
|
|
8
|
+
const ResponsiveSubmenuContext = /*#__PURE__*/createContext({
|
|
9
|
+
open: false,
|
|
10
|
+
setOpen: noop
|
|
11
|
+
});
|
|
12
|
+
const useResponsiveSubmenuContext = () => {
|
|
13
|
+
return useContext(ResponsiveSubmenuContext);
|
|
14
|
+
};
|
|
4
15
|
/**
|
|
5
16
|
* Based on Radix-UI Sub
|
|
6
17
|
* @see https://www.radix-ui.com/docs/primitives/components/dropdown-menu#sub
|
|
@@ -8,13 +19,30 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
|
8
19
|
const DropdownMenuSubMenuRoot = props => {
|
|
9
20
|
const {
|
|
10
21
|
children,
|
|
22
|
+
defaultOpen,
|
|
23
|
+
open,
|
|
24
|
+
onOpenChange,
|
|
11
25
|
...rest
|
|
12
26
|
} = props;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
const [isSubMenuOpen = false, setIsSubMenuOpen] = useControllableState({
|
|
28
|
+
prop: open,
|
|
29
|
+
onChange: onOpenChange,
|
|
30
|
+
defaultProp: defaultOpen
|
|
31
|
+
});
|
|
32
|
+
const handleOpenChange = useCallback(value => {
|
|
33
|
+
setIsSubMenuOpen(value);
|
|
34
|
+
}, [setIsSubMenuOpen]);
|
|
35
|
+
return jsx(SubMenuMenuProvider, {
|
|
36
|
+
isSubMenuOpen: isSubMenuOpen,
|
|
37
|
+
setIsSubMenuOpen: setIsSubMenuOpen,
|
|
38
|
+
children: jsx(DropdownMenuPrimitive.Sub, {
|
|
39
|
+
...rest,
|
|
40
|
+
onOpenChange: handleOpenChange,
|
|
41
|
+
open: isSubMenuOpen,
|
|
42
|
+
children: children
|
|
43
|
+
})
|
|
16
44
|
});
|
|
17
45
|
};
|
|
18
46
|
DropdownMenuSubMenuRoot.displayName = 'DropdownMenuSubMenuRoot';
|
|
19
47
|
|
|
20
|
-
export { DropdownMenuSubMenuRoot };
|
|
48
|
+
export { DropdownMenuSubMenuRoot, useResponsiveSubmenuContext };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
3
|
-
import { forwardRef } from 'react';
|
|
3
|
+
import { forwardRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { MenuItemSideContent, MenuItemMainContent } from '../../util-components/menu-item-sections/menu-item-sections.js';
|
|
5
5
|
import { useForkRef } from '../../utils/useForkRef.js';
|
|
6
6
|
import { useSubMenuFocus } from '../../utils/useSubMenuFocus.hook.js';
|
|
7
|
+
import { useResponsiveSubmenuContext } from './dropdown-menu-sub-menu-root.js';
|
|
7
8
|
import styles from './dropdown-menu.module.js';
|
|
9
|
+
import { useFullScreenMenu } from './FullScreenMenuContext.js';
|
|
8
10
|
|
|
9
11
|
const DropdownMenuSubMenuTriggerRoot = /*#__PURE__*/forwardRef((props, forwardedRef) => {
|
|
10
12
|
const {
|
|
@@ -12,11 +14,29 @@ const DropdownMenuSubMenuTriggerRoot = /*#__PURE__*/forwardRef((props, forwarded
|
|
|
12
14
|
...rest
|
|
13
15
|
} = props;
|
|
14
16
|
const [localRef, setRefFocusNoop] = useSubMenuFocus();
|
|
17
|
+
const {
|
|
18
|
+
open
|
|
19
|
+
} = useResponsiveSubmenuContext();
|
|
20
|
+
const {
|
|
21
|
+
isMenuFullScreenEnabled
|
|
22
|
+
} = useFullScreenMenu();
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!open) {
|
|
25
|
+
localRef.current?.focus();
|
|
26
|
+
}
|
|
27
|
+
}, [localRef, open]);
|
|
28
|
+
const handleOnPointerMove = useCallback(e => {
|
|
29
|
+
setRefFocusNoop();
|
|
30
|
+
// we should call prevent default so hover doesn't trigger submenu, which is confusing on full screen.
|
|
31
|
+
if (isMenuFullScreenEnabled) {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
}
|
|
34
|
+
}, [isMenuFullScreenEnabled, setRefFocusNoop]);
|
|
15
35
|
return jsx(DropdownMenuPrimitive.SubTrigger, {
|
|
16
36
|
...rest,
|
|
17
37
|
ref: useForkRef(localRef, forwardedRef),
|
|
18
38
|
className: styles.item,
|
|
19
|
-
onPointerMove:
|
|
39
|
+
onPointerMove: handleOnPointerMove,
|
|
20
40
|
children: children
|
|
21
41
|
});
|
|
22
42
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import '../../index.css';
|
|
2
|
-
var styles = {"content":"bp_dropdown_menu_module_content--
|
|
2
|
+
var styles = {"content":"bp_dropdown_menu_module_content--00a5d","fullScreenContent":"bp_dropdown_menu_module_fullScreenContent--00a5d","ellipsis":"bp_dropdown_menu_module_ellipsis--00a5d","checkmark":"bp_dropdown_menu_module_checkmark--00a5d","item":"bp_dropdown_menu_module_item--00a5d","subMenuTrigger":"bp_dropdown_menu_module_subMenuTrigger--00a5d","radioItem":"bp_dropdown_menu_module_radioItem--00a5d","checkboxItem":"bp_dropdown_menu_module_checkboxItem--00a5d","dropdownMenuItemSeparator":"bp_dropdown_menu_module_dropdownMenuItemSeparator--00a5d","menuHeader":"bp_dropdown_menu_module_menuHeader--00a5d","headerTextContent":"bp_dropdown_menu_module_headerTextContent--00a5d","submenuCloseButton":"bp_dropdown_menu_module_submenuCloseButton--00a5d","menuCloseButton":"bp_dropdown_menu_module_menuCloseButton--00a5d"};
|
|
3
3
|
|
|
4
4
|
export { styles as default };
|
|
@@ -20,6 +20,14 @@ export declare const DropdownMenu: {
|
|
|
20
20
|
MainContent: ({ className, label, description, ...rest }: import("../../util-components/menu-item-sections/menu-item-sections").MenuItemMainContentProps) => import("react/jsx-runtime").JSX.Element;
|
|
21
21
|
};
|
|
22
22
|
SubMenuContent: import("react").ForwardRefExoticComponent<import("./dropdown-menu-sub-menu-content").DropdownMenuSubMenuContentProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
23
|
+
Header: (({ children, className, ...rest }: import("react").HTMLAttributes<HTMLDivElement>) => import("react/jsx-runtime").JSX.Element) & {
|
|
24
|
+
MenuCloseButton: import("react").ForwardRefExoticComponent<Omit<import("./dropdown-menu-header").MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
25
|
+
SubMenuBackButton: import("react").ForwardRefExoticComponent<Omit<import("./dropdown-menu-header").MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
|
|
26
|
+
TextContent: import("react").ForwardRefExoticComponent<Omit<import("react").HTMLAttributes<HTMLDivElement>, "children"> & {
|
|
27
|
+
title: string;
|
|
28
|
+
subtitle?: string;
|
|
29
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
30
|
+
};
|
|
23
31
|
};
|
|
24
32
|
export { type DropdownMenuContentProps } from './dropdown-menu-content';
|
|
25
33
|
export { type DropdownMenuItemProps } from './dropdown-menu-item';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DropdownMenuCheckboxItem } from './dropdown-menu-checkbox-item.js';
|
|
2
2
|
import { DropdownMenuContent } from './dropdown-menu-content.js';
|
|
3
3
|
import { DropdownMenuGroup } from './dropdown-menu-group.js';
|
|
4
|
+
import { DropdownMenuHeader } from './dropdown-menu-header.js';
|
|
4
5
|
import { DropdownMenuItem } from './dropdown-menu-item.js';
|
|
5
6
|
import { DropdownMenuRadioGroup } from './dropdown-menu-radio-group.js';
|
|
6
7
|
import { DropdownMenuRadioSelectItem } from './dropdown-menu-radio-select-item.js';
|
|
@@ -23,7 +24,8 @@ const DropdownMenu = {
|
|
|
23
24
|
Separator: DropdownMenuSeparator,
|
|
24
25
|
SubMenuRoot: DropdownMenuSubMenuRoot,
|
|
25
26
|
SubMenuTrigger: DropdownMenuSubMenuTrigger,
|
|
26
|
-
SubMenuContent: DropdownMenuSubMenuContent
|
|
27
|
+
SubMenuContent: DropdownMenuSubMenuContent,
|
|
28
|
+
Header: DropdownMenuHeader
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export { DropdownMenu };
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
const filterNonHtmlProps = restProps => {
|
|
2
2
|
const div = document.createElement('div');
|
|
3
|
+
function isDivProperty(prop) {
|
|
4
|
+
return prop in div;
|
|
5
|
+
}
|
|
6
|
+
function isHtmlProperty(prop) {
|
|
7
|
+
return prop.toLowerCase().replace('capture', '') in HTMLElement.prototype;
|
|
8
|
+
}
|
|
9
|
+
function isException(prop) {
|
|
10
|
+
return prop.startsWith('data-') || prop.startsWith('aria-');
|
|
11
|
+
}
|
|
3
12
|
return Object.fromEntries(Object.entries(restProps).filter(([prop, value]) => {
|
|
4
|
-
|
|
5
|
-
const isDivProperty = (prop in div);
|
|
6
|
-
const isHtmlProperty = (prop.toLowerCase().replace('capture', '') in HTMLElement.prototype);
|
|
7
|
-
const isException = exceptions.has(prop);
|
|
8
|
-
return isDivProperty || isHtmlProperty || isException;
|
|
13
|
+
return isException(prop) || isDivProperty(prop) || isHtmlProperty(prop);
|
|
9
14
|
}));
|
|
10
15
|
};
|
|
11
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@box/blueprint-web",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"publishConfig": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"react-stately": "^3.31.1",
|
|
64
64
|
"tsx": "^4.16.5"
|
|
65
65
|
},
|
|
66
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "164a2abe192db942df9ea7e09bd39fbd2a9a1d2e",
|
|
67
67
|
"module": "lib-esm/index.js",
|
|
68
68
|
"main": "lib-esm/index.js",
|
|
69
69
|
"exports": {
|