@fluentui-react-native/menu 0.5.1 → 0.7.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 (184) 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 +4 -1
  27. package/lib/MenuItem/useMenuItem.js.map +1 -1
  28. package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts +3 -0
  29. package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -0
  30. package/lib/MenuItemCheckbox/MenuItemCheckbox.js +25 -0
  31. package/lib/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -0
  32. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts +5 -0
  33. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts.map +1 -0
  34. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.js +26 -0
  35. package/lib/MenuItemCheckbox/MenuItemCheckbox.styling.js.map +1 -0
  36. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.d.ts +49 -0
  37. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.d.ts.map +1 -0
  38. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.js +2 -0
  39. package/lib/MenuItemCheckbox/MenuItemCheckbox.types.js.map +1 -0
  40. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts +5 -0
  41. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts.map +1 -0
  42. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.js +46 -0
  43. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.js.map +1 -0
  44. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts +5 -0
  45. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts.map +1 -0
  46. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js +47 -0
  47. package/lib/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js.map +1 -0
  48. package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts +3 -0
  49. package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -0
  50. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js +26 -0
  51. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -0
  52. package/lib/MenuList/MenuList.types.d.ts +1 -0
  53. package/lib/MenuList/MenuList.types.d.ts.map +1 -1
  54. package/lib/MenuPopover/MenuPopover.d.ts.map +1 -1
  55. package/lib/MenuPopover/MenuPopover.js +1 -1
  56. package/lib/MenuPopover/MenuPopover.js.map +1 -1
  57. package/lib/MenuPopover/MenuPopover.types.d.ts +3 -0
  58. package/lib/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  59. package/lib/MenuPopover/MenuPopover.types.js.map +1 -1
  60. package/lib/MenuPopover/useMenuPopover.d.ts.map +1 -1
  61. package/lib/MenuPopover/useMenuPopover.js +7 -1
  62. package/lib/MenuPopover/useMenuPopover.js.map +1 -1
  63. package/lib/MenuTrigger/useMenuTrigger.d.ts +2 -1
  64. package/lib/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  65. package/lib/MenuTrigger/useMenuTrigger.js +9 -3
  66. package/lib/MenuTrigger/useMenuTrigger.js.map +1 -1
  67. package/lib/context/menuContext.d.ts.map +1 -1
  68. package/lib/context/menuContext.js +1 -0
  69. package/lib/context/menuContext.js.map +1 -1
  70. package/lib/context/menuListContext.d.ts +1 -0
  71. package/lib/context/menuListContext.d.ts.map +1 -0
  72. package/lib/context/menuListContext.js +2 -0
  73. package/lib/context/menuListContext.js.map +1 -0
  74. package/lib/index.d.ts +1 -0
  75. package/lib/index.d.ts.map +1 -1
  76. package/lib/index.js +1 -0
  77. package/lib/index.js.map +1 -1
  78. package/lib-commonjs/Menu/Menu.types.d.ts +8 -3
  79. package/lib-commonjs/Menu/Menu.types.d.ts.map +1 -1
  80. package/lib-commonjs/Menu/Menu.types.js.map +1 -1
  81. package/lib-commonjs/Menu/useMenu.d.ts.map +1 -1
  82. package/lib-commonjs/Menu/useMenu.js +18 -9
  83. package/lib-commonjs/Menu/useMenu.js.map +1 -1
  84. package/lib-commonjs/Menu/useMenuContextValue.js +2 -1
  85. package/lib-commonjs/Menu/useMenuContextValue.js.map +1 -1
  86. package/lib-commonjs/MenuItem/MenuItem.d.ts.map +1 -1
  87. package/lib-commonjs/MenuItem/MenuItem.js +3 -1
  88. package/lib-commonjs/MenuItem/MenuItem.js.map +1 -1
  89. package/lib-commonjs/MenuItem/MenuItem.styling.d.ts.map +1 -1
  90. package/lib-commonjs/MenuItem/MenuItem.styling.js +7 -0
  91. package/lib-commonjs/MenuItem/MenuItem.styling.js.map +1 -1
  92. package/lib-commonjs/MenuItem/MenuItem.types.d.ts +12 -7
  93. package/lib-commonjs/MenuItem/MenuItem.types.d.ts.map +1 -1
  94. package/lib-commonjs/MenuItem/MenuItemTokens.d.ts.map +1 -1
  95. package/lib-commonjs/MenuItem/MenuItemTokens.js +2 -0
  96. package/lib-commonjs/MenuItem/MenuItemTokens.js.map +1 -1
  97. package/lib-commonjs/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
  98. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js +2 -0
  99. package/lib-commonjs/MenuItem/MenuItemTokens.win32.js.map +1 -1
  100. package/lib-commonjs/MenuItem/useMenuItem.d.ts.map +1 -1
  101. package/lib-commonjs/MenuItem/useMenuItem.js +4 -1
  102. package/lib-commonjs/MenuItem/useMenuItem.js.map +1 -1
  103. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts +3 -0
  104. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -0
  105. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js +28 -0
  106. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -0
  107. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts +5 -0
  108. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.d.ts.map +1 -0
  109. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.js +29 -0
  110. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.styling.js.map +1 -0
  111. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.d.ts +49 -0
  112. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.d.ts.map +1 -0
  113. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.js +5 -0
  114. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.types.js.map +1 -0
  115. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts +5 -0
  116. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.d.ts.map +1 -0
  117. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.js +50 -0
  118. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.js.map +1 -0
  119. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts +5 -0
  120. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.d.ts.map +1 -0
  121. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js +51 -0
  122. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckboxTokens.win32.js.map +1 -0
  123. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts +3 -0
  124. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -0
  125. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js +30 -0
  126. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -0
  127. package/lib-commonjs/MenuList/MenuList.types.d.ts +1 -0
  128. package/lib-commonjs/MenuList/MenuList.types.d.ts.map +1 -1
  129. package/lib-commonjs/MenuPopover/MenuPopover.d.ts.map +1 -1
  130. package/lib-commonjs/MenuPopover/MenuPopover.js +1 -1
  131. package/lib-commonjs/MenuPopover/MenuPopover.js.map +1 -1
  132. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts +3 -0
  133. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  134. package/lib-commonjs/MenuPopover/MenuPopover.types.js.map +1 -1
  135. package/lib-commonjs/MenuPopover/useMenuPopover.d.ts.map +1 -1
  136. package/lib-commonjs/MenuPopover/useMenuPopover.js +8 -1
  137. package/lib-commonjs/MenuPopover/useMenuPopover.js.map +1 -1
  138. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts +2 -1
  139. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  140. package/lib-commonjs/MenuTrigger/useMenuTrigger.js +9 -3
  141. package/lib-commonjs/MenuTrigger/useMenuTrigger.js.map +1 -1
  142. package/lib-commonjs/context/menuContext.d.ts.map +1 -1
  143. package/lib-commonjs/context/menuContext.js +1 -0
  144. package/lib-commonjs/context/menuContext.js.map +1 -1
  145. package/lib-commonjs/context/menuListContext.d.ts +1 -0
  146. package/lib-commonjs/context/menuListContext.d.ts.map +1 -0
  147. package/lib-commonjs/context/menuListContext.js +2 -0
  148. package/lib-commonjs/context/menuListContext.js.map +1 -0
  149. package/lib-commonjs/index.d.ts +1 -0
  150. package/lib-commonjs/index.d.ts.map +1 -1
  151. package/lib-commonjs/index.js +3 -1
  152. package/lib-commonjs/index.js.map +1 -1
  153. package/package.json +1 -1
  154. package/src/Menu/Menu.types.ts +8 -3
  155. package/src/Menu/useMenu.ts +26 -3
  156. package/src/Menu/useMenuContextValue.ts +1 -1
  157. package/src/MenuItem/MenuItem.styling.ts +10 -0
  158. package/src/MenuItem/MenuItem.tsx +3 -1
  159. package/src/MenuItem/MenuItem.types.ts +14 -8
  160. package/src/MenuItem/MenuItemTokens.ts +2 -0
  161. package/src/MenuItem/MenuItemTokens.win32.ts +2 -0
  162. package/src/MenuItem/useMenuItem.ts +3 -1
  163. package/src/MenuItemCheckbox/MenuItemCheckbox.styling.ts +45 -0
  164. package/src/MenuItemCheckbox/MenuItemCheckbox.tsx +37 -0
  165. package/src/MenuItemCheckbox/MenuItemCheckbox.types.ts +58 -0
  166. package/src/MenuItemCheckbox/MenuItemCheckboxTokens.ts +49 -0
  167. package/src/MenuItemCheckbox/MenuItemCheckboxTokens.win32.ts +50 -0
  168. package/src/MenuItemCheckbox/useMenuItemCheckbox.ts +40 -0
  169. package/src/MenuList/MenuList.types.ts +3 -1
  170. package/src/MenuPopover/MenuPopover.tsx +7 -1
  171. package/src/MenuPopover/MenuPopover.types.ts +3 -0
  172. package/src/MenuPopover/useMenuPopover.ts +8 -1
  173. package/src/MenuTrigger/useMenuTrigger.ts +10 -3
  174. package/src/context/menuContext.ts +1 -0
  175. package/src/context/{menuTriggerContext.ts → menuListContext.ts} +0 -0
  176. package/src/index.ts +1 -0
  177. package/lib/context/menuTriggerContext.d.ts +0 -1
  178. package/lib/context/menuTriggerContext.d.ts.map +0 -1
  179. package/lib/context/menuTriggerContext.js +0 -2
  180. package/lib/context/menuTriggerContext.js.map +0 -1
  181. package/lib-commonjs/context/menuTriggerContext.d.ts +0 -1
  182. package/lib-commonjs/context/menuTriggerContext.d.ts.map +0 -1
  183. package/lib-commonjs/context/menuTriggerContext.js +0 -2
  184. package/lib-commonjs/context/menuTriggerContext.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluentui-react-native/menu",
3
- "version": "0.5.1",
3
+ "version": "0.7.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",
@@ -1,14 +1,19 @@
1
- import type { IViewProps } from '@fluentui-react-native/adapters';
1
+ import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
2
2
  import React from 'react';
3
+ import type { MenuListProps } from '../MenuList/MenuList.types';
3
4
 
4
5
  export const menuName = 'Menu';
5
6
 
6
- export interface MenuProps extends Omit<IViewProps, 'onPress'> {
7
+ export interface MenuProps extends MenuListProps {
8
+ defaultOpen?: boolean;
7
9
  open?: boolean;
10
+ onOpenChange?: (e: InteractionEvent, isOpen: boolean) => void;
11
+ openOnHover?: boolean;
8
12
  }
9
13
 
10
14
  export interface MenuState extends MenuProps {
15
+ isControlled: boolean;
11
16
  isSubmenu: boolean;
12
- setOpen: (isOpen: boolean) => void;
17
+ setOpen: (e: InteractionEvent, isOpen: boolean) => void;
13
18
  triggerRef: React.RefObject<React.Component>;
14
19
  }
@@ -1,21 +1,44 @@
1
+ import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
1
2
  import React from 'react';
2
3
  import { useMenuContext } from '../context/menuContext';
3
4
  import { MenuProps, MenuState } from './Menu.types';
4
5
 
5
6
  export const useMenu = (props: MenuProps): MenuState => {
6
- const [open, setOpen] = useMenuOpenState(props);
7
7
  const triggerRef = React.useRef(null);
8
8
  const context = useMenuContext();
9
9
  const isSubmenu = context.triggerRef !== null;
10
+ const isControlled = typeof props.open !== 'undefined';
11
+ const [open, setOpen] = useMenuOpenState(isControlled, props);
10
12
 
11
13
  return {
14
+ ...props,
12
15
  open,
13
16
  setOpen,
14
17
  triggerRef,
15
18
  isSubmenu,
19
+ isControlled,
16
20
  };
17
21
  };
18
22
 
19
- const useMenuOpenState = (props: MenuProps) => {
20
- return React.useState<boolean>(props.open);
23
+ const useMenuOpenState = (isControlled: boolean, props: MenuProps): [boolean, (e: InteractionEvent, isOpen: boolean) => void] => {
24
+ const { defaultOpen, onOpenChange, open } = props;
25
+ const initialState = typeof defaultOpen !== 'undefined' ? defaultOpen : !!open;
26
+ const [openInternal, setOpenInternal] = React.useState<boolean>(initialState);
27
+
28
+ const state = isControlled ? open : openInternal;
29
+
30
+ const setOpen = React.useCallback(
31
+ (e: InteractionEvent, isOpen: boolean) => {
32
+ const openPrev = state;
33
+ if (!isControlled) {
34
+ setOpenInternal(isOpen);
35
+ }
36
+ if (onOpenChange && openPrev !== isOpen) {
37
+ onOpenChange(e, isOpen);
38
+ }
39
+ },
40
+ [isControlled, state, onOpenChange, setOpenInternal],
41
+ );
42
+
43
+ return [state, setOpen];
21
44
  };
@@ -2,5 +2,5 @@ import { MenuContextValue } from '../context/menuContext';
2
2
  import { MenuState } from './Menu.types';
3
3
 
4
4
  export const useMenuContextValue = (state: MenuState): MenuContextValue => {
5
- return { open: state.open, setOpen: state.setOpen, triggerRef: state.triggerRef, isSubmenu: state.isSubmenu };
5
+ return { ...state };
6
6
  };
@@ -21,6 +21,16 @@ export const stylingSettings: UseStylingOptions<MenuItemProps, MenuItemSlotProps
21
21
  }),
22
22
  ['backgroundColor', ...layoutStyles.keys],
23
23
  ),
24
+ checkmark: buildProps(
25
+ (tokens: MenuItemTokens) => ({
26
+ style: {
27
+ height: tokens.checkmarkSize,
28
+ width: tokens.checkmarkSize,
29
+ marginEnd: tokens.gap,
30
+ },
31
+ }),
32
+ ['checkmarkSize', 'gap'],
33
+ ),
24
34
  content: buildProps(
25
35
  (tokens: MenuItemTokens, theme: Theme) => {
26
36
  return {
@@ -12,6 +12,7 @@ export const MenuItem = compose<MenuItemType>({
12
12
  ...stylingSettings,
13
13
  slots: {
14
14
  root: View,
15
+ checkmark: View,
15
16
  content: Text,
16
17
  submenuIndicator: SvgXml,
17
18
  },
@@ -28,8 +29,9 @@ export const MenuItem = compose<MenuItemType>({
28
29
 
29
30
  return (
30
31
  <Slots.root {...mergedProps}>
32
+ {menuItem.hasCheckmarks && <Slots.checkmark />}
31
33
  {mergedProps.content && <Slots.content>{mergedProps.content}</Slots.content>}
32
- {mergedProps.hasSubmenu && <Slots.submenuIndicator xml={chevronXml} />}
34
+ {menuItem.hasSubmenu && <Slots.submenuIndicator xml={chevronXml} />}
33
35
  </Slots.root>
34
36
  );
35
37
  };
@@ -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,
@@ -12,6 +12,7 @@ export const useMenuItem = (props: MenuItemProps): MenuItemState => {
12
12
  const pressable = useAsPressable({ ...rest, disabled, onPress: onClick });
13
13
  const onKeyProps = useKeyProps(onClick, ' ', 'Enter');
14
14
  const hasSubmenu = useMenuContext().isSubmenu;
15
+ const hasCheckmarks = useMenuContext().hasCheckmarks;
15
16
 
16
17
  return {
17
18
  props: {
@@ -23,11 +24,12 @@ export const useMenuItem = (props: MenuItemProps): MenuItemState => {
23
24
  accessibilityState: getAccessibilityState(disabled, accessibilityState),
24
25
  enableFocusRing: true,
25
26
  focusable: !disabled,
26
- hasSubmenu,
27
27
  ref: componentRef,
28
28
  ...onKeyProps,
29
29
  },
30
30
  state: pressable.state,
31
+ hasSubmenu,
32
+ hasCheckmarks,
31
33
  };
32
34
  };
33
35
 
@@ -0,0 +1,45 @@
1
+ import { Theme, UseStylingOptions, buildProps } from '@fluentui-react-native/framework';
2
+ import { fontStyles, layoutStyles } from '@fluentui-react-native/tokens';
3
+ import { defaultMenuItemCheckboxTokens } from './MenuItemCheckboxTokens';
4
+ import { menuItemCheckboxName, MenuItemCheckboxProps, MenuItemCheckboxTokens, MenuItemCheckboxSlotProps } from './MenuItemCheckbox.types';
5
+
6
+ export const menuItemCheckboxStates: (keyof MenuItemCheckboxTokens)[] = ['hovered', 'focused', 'pressed', 'disabled', 'checked'];
7
+
8
+ export const stylingSettings: UseStylingOptions<MenuItemCheckboxProps, MenuItemCheckboxSlotProps, MenuItemCheckboxTokens> = {
9
+ tokens: [defaultMenuItemCheckboxTokens, menuItemCheckboxName],
10
+ states: menuItemCheckboxStates,
11
+ slotProps: {
12
+ root: buildProps(
13
+ (tokens: MenuItemCheckboxTokens, theme: Theme) => ({
14
+ style: {
15
+ alignItems: 'center',
16
+ backgroundColor: tokens.backgroundColor,
17
+ display: 'flex',
18
+ flexDirection: 'row',
19
+ ...layoutStyles.from(tokens, theme),
20
+ },
21
+ }),
22
+ ['backgroundColor', ...layoutStyles.keys],
23
+ ),
24
+ checkmark: buildProps(
25
+ (tokens: MenuItemCheckboxTokens) => ({
26
+ color: tokens.color,
27
+ height: tokens.checkmarkSize,
28
+ width: tokens.checkmarkSize,
29
+ viewBox: '0 0 ' + (tokens.checkmarkSize - tokens.checkmarkPadding * 2) + ' ' + (tokens.checkmarkSize - tokens.checkmarkPadding * 2),
30
+ style: { marginEnd: tokens.gap },
31
+ }),
32
+ ['checkmarkSize', 'gap', 'color'],
33
+ ),
34
+ content: buildProps(
35
+ (tokens: MenuItemCheckboxTokens, theme: Theme) => ({
36
+ style: {
37
+ flexGrow: 1,
38
+ color: tokens.color,
39
+ ...fontStyles.from(tokens, theme),
40
+ },
41
+ }),
42
+ ['color', ...fontStyles.keys],
43
+ ),
44
+ },
45
+ };
@@ -0,0 +1,37 @@
1
+ /** @jsx withSlots */
2
+ import { View } from 'react-native';
3
+ import { SvgXml } from 'react-native-svg';
4
+ import { compose, mergeProps, UseSlots, withSlots } from '@fluentui-react-native/framework';
5
+ import { Text } from '@fluentui-react-native/experimental-text';
6
+ import { menuItemCheckboxName, MenuItemCheckboxProps, MenuItemCheckboxType } from './MenuItemCheckbox.types';
7
+ import { useMenuItemCheckbox } from './useMenuItemCheckbox';
8
+ import { stylingSettings } from './MenuItemCheckbox.styling';
9
+
10
+ export const MenuItemCheckbox = compose<MenuItemCheckboxType>({
11
+ displayName: menuItemCheckboxName,
12
+ ...stylingSettings,
13
+ slots: {
14
+ root: View,
15
+ checkmark: SvgXml,
16
+ content: Text,
17
+ },
18
+ useRender: (userProps: MenuItemCheckboxProps, useSlots: UseSlots<MenuItemCheckboxType>) => {
19
+ const menuItem = useMenuItemCheckbox(userProps);
20
+ const Slots = useSlots(userProps, (layer): boolean => menuItem.state[layer]);
21
+
22
+ return (final: MenuItemCheckboxProps) => {
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>`;
28
+
29
+ return (
30
+ <Slots.root {...mergedProps}>
31
+ <Slots.checkmark xml={chevronXml} />
32
+ {mergedProps.content && <Slots.content>{mergedProps.content}</Slots.content>}
33
+ </Slots.root>
34
+ );
35
+ };
36
+ },
37
+ });
@@ -0,0 +1,58 @@
1
+ import * as React from 'react';
2
+ import { ColorValue, ViewProps } from 'react-native';
3
+ import { XmlProps } from 'react-native-svg';
4
+ import type { IViewProps } from '@fluentui-react-native/adapters';
5
+ import { TextProps } from '@fluentui-react-native/experimental-text';
6
+ import { IFocusable, InteractionEvent, IPressableHooks, IWithPressableOptions } from '@fluentui-react-native/interactive-hooks';
7
+ import { FontTokens, IBorderTokens, IColorTokens, LayoutTokens } from '@fluentui-react-native/tokens';
8
+
9
+ export const menuItemCheckboxName = 'MenuItemCheckbox';
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;
19
+ disabled?: MenuItemCheckboxTokens;
20
+ focused?: MenuItemCheckboxTokens;
21
+ hovered?: MenuItemCheckboxTokens;
22
+ pressed?: MenuItemCheckboxTokens;
23
+ }
24
+
25
+ export interface MenuItemCheckboxProps extends Omit<IWithPressableOptions<ViewProps>, 'onPress'> {
26
+ content: string;
27
+
28
+ /**
29
+ * Applies disabled styles to menu item but remains focusable
30
+ */
31
+ disabled?: boolean;
32
+
33
+ /**
34
+ * A RefObject to access the IButton interface. Use this to access the public methods and properties of the component.
35
+ */
36
+ componentRef?: React.RefObject<IFocusable>;
37
+
38
+ /**
39
+ * A callback to call on button click event
40
+ */
41
+ onClick?: (e: InteractionEvent) => void;
42
+ }
43
+
44
+ export interface MenuItemCheckboxState extends IPressableHooks<MenuItemCheckboxProps & React.ComponentPropsWithRef<any>> {
45
+ hasCheckmarks?: boolean;
46
+ }
47
+
48
+ export interface MenuItemCheckboxSlotProps {
49
+ root: React.PropsWithRef<IViewProps>;
50
+ checkmark?: XmlProps;
51
+ content?: TextProps;
52
+ }
53
+
54
+ export interface MenuItemCheckboxType {
55
+ props: MenuItemCheckboxProps;
56
+ tokens: MenuItemCheckboxTokens;
57
+ slotProps: MenuItemCheckboxSlotProps;
58
+ }
@@ -0,0 +1,49 @@
1
+ import { FontWeightValue, Theme } from '@fluentui-react-native/framework';
2
+ import { globalTokens } from '@fluentui-react-native/theme-tokens';
3
+ import { TokenSettings } from '@fluentui-react-native/use-styling';
4
+ import { MenuItemCheckboxTokens } from './MenuItemCheckbox.types';
5
+
6
+ export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens, Theme> = (t: Theme): MenuItemCheckboxTokens => ({
7
+ backgroundColor: t.colors.neutralBackground1,
8
+ borderRadius: globalTokens.corner.radius.medium,
9
+ checkmarkPadding: globalTokens.spacing.none,
10
+ checkmarkSize: 16,
11
+ checkmarkVisibility: 0,
12
+ color: t.colors.neutralForeground2,
13
+ fontFamily: t.typography.families.primary,
14
+ fontSize: globalTokens.font.size[300],
15
+ fontWeight: globalTokens.font.weight.regular as FontWeightValue,
16
+ gap: globalTokens.spacing.xs,
17
+ minHeight: 32,
18
+ minWidth: 160,
19
+ maxWidth: 300,
20
+ padding: globalTokens.spacing.sNudge,
21
+ hovered: {
22
+ backgroundColor: t.colors.neutralBackground1Hover,
23
+ color: t.colors.neutralForeground2Hover,
24
+ checked: {
25
+ checkmarkColor: t.colors.neutralForeground2Hover,
26
+ checkmarkVisibility: 1,
27
+ },
28
+ },
29
+ pressed: {
30
+ backgroundColor: t.colors.neutralBackground1Pressed,
31
+ color: t.colors.neutralForeground2Pressed,
32
+ checked: {
33
+ checkmarkColor: t.colors.neutralForeground2Pressed,
34
+ checkmarkVisibility: 1,
35
+ },
36
+ },
37
+ disabled: {
38
+ backgroundColor: t.colors.neutralBackground1,
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,
48
+ },
49
+ });
@@ -0,0 +1,50 @@
1
+ import { FontWeightValue, Theme } from '@fluentui-react-native/framework';
2
+ import { globalTokens } from '@fluentui-react-native/theme-tokens';
3
+ import { TokenSettings } from '@fluentui-react-native/use-styling';
4
+ import { MenuItemCheckboxTokens } from './MenuItemCheckbox.types';
5
+
6
+ export const defaultMenuItemCheckboxTokens: TokenSettings<MenuItemCheckboxTokens, Theme> = (t: Theme): MenuItemCheckboxTokens => ({
7
+ backgroundColor: t.colors.neutralBackground1,
8
+ borderRadius: globalTokens.corner.radius.none,
9
+ checkmarkPadding: globalTokens.spacing.xxs,
10
+ checkmarkSize: 16,
11
+ checkmarkVisibility: 0,
12
+ color: t.colors.neutralForeground1,
13
+ fontFamily: t.typography.families.primary,
14
+ fontSize: globalTokens.font.size[200],
15
+ fontWeight: globalTokens.font.weight.regular as FontWeightValue,
16
+ gap: globalTokens.spacing.xs,
17
+ minHeight: 24,
18
+ minWidth: 160,
19
+ maxWidth: 300,
20
+ padding: globalTokens.spacing.xs,
21
+ paddingHorizontal: globalTokens.spacing.s,
22
+ hovered: {
23
+ backgroundColor: t.colors.neutralBackground1Hover,
24
+ color: t.colors.neutralForeground1Hover,
25
+ checked: {
26
+ checkmarkColor: t.colors.neutralForeground1Hover,
27
+ checkmarkVisibility: 1,
28
+ },
29
+ },
30
+ pressed: {
31
+ backgroundColor: t.colors.neutralBackground1Pressed,
32
+ color: t.colors.neutralForeground1Pressed,
33
+ checked: {
34
+ checkmarkColor: t.colors.neutralForeground1Pressed,
35
+ checkmarkVisibility: 1,
36
+ },
37
+ },
38
+ disabled: {
39
+ backgroundColor: t.colors.neutralBackground1,
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,
49
+ },
50
+ });
@@ -0,0 +1,40 @@
1
+ import * as React from 'react';
2
+ import { AccessibilityState } from 'react-native';
3
+ import { MenuItemCheckboxProps, MenuItemCheckboxState } from './MenuItemCheckbox.types';
4
+ import { memoize } from '@fluentui-react-native/framework';
5
+ import { useAsPressable, useKeyProps } from '@fluentui-react-native/interactive-hooks';
6
+ import { useMenuContext } from '../context/menuContext';
7
+
8
+ export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
9
+ // attach the pressable state handlers
10
+ 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 hasCheckmarks = useMenuContext().hasCheckmarks;
15
+
16
+ return {
17
+ props: {
18
+ ...pressable.props,
19
+ accessible: true,
20
+ accessibilityRole: 'button',
21
+ onAccessibilityTap: props.onAccessibilityTap || props.onClick,
22
+ accessibilityLabel: props.accessibilityLabel,
23
+ accessibilityState: getAccessibilityState(disabled, accessibilityState),
24
+ enableFocusRing: true,
25
+ focusable: !disabled,
26
+ ref: componentRef,
27
+ ...onKeyProps,
28
+ },
29
+ state: pressable.state,
30
+ hasCheckmarks,
31
+ };
32
+ };
33
+
34
+ const getAccessibilityState = memoize(getAccessibilityStateWorker);
35
+ function getAccessibilityStateWorker(disabled: boolean, accessibilityState?: AccessibilityState) {
36
+ if (accessibilityState) {
37
+ return { disabled, ...accessibilityState };
38
+ }
39
+ return { disabled };
40
+ }
@@ -5,7 +5,9 @@ export const menuListName = 'MenuList';
5
5
 
6
6
  export interface MenuListTokens extends LayoutTokens, IBackgroundColorTokens {}
7
7
 
8
- export interface MenuListProps extends Omit<IViewProps, 'onPress'> {}
8
+ export interface MenuListProps extends Omit<IViewProps, 'onPress'> {
9
+ hasCheckmarks?: boolean;
10
+ }
9
11
 
10
12
  export interface MenuListSlotProps {
11
13
  root: React.PropsWithRef<IViewProps>;
@@ -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,6 +7,7 @@ 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,
10
11
  isSubmenu: false,
11
12
  open: false,
12
13
  setOpen: () => false,
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"menuTriggerContext.d.ts","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=menuTriggerContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"menuTriggerContext.js","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=menuTriggerContext.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"menuTriggerContext.d.ts","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=menuTriggerContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"menuTriggerContext.js","sourceRoot":"","sources":["../../src/context/menuTriggerContext.ts"],"names":[],"mappings":""}