@fluentui-react-native/menu 0.10.0 → 0.12.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 (108) hide show
  1. package/CHANGELOG.json +64 -1
  2. package/CHANGELOG.md +29 -2
  3. package/lib/Menu/useMenu.d.ts.map +1 -1
  4. package/lib/Menu/useMenu.js +5 -1
  5. package/lib/Menu/useMenu.js.map +1 -1
  6. package/lib/MenuItem/MenuItem.d.ts.map +1 -1
  7. package/lib/MenuItem/MenuItem.js +4 -2
  8. package/lib/MenuItem/MenuItem.js.map +1 -1
  9. package/lib/MenuItem/MenuItem.styling.d.ts.map +1 -1
  10. package/lib/MenuItem/MenuItem.styling.js +6 -0
  11. package/lib/MenuItem/MenuItem.styling.js.map +1 -1
  12. package/lib/MenuItem/MenuItem.types.d.ts +2 -0
  13. package/lib/MenuItem/MenuItem.types.d.ts.map +1 -1
  14. package/lib/MenuItem/MenuItemTokens.d.ts.map +1 -1
  15. package/lib/MenuItem/MenuItemTokens.js +2 -0
  16. package/lib/MenuItem/MenuItemTokens.js.map +1 -1
  17. package/lib/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
  18. package/lib/MenuItem/MenuItemTokens.win32.js +2 -0
  19. package/lib/MenuItem/MenuItemTokens.win32.js.map +1 -1
  20. package/lib/MenuItem/useMenuItem.d.ts.map +1 -1
  21. package/lib/MenuItem/useMenuItem.js +4 -2
  22. package/lib/MenuItem/useMenuItem.js.map +1 -1
  23. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js +1 -1
  24. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  25. package/lib/MenuPopover/MenuPopover.d.ts +1 -2
  26. package/lib/MenuPopover/MenuPopover.d.ts.map +1 -1
  27. package/lib/MenuPopover/MenuPopover.js +1 -1
  28. package/lib/MenuPopover/MenuPopover.js.map +1 -1
  29. package/lib/MenuPopover/MenuPopover.types.d.ts +5 -4
  30. package/lib/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  31. package/lib/MenuPopover/useMenuPopover.d.ts.map +1 -1
  32. package/lib/MenuPopover/useMenuPopover.js +13 -2
  33. package/lib/MenuPopover/useMenuPopover.js.map +1 -1
  34. package/lib/MenuTrigger/MenuTrigger.d.ts.map +1 -1
  35. package/lib/MenuTrigger/MenuTrigger.js +27 -4
  36. package/lib/MenuTrigger/MenuTrigger.js.map +1 -1
  37. package/lib/MenuTrigger/MenuTrigger.types.d.ts +14 -2
  38. package/lib/MenuTrigger/MenuTrigger.types.d.ts.map +1 -1
  39. package/lib/MenuTrigger/MenuTrigger.types.js.map +1 -1
  40. package/lib/MenuTrigger/useMenuTrigger.d.ts +2 -11
  41. package/lib/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  42. package/lib/MenuTrigger/useMenuTrigger.js +29 -1
  43. package/lib/MenuTrigger/useMenuTrigger.js.map +1 -1
  44. package/lib/context/menuTriggerContext.d.ts +9 -0
  45. package/lib/context/menuTriggerContext.d.ts.map +1 -0
  46. package/lib/context/menuTriggerContext.js +9 -0
  47. package/lib/context/menuTriggerContext.js.map +1 -0
  48. package/lib-commonjs/Menu/useMenu.d.ts.map +1 -1
  49. package/lib-commonjs/Menu/useMenu.js +5 -1
  50. package/lib-commonjs/Menu/useMenu.js.map +1 -1
  51. package/lib-commonjs/MenuItem/MenuItem.d.ts.map +1 -1
  52. package/lib-commonjs/MenuItem/MenuItem.js +3 -1
  53. package/lib-commonjs/MenuItem/MenuItem.js.map +1 -1
  54. package/lib-commonjs/MenuItem/MenuItem.styling.d.ts.map +1 -1
  55. package/lib-commonjs/MenuItem/MenuItem.styling.js +6 -0
  56. package/lib-commonjs/MenuItem/MenuItem.styling.js.map +1 -1
  57. package/lib-commonjs/MenuItem/MenuItem.types.d.ts +2 -0
  58. package/lib-commonjs/MenuItem/MenuItem.types.d.ts.map +1 -1
  59. package/lib-commonjs/MenuItem/MenuItemTokens.d.ts.map +1 -1
  60. package/lib-commonjs/MenuItem/MenuItemTokens.js +2 -0
  61. package/lib-commonjs/MenuItem/MenuItemTokens.js.map +1 -1
  62. package/lib-commonjs/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
  63. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js +2 -0
  64. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js.map +1 -1
  65. package/lib-commonjs/MenuItem/useMenuItem.d.ts.map +1 -1
  66. package/lib-commonjs/MenuItem/useMenuItem.js +4 -2
  67. package/lib-commonjs/MenuItem/useMenuItem.js.map +1 -1
  68. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js +1 -1
  69. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  70. package/lib-commonjs/MenuPopover/MenuPopover.d.ts +1 -2
  71. package/lib-commonjs/MenuPopover/MenuPopover.d.ts.map +1 -1
  72. package/lib-commonjs/MenuPopover/MenuPopover.js +1 -1
  73. package/lib-commonjs/MenuPopover/MenuPopover.js.map +1 -1
  74. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts +5 -4
  75. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  76. package/lib-commonjs/MenuPopover/useMenuPopover.d.ts.map +1 -1
  77. package/lib-commonjs/MenuPopover/useMenuPopover.js +12 -1
  78. package/lib-commonjs/MenuPopover/useMenuPopover.js.map +1 -1
  79. package/lib-commonjs/MenuTrigger/MenuTrigger.d.ts.map +1 -1
  80. package/lib-commonjs/MenuTrigger/MenuTrigger.js +25 -3
  81. package/lib-commonjs/MenuTrigger/MenuTrigger.js.map +1 -1
  82. package/lib-commonjs/MenuTrigger/MenuTrigger.types.d.ts +14 -2
  83. package/lib-commonjs/MenuTrigger/MenuTrigger.types.d.ts.map +1 -1
  84. package/lib-commonjs/MenuTrigger/MenuTrigger.types.js.map +1 -1
  85. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts +2 -11
  86. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  87. package/lib-commonjs/MenuTrigger/useMenuTrigger.js +30 -1
  88. package/lib-commonjs/MenuTrigger/useMenuTrigger.js.map +1 -1
  89. package/lib-commonjs/context/menuTriggerContext.d.ts +9 -0
  90. package/lib-commonjs/context/menuTriggerContext.d.ts.map +1 -0
  91. package/lib-commonjs/context/menuTriggerContext.js +14 -0
  92. package/lib-commonjs/context/menuTriggerContext.js.map +1 -0
  93. package/package.json +3 -3
  94. package/src/Menu/useMenu.ts +6 -0
  95. package/src/MenuItem/MenuItem.styling.ts +7 -0
  96. package/src/MenuItem/MenuItem.tsx +10 -5
  97. package/src/MenuItem/MenuItem.types.ts +2 -0
  98. package/src/MenuItem/MenuItemTokens.ts +2 -0
  99. package/src/MenuItem/MenuItemTokens.win32.ts +2 -0
  100. package/src/MenuItem/useMenuItem.ts +4 -2
  101. package/src/MenuItemCheckbox/useMenuItemCheckbox.ts +1 -1
  102. package/src/MenuPopover/MenuPopover.tsx +2 -0
  103. package/src/MenuPopover/MenuPopover.types.ts +5 -3
  104. package/src/MenuPopover/useMenuPopover.ts +17 -3
  105. package/src/MenuTrigger/MenuTrigger.tsx +32 -5
  106. package/src/MenuTrigger/MenuTrigger.types.ts +17 -3
  107. package/src/MenuTrigger/useMenuTrigger.ts +38 -4
  108. package/src/context/menuTriggerContext.ts +10 -0
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ /**
3
+ * This context keeps track of whether a trigger component is for a submenu.
4
+ * This allows the trigger to show a submenu indicator.
5
+ */
6
+ export declare const MenuTriggerContext: React.Context<boolean>;
7
+ export declare const MenuTriggerProvider: React.Provider<boolean>;
8
+ export declare const useMenuTriggerContext: () => boolean;
9
+ //# sourceMappingURL=menuTriggerContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menuTriggerContext.d.ts","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;GAGG;AACH,eAAO,MAAM,kBAAkB,wBAAsC,CAAC;AAEtE,eAAO,MAAM,mBAAmB,yBAA8B,CAAC;AAC/D,eAAO,MAAM,qBAAqB,eAA6C,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useMenuTriggerContext = exports.MenuTriggerProvider = exports.MenuTriggerContext = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var react_1 = (0, tslib_1.__importDefault)(require("react"));
6
+ /**
7
+ * This context keeps track of whether a trigger component is for a submenu.
8
+ * This allows the trigger to show a submenu indicator.
9
+ */
10
+ exports.MenuTriggerContext = react_1.default.createContext(false);
11
+ exports.MenuTriggerProvider = exports.MenuTriggerContext.Provider;
12
+ var useMenuTriggerContext = function () { return react_1.default.useContext(exports.MenuTriggerContext); };
13
+ exports.useMenuTriggerContext = useMenuTriggerContext;
14
+ //# sourceMappingURL=menuTriggerContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"menuTriggerContext.js","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":";;;;AAAA,6DAA0B;AAE1B;;;GAGG;AACU,QAAA,kBAAkB,GAAG,eAAK,CAAC,aAAa,CAAU,KAAK,CAAC,CAAC;AAEzD,QAAA,mBAAmB,GAAG,0BAAkB,CAAC,QAAQ,CAAC;AACxD,IAAM,qBAAqB,GAAG,cAAM,OAAA,eAAK,CAAC,UAAU,CAAC,0BAAkB,CAAC,EAApC,CAAoC,CAAC;AAAnE,QAAA,qBAAqB,yBAA8C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui-react-native/menu",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "A cross-platform Menu component using the Fluent Design System",
5
5
  "main": "lib-commonjs/index.js",
6
6
  "module": "lib/index.js",
@@ -23,10 +23,10 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "@fluentui-react-native/adapters": ">=0.8.5 <1.0.0",
26
- "@fluentui-react-native/callout": ">=0.20.3 <1.0.0",
26
+ "@fluentui-react-native/callout": ">=0.20.5 <1.0.0",
27
27
  "@fluentui-react-native/experimental-text": ">=0.9.0 <1.0.0",
28
28
  "@fluentui-react-native/framework": "0.7.30",
29
- "@fluentui-react-native/interactive-hooks": ">=0.15.10 <1.0.0",
29
+ "@fluentui-react-native/interactive-hooks": ">=0.16.1 <1.0.0",
30
30
  "@fluentui-react-native/theme-tokens": ">=0.18.0 <1.0.0",
31
31
  "@fluentui-react-native/tokens": ">=0.14.0 <1.0.0",
32
32
  "@fluentui-react-native/use-styling": ">=0.8.3 <1.0.0",
@@ -10,7 +10,13 @@ export const useMenu = (props: MenuProps): MenuState => {
10
10
  const isControlled = typeof props.open !== 'undefined';
11
11
  const [open, setOpen] = useMenuOpenState(isControlled, props);
12
12
 
13
+ // Default behaviot for submenu is to open on hover
14
+ // the ...props line below will override this behavior for a submenu
15
+ // or apply openOnHover if passed into a root Menu.
16
+ const openOnHover = isSubmenu;
17
+
13
18
  return {
19
+ openOnHover,
14
20
  ...props,
15
21
  open,
16
22
  setOpen,
@@ -47,6 +47,13 @@ export const stylingSettings: UseStylingOptions<MenuItemProps, MenuItemSlotProps
47
47
  (tokens: MenuItemTokens) => {
48
48
  return {
49
49
  color: tokens.color,
50
+ height: 16,
51
+ width: 16,
52
+ viewBox:
53
+ '0 0 ' +
54
+ (tokens.submenuIndicatorSize - tokens.submenuIndicatorPadding * 2) +
55
+ ' ' +
56
+ (tokens.submenuIndicatorSize - tokens.submenuIndicatorPadding * 2),
50
57
  };
51
58
  },
52
59
  ['color'],
@@ -1,5 +1,5 @@
1
1
  /** @jsx withSlots */
2
- import { View } from 'react-native';
2
+ import { I18nManager, View } from 'react-native';
3
3
  import { SvgXml } from 'react-native-svg';
4
4
  import { compose, mergeProps, UseSlots, withSlots } from '@fluentui-react-native/framework';
5
5
  import { Text } from '@fluentui-react-native/experimental-text';
@@ -22,10 +22,15 @@ export const MenuItem = compose<MenuItemType>({
22
22
 
23
23
  return (final: MenuItemProps) => {
24
24
  const mergedProps = mergeProps(menuItem.props, final);
25
- const chevronXml = `
26
- <svg width="12" height="16" viewBox="0 0 11 6">
27
- <path fill='currentColor' d='M0.646447 0.646447C0.841709 0.451184 1.15829 0.451184 1.35355 0.646447L5.5 4.79289L9.64645 0.646447C9.84171 0.451185 10.1583 0.451185 10.3536 0.646447C10.5488 0.841709 10.5488 1.15829 10.3536 1.35355L5.85355 5.85355C5.65829 6.04882 5.34171 6.04882 5.14645 5.85355L0.646447 1.35355C0.451184 1.15829 0.451184 0.841709 0.646447 0.646447Z' />
28
- </svg>`;
25
+ const chevronXml = I18nManager.isRTL
26
+ ? `
27
+ <svg>
28
+ <path fill='currentColor' d='M7.35355 2.14645C7.54882 2.34171 7.54882 2.65829 7.35355 2.85355L4.20711 6L7.35355 9.14645C7.54882 9.34171 7.54882 9.65829 7.35355 9.85355C7.15829 10.0488 6.84171 10.0488 6.64645 9.85355L3.14645 6.35355C2.95118 6.15829 2.95118 5.84171 3.14645 5.64645L6.64645 2.14645C6.84171 1.95118 7.15829 1.95118 7.35355 2.14645Z' />
29
+ </svg>`
30
+ : `
31
+ <svg>
32
+ <path fill='currentColor' d='M4.64645 2.14645C4.45118 2.34171 4.45118 2.65829 4.64645 2.85355L7.79289 6L4.64645 9.14645C4.45118 9.34171 4.45118 9.65829 4.64645 9.85355C4.84171 10.0488 5.15829 10.0488 5.35355 9.85355L8.85355 6.35355C9.04882 6.15829 9.04882 5.84171 8.85355 5.64645L5.35355 2.14645C5.15829 1.95118 4.84171 1.95118 4.64645 2.14645Z' />
33
+ </svg>`;
29
34
 
30
35
  return (
31
36
  <Slots.root {...mergedProps}>
@@ -10,6 +10,8 @@ export const menuItemName = 'MenuItem';
10
10
 
11
11
  export interface MenuItemTokens extends LayoutTokens, FontTokens, IBorderTokens, IColorTokens {
12
12
  checkmarkSize?: number;
13
+ submenuIndicatorPadding?: number;
14
+ submenuIndicatorSize?: number;
13
15
  gap?: number;
14
16
 
15
17
  disabled?: MenuItemTokens;
@@ -7,6 +7,8 @@ export const defaultMenuItemTokens: TokenSettings<MenuItemTokens, Theme> = (t: T
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.medium,
9
9
  checkmarkSize: 16,
10
+ submenuIndicatorPadding: globalTokens.spacing.none,
11
+ submenuIndicatorSize: 16,
10
12
  color: t.colors.neutralForeground2,
11
13
  fontFamily: t.typography.families.primary,
12
14
  fontSize: globalTokens.font.size[300],
@@ -7,6 +7,8 @@ export const defaultMenuItemTokens: TokenSettings<MenuItemTokens, Theme> = (t: T
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.none,
9
9
  checkmarkSize: 16,
10
+ submenuIndicatorPadding: globalTokens.spacing.xxs,
11
+ submenuIndicatorSize: 16,
10
12
  color: t.colors.neutralForeground1,
11
13
  fontFamily: t.typography.families.primary,
12
14
  fontSize: globalTokens.font.size[200],
@@ -5,6 +5,7 @@ import { memoize } from '@fluentui-react-native/framework';
5
5
  import { useAsPressable, useKeyProps } from '@fluentui-react-native/interactive-hooks';
6
6
  import { useMenuContext } from '../context/menuContext';
7
7
  import { useMenuListContext } from '../context/menuListContext';
8
+ import { useMenuTriggerContext } from '../context/menuTriggerContext';
8
9
 
9
10
  export const useMenuItem = (props: MenuItemProps): MenuItemState => {
10
11
  // attach the pressable state handlers
@@ -12,7 +13,8 @@ export const useMenuItem = (props: MenuItemProps): MenuItemState => {
12
13
  const { onClick, accessibilityState, componentRef = defaultComponentRef, disabled, ...rest } = props;
13
14
  const pressable = useAsPressable({ ...rest, disabled, onPress: onClick });
14
15
  const onKeyProps = useKeyProps(onClick, ' ', 'Enter');
15
- const hasSubmenu = useMenuContext().isSubmenu;
16
+ const isTrigger = useMenuTriggerContext();
17
+ const hasSubmenu = useMenuContext().isSubmenu && isTrigger;
16
18
  const hasCheckmarks = useMenuListContext().hasCheckmarks;
17
19
 
18
20
  return {
@@ -21,7 +23,7 @@ export const useMenuItem = (props: MenuItemProps): MenuItemState => {
21
23
  accessible: true,
22
24
  accessibilityRole: 'menuitem',
23
25
  onAccessibilityTap: props.onAccessibilityTap || props.onClick,
24
- accessibilityLabel: props.accessibilityLabel,
26
+ accessibilityLabel: props.accessibilityLabel || props.content,
25
27
  accessibilityState: getAccessibilityState(disabled, accessibilityState),
26
28
  enableFocusRing: true,
27
29
  focusable: !disabled,
@@ -93,7 +93,7 @@ export const useMenuCheckboxInteraction = (
93
93
  ...pressable.props,
94
94
  accessible: true,
95
95
  accessibilityActions: accessibilityActionsProp,
96
- accessibilityLabel: props.accessibilityLabel,
96
+ accessibilityLabel: props.accessibilityLabel || props.content,
97
97
  accessibilityRole: 'menuitem',
98
98
  accessibilityState: getAccessibilityState(disabled, state.checked, accessibilityState),
99
99
  enableFocusRing: true,
@@ -11,12 +11,14 @@ export const MenuPopover = stagedComponent((props: MenuPopoverProps) => {
11
11
  return (_rest: MenuPopoverProps, children: React.ReactNode) => {
12
12
  return (
13
13
  <Callout
14
+ accessibilityRole={state.accessibilityRole}
14
15
  borderWidth={1}
15
16
  borderColor={theme.colors.neutralStrokeAccessible}
16
17
  target={state.triggerRef}
17
18
  onDismiss={state.onDismiss}
18
19
  dismissBehaviors={state.dismissBehaviors}
19
20
  setInitialFocus={state.setInitialFocus}
21
+ directionalHint={state.directionalHint}
20
22
  doNotTakePointerCapture={state.doNotTakePointerCapture}
21
23
  >
22
24
  {children}
@@ -1,11 +1,13 @@
1
- import type { IViewProps } from '@fluentui-react-native/adapters';
2
- import { DismissBehaviors } from '@fluentui-react-native/callout';
1
+ import { DirectionalHint, DismissBehaviors, ICalloutProps } from '@fluentui-react-native/callout';
2
+ import { AccessibilityRole } from 'react-native';
3
3
 
4
4
  export const menuPopoverName = 'MenuPopover';
5
5
 
6
- export interface MenuPopoverProps extends Omit<IViewProps, 'onPress'> {}
6
+ export type MenuPopoverProps = ICalloutProps;
7
7
 
8
8
  export interface MenuPopoverState {
9
+ accessibilityRole: AccessibilityRole;
10
+ directionalHint?: DirectionalHint;
9
11
  dismissBehaviors: DismissBehaviors[];
10
12
  doNotTakePointerCapture: boolean;
11
13
  onDismiss: () => void;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { Platform } from 'react-native';
3
- import { DismissBehaviors } from '@fluentui-react-native/callout';
2
+ import { I18nManager, Platform } from 'react-native';
3
+ import { DirectionalHint, DismissBehaviors } from '@fluentui-react-native/callout';
4
4
  import { useMenuContext } from '../context/menuContext';
5
5
  import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types';
6
6
 
@@ -13,11 +13,25 @@ export const useMenuPopover = (_props: MenuPopoverProps): MenuPopoverState => {
13
13
  const dismissBehaviors = context.isControlled
14
14
  ? (['preventDismissOnKeyDown', 'preventDismissOnClickOutside'] as DismissBehaviors[])
15
15
  : undefined;
16
+ const directionalHint = getDirectionalHint(context.isSubmenu, I18nManager.isRTL);
16
17
 
17
18
  // Initial focus behavior differs per platform, Windows platforms move focus
18
19
  // automatically onto first element of Callout
19
20
  const setInitialFocus = Platform.OS === ('win32' as any) || Platform.OS === 'windows';
20
21
  const doNotTakePointerCapture = context.openOnHover;
22
+ const accessibilityRole = 'menu';
21
23
 
22
- return { triggerRef, onDismiss, dismissBehaviors, doNotTakePointerCapture, setInitialFocus };
24
+ return { accessibilityRole, triggerRef, onDismiss, directionalHint, dismissBehaviors, doNotTakePointerCapture, setInitialFocus };
25
+ };
26
+
27
+ const getDirectionalHint = (isSubmenu: boolean, isRtl: boolean): DirectionalHint | undefined => {
28
+ if (!isSubmenu) {
29
+ return undefined;
30
+ }
31
+
32
+ if (isRtl) {
33
+ return 'leftTopEdge';
34
+ }
35
+
36
+ return 'rightTopEdge';
23
37
  };
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
- import { stagedComponent } from '@fluentui-react-native/framework';
3
- import { menuTriggerName, MenuTriggerProps } from './MenuTrigger.types';
2
+ import { memoize, stagedComponent } from '@fluentui-react-native/framework';
3
+ import { menuTriggerName, MenuTriggerProps, MenuTriggerState } from './MenuTrigger.types';
4
4
  import { useMenuTrigger } from './useMenuTrigger';
5
+ import { AccessibilityActionEvent } from 'react-native';
6
+ import { MenuTriggerProvider } from '../context/menuTriggerContext';
5
7
 
6
8
  export const MenuTrigger = stagedComponent((props: MenuTriggerProps) => {
7
- const state = useMenuTrigger(props);
9
+ const menuTrigger = useMenuTrigger(props);
8
10
 
9
11
  return (_rest: MenuTriggerProps, children: React.ReactNode) => {
10
12
  const childrenArray = React.Children.toArray(children) as React.ReactElement[];
@@ -15,12 +17,37 @@ export const MenuTrigger = stagedComponent((props: MenuTriggerProps) => {
15
17
  }
16
18
  }
17
19
 
20
+ // In order to properly support accessibility without erasing props set on the
21
+ // child component which may affect accessibility, we need to modify the
22
+ // state in the inner render so we can access the child component and its props.
18
23
  const child = childrenArray[0];
19
- const revised = React.cloneElement(child, state);
24
+ const revisedState = getRevisedState(menuTrigger, child.props);
25
+ const revised = React.cloneElement(child, revisedState);
20
26
 
21
- return <>{revised}</>;
27
+ return <MenuTriggerProvider value={menuTrigger.hasSubmenu}>{revised}</MenuTriggerProvider>;
22
28
  };
23
29
  });
24
30
  MenuTrigger.displayName = menuTriggerName;
25
31
 
32
+ const getRevisedState = memoize(getRevisedStateWorker);
33
+ function getRevisedStateWorker(state: MenuTriggerState, props: any): MenuTriggerState {
34
+ const revisedState = { ...state };
35
+ if (props.accessibilityState) {
36
+ revisedState.props.accessibilityState = { ...state.props.accessibilityState, ...props.accessibilityState };
37
+ }
38
+
39
+ if (props.accessibilityActions) {
40
+ revisedState.props.accessibilityActions = { ...state.props.accessibilityActions, ...props.accessibilityActions };
41
+ }
42
+
43
+ if (props.onAccessibilityAction) {
44
+ revisedState.props.onAccessibilityAction = (e: AccessibilityActionEvent) => {
45
+ state.props.onAccessibilityAction(e);
46
+ props.onAccessibilityAction(e);
47
+ };
48
+ }
49
+
50
+ return revisedState;
51
+ }
52
+
26
53
  export default MenuTrigger;
@@ -1,7 +1,21 @@
1
- import type { IViewProps } from '@fluentui-react-native/adapters';
1
+ import { InteractionEvent, IWithPressableOptions } from '@fluentui-react-native/interactive-hooks';
2
+ import { ViewProps } from 'react-native';
2
3
 
3
4
  export const menuTriggerName = 'MenuTrigger';
4
5
 
5
- export interface MenuTriggerProps extends Omit<IViewProps, 'onPress'> {}
6
+ export interface MenuTriggerProps extends Omit<IWithPressableOptions<ViewProps>, 'onPress'> {
7
+ /**
8
+ * A RefObject to refer to the trigger component.
9
+ */
10
+ componentRef?: React.RefObject<React.Component>;
6
11
 
7
- export interface MenuTriggerState {}
12
+ /**
13
+ * A callback to call on button click event
14
+ */
15
+ onClick?: (e: InteractionEvent) => void;
16
+ }
17
+
18
+ export interface MenuTriggerState {
19
+ props: MenuTriggerProps;
20
+ hasSubmenu: boolean;
21
+ }
@@ -1,15 +1,36 @@
1
1
  import { useMenuContext } from '../context/menuContext';
2
2
  import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
3
- import { MenuTriggerProps } from './MenuTrigger.types';
4
- import { Platform } from 'react-native';
3
+ import { MenuTriggerProps, MenuTriggerState } from './MenuTrigger.types';
4
+ import { AccessibilityActionEvent, AccessibilityActionName, Platform } from 'react-native';
5
+ import React from 'react';
5
6
 
6
- export const useMenuTrigger = (_props: MenuTriggerProps) => {
7
+ const accessibilityActions =
8
+ Platform.OS === ('win32' as any) ? [{ name: 'Expand' as AccessibilityActionName }, { name: 'Collapse' as AccessibilityActionName }] : [];
9
+
10
+ export const useMenuTrigger = (_props: MenuTriggerProps): MenuTriggerState => {
7
11
  const context = useMenuContext();
8
12
 
9
13
  const setOpen = context.setOpen;
10
14
  const open = context.open;
11
15
  const openOnHover = context.openOnHover;
12
16
  const triggerRef = context.triggerRef;
17
+ const accessibilityState = context.open ? { expanded: true } : { expanded: false };
18
+ const onAccessibilityAction = React.useCallback(
19
+ (e: AccessibilityActionEvent) => {
20
+ if (Platform.OS === ('win32' as any)) {
21
+ switch (e.nativeEvent.actionName) {
22
+ case 'Expand':
23
+ setOpen(e, true /* isOpen */);
24
+ break;
25
+
26
+ case 'Collapse':
27
+ setOpen(e, false /* isOpen */);
28
+ break;
29
+ }
30
+ }
31
+ },
32
+ [setOpen],
33
+ );
13
34
 
14
35
  const delayHover = Platform.select({
15
36
  macos: 100,
@@ -32,5 +53,18 @@ export const useMenuTrigger = (_props: MenuTriggerProps) => {
32
53
  setOpen(e, !open);
33
54
  };
34
55
 
35
- return { onClick, onHoverIn, onHoverOut, componentRef: triggerRef, delayHoverIn: delayHover, delayHoverOut: delayHover };
56
+ return {
57
+ props: {
58
+ onClick,
59
+ onHoverIn,
60
+ onHoverOut: Platform.OS === ('win32' as any) && onHoverOut,
61
+ componentRef: triggerRef,
62
+ delayHoverIn: delayHover,
63
+ delayHoverOut: Platform.OS === ('win32' as any) && delayHover,
64
+ accessibilityState,
65
+ accessibilityActions,
66
+ onAccessibilityAction,
67
+ },
68
+ hasSubmenu: context.isSubmenu,
69
+ };
36
70
  };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+
3
+ /**
4
+ * This context keeps track of whether a trigger component is for a submenu.
5
+ * This allows the trigger to show a submenu indicator.
6
+ */
7
+ export const MenuTriggerContext = React.createContext<boolean>(false);
8
+
9
+ export const MenuTriggerProvider = MenuTriggerContext.Provider;
10
+ export const useMenuTriggerContext = () => React.useContext(MenuTriggerContext);