@fluentui-react-native/menu 0.5.2 → 0.8.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 (200) hide show
  1. package/CHANGELOG.json +46 -1
  2. package/CHANGELOG.md +26 -2
  3. package/lib/Menu/Menu.types.d.ts +8 -3
  4. package/lib/Menu/Menu.types.d.ts.map +1 -1
  5. package/lib/Menu/Menu.types.js.map +1 -1
  6. package/lib/Menu/useMenu.d.ts.map +1 -1
  7. package/lib/Menu/useMenu.js +19 -9
  8. package/lib/Menu/useMenu.js.map +1 -1
  9. package/lib/Menu/useMenuContextValue.js +2 -1
  10. package/lib/Menu/useMenuContextValue.js.map +1 -1
  11. package/lib/MenuItem/MenuItem.d.ts.map +1 -1
  12. package/lib/MenuItem/MenuItem.js +3 -1
  13. package/lib/MenuItem/MenuItem.js.map +1 -1
  14. package/lib/MenuItem/MenuItem.styling.d.ts.map +1 -1
  15. package/lib/MenuItem/MenuItem.styling.js +7 -0
  16. package/lib/MenuItem/MenuItem.styling.js.map +1 -1
  17. package/lib/MenuItem/MenuItem.types.d.ts +12 -7
  18. package/lib/MenuItem/MenuItem.types.d.ts.map +1 -1
  19. package/lib/MenuItem/MenuItemTokens.d.ts.map +1 -1
  20. package/lib/MenuItem/MenuItemTokens.js +2 -0
  21. package/lib/MenuItem/MenuItemTokens.js.map +1 -1
  22. package/lib/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
  23. package/lib/MenuItem/MenuItemTokens.win32.js +2 -0
  24. package/lib/MenuItem/MenuItemTokens.win32.js.map +1 -1
  25. package/lib/MenuItem/useMenuItem.d.ts.map +1 -1
  26. package/lib/MenuItem/useMenuItem.js +5 -1
  27. package/lib/MenuItem/useMenuItem.js.map +1 -1
  28. package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
  29. package/lib/MenuItemCheckbox/MenuItemCheckbox.js +6 -1
  30. package/lib/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
  31. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts.map +1 -1
  32. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.js +12 -6
  33. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.js.map +1 -1
  34. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.d.ts +14 -9
  35. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.d.ts.map +1 -1
  36. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.js.map +1 -1
  37. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts.map +1 -1
  38. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.js +20 -0
  39. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.js.map +1 -1
  40. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts.map +1 -1
  41. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js +20 -0
  42. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js.map +1 -1
  43. package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
  44. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js +31 -12
  45. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  46. package/lib/MenuList/MenuList.d.ts.map +1 -1
  47. package/lib/MenuList/MenuList.js +8 -2
  48. package/lib/MenuList/MenuList.js.map +1 -1
  49. package/lib/MenuList/MenuList.types.d.ts +8 -0
  50. package/lib/MenuList/MenuList.types.d.ts.map +1 -1
  51. package/lib/MenuList/MenuList.types.js.map +1 -1
  52. package/lib/MenuList/useMenuContextValue.d.ts +4 -0
  53. package/lib/MenuList/useMenuContextValue.d.ts.map +1 -0
  54. package/lib/MenuList/useMenuContextValue.js +5 -0
  55. package/lib/MenuList/useMenuContextValue.js.map +1 -0
  56. package/lib/MenuList/useMenuList.d.ts +3 -0
  57. package/lib/MenuList/useMenuList.d.ts.map +1 -0
  58. package/lib/MenuList/useMenuList.js +31 -0
  59. package/lib/MenuList/useMenuList.js.map +1 -0
  60. package/lib/MenuPopover/MenuPopover.d.ts.map +1 -1
  61. package/lib/MenuPopover/MenuPopover.js +1 -1
  62. package/lib/MenuPopover/MenuPopover.js.map +1 -1
  63. package/lib/MenuPopover/MenuPopover.types.d.ts +3 -0
  64. package/lib/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  65. package/lib/MenuPopover/MenuPopover.types.js.map +1 -1
  66. package/lib/MenuPopover/useMenuPopover.d.ts.map +1 -1
  67. package/lib/MenuPopover/useMenuPopover.js +7 -1
  68. package/lib/MenuPopover/useMenuPopover.js.map +1 -1
  69. package/lib/MenuTrigger/useMenuTrigger.d.ts +2 -1
  70. package/lib/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  71. package/lib/MenuTrigger/useMenuTrigger.js +9 -3
  72. package/lib/MenuTrigger/useMenuTrigger.js.map +1 -1
  73. package/lib/context/menuContext.d.ts.map +1 -1
  74. package/lib/context/menuContext.js +5 -0
  75. package/lib/context/menuContext.js.map +1 -1
  76. package/lib/context/menuListContext.d.ts +10 -0
  77. package/lib/context/menuListContext.d.ts.map +1 -0
  78. package/lib/context/menuListContext.js +11 -0
  79. package/lib/context/menuListContext.js.map +1 -0
  80. package/lib/index.d.ts +1 -0
  81. package/lib/index.d.ts.map +1 -1
  82. package/lib/index.js +1 -0
  83. package/lib/index.js.map +1 -1
  84. package/lib-commonjs/Menu/Menu.types.d.ts +8 -3
  85. package/lib-commonjs/Menu/Menu.types.d.ts.map +1 -1
  86. package/lib-commonjs/Menu/Menu.types.js.map +1 -1
  87. package/lib-commonjs/Menu/useMenu.d.ts.map +1 -1
  88. package/lib-commonjs/Menu/useMenu.js +18 -9
  89. package/lib-commonjs/Menu/useMenu.js.map +1 -1
  90. package/lib-commonjs/Menu/useMenuContextValue.js +2 -1
  91. package/lib-commonjs/Menu/useMenuContextValue.js.map +1 -1
  92. package/lib-commonjs/MenuItem/MenuItem.d.ts.map +1 -1
  93. package/lib-commonjs/MenuItem/MenuItem.js +3 -1
  94. package/lib-commonjs/MenuItem/MenuItem.js.map +1 -1
  95. package/lib-commonjs/MenuItem/MenuItem.styling.d.ts.map +1 -1
  96. package/lib-commonjs/MenuItem/MenuItem.styling.js +7 -0
  97. package/lib-commonjs/MenuItem/MenuItem.styling.js.map +1 -1
  98. package/lib-commonjs/MenuItem/MenuItem.types.d.ts +12 -7
  99. package/lib-commonjs/MenuItem/MenuItem.types.d.ts.map +1 -1
  100. package/lib-commonjs/MenuItem/MenuItemTokens.d.ts.map +1 -1
  101. package/lib-commonjs/MenuItem/MenuItemTokens.js +2 -0
  102. package/lib-commonjs/MenuItem/MenuItemTokens.js.map +1 -1
  103. package/lib-commonjs/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
  104. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js +2 -0
  105. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js.map +1 -1
  106. package/lib-commonjs/MenuItem/useMenuItem.d.ts.map +1 -1
  107. package/lib-commonjs/MenuItem/useMenuItem.js +5 -1
  108. package/lib-commonjs/MenuItem/useMenuItem.js.map +1 -1
  109. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
  110. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js +6 -1
  111. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
  112. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts.map +1 -1
  113. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.js +12 -6
  114. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.js.map +1 -1
  115. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.d.ts +14 -9
  116. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.d.ts.map +1 -1
  117. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.js.map +1 -1
  118. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts.map +1 -1
  119. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.js +20 -0
  120. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.js.map +1 -1
  121. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts.map +1 -1
  122. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js +20 -0
  123. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js.map +1 -1
  124. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
  125. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js +29 -10
  126. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  127. package/lib-commonjs/MenuList/MenuList.d.ts.map +1 -1
  128. package/lib-commonjs/MenuList/MenuList.js +8 -2
  129. package/lib-commonjs/MenuList/MenuList.js.map +1 -1
  130. package/lib-commonjs/MenuList/MenuList.types.d.ts +8 -0
  131. package/lib-commonjs/MenuList/MenuList.types.d.ts.map +1 -1
  132. package/lib-commonjs/MenuList/MenuList.types.js.map +1 -1
  133. package/lib-commonjs/MenuList/useMenuContextValue.d.ts +4 -0
  134. package/lib-commonjs/MenuList/useMenuContextValue.d.ts.map +1 -0
  135. package/lib-commonjs/MenuList/useMenuContextValue.js +9 -0
  136. package/lib-commonjs/MenuList/useMenuContextValue.js.map +1 -0
  137. package/lib-commonjs/MenuList/useMenuList.d.ts +3 -0
  138. package/lib-commonjs/MenuList/useMenuList.d.ts.map +1 -0
  139. package/lib-commonjs/MenuList/useMenuList.js +35 -0
  140. package/lib-commonjs/MenuList/useMenuList.js.map +1 -0
  141. package/lib-commonjs/MenuPopover/MenuPopover.d.ts.map +1 -1
  142. package/lib-commonjs/MenuPopover/MenuPopover.js +1 -1
  143. package/lib-commonjs/MenuPopover/MenuPopover.js.map +1 -1
  144. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts +3 -0
  145. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  146. package/lib-commonjs/MenuPopover/MenuPopover.types.js.map +1 -1
  147. package/lib-commonjs/MenuPopover/useMenuPopover.d.ts.map +1 -1
  148. package/lib-commonjs/MenuPopover/useMenuPopover.js +8 -1
  149. package/lib-commonjs/MenuPopover/useMenuPopover.js.map +1 -1
  150. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts +2 -1
  151. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  152. package/lib-commonjs/MenuTrigger/useMenuTrigger.js +9 -3
  153. package/lib-commonjs/MenuTrigger/useMenuTrigger.js.map +1 -1
  154. package/lib-commonjs/context/menuContext.d.ts.map +1 -1
  155. package/lib-commonjs/context/menuContext.js +5 -0
  156. package/lib-commonjs/context/menuContext.js.map +1 -1
  157. package/lib-commonjs/context/menuListContext.d.ts +10 -0
  158. package/lib-commonjs/context/menuListContext.d.ts.map +1 -0
  159. package/lib-commonjs/context/menuListContext.js +16 -0
  160. package/lib-commonjs/context/menuListContext.js.map +1 -0
  161. package/lib-commonjs/index.d.ts +1 -0
  162. package/lib-commonjs/index.d.ts.map +1 -1
  163. package/lib-commonjs/index.js +3 -1
  164. package/lib-commonjs/index.js.map +1 -1
  165. package/package.json +1 -1
  166. package/src/Menu/Menu.types.ts +8 -3
  167. package/src/Menu/useMenu.ts +26 -3
  168. package/src/Menu/useMenuContextValue.ts +1 -1
  169. package/src/MenuItem/MenuItem.styling.ts +10 -0
  170. package/src/MenuItem/MenuItem.tsx +3 -1
  171. package/src/MenuItem/MenuItem.types.ts +14 -8
  172. package/src/MenuItem/MenuItemTokens.ts +2 -0
  173. package/src/MenuItem/MenuItemTokens.win32.ts +2 -0
  174. package/src/MenuItem/useMenuItem.ts +5 -2
  175. package/src/MenuItemCheckbox/MenuItemCheckbox.styling.ts +19 -10
  176. package/src/MenuItemCheckbox/MenuItemCheckbox.tsx +12 -1
  177. package/src/MenuItemCheckbox/MenuItemCheckbox.types.ts +14 -10
  178. package/src/MenuItemCheckbox/MenuItemCheckboxTokens.ts +20 -0
  179. package/src/MenuItemCheckbox/MenuItemCheckboxTokens.win32.ts +20 -0
  180. package/src/MenuItemCheckbox/useMenuItemCheckbox.ts +64 -16
  181. package/src/MenuList/MenuList.tsx +11 -2
  182. package/src/MenuList/MenuList.types.ts +11 -1
  183. package/src/MenuList/useMenuContextValue.ts +6 -0
  184. package/src/MenuList/useMenuList.ts +49 -0
  185. package/src/MenuPopover/MenuPopover.tsx +7 -1
  186. package/src/MenuPopover/MenuPopover.types.ts +3 -0
  187. package/src/MenuPopover/useMenuPopover.ts +8 -1
  188. package/src/MenuTrigger/useMenuTrigger.ts +10 -3
  189. package/src/context/menuContext.ts +5 -0
  190. package/src/context/menuListContext.ts +18 -0
  191. package/src/index.ts +1 -0
  192. package/lib/context/menuTriggerContext.d.ts +0 -1
  193. package/lib/context/menuTriggerContext.d.ts.map +0 -1
  194. package/lib/context/menuTriggerContext.js +0 -2
  195. package/lib/context/menuTriggerContext.js.map +0 -1
  196. package/lib-commonjs/context/menuTriggerContext.d.ts +0 -1
  197. package/lib-commonjs/context/menuTriggerContext.d.ts.map +0 -1
  198. package/lib-commonjs/context/menuTriggerContext.js +0 -2
  199. package/lib-commonjs/context/menuTriggerContext.js.map +0 -1
  200. package/src/context/menuTriggerContext.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ViewProps } from 'react-native';
3
- import { SvgProps, XmlProps } from 'react-native-svg';
3
+ import { XmlProps } from 'react-native-svg';
4
4
  import type { IViewProps } from '@fluentui-react-native/adapters';
5
5
  import { TextProps } from '@fluentui-react-native/experimental-text';
6
6
  import { IFocusable, InteractionEvent, IPressableHooks, IWithPressableOptions } from '@fluentui-react-native/interactive-hooks';
@@ -9,6 +9,9 @@ import { FontTokens, IBorderTokens, IColorTokens, LayoutTokens } from '@fluentui
9
9
  export const menuItemName = 'MenuItem';
10
10
 
11
11
  export interface MenuItemTokens extends LayoutTokens, FontTokens, IBorderTokens, IColorTokens {
12
+ checkmarkSize?: number;
13
+ gap?: number;
14
+
12
15
  disabled?: MenuItemTokens;
13
16
  focused?: MenuItemTokens;
14
17
  hovered?: MenuItemTokens;
@@ -28,23 +31,26 @@ export interface MenuItemProps extends Omit<IWithPressableOptions<ViewProps>, 'o
28
31
  */
29
32
  componentRef?: React.RefObject<IFocusable>;
30
33
 
31
- /**
32
- * If the menu item is a trigger for a submenu
33
- */
34
- hasSubmenu?: boolean;
35
-
36
34
  /**
37
35
  * A callback to call on button click event
38
36
  */
39
37
  onClick?: (e: InteractionEvent) => void;
40
38
  }
41
39
 
42
- export type MenuItemState = IPressableHooks<MenuItemProps & React.ComponentPropsWithRef<any>>;
40
+ export interface MenuItemState extends IPressableHooks<MenuItemProps & React.ComponentPropsWithRef<any>> {
41
+ hasCheckmarks?: boolean;
42
+
43
+ /**
44
+ * If the menu item is a trigger for a submenu
45
+ */
46
+ hasSubmenu?: boolean;
47
+ }
43
48
 
44
49
  export interface MenuItemSlotProps {
45
50
  root: React.PropsWithRef<IViewProps>;
46
51
  content?: TextProps;
47
- submenuIndicator?: SvgProps | XmlProps;
52
+ checkmark?: React.PropsWithRef<IViewProps>;
53
+ submenuIndicator?: XmlProps;
48
54
  }
49
55
 
50
56
  export interface MenuItemType {
@@ -6,10 +6,12 @@ import { MenuItemTokens } from './MenuItem.types';
6
6
  export const defaultMenuItemTokens: TokenSettings<MenuItemTokens, Theme> = (t: Theme): MenuItemTokens => ({
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.medium,
9
+ checkmarkSize: 16,
9
10
  color: t.colors.neutralForeground2,
10
11
  fontFamily: t.typography.families.primary,
11
12
  fontSize: globalTokens.font.size[300],
12
13
  fontWeight: globalTokens.font.weight.regular as FontWeightValue,
14
+ gap: globalTokens.spacing.xs,
13
15
  minHeight: 32,
14
16
  minWidth: 160,
15
17
  maxWidth: 300,
@@ -6,10 +6,12 @@ import { MenuItemTokens } from './MenuItem.types';
6
6
  export const defaultMenuItemTokens: TokenSettings<MenuItemTokens, Theme> = (t: Theme): MenuItemTokens => ({
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.none,
9
+ checkmarkSize: 16,
9
10
  color: t.colors.neutralForeground1,
10
11
  fontFamily: t.typography.families.primary,
11
12
  fontSize: globalTokens.font.size[200],
12
13
  fontWeight: globalTokens.font.weight.regular as FontWeightValue,
14
+ gap: globalTokens.spacing.xs,
13
15
  minHeight: 24,
14
16
  minWidth: 160,
15
17
  maxWidth: 300,
@@ -4,6 +4,7 @@ import { MenuItemProps, MenuItemState } from './MenuItem.types';
4
4
  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
+ import { useMenuListContext } from '../context/menuListContext';
7
8
 
8
9
  export const useMenuItem = (props: MenuItemProps): MenuItemState => {
9
10
  // attach the pressable state handlers
@@ -12,22 +13,24 @@ export const useMenuItem = (props: MenuItemProps): MenuItemState => {
12
13
  const pressable = useAsPressable({ ...rest, disabled, onPress: onClick });
13
14
  const onKeyProps = useKeyProps(onClick, ' ', 'Enter');
14
15
  const hasSubmenu = useMenuContext().isSubmenu;
16
+ const hasCheckmarks = useMenuListContext().hasCheckmarks;
15
17
 
16
18
  return {
17
19
  props: {
18
20
  ...pressable.props,
19
21
  accessible: true,
20
- accessibilityRole: 'button',
22
+ accessibilityRole: 'menuitem',
21
23
  onAccessibilityTap: props.onAccessibilityTap || props.onClick,
22
24
  accessibilityLabel: props.accessibilityLabel,
23
25
  accessibilityState: getAccessibilityState(disabled, accessibilityState),
24
26
  enableFocusRing: true,
25
27
  focusable: !disabled,
26
- hasSubmenu,
27
28
  ref: componentRef,
28
29
  ...onKeyProps,
29
30
  },
30
31
  state: pressable.state,
32
+ hasSubmenu,
33
+ hasCheckmarks,
31
34
  };
32
35
  };
33
36
 
@@ -3,7 +3,7 @@ import { fontStyles, layoutStyles } from '@fluentui-react-native/tokens';
3
3
  import { defaultMenuItemCheckboxTokens } from './MenuItemCheckboxTokens';
4
4
  import { menuItemCheckboxName, MenuItemCheckboxProps, MenuItemCheckboxTokens, MenuItemCheckboxSlotProps } from './MenuItemCheckbox.types';
5
5
 
6
- export const menuItemCheckboxStates: (keyof MenuItemCheckboxTokens)[] = ['hovered', 'focused', 'pressed', 'disabled'];
6
+ export const menuItemCheckboxStates: (keyof MenuItemCheckboxTokens)[] = ['hovered', 'focused', 'pressed', 'disabled', 'checked'];
7
7
 
8
8
  export const stylingSettings: UseStylingOptions<MenuItemCheckboxProps, MenuItemCheckboxSlotProps, MenuItemCheckboxTokens> = {
9
9
  tokens: [defaultMenuItemCheckboxTokens, menuItemCheckboxName],
@@ -21,16 +21,25 @@ export const stylingSettings: UseStylingOptions<MenuItemCheckboxProps, MenuItemC
21
21
  }),
22
22
  ['backgroundColor', ...layoutStyles.keys],
23
23
  ),
24
+ checkmark: buildProps(
25
+ (tokens: MenuItemCheckboxTokens) => ({
26
+ opacity: tokens.checkmarkVisibility,
27
+ color: tokens.color,
28
+ height: tokens.checkmarkSize,
29
+ width: tokens.checkmarkSize,
30
+ viewBox: '0 0 ' + (tokens.checkmarkSize - tokens.checkmarkPadding * 2) + ' ' + (tokens.checkmarkSize - tokens.checkmarkPadding * 2),
31
+ style: { marginEnd: tokens.gap },
32
+ }),
33
+ ['checkmarkSize', 'gap', 'color'],
34
+ ),
24
35
  content: buildProps(
25
- (tokens: MenuItemCheckboxTokens, theme: Theme) => {
26
- return {
27
- style: {
28
- flexGrow: 1,
29
- color: tokens.color,
30
- ...fontStyles.from(tokens, theme),
31
- },
32
- };
33
- },
36
+ (tokens: MenuItemCheckboxTokens, theme: Theme) => ({
37
+ style: {
38
+ flexGrow: 1,
39
+ color: tokens.color,
40
+ ...fontStyles.from(tokens, theme),
41
+ },
42
+ }),
34
43
  ['color', ...fontStyles.keys],
35
44
  ),
36
45
  },
@@ -1,5 +1,6 @@
1
1
  /** @jsx withSlots */
2
2
  import { View } from 'react-native';
3
+ import { SvgXml } from 'react-native-svg';
3
4
  import { compose, mergeProps, UseSlots, withSlots } from '@fluentui-react-native/framework';
4
5
  import { Text } from '@fluentui-react-native/experimental-text';
5
6
  import { menuItemCheckboxName, MenuItemCheckboxProps, MenuItemCheckboxType } from './MenuItemCheckbox.types';
@@ -11,6 +12,7 @@ export const MenuItemCheckbox = compose<MenuItemCheckboxType>({
11
12
  ...stylingSettings,
12
13
  slots: {
13
14
  root: View,
15
+ checkmark: SvgXml,
14
16
  content: Text,
15
17
  },
16
18
  useRender: (userProps: MenuItemCheckboxProps, useSlots: UseSlots<MenuItemCheckboxType>) => {
@@ -19,8 +21,17 @@ export const MenuItemCheckbox = compose<MenuItemCheckboxType>({
19
21
 
20
22
  return (final: MenuItemCheckboxProps) => {
21
23
  const mergedProps = mergeProps(menuItem.props, final);
24
+ const chevronXml = `
25
+ <svg>
26
+ <path fill='currentColor' d='M9.85355 3.14645C10.0488 3.34171 10.0488 3.65829 9.85355 3.85355L5.35355 8.35355C5.15829 8.54882 4.84171 8.54882 4.64645 8.35355L2.64645 6.35355C2.45118 6.15829 2.45118 5.84171 2.64645 5.64645C2.84171 5.45118 3.15829 5.45118 3.35355 5.64645L5 7.29289L9.14645 3.14645C9.34171 2.95118 9.65829 2.95118 9.85355 3.14645Z' />
27
+ </svg>`;
22
28
 
23
- return <Slots.root {...mergedProps}>{mergedProps.content && <Slots.content>{mergedProps.content}</Slots.content>}</Slots.root>;
29
+ return (
30
+ <Slots.root {...mergedProps}>
31
+ <Slots.checkmark xml={chevronXml} />
32
+ {mergedProps.content && <Slots.content>{mergedProps.content}</Slots.content>}
33
+ </Slots.root>
34
+ );
24
35
  };
25
36
  },
26
37
  });
@@ -1,13 +1,21 @@
1
1
  import * as React from 'react';
2
- import { ViewProps } from 'react-native';
2
+ import { ColorValue, ViewProps } from 'react-native';
3
+ import { XmlProps } from 'react-native-svg';
3
4
  import type { IViewProps } from '@fluentui-react-native/adapters';
4
5
  import { TextProps } from '@fluentui-react-native/experimental-text';
5
- import { IFocusable, InteractionEvent, IPressableHooks, IWithPressableOptions } from '@fluentui-react-native/interactive-hooks';
6
+ import { IFocusable, IPressableHooks, IWithPressableOptions } from '@fluentui-react-native/interactive-hooks';
6
7
  import { FontTokens, IBorderTokens, IColorTokens, LayoutTokens } from '@fluentui-react-native/tokens';
7
8
 
8
9
  export const menuItemCheckboxName = 'MenuItemCheckbox';
9
10
 
10
11
  export interface MenuItemCheckboxTokens extends LayoutTokens, FontTokens, IBorderTokens, IColorTokens {
12
+ checkmarkColor?: ColorValue;
13
+ checkmarkPadding?: number;
14
+ checkmarkSize?: number;
15
+ checkmarkVisibility?: number;
16
+ gap?: number;
17
+
18
+ checked?: MenuItemCheckboxTokens;
11
19
  disabled?: MenuItemCheckboxTokens;
12
20
  focused?: MenuItemCheckboxTokens;
13
21
  hovered?: MenuItemCheckboxTokens;
@@ -28,20 +36,16 @@ export interface MenuItemCheckboxProps extends Omit<IWithPressableOptions<ViewPr
28
36
  componentRef?: React.RefObject<IFocusable>;
29
37
 
30
38
  /**
31
- * If the menu item is a trigger for a submenu
32
- */
33
- hasSubmenu?: boolean;
34
-
35
- /**
36
- * A callback to call on button click event
39
+ * Identifier for the control
37
40
  */
38
- onClick?: (e: InteractionEvent) => void;
41
+ name: string;
39
42
  }
40
43
 
41
- export type MenuItemCheckboxState = IPressableHooks<MenuItemCheckboxProps & React.ComponentPropsWithRef<any>>;
44
+ export interface MenuItemCheckboxState extends IPressableHooks<MenuItemCheckboxProps & React.ComponentPropsWithRef<any>> {}
42
45
 
43
46
  export interface MenuItemCheckboxSlotProps {
44
47
  root: React.PropsWithRef<IViewProps>;
48
+ checkmark?: XmlProps;
45
49
  content?: TextProps;
46
50
  }
47
51
 
@@ -6,10 +6,14 @@ import { MenuItemCheckboxTokens } from './MenuItemCheckbox.types';
6
6
  export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens, Theme> = (t: Theme): MenuItemCheckboxTokens => ({
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.medium,
9
+ checkmarkPadding: globalTokens.spacing.none,
10
+ checkmarkSize: 16,
11
+ checkmarkVisibility: 0,
9
12
  color: t.colors.neutralForeground2,
10
13
  fontFamily: t.typography.families.primary,
11
14
  fontSize: globalTokens.font.size[300],
12
15
  fontWeight: globalTokens.font.weight.regular as FontWeightValue,
16
+ gap: globalTokens.spacing.xs,
13
17
  minHeight: 32,
14
18
  minWidth: 160,
15
19
  maxWidth: 300,
@@ -17,13 +21,29 @@ export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens
17
21
  hovered: {
18
22
  backgroundColor: t.colors.neutralBackground1Hover,
19
23
  color: t.colors.neutralForeground2Hover,
24
+ checked: {
25
+ checkmarkColor: t.colors.neutralForeground2Hover,
26
+ checkmarkVisibility: 1,
27
+ },
20
28
  },
21
29
  pressed: {
22
30
  backgroundColor: t.colors.neutralBackground1Pressed,
23
31
  color: t.colors.neutralForeground2Pressed,
32
+ checked: {
33
+ checkmarkColor: t.colors.neutralForeground2Pressed,
34
+ checkmarkVisibility: 1,
35
+ },
24
36
  },
25
37
  disabled: {
26
38
  backgroundColor: t.colors.neutralBackground1,
27
39
  color: t.colors.neutralForegroundDisabled,
40
+ checked: {
41
+ checkmarkColor: t.colors.neutralForegroundDisabled,
42
+ checkmarkVisibility: 1,
43
+ },
44
+ },
45
+ checked: {
46
+ checkmarkColor: t.colors.neutralForeground2,
47
+ checkmarkVisibility: 1,
28
48
  },
29
49
  });
@@ -6,10 +6,14 @@ import { MenuItemCheckboxTokens } from './MenuItemCheckbox.types';
6
6
  export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens, Theme> = (t: Theme): MenuItemCheckboxTokens => ({
7
7
  backgroundColor: t.colors.neutralBackground1,
8
8
  borderRadius: globalTokens.corner.radius.none,
9
+ checkmarkPadding: globalTokens.spacing.xxs,
10
+ checkmarkSize: 16,
11
+ checkmarkVisibility: 0,
9
12
  color: t.colors.neutralForeground1,
10
13
  fontFamily: t.typography.families.primary,
11
14
  fontSize: globalTokens.font.size[200],
12
15
  fontWeight: globalTokens.font.weight.regular as FontWeightValue,
16
+ gap: globalTokens.spacing.xs,
13
17
  minHeight: 24,
14
18
  minWidth: 160,
15
19
  maxWidth: 300,
@@ -18,13 +22,29 @@ export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens
18
22
  hovered: {
19
23
  backgroundColor: t.colors.neutralBackground1Hover,
20
24
  color: t.colors.neutralForeground1Hover,
25
+ checked: {
26
+ checkmarkColor: t.colors.neutralForeground1Hover,
27
+ checkmarkVisibility: 1,
28
+ },
21
29
  },
22
30
  pressed: {
23
31
  backgroundColor: t.colors.neutralBackground1Pressed,
24
32
  color: t.colors.neutralForeground1Pressed,
33
+ checked: {
34
+ checkmarkColor: t.colors.neutralForeground1Pressed,
35
+ checkmarkVisibility: 1,
36
+ },
25
37
  },
26
38
  disabled: {
27
39
  backgroundColor: t.colors.neutralBackground1,
28
40
  color: t.colors.neutralForegroundDisabled,
41
+ checked: {
42
+ checkmarkColor: t.colors.neutralForegroundDisabled,
43
+ checkmarkVisibility: 1,
44
+ },
45
+ },
46
+ checked: {
47
+ checkmarkColor: t.colors.neutralForeground1,
48
+ checkmarkVisibility: 1,
29
49
  },
30
50
  });
@@ -1,40 +1,88 @@
1
1
  import * as React from 'react';
2
- import { AccessibilityState } from 'react-native';
2
+ import { AccessibilityActionEvent, AccessibilityState } from 'react-native';
3
3
  import { MenuItemCheckboxProps, MenuItemCheckboxState } from './MenuItemCheckbox.types';
4
4
  import { memoize } from '@fluentui-react-native/framework';
5
- import { useAsPressable, useKeyProps } from '@fluentui-react-native/interactive-hooks';
6
- import { useMenuContext } from '../context/menuContext';
5
+ import {
6
+ InteractionEvent,
7
+ useAsPressable,
8
+ useKeyProps,
9
+ useOnPressWithFocus,
10
+ useViewCommandFocus,
11
+ } from '@fluentui-react-native/interactive-hooks';
12
+ import { useMenuListContext } from '../context/menuListContext';
13
+
14
+ const defaultAccessibilityActions = [{ name: 'Toggle' }];
7
15
 
8
16
  export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
9
17
  // attach the pressable state handlers
10
18
  const defaultComponentRef = React.useRef(null);
11
- const { onClick, accessibilityState, componentRef = defaultComponentRef, disabled, ...rest } = props;
12
- const pressable = useAsPressable({ ...rest, disabled, onPress: onClick });
13
- const onKeyProps = useKeyProps(onClick, ' ', 'Enter');
14
- const hasSubmenu = useMenuContext().isSubmenu;
19
+ const {
20
+ accessibilityActions,
21
+ accessibilityState,
22
+ componentRef = defaultComponentRef,
23
+ disabled,
24
+ name,
25
+ onAccessibilityAction,
26
+ ...rest
27
+ } = props;
28
+ const context = useMenuListContext();
29
+ const checked = context.checked?.[name];
30
+ const onCheckedChange = context.onCheckedChange;
31
+
32
+ const toggleChecked = React.useCallback(
33
+ (e: InteractionEvent) => {
34
+ onCheckedChange(e, name, !checked);
35
+ },
36
+ [checked, name, onCheckedChange],
37
+ );
38
+ // Ensure focus is placed on checkbox after click
39
+ const toggleCheckedWithFocus = useOnPressWithFocus(componentRef, toggleChecked);
40
+
41
+ const pressable = useAsPressable({ onPress: toggleCheckedWithFocus, ...rest });
42
+ const buttonRef = useViewCommandFocus(componentRef);
43
+
44
+ const onKeyProps = useKeyProps(toggleChecked, ' ');
45
+ const accessibilityActionsProp = accessibilityActions
46
+ ? [...defaultAccessibilityActions, ...accessibilityActions]
47
+ : defaultAccessibilityActions;
48
+ const onAccessibilityActionProp = React.useCallback(
49
+ (event: AccessibilityActionEvent) => {
50
+ if (event.nativeEvent.actionName === 'Toggle') {
51
+ toggleChecked(event);
52
+ }
53
+ onAccessibilityAction && onAccessibilityAction(event);
54
+ },
55
+ [toggleChecked, onAccessibilityAction],
56
+ );
57
+
58
+ const state = {
59
+ ...pressable.state,
60
+ disabled: !!props.disabled,
61
+ checked: checked,
62
+ };
15
63
 
16
64
  return {
17
65
  props: {
18
66
  ...pressable.props,
19
67
  accessible: true,
20
- accessibilityRole: 'button',
21
- onAccessibilityTap: props.onAccessibilityTap || props.onClick,
68
+ accessibilityActions: accessibilityActionsProp,
22
69
  accessibilityLabel: props.accessibilityLabel,
23
- accessibilityState: getAccessibilityState(disabled, accessibilityState),
70
+ accessibilityRole: 'menuitem',
71
+ accessibilityState: getAccessibilityState(disabled, state.checked, accessibilityState),
24
72
  enableFocusRing: true,
25
73
  focusable: !disabled,
26
- hasSubmenu,
27
- ref: componentRef,
74
+ onAccessibilityAction: onAccessibilityActionProp,
75
+ ref: buttonRef,
28
76
  ...onKeyProps,
29
77
  },
30
- state: pressable.state,
78
+ state: state,
31
79
  };
32
80
  };
33
81
 
34
82
  const getAccessibilityState = memoize(getAccessibilityStateWorker);
35
- function getAccessibilityStateWorker(disabled: boolean, accessibilityState?: AccessibilityState) {
83
+ function getAccessibilityStateWorker(disabled: boolean, checked: boolean, accessibilityState?: AccessibilityState) {
36
84
  if (accessibilityState) {
37
- return { disabled, ...accessibilityState };
85
+ return { disabled, checked, ...accessibilityState };
38
86
  }
39
- return { disabled };
87
+ return { disabled, checked };
40
88
  }
@@ -4,6 +4,9 @@ import { View } from 'react-native';
4
4
  import { compose, UseSlots, withSlots } from '@fluentui-react-native/framework';
5
5
  import { menuListName, MenuListProps, MenuListType } from './MenuList.types';
6
6
  import { stylingSettings } from './MenuList.styling';
7
+ import { MenuListProvider } from '../context/menuListContext';
8
+ import { useMenuList } from './useMenuList';
9
+ import { useMenuListContextValue } from './useMenuContextValue';
7
10
 
8
11
  export const MenuList = compose<MenuListType>({
9
12
  displayName: menuListName,
@@ -12,10 +15,16 @@ export const MenuList = compose<MenuListType>({
12
15
  root: View,
13
16
  },
14
17
  useRender: (userProps: MenuListProps, useSlots: UseSlots<MenuListType>) => {
15
- const Slots = useSlots(userProps);
18
+ const menuList = useMenuList(userProps);
19
+ const contextValue = useMenuListContextValue(menuList);
20
+ const Slots = useSlots(menuList);
16
21
 
17
22
  return (_final: MenuListProps, children: React.ReactNode) => {
18
- return <Slots.root>{children}</Slots.root>;
23
+ return (
24
+ <MenuListProvider value={contextValue}>
25
+ <Slots.root>{children}</Slots.root>
26
+ </MenuListProvider>
27
+ );
19
28
  };
20
29
  },
21
30
  });
@@ -1,11 +1,21 @@
1
1
  import type { IViewProps } from '@fluentui-react-native/adapters';
2
+ import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
2
3
  import { IBackgroundColorTokens, LayoutTokens } from '@fluentui-react-native/tokens';
3
4
 
4
5
  export const menuListName = 'MenuList';
5
6
 
6
7
  export interface MenuListTokens extends LayoutTokens, IBackgroundColorTokens {}
7
8
 
8
- export interface MenuListProps extends Omit<IViewProps, 'onPress'> {}
9
+ export interface MenuListProps extends Omit<IViewProps, 'onPress'> {
10
+ checked?: Record<string, boolean>;
11
+ defaultChecked?: Record<string, boolean>;
12
+ hasCheckmarks?: boolean;
13
+ onCheckedChange?: (e: InteractionEvent, name: string, isChecked: boolean) => void;
14
+ }
15
+
16
+ export interface MenuListState extends MenuListProps {
17
+ isCheckedControlled: boolean;
18
+ }
9
19
 
10
20
  export interface MenuListSlotProps {
11
21
  root: React.PropsWithRef<IViewProps>;
@@ -0,0 +1,6 @@
1
+ import { MenuListContextValue } from '../context/menuListContext';
2
+ import { MenuListState } from './MenuList.types';
3
+
4
+ export const useMenuListContextValue = (state: MenuListState): MenuListContextValue => {
5
+ return { ...state };
6
+ };
@@ -0,0 +1,49 @@
1
+ import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
2
+ import React from 'react';
3
+ import { useMenuContext } from '../context/menuContext';
4
+ import { MenuListProps, MenuListState } from './MenuList.types';
5
+
6
+ export const useMenuList = (_props: MenuListProps): MenuListState => {
7
+ const context = useMenuContext();
8
+
9
+ // MenuList v2 needs to be able to be standalone, but this is not in scope for v1
10
+ // Assuming that checked information will come from parent Menu
11
+ const isCheckedControlled = typeof context.checked !== 'undefined';
12
+ const [checked, onCheckedChange] = useMenuCheckedState(isCheckedControlled, context);
13
+
14
+ return {
15
+ ...context,
16
+ isCheckedControlled,
17
+ checked,
18
+ onCheckedChange,
19
+ };
20
+ };
21
+
22
+ const useMenuCheckedState = (
23
+ isControlled: boolean,
24
+ props: MenuListProps,
25
+ ): [Record<string, boolean>, (e: InteractionEvent, name: string, isChecked: boolean) => void] => {
26
+ const { defaultChecked, onCheckedChange, checked } = props;
27
+ const initialState = defaultChecked ?? checked ?? {};
28
+ const [checkedInternal, setCheckedInternal] = React.useState<Record<string, boolean>>(initialState);
29
+
30
+ const state = isControlled ? checked : checkedInternal;
31
+
32
+ const setChecked = React.useCallback(
33
+ (e: InteractionEvent, name: string, isChecked: boolean) => {
34
+ if (!isControlled) {
35
+ const curChecked = state;
36
+ curChecked[name] = isChecked;
37
+ const updatedChecked = { ...curChecked };
38
+ setCheckedInternal(updatedChecked);
39
+ }
40
+
41
+ if (onCheckedChange) {
42
+ onCheckedChange(e, name, isChecked);
43
+ }
44
+ },
45
+ [isControlled, state, onCheckedChange, setCheckedInternal],
46
+ );
47
+
48
+ return [state, setChecked];
49
+ };
@@ -10,7 +10,13 @@ export const MenuPopover = stagedComponent((props: MenuPopoverProps) => {
10
10
 
11
11
  return (_rest: MenuPopoverProps, children: React.ReactNode) => {
12
12
  return (
13
- <Callout target={state.triggerRef} borderWidth={1} borderColor={theme.colors.neutralStrokeAccessible}>
13
+ <Callout
14
+ borderWidth={1}
15
+ borderColor={theme.colors.neutralStrokeAccessible}
16
+ target={state.triggerRef}
17
+ onDismiss={state.onDismiss}
18
+ dismissBehaviors={state.dismissBehaviors}
19
+ >
14
20
  {children}
15
21
  </Callout>
16
22
  );
@@ -1,9 +1,12 @@
1
1
  import type { IViewProps } from '@fluentui-react-native/adapters';
2
+ import { DismissBehaviors } from '@fluentui-react-native/callout';
2
3
 
3
4
  export const menuPopoverName = 'MenuPopover';
4
5
 
5
6
  export interface MenuPopoverProps extends Omit<IViewProps, 'onPress'> {}
6
7
 
7
8
  export interface MenuPopoverState {
9
+ dismissBehaviors: DismissBehaviors[];
10
+ onDismiss: () => void;
8
11
  triggerRef: React.RefObject<React.Component>;
9
12
  }
@@ -1,10 +1,17 @@
1
+ import React from 'react';
2
+ import { DismissBehaviors } from '@fluentui-react-native/callout';
1
3
  import { useMenuContext } from '../context/menuContext';
2
4
  import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types';
3
5
 
4
6
  export const useMenuPopover = (_props: MenuPopoverProps): MenuPopoverState => {
5
7
  const context = useMenuContext();
8
+ const setOpen = context.setOpen;
6
9
 
7
10
  const triggerRef = context.triggerRef;
11
+ const onDismiss = React.useCallback(() => setOpen(undefined, false /* isOpen */), [setOpen]);
12
+ const dismissBehaviors = context.isControlled
13
+ ? (['preventDismissOnKeyDown', 'preventDismissOnClickOutside'] as DismissBehaviors[])
14
+ : undefined;
8
15
 
9
- return { triggerRef };
16
+ return { triggerRef, onDismiss, dismissBehaviors };
10
17
  };
@@ -7,11 +7,18 @@ export const useMenuTrigger = (_props: MenuTriggerProps) => {
7
7
 
8
8
  const setOpen = context.setOpen;
9
9
  const open = context.open;
10
+ const openOnHover = context.openOnHover;
10
11
  const triggerRef = context.triggerRef;
11
12
 
12
- const onClick = (_e: InteractionEvent) => {
13
- setOpen(!open);
13
+ const onHoverIn = (e: InteractionEvent) => {
14
+ if (openOnHover) {
15
+ setOpen(e, true /* isOpen */);
16
+ }
14
17
  };
15
18
 
16
- return { onClick, componentRef: triggerRef };
19
+ const onClick = (e: InteractionEvent) => {
20
+ setOpen(e, !open);
21
+ };
22
+
23
+ return { onClick, onHoverIn, componentRef: triggerRef };
17
24
  };
@@ -7,8 +7,13 @@ import type { MenuState } from '../Menu/Menu.types';
7
7
  export type MenuContextValue = MenuState;
8
8
 
9
9
  export const MenuContext = React.createContext<MenuContextValue>({
10
+ isControlled: false,
11
+ checked: {},
12
+ defaultChecked: {},
13
+ hasCheckmarks: false,
10
14
  isSubmenu: false,
11
15
  open: false,
16
+ onCheckedChange: () => false,
12
17
  setOpen: () => false,
13
18
  triggerRef: null,
14
19
  });
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import type { MenuListState } from '../MenuList/MenuList.types';
3
+
4
+ /**
5
+ * Context shared between Menu and its child components
6
+ */
7
+ export type MenuListContextValue = MenuListState;
8
+
9
+ export const MenuListContext = React.createContext<MenuListContextValue>({
10
+ isCheckedControlled: false,
11
+ checked: {},
12
+ defaultChecked: {},
13
+ hasCheckmarks: false,
14
+ onCheckedChange: () => false,
15
+ });
16
+
17
+ export const MenuListProvider = MenuListContext.Provider;
18
+ export const useMenuListContext = () => React.useContext(MenuListContext);
package/src/index.ts CHANGED
@@ -2,4 +2,5 @@ export { Menu } from './Menu/Menu';
2
2
  export { MenuTrigger } from './MenuTrigger/MenuTrigger';
3
3
  export { MenuPopover } from './MenuPopover/MenuPopover';
4
4
  export { MenuItem } from './MenuItem/MenuItem';
5
+ export { MenuItemCheckbox } from './MenuItemCheckbox/MenuItemCheckbox';
5
6
  export { MenuList } from './MenuList/MenuList';
@@ -1 +0,0 @@
1
- //# sourceMappingURL=menuTriggerContext.d.ts.map