@box/blueprint-web 9.8.1 → 9.9.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.
Files changed (28) hide show
  1. package/lib-esm/index.css +56 -11
  2. package/lib-esm/primitives/context-menu/context-menu-item.js +4 -0
  3. package/lib-esm/primitives/context-menu/context-menu-menu-content.d.ts +5 -0
  4. package/lib-esm/primitives/context-menu/context-menu-menu-content.js +43 -3
  5. package/lib-esm/primitives/context-menu/context-menu-menu-header.d.ts +15 -0
  6. package/lib-esm/primitives/context-menu/context-menu-menu-header.js +95 -0
  7. package/lib-esm/primitives/context-menu/context-menu-menu-root.d.ts +3 -1
  8. package/lib-esm/primitives/context-menu/context-menu-menu-root.js +8 -3
  9. package/lib-esm/primitives/context-menu/context-menu-sub-menu-content.js +32 -2
  10. package/lib-esm/primitives/context-menu/context-menu-sub-menu-root.d.ts +6 -0
  11. package/lib-esm/primitives/context-menu/context-menu-sub-menu-root.js +32 -4
  12. package/lib-esm/primitives/context-menu/context-menu-sub-menu-trigger.js +23 -2
  13. package/lib-esm/primitives/context-menu/context-menu.module.js +1 -1
  14. package/lib-esm/primitives/context-menu/index.d.ts +8 -0
  15. package/lib-esm/primitives/context-menu/index.js +3 -1
  16. package/lib-esm/primitives/dropdown-menu/dropdown-menu-content.js +2 -2
  17. package/lib-esm/primitives/dropdown-menu/dropdown-menu-header.js +3 -3
  18. package/lib-esm/primitives/dropdown-menu/dropdown-menu-root.js +1 -1
  19. package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-content.js +2 -2
  20. package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-root.js +1 -1
  21. package/lib-esm/primitives/dropdown-menu/dropdown-menu-sub-menu-trigger.js +2 -2
  22. package/lib-esm/primitives/menu-utils/responsiveness/FullScreenContextMenuContext.d.ts +12 -0
  23. package/lib-esm/primitives/menu-utils/responsiveness/FullScreenContextMenuContext.js +32 -0
  24. package/lib-esm/primitives/{dropdown-menu/FullScreenMenuContext.d.ts → menu-utils/responsiveness/FullScreenDropdownMenuContext.d.ts} +1 -1
  25. package/lib-esm/primitives/{dropdown-menu/FullScreenMenuContext.js → menu-utils/responsiveness/FullScreenDropdownMenuContext.js} +3 -3
  26. package/lib-esm/primitives/{dropdown-menu → menu-utils/responsiveness}/SubMenuContext.js +1 -1
  27. package/package.json +4 -4
  28. /package/lib-esm/primitives/{dropdown-menu → menu-utils/responsiveness}/SubMenuContext.d.ts +0 -0
package/lib-esm/index.css CHANGED
@@ -4119,11 +4119,6 @@ table.bp_inline_table_module_inlineTable--b023b tr:not(:last-child) td{
4119
4119
  .bp_dropdown_menu_module_dropdownMenuItemSeparator--00a5d{
4120
4120
  margin-block:var(--space-2);
4121
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
4122
 
4128
4123
  .bp_dropdown_menu_module_menuHeader--00a5d{
4129
4124
  align-items:center;
@@ -4734,7 +4729,7 @@ div[data-radix-popper-content-wrapper]:has([role=menu]):has([data-state=open]):h
4734
4729
  }
4735
4730
  }
4736
4731
 
4737
- .bp_context_menu_module_menuBlock--b67f6{
4732
+ .bp_context_menu_module_menuBlock--77fef{
4738
4733
  background-color:var(--surface-menu-surface);
4739
4734
  border:var(--border-1) solid var(--border-card-border);
4740
4735
  border-radius:var(--radius-3);
@@ -4745,11 +4740,28 @@ div[data-radix-popper-content-wrapper]:has([role=menu]):has([data-state=open]):h
4745
4740
  min-width:var(--blueprint-web-context-menu-min-width, 160px);
4746
4741
  padding:var(--space-3);
4747
4742
  }
4748
- .bp_context_menu_module_menuBlock--b67f6 .bp_context_menu_module_menuItemsSeparator--b67f6{
4743
+ .bp_context_menu_module_menuBlock--77fef[data-menu-fullscreen=true]{
4744
+ border:unset;
4745
+ border-radius:unset;
4746
+ display:flex;
4747
+ flex-direction:column;
4748
+ height:100vh;
4749
+ max-height:none;
4750
+ max-width:none;
4751
+ overflow:hidden;
4752
+ padding:unset;
4753
+ position:relative;
4754
+ width:100vw;
4755
+ }
4756
+ .bp_context_menu_module_menuBlock--77fef[data-menu-fullscreen=true] .bp_context_menu_module_fullScreenContent--77fef{
4757
+ overflow-y:auto;
4758
+ padding:var(--space-3);
4759
+ }
4760
+ .bp_context_menu_module_menuBlock--77fef .bp_context_menu_module_menuItemsSeparator--77fef{
4749
4761
  margin-block:var(--space-2);
4750
4762
  margin-inline:var(--space-2);
4751
4763
  }
4752
- .bp_context_menu_module_menuBlock--b67f6 .bp_context_menu_module_menuItem--b67f6{
4764
+ .bp_context_menu_module_menuBlock--77fef .bp_context_menu_module_menuItem--77fef{
4753
4765
  align-items:center;
4754
4766
  background-color:var(--surface-menu-surface);
4755
4767
  border:var(--border-2) solid #0000;
@@ -4762,17 +4774,50 @@ div[data-radix-popper-content-wrapper]:has([role=menu]):has([data-state=open]):h
4762
4774
  padding-inline:var(--space-2);
4763
4775
  position:relative;
4764
4776
  }
4765
- .bp_context_menu_module_menuBlock--b67f6 .bp_context_menu_module_menuItem--b67f6[data-disabled]{
4777
+ .bp_context_menu_module_menuBlock--77fef .bp_context_menu_module_menuItem--77fef[data-disabled]{
4766
4778
  opacity:60%;
4767
4779
  pointer-events:none;
4768
4780
  }
4769
- .bp_context_menu_module_menuBlock--b67f6 .bp_context_menu_module_menuItem--b67f6[data-highlighted]:not(:hover){
4781
+ .bp_context_menu_module_menuBlock--77fef .bp_context_menu_module_menuItem--77fef[data-highlighted]:not(:hover){
4770
4782
  background-color:var(--surface-menu-surface-focus);
4771
4783
  border:var(--border-2) solid var(--outline-focus-on-light);
4772
4784
  }
4773
- .bp_context_menu_module_menuBlock--b67f6 .bp_context_menu_module_menuItem--b67f6:hover{
4785
+ .bp_context_menu_module_menuBlock--77fef .bp_context_menu_module_menuItem--77fef:hover{
4774
4786
  background-color:var(--surface-menu-surface-focus);
4775
4787
  }
4788
+ div[data-radix-popper-content-wrapper]:has([role=menu]):has([data-state=open]):has([data-menu-fullscreen=true]){
4789
+ height:100%;
4790
+ transform:none !important;
4791
+ width:100%;
4792
+ }
4793
+
4794
+ .bp_context_menu_module_menuHeader--77fef{
4795
+ align-items:center;
4796
+ box-shadow:var(--dropshadow-1);
4797
+ display:grid;
4798
+ gap:var(--space-2);
4799
+ grid-template-areas:"submenu-close content close";
4800
+ grid-template-columns:auto minmax(0, 1fr) auto;
4801
+ padding:var(--space-3);
4802
+ }
4803
+
4804
+ .bp_context_menu_module_headerTextContent--77fef{
4805
+ display:grid;
4806
+ }
4807
+
4808
+ .bp_context_menu_module_submenuCloseButton--77fef{
4809
+ grid-area:submenu-close;
4810
+ }
4811
+
4812
+ .bp_context_menu_module_menuCloseButton--77fef{
4813
+ grid-area:close;
4814
+ }
4815
+
4816
+ .bp_context_menu_module_ellipsis--77fef{
4817
+ overflow:hidden;
4818
+ text-overflow:ellipsis;
4819
+ white-space:nowrap;
4820
+ }
4776
4821
 
4777
4822
  .bp_notification_module_viewport--5d3de{
4778
4823
  all:unset;
@@ -19,6 +19,10 @@ const MenuItemRoot = /*#__PURE__*/forwardRef((props, forwardedRef) => {
19
19
  className: clsx(styles.menuItem, className),
20
20
  onPointerLeave: composeEventHandlers(props.onPointerLeave, preventDefault),
21
21
  onPointerMove: composeEventHandlers(props.onPointerMove, preventDefault),
22
+ // If click starts at trigger button, and ends on the item, it should not trigger the item.
23
+ // Note: this also has a side effect of not allowing to start click and end on a different time.
24
+ // TODO: see if possible to make https://github.com/radix-ui/primitives/blob/b32a93318cdfce383c2eec095710d35ffbd33a1c/packages/react/menu/src/Menu.tsx#L646
25
+ onPointerUp: preventDefault,
22
26
  children: children
23
27
  });
24
28
  });
@@ -1,4 +1,9 @@
1
1
  import * as ContextMenuPrimitve from '@radix-ui/react-context-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 ContextMenuMenuContentProps extends ContextMenuPrimitve.ContextMenuContentProps {
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 ContextMenuPrimitve from '@radix-ui/react-context-menu';
3
3
  import clsx from 'clsx';
4
- import { forwardRef } from 'react';
4
+ import { forwardRef, Children, isValidElement } from 'react';
5
+ import { useFullScreenContextMenu } from '../menu-utils/responsiveness/FullScreenContextMenuContext.js';
6
+ import { ContextMenuHeader } from './context-menu-menu-header.js';
5
7
  import styles from './context-menu.module.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 === ContextMenuHeader) {
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](https://www.radix-ui.com/docs/primitives/components/context-menu#content).
9
27
  */
@@ -14,6 +32,28 @@ const ContextMenuMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef) =>
14
32
  className,
15
33
  ...rest
16
34
  } = props;
35
+ const {
36
+ isMenuFullScreenEnabled
37
+ } = useFullScreenContextMenu();
38
+ if (isMenuFullScreenEnabled) {
39
+ const {
40
+ Header,
41
+ OtherChildren
42
+ } = sortMenuChildren(children);
43
+ return jsx(ContextMenuPrimitve.Portal, {
44
+ container: container,
45
+ children: jsxs(ContextMenuPrimitve.Content, {
46
+ ...rest,
47
+ ref: forwardedRef,
48
+ className: clsx(styles.menuBlock, className),
49
+ "data-menu-fullscreen": true,
50
+ children: [Header, jsx("div", {
51
+ className: styles.fullScreenContent,
52
+ children: OtherChildren
53
+ })]
54
+ })
55
+ });
56
+ }
17
57
  return jsx(ContextMenuPrimitve.Portal, {
18
58
  container: container,
19
59
  children: jsx(ContextMenuPrimitve.Content, {
@@ -26,4 +66,4 @@ const ContextMenuMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef) =>
26
66
  });
27
67
  ContextMenuMenuContent.displayName = 'ContextMenuMenuContent';
28
68
 
29
- export { ContextMenuMenuContent };
69
+ export { ContextMenuMenuContent, 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 ContextMenuHeader: (({ 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,95 @@
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 { useSubMenu } from '../menu-utils/responsiveness/SubMenuContext.js';
9
+ import styles from './context-menu.module.js';
10
+
11
+ const MenuCloseButton = /*#__PURE__*/forwardRef(({
12
+ onClick,
13
+ className,
14
+ ...rest
15
+ }, ref) => {
16
+ const handleCloseClick = useCallback(() => {
17
+ // Currently, there is no option to control the ContextMenu as no `open` prop is available.
18
+ // There is an open issue in the Radix-UI repository: https://github.com/radix-ui/primitives/issues/1307#issuecomment-1688574553.
19
+ // As a short-term workaround, until the issue is resolved, dispatch an Escape key event to close the menu:
20
+ const escapeEvent = new KeyboardEvent('keydown', {
21
+ key: 'Escape'
22
+ });
23
+ document.dispatchEvent(escapeEvent);
24
+ }, []);
25
+ return jsx(IconButton, {
26
+ ref: ref,
27
+ ...rest,
28
+ className: clsx(styles.menuCloseButton, className),
29
+ icon: XMark,
30
+ onClick: composeEventHandlers(handleCloseClick, onClick),
31
+ variant: "small-utility"
32
+ });
33
+ });
34
+ const SubMenuBackButton = /*#__PURE__*/forwardRef(({
35
+ onClick,
36
+ className,
37
+ ...rest
38
+ }, ref) => {
39
+ const {
40
+ setIsSubMenuOpen
41
+ } = useSubMenu();
42
+ const handleCloseClick = useCallback(() => {
43
+ setIsSubMenuOpen(false);
44
+ }, [setIsSubMenuOpen]);
45
+ return jsx(IconButton, {
46
+ ref: ref,
47
+ ...rest,
48
+ className: clsx(styles.submenuCloseButton, className),
49
+ icon: PointerChevronLeft,
50
+ onClick: composeEventHandlers(handleCloseClick, onClick)
51
+ });
52
+ });
53
+ const MenuHeaderPrimitive = ({
54
+ children,
55
+ className,
56
+ ...rest
57
+ }) => {
58
+ return jsx("div", {
59
+ ...rest,
60
+ className: clsx(styles.menuHeader, className),
61
+ role: "presentation",
62
+ children: children
63
+ });
64
+ };
65
+ const TextContent = /*#__PURE__*/forwardRef(({
66
+ title,
67
+ subtitle,
68
+ className,
69
+ ...rest
70
+ }, ref) => {
71
+ return jsxs("div", {
72
+ ref: ref,
73
+ ...rest,
74
+ className: clsx(className, styles.headerTextContent),
75
+ children: [jsx(Text, {
76
+ as: "span",
77
+ className: styles.ellipsis,
78
+ variant: "bodyLargeBold",
79
+ children: title
80
+ }), subtitle && jsx(Text, {
81
+ as: "span",
82
+ className: styles.ellipsis,
83
+ color: "textOnLightSecondary",
84
+ variant: "caption",
85
+ children: subtitle
86
+ })]
87
+ });
88
+ });
89
+ const ContextMenuHeader = Object.assign(MenuHeaderPrimitive, {
90
+ MenuCloseButton,
91
+ SubMenuBackButton,
92
+ TextContent
93
+ });
94
+
95
+ export { ContextMenuHeader };
@@ -1,5 +1,7 @@
1
1
  import { type ContextMenuProps as ContextMenuPrimitiveProps } from '@radix-ui/react-context-menu';
2
- export type ContextMenuMenuRootProps = ContextMenuPrimitiveProps;
2
+ export type ContextMenuMenuRootProps = ContextMenuPrimitiveProps & {
3
+ isFullScreenEnabled?: boolean;
4
+ };
3
5
  /**
4
6
  * Contains all the parts of a context menu.<br>
5
7
  * Based on Radix-UI context menu [Root](https://www.radix-ui.com/docs/primitives/components/context-menu#root).
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { Root } from '@radix-ui/react-context-menu';
3
+ import { FullScreenMenuProvider } from '../menu-utils/responsiveness/FullScreenContextMenuContext.js';
3
4
 
4
5
  /**
5
6
  * Contains all the parts of a context menu.<br>
@@ -8,11 +9,15 @@ import { Root } from '@radix-ui/react-context-menu';
8
9
  const ContextMenuMenuRoot = props => {
9
10
  const {
10
11
  children,
12
+ isFullScreenEnabled,
11
13
  ...rest
12
14
  } = props;
13
- return jsx(Root, {
14
- ...rest,
15
- children: children
15
+ return jsx(FullScreenMenuProvider, {
16
+ isFullScreenEnabled: isFullScreenEnabled,
17
+ children: jsx(Root, {
18
+ ...rest,
19
+ children: children
20
+ })
16
21
  });
17
22
  };
18
23
 
@@ -1,7 +1,9 @@
1
- import { jsx } from 'react/jsx-runtime';
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import * as ContextMenuPrimitve from '@radix-ui/react-context-menu';
3
3
  import clsx from 'clsx';
4
- import { forwardRef } from 'react';
4
+ import { forwardRef, useCallback } from 'react';
5
+ import { useFullScreenContextMenu } from '../menu-utils/responsiveness/FullScreenContextMenuContext.js';
6
+ import { sortMenuChildren } from './context-menu-menu-content.js';
5
7
  import styles from './context-menu.module.js';
6
8
 
7
9
  const alignOffsetPx = -12;
@@ -17,6 +19,34 @@ const ContextMenuSubMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef)
17
19
  className,
18
20
  ...rest
19
21
  } = props;
22
+ const {
23
+ isMenuFullScreenEnabled
24
+ } = useFullScreenContextMenu();
25
+ const preventDefault = useCallback(event => event.preventDefault(), []);
26
+ if (isMenuFullScreenEnabled) {
27
+ const {
28
+ Header,
29
+ OtherChildren
30
+ } = sortMenuChildren(children);
31
+ return jsx(ContextMenuPrimitve.Portal, {
32
+ container: container,
33
+ children: jsxs(ContextMenuPrimitve.SubContent, {
34
+ ...rest,
35
+ ref: forwardedRef,
36
+ alignOffset: alignOffsetPx,
37
+ className: clsx(styles.menuBlock, className),
38
+ "data-menu-fullscreen": true,
39
+ // Prevents submenu from closing, when detects focus outside submenu in fullscreen mode,
40
+ // while mouse is moving over submenu.
41
+ onFocusOutside: preventDefault,
42
+ sideOffset: sideOffsetPx,
43
+ children: [Header, jsx("div", {
44
+ className: styles.fullScreenContent,
45
+ children: OtherChildren
46
+ })]
47
+ })
48
+ });
49
+ }
20
50
  return jsx(ContextMenuPrimitve.Portal, {
21
51
  container: container,
22
52
  children: jsx(ContextMenuPrimitve.SubContent, {
@@ -1,5 +1,10 @@
1
1
  import { type ContextMenuSubProps as ContextMenuPrimitiveSubMenuProps } from '@radix-ui/react-context-menu';
2
2
  export type ContextMenuSubMenuRootProps = ContextMenuPrimitiveSubMenuProps;
3
+ interface ResponsiveSubmenuContextValue {
4
+ open: boolean;
5
+ setOpen: (open: boolean) => void;
6
+ }
7
+ export declare const useResponsiveSubmenuContext: () => ResponsiveSubmenuContextValue;
3
8
  /**
4
9
  * Container for all the parts of a sub menu.
5
10
  * Based on Radix-UI [SubMenu component](https://www.radix-ui.com/docs/primitives/components/context-menu#sub).
@@ -8,3 +13,4 @@ export declare const ContextMenuSubMenuRoot: {
8
13
  (props: ContextMenuSubMenuRootProps): 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 { Sub } from '@radix-ui/react-context-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 '../menu-utils/responsiveness/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
  * Container for all the parts of a sub menu.
6
17
  * Based on Radix-UI [SubMenu component](https://www.radix-ui.com/docs/primitives/components/context-menu#sub).
@@ -8,13 +19,30 @@ import { Sub } from '@radix-ui/react-context-menu';
8
19
  const ContextMenuSubMenuRoot = props => {
9
20
  const {
10
21
  children,
22
+ defaultOpen,
23
+ open,
24
+ onOpenChange,
11
25
  ...rest
12
26
  } = props;
13
- return jsx(Sub, {
14
- ...rest,
15
- children: children
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(Sub, {
39
+ ...rest,
40
+ onOpenChange: handleOpenChange,
41
+ open: isSubMenuOpen,
42
+ children: children
43
+ })
16
44
  });
17
45
  };
18
46
  ContextMenuSubMenuRoot.displayName = 'ContextMenuSubMenuRoot';
19
47
 
20
- export { ContextMenuSubMenuRoot };
48
+ export { ContextMenuSubMenuRoot, useResponsiveSubmenuContext };
@@ -1,9 +1,12 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { SubTrigger } from '@radix-ui/react-context-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
+ import { composeEventHandlers } from '../../utils/composeEventHandlers.js';
5
6
  import { useForkRef } from '../../utils/useForkRef.js';
6
7
  import { useSubMenuFocus } from '../../utils/useSubMenuFocus.hook.js';
8
+ import { useFullScreenContextMenu } from '../menu-utils/responsiveness/FullScreenContextMenuContext.js';
9
+ import { useResponsiveSubmenuContext } from './context-menu-sub-menu-root.js';
7
10
  import styles from './context-menu.module.js';
8
11
 
9
12
  const ContextMenuSubTriggerRoot = /*#__PURE__*/forwardRef((props, forwardedRef) => {
@@ -12,11 +15,29 @@ const ContextMenuSubTriggerRoot = /*#__PURE__*/forwardRef((props, forwardedRef)
12
15
  ...rest
13
16
  } = props;
14
17
  const [localRef, setRefFocusNoop] = useSubMenuFocus();
18
+ const {
19
+ open
20
+ } = useResponsiveSubmenuContext();
21
+ const {
22
+ isMenuFullScreenEnabled
23
+ } = useFullScreenContextMenu();
24
+ useEffect(() => {
25
+ if (!open) {
26
+ localRef.current?.focus();
27
+ }
28
+ }, [localRef, open]);
29
+ const handleOnPointerMove = useCallback(e => {
30
+ setRefFocusNoop();
31
+ // we should call prevent default so hover doesn't trigger submenu, which is confusing on full screen.
32
+ if (isMenuFullScreenEnabled) {
33
+ e.preventDefault();
34
+ }
35
+ }, [isMenuFullScreenEnabled, setRefFocusNoop]);
15
36
  return jsx(SubTrigger, {
16
37
  ...rest,
17
38
  ref: useForkRef(localRef, forwardedRef),
18
39
  className: styles.menuItem,
19
- onPointerMove: setRefFocusNoop,
40
+ onPointerMove: composeEventHandlers(props.onPointerMove, handleOnPointerMove),
20
41
  children: children
21
42
  });
22
43
  });
@@ -1,4 +1,4 @@
1
1
  import '../../index.css';
2
- var styles = {"menuBlock":"bp_context_menu_module_menuBlock--b67f6","menuItemsSeparator":"bp_context_menu_module_menuItemsSeparator--b67f6","menuItem":"bp_context_menu_module_menuItem--b67f6"};
2
+ var styles = {"menuBlock":"bp_context_menu_module_menuBlock--77fef","fullScreenContent":"bp_context_menu_module_fullScreenContent--77fef","menuItemsSeparator":"bp_context_menu_module_menuItemsSeparator--77fef","menuItem":"bp_context_menu_module_menuItem--77fef","menuHeader":"bp_context_menu_module_menuHeader--77fef","headerTextContent":"bp_context_menu_module_headerTextContent--77fef","submenuCloseButton":"bp_context_menu_module_submenuCloseButton--77fef","menuCloseButton":"bp_context_menu_module_menuCloseButton--77fef","ellipsis":"bp_context_menu_module_ellipsis--77fef"};
3
3
 
4
4
  export { styles as default };
@@ -16,6 +16,14 @@ export declare const ContextMenu: {
16
16
  SideContent: ({ children, className, ...rest }: import("../../util-components/menu-item-sections/menu-item-sections").MenuItemSideContentProps) => import("react/jsx-runtime").JSX.Element;
17
17
  MainContent: ({ className, label, description, ...rest }: import("../../util-components/menu-item-sections/menu-item-sections").MenuItemMainContentProps) => import("react/jsx-runtime").JSX.Element;
18
18
  };
19
+ Header: (({ children, className, ...rest }: import("react").HTMLAttributes<HTMLDivElement>) => import("react/jsx-runtime").JSX.Element) & {
20
+ MenuCloseButton: import("react").ForwardRefExoticComponent<Omit<import("./context-menu-menu-header").MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
21
+ SubMenuBackButton: import("react").ForwardRefExoticComponent<Omit<import("./context-menu-menu-header").MenuCloseButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
22
+ TextContent: import("react").ForwardRefExoticComponent<Omit<import("react").HTMLAttributes<HTMLDivElement>, "children"> & {
23
+ title: string;
24
+ subtitle?: string;
25
+ } & import("react").RefAttributes<HTMLDivElement>>;
26
+ };
19
27
  };
20
28
  export type { ContextMenuItemProps } from './context-menu-item';
21
29
  export type { ContextMenuMenuContentProps } from './context-menu-menu-content';
@@ -1,6 +1,7 @@
1
1
  import { ContextMenuItem } from './context-menu-item.js';
2
2
  import { ItemsSeparator } from './context-menu-items-separator.js';
3
3
  import { ContextMenuMenuContent } from './context-menu-menu-content.js';
4
+ import { ContextMenuHeader } from './context-menu-menu-header.js';
4
5
  import { ContextMenuMenuRoot } from './context-menu-menu-root.js';
5
6
  import { ContextMenuMenuTrigger } from './context-menu-menu-trigger.js';
6
7
  import { ContextMenuSubMenuContent } from './context-menu-sub-menu-content.js';
@@ -15,7 +16,8 @@ const ContextMenu = {
15
16
  SubMenuTrigger: ContextMenuSubMenuTrigger,
16
17
  SubMenuContent: ContextMenuSubMenuContent,
17
18
  ItemsSeparator,
18
- Item: ContextMenuItem
19
+ Item: ContextMenuItem,
20
+ Header: ContextMenuHeader
19
21
  };
20
22
 
21
23
  export { ContextMenu };
@@ -2,9 +2,9 @@ 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
4
  import { forwardRef, Children, isValidElement } from 'react';
5
+ import { useFullScreenDropdownMenu } from '../menu-utils/responsiveness/FullScreenDropdownMenuContext.js';
5
6
  import { DropdownMenuHeader } from './dropdown-menu-header.js';
6
7
  import styles from './dropdown-menu.module.js';
7
- import { useFullScreenMenu } from './FullScreenMenuContext.js';
8
8
 
9
9
  const sortMenuChildren = children => {
10
10
  return Children.toArray(children).reduce((acc, child) => {
@@ -35,7 +35,7 @@ const DropdownMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef) => {
35
35
  } = props;
36
36
  const {
37
37
  isMenuFullScreenEnabled
38
- } = useFullScreenMenu();
38
+ } = useFullScreenDropdownMenu();
39
39
  if (isMenuFullScreenEnabled) {
40
40
  const {
41
41
  Header,
@@ -5,9 +5,9 @@ import { forwardRef, useCallback } from 'react';
5
5
  import { Text } from '../../text/text.js';
6
6
  import { composeEventHandlers } from '../../utils/composeEventHandlers.js';
7
7
  import { IconButton } from '../icon-button/icon-button.js';
8
+ import { useFullScreenDropdownMenu } from '../menu-utils/responsiveness/FullScreenDropdownMenuContext.js';
9
+ import { useSubMenu } from '../menu-utils/responsiveness/SubMenuContext.js';
8
10
  import styles from './dropdown-menu.module.js';
9
- import { useFullScreenMenu } from './FullScreenMenuContext.js';
10
- import { useSubMenu } from './SubMenuContext.js';
11
11
 
12
12
  const MenuCloseButton = /*#__PURE__*/forwardRef(({
13
13
  onClick,
@@ -16,7 +16,7 @@ const MenuCloseButton = /*#__PURE__*/forwardRef(({
16
16
  }, ref) => {
17
17
  const {
18
18
  setIsMenuOpen
19
- } = useFullScreenMenu();
19
+ } = useFullScreenDropdownMenu();
20
20
  const handleCloseClick = useCallback(() => {
21
21
  setIsMenuOpen(false);
22
22
  }, [setIsMenuOpen]);
@@ -1,7 +1,7 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
  import { useControllableState } from '../../utils/useControllableState.js';
4
- import { FullScreenMenuProvider } from './FullScreenMenuContext.js';
4
+ import { FullScreenMenuProvider } from '../menu-utils/responsiveness/FullScreenDropdownMenuContext.js';
5
5
 
6
6
  /**
7
7
  * The Dropdown-menu component displays a menu to the user, such as a set of actions or functions—triggered by a button.
@@ -2,9 +2,9 @@ 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
4
  import { forwardRef, useCallback } from 'react';
5
+ import { useFullScreenDropdownMenu } from '../menu-utils/responsiveness/FullScreenDropdownMenuContext.js';
5
6
  import { sortMenuChildren } from './dropdown-menu-content.js';
6
7
  import styles from './dropdown-menu.module.js';
7
- import { useFullScreenMenu } from './FullScreenMenuContext.js';
8
8
 
9
9
  /**
10
10
  * Based on Radix-UI Sub Content
@@ -19,7 +19,7 @@ const DropdownMenuSubMenuContent = /*#__PURE__*/forwardRef((props, forwardedRef)
19
19
  } = props;
20
20
  const {
21
21
  isMenuFullScreenEnabled
22
- } = useFullScreenMenu();
22
+ } = useFullScreenDropdownMenu();
23
23
  const preventDefault = useCallback(event => event.preventDefault(), []);
24
24
  if (isMenuFullScreenEnabled) {
25
25
  const {
@@ -3,7 +3,7 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
3
3
  import noop from 'lodash/noop';
4
4
  import { useContext, useCallback, createContext } from 'react';
5
5
  import { useControllableState } from '../../utils/useControllableState.js';
6
- import { SubMenuMenuProvider } from './SubMenuContext.js';
6
+ import { SubMenuMenuProvider } from '../menu-utils/responsiveness/SubMenuContext.js';
7
7
 
8
8
  const ResponsiveSubmenuContext = /*#__PURE__*/createContext({
9
9
  open: false,
@@ -5,9 +5,9 @@ import { MenuItemSideContent, MenuItemMainContent } from '../../util-components/
5
5
  import { composeEventHandlers } from '../../utils/composeEventHandlers.js';
6
6
  import { useForkRef } from '../../utils/useForkRef.js';
7
7
  import { useSubMenuFocus } from '../../utils/useSubMenuFocus.hook.js';
8
+ import { useFullScreenDropdownMenu } from '../menu-utils/responsiveness/FullScreenDropdownMenuContext.js';
8
9
  import { useResponsiveSubmenuContext } from './dropdown-menu-sub-menu-root.js';
9
10
  import styles from './dropdown-menu.module.js';
10
- import { useFullScreenMenu } from './FullScreenMenuContext.js';
11
11
 
12
12
  const DropdownMenuSubMenuTriggerRoot = /*#__PURE__*/forwardRef((props, forwardedRef) => {
13
13
  const {
@@ -20,7 +20,7 @@ const DropdownMenuSubMenuTriggerRoot = /*#__PURE__*/forwardRef((props, forwarded
20
20
  } = useResponsiveSubmenuContext();
21
21
  const {
22
22
  isMenuFullScreenEnabled
23
- } = useFullScreenMenu();
23
+ } = useFullScreenDropdownMenu();
24
24
  useEffect(() => {
25
25
  if (!open) {
26
26
  localRef.current?.focus();
@@ -0,0 +1,12 @@
1
+ import React, { type ReactNode } from 'react';
2
+ interface FullScreenMenuContextProps {
3
+ isMenuFullScreenEnabled: boolean;
4
+ setIsMenuFullScreenEnabled: React.Dispatch<React.SetStateAction<boolean>>;
5
+ }
6
+ interface FullScreenProviderProps {
7
+ children: ReactNode;
8
+ isFullScreenEnabled?: boolean;
9
+ }
10
+ export declare const FullScreenMenuProvider: ({ children, isFullScreenEnabled }: FullScreenProviderProps) => import("react/jsx-runtime").JSX.Element;
11
+ export declare const useFullScreenContextMenu: () => FullScreenMenuContextProps;
12
+ export {};
@@ -0,0 +1,32 @@
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
+ }) => {
9
+ const [isMenuFullScreenEnabled, setIsMenuFullScreenEnabled] = useState(isFullScreenEnabled || false);
10
+ useEffect(() => {
11
+ if (isFullScreenEnabled !== undefined) {
12
+ setIsMenuFullScreenEnabled(isFullScreenEnabled);
13
+ }
14
+ }, [isFullScreenEnabled]);
15
+ const value = useMemo(() => ({
16
+ isMenuFullScreenEnabled,
17
+ setIsMenuFullScreenEnabled
18
+ }), [isMenuFullScreenEnabled, setIsMenuFullScreenEnabled]);
19
+ return jsx(FullScreenMenuContext.Provider, {
20
+ value: value,
21
+ children: children
22
+ });
23
+ };
24
+ const useFullScreenContextMenu = () => {
25
+ const context = useContext(FullScreenMenuContext);
26
+ if (context === null) {
27
+ throw new Error('useFullScreenMenu must be used within a menu ContextMenu.Root');
28
+ }
29
+ return context;
30
+ };
31
+
32
+ export { FullScreenMenuProvider, useFullScreenContextMenu };
@@ -12,5 +12,5 @@ interface FullScreenProviderProps {
12
12
  setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean | undefined>>;
13
13
  }
14
14
  export declare const FullScreenMenuProvider: ({ children, isFullScreenEnabled, isMenuOpen, setIsMenuOpen, }: FullScreenProviderProps) => import("react/jsx-runtime").JSX.Element;
15
- export declare const useFullScreenMenu: () => FullScreenMenuContextProps;
15
+ export declare const useFullScreenDropdownMenu: () => FullScreenMenuContextProps;
16
16
  export {};
@@ -25,12 +25,12 @@ const FullScreenMenuProvider = ({
25
25
  children: children
26
26
  });
27
27
  };
28
- const useFullScreenMenu = () => {
28
+ const useFullScreenDropdownMenu = () => {
29
29
  const context = useContext(FullScreenMenuContext);
30
30
  if (context === null) {
31
- throw new Error('useFullScreenMenu must be used within a DropdownMenu.Root');
31
+ throw new Error('useFullScreenMenu must be used within a menu DropdownMenu.Root');
32
32
  }
33
33
  return context;
34
34
  };
35
35
 
36
- export { FullScreenMenuProvider, useFullScreenMenu };
36
+ export { FullScreenMenuProvider, useFullScreenDropdownMenu };
@@ -19,7 +19,7 @@ const SubMenuMenuProvider = ({
19
19
  const useSubMenu = () => {
20
20
  const context = useContext(SubMenuContext);
21
21
  if (context === null) {
22
- throw new Error('useSubMenu must be used within a DropdownMenu.Root');
22
+ throw new Error('useSubMenu must be used within a menu .Root component');
23
23
  }
24
24
  return context;
25
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@box/blueprint-web",
3
- "version": "9.8.1",
3
+ "version": "9.9.0",
4
4
  "type": "module",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {
@@ -22,8 +22,8 @@
22
22
  "**/*.scss"
23
23
  ],
24
24
  "dependencies": {
25
- "@ariakit/react": "0.4.14",
26
- "@ariakit/react-core": "0.4.14",
25
+ "@ariakit/react": "0.4.15",
26
+ "@ariakit/react-core": "0.4.15",
27
27
  "@box/blueprint-web-assets": "^4.31.1",
28
28
  "@internationalized/date": "^3.5.4",
29
29
  "@radix-ui/react-accordion": "1.1.2",
@@ -63,7 +63,7 @@
63
63
  "react-stately": "^3.31.1",
64
64
  "tsx": "^4.16.5"
65
65
  },
66
- "gitHead": "31d71b2965eb39b2d6fe9febe825c915ec9e9549",
66
+ "gitHead": "669489f834920de03a726e0106414e4065a239c1",
67
67
  "module": "lib-esm/index.js",
68
68
  "main": "lib-esm/index.js",
69
69
  "exports": {