@fluentui-react-native/menu 0.9.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.json +120 -1
- package/CHANGELOG.md +41 -2
- package/lib/Menu/useMenu.d.ts.map +1 -1
- package/lib/Menu/useMenu.js +5 -1
- package/lib/Menu/useMenu.js.map +1 -1
- package/lib/MenuItem/MenuItem.d.ts.map +1 -1
- package/lib/MenuItem/MenuItem.js +4 -2
- package/lib/MenuItem/MenuItem.js.map +1 -1
- package/lib/MenuItem/MenuItem.styling.d.ts.map +1 -1
- package/lib/MenuItem/MenuItem.styling.js +6 -0
- package/lib/MenuItem/MenuItem.styling.js.map +1 -1
- package/lib/MenuItem/MenuItem.types.d.ts +2 -0
- package/lib/MenuItem/MenuItem.types.d.ts.map +1 -1
- package/lib/MenuItem/MenuItemTokens.d.ts.map +1 -1
- package/lib/MenuItem/MenuItemTokens.js +2 -0
- package/lib/MenuItem/MenuItemTokens.js.map +1 -1
- package/lib/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
- package/lib/MenuItem/MenuItemTokens.win32.js +2 -0
- package/lib/MenuItem/MenuItemTokens.win32.js.map +1 -1
- package/lib/MenuItem/useMenuItem.d.ts.map +1 -1
- package/lib/MenuItem/useMenuItem.js +3 -1
- package/lib/MenuItem/useMenuItem.js.map +1 -1
- package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts +4 -2
- package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
- package/lib/MenuItemCheckbox/MenuItemCheckbox.js +11 -8
- package/lib/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
- package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts +10 -0
- package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
- package/lib/MenuItemCheckbox/useMenuItemCheckbox.js +28 -14
- package/lib/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
- package/lib/MenuItemRadio/MenuItemRadio.d.ts +4 -0
- package/lib/MenuItemRadio/MenuItemRadio.d.ts.map +1 -0
- package/lib/MenuItemRadio/MenuItemRadio.js +12 -0
- package/lib/MenuItemRadio/MenuItemRadio.js.map +1 -0
- package/lib/MenuItemRadio/useMenuItemRadio.d.ts +3 -0
- package/lib/MenuItemRadio/useMenuItemRadio.d.ts.map +1 -0
- package/lib/MenuItemRadio/useMenuItemRadio.js +15 -0
- package/lib/MenuItemRadio/useMenuItemRadio.js.map +1 -0
- package/lib/MenuList/MenuList.types.d.ts +1 -0
- package/lib/MenuList/MenuList.types.d.ts.map +1 -1
- package/lib/MenuList/useMenuList.d.ts.map +1 -1
- package/lib/MenuList/useMenuList.js +13 -3
- package/lib/MenuList/useMenuList.js.map +1 -1
- package/lib/MenuPopover/MenuPopover.d.ts.map +1 -1
- package/lib/MenuPopover/MenuPopover.js +1 -1
- package/lib/MenuPopover/MenuPopover.js.map +1 -1
- package/lib/MenuPopover/MenuPopover.types.d.ts +4 -1
- package/lib/MenuPopover/MenuPopover.types.d.ts.map +1 -1
- package/lib/MenuPopover/useMenuPopover.d.ts.map +1 -1
- package/lib/MenuPopover/useMenuPopover.js +16 -1
- package/lib/MenuPopover/useMenuPopover.js.map +1 -1
- package/lib/MenuTrigger/MenuTrigger.d.ts.map +1 -1
- package/lib/MenuTrigger/MenuTrigger.js +4 -3
- package/lib/MenuTrigger/MenuTrigger.js.map +1 -1
- package/lib/MenuTrigger/MenuTrigger.types.d.ts +11 -2
- package/lib/MenuTrigger/MenuTrigger.types.d.ts.map +1 -1
- package/lib/MenuTrigger/MenuTrigger.types.js.map +1 -1
- package/lib/MenuTrigger/useMenuTrigger.d.ts +2 -8
- package/lib/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
- package/lib/MenuTrigger/useMenuTrigger.js +14 -1
- package/lib/MenuTrigger/useMenuTrigger.js.map +1 -1
- package/lib/context/menuTriggerContext.d.ts +9 -0
- package/lib/context/menuTriggerContext.d.ts.map +1 -0
- package/lib/context/menuTriggerContext.js +9 -0
- package/lib/context/menuTriggerContext.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib-commonjs/Menu/useMenu.d.ts.map +1 -1
- package/lib-commonjs/Menu/useMenu.js +5 -1
- package/lib-commonjs/Menu/useMenu.js.map +1 -1
- package/lib-commonjs/MenuItem/MenuItem.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/MenuItem.js +3 -1
- package/lib-commonjs/MenuItem/MenuItem.js.map +1 -1
- package/lib-commonjs/MenuItem/MenuItem.styling.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/MenuItem.styling.js +6 -0
- package/lib-commonjs/MenuItem/MenuItem.styling.js.map +1 -1
- package/lib-commonjs/MenuItem/MenuItem.types.d.ts +2 -0
- package/lib-commonjs/MenuItem/MenuItem.types.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/MenuItemTokens.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/MenuItemTokens.js +2 -0
- package/lib-commonjs/MenuItem/MenuItemTokens.js.map +1 -1
- package/lib-commonjs/MenuItem/MenuItemTokens.win32.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/MenuItemTokens.win32.js +2 -0
- package/lib-commonjs/MenuItem/MenuItemTokens.win32.js.map +1 -1
- package/lib-commonjs/MenuItem/useMenuItem.d.ts.map +1 -1
- package/lib-commonjs/MenuItem/useMenuItem.js +3 -1
- package/lib-commonjs/MenuItem/useMenuItem.js.map +1 -1
- package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts +4 -2
- package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
- package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js +12 -8
- package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
- package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts +10 -0
- package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
- package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js +31 -16
- package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
- package/lib-commonjs/MenuItemRadio/MenuItemRadio.d.ts +4 -0
- package/lib-commonjs/MenuItemRadio/MenuItemRadio.d.ts.map +1 -0
- package/lib-commonjs/MenuItemRadio/MenuItemRadio.js +15 -0
- package/lib-commonjs/MenuItemRadio/MenuItemRadio.js.map +1 -0
- package/lib-commonjs/MenuItemRadio/useMenuItemRadio.d.ts +3 -0
- package/lib-commonjs/MenuItemRadio/useMenuItemRadio.d.ts.map +1 -0
- package/lib-commonjs/MenuItemRadio/useMenuItemRadio.js +20 -0
- package/lib-commonjs/MenuItemRadio/useMenuItemRadio.js.map +1 -0
- package/lib-commonjs/MenuList/MenuList.types.d.ts +1 -0
- package/lib-commonjs/MenuList/MenuList.types.d.ts.map +1 -1
- package/lib-commonjs/MenuList/useMenuList.d.ts.map +1 -1
- package/lib-commonjs/MenuList/useMenuList.js +13 -3
- package/lib-commonjs/MenuList/useMenuList.js.map +1 -1
- package/lib-commonjs/MenuPopover/MenuPopover.d.ts.map +1 -1
- package/lib-commonjs/MenuPopover/MenuPopover.js +1 -1
- package/lib-commonjs/MenuPopover/MenuPopover.js.map +1 -1
- package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts +4 -1
- package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts.map +1 -1
- package/lib-commonjs/MenuPopover/useMenuPopover.d.ts.map +1 -1
- package/lib-commonjs/MenuPopover/useMenuPopover.js +16 -1
- package/lib-commonjs/MenuPopover/useMenuPopover.js.map +1 -1
- package/lib-commonjs/MenuTrigger/MenuTrigger.d.ts.map +1 -1
- package/lib-commonjs/MenuTrigger/MenuTrigger.js +4 -3
- package/lib-commonjs/MenuTrigger/MenuTrigger.js.map +1 -1
- package/lib-commonjs/MenuTrigger/MenuTrigger.types.d.ts +11 -2
- package/lib-commonjs/MenuTrigger/MenuTrigger.types.d.ts.map +1 -1
- package/lib-commonjs/MenuTrigger/MenuTrigger.types.js.map +1 -1
- package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts +2 -8
- package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
- package/lib-commonjs/MenuTrigger/useMenuTrigger.js +14 -1
- package/lib-commonjs/MenuTrigger/useMenuTrigger.js.map +1 -1
- package/lib-commonjs/context/menuTriggerContext.d.ts +9 -0
- package/lib-commonjs/context/menuTriggerContext.d.ts.map +1 -0
- package/lib-commonjs/context/menuTriggerContext.js +14 -0
- package/lib-commonjs/context/menuTriggerContext.js.map +1 -0
- package/lib-commonjs/index.d.ts +1 -0
- package/lib-commonjs/index.d.ts.map +1 -1
- package/lib-commonjs/index.js +3 -1
- package/lib-commonjs/index.js.map +1 -1
- package/package.json +7 -7
- package/src/Menu/useMenu.ts +6 -0
- package/src/MenuItem/MenuItem.styling.ts +7 -0
- package/src/MenuItem/MenuItem.tsx +10 -5
- package/src/MenuItem/MenuItem.types.ts +2 -0
- package/src/MenuItem/MenuItemTokens.ts +2 -0
- package/src/MenuItem/MenuItemTokens.win32.ts +2 -0
- package/src/MenuItem/useMenuItem.ts +3 -1
- package/src/MenuItemCheckbox/MenuItemCheckbox.tsx +29 -16
- package/src/MenuItemCheckbox/useMenuItemCheckbox.ts +39 -20
- package/src/MenuItemRadio/MenuItemRadio.tsx +16 -0
- package/src/MenuItemRadio/useMenuItemRadio.ts +21 -0
- package/src/MenuList/MenuList.types.ts +1 -0
- package/src/MenuList/useMenuList.ts +22 -3
- package/src/MenuPopover/MenuPopover.tsx +3 -0
- package/src/MenuPopover/MenuPopover.types.ts +4 -1
- package/src/MenuPopover/useMenuPopover.ts +21 -2
- package/src/MenuTrigger/MenuTrigger.tsx +4 -3
- package/src/MenuTrigger/MenuTrigger.types.ts +14 -3
- package/src/MenuTrigger/useMenuTrigger.ts +18 -3
- package/src/context/menuTriggerContext.ts +10 -0
- package/src/index.ts +1 -0
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
16
|
+
const isTrigger = useMenuTriggerContext();
|
|
17
|
+
const hasSubmenu = useMenuContext().isSubmenu && isTrigger;
|
|
16
18
|
const hasCheckmarks = useMenuListContext().hasCheckmarks;
|
|
17
19
|
|
|
18
20
|
return {
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/** @jsx withSlots */
|
|
2
2
|
import { View } from 'react-native';
|
|
3
3
|
import { SvgXml } from 'react-native-svg';
|
|
4
|
-
import { compose, mergeProps, UseSlots, withSlots } from '@fluentui-react-native/framework';
|
|
4
|
+
import { compose, mergeProps, Slots, UseSlots, withSlots } from '@fluentui-react-native/framework';
|
|
5
5
|
import { Text } from '@fluentui-react-native/experimental-text';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
menuItemCheckboxName,
|
|
8
|
+
MenuItemCheckboxProps,
|
|
9
|
+
MenuItemCheckboxSlotProps,
|
|
10
|
+
MenuItemCheckboxState,
|
|
11
|
+
MenuItemCheckboxType,
|
|
12
|
+
} from './MenuItemCheckbox.types';
|
|
7
13
|
import { useMenuItemCheckbox } from './useMenuItemCheckbox';
|
|
8
14
|
import { stylingSettings } from './MenuItemCheckbox.styling';
|
|
9
15
|
|
|
@@ -19,19 +25,26 @@ export const MenuItemCheckbox = compose<MenuItemCheckboxType>({
|
|
|
19
25
|
const menuItem = useMenuItemCheckbox(userProps);
|
|
20
26
|
const Slots = useSlots(userProps, (layer): boolean => menuItem.state[layer]);
|
|
21
27
|
|
|
22
|
-
return (
|
|
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
|
-
};
|
|
28
|
+
return menuItemFinalRender(menuItem, Slots);
|
|
36
29
|
},
|
|
37
30
|
});
|
|
31
|
+
|
|
32
|
+
export const menuItemFinalRender = (
|
|
33
|
+
menuItem: MenuItemCheckboxState,
|
|
34
|
+
Slots: Slots<MenuItemCheckboxSlotProps>,
|
|
35
|
+
): React.FunctionComponent<MenuItemCheckboxProps> => {
|
|
36
|
+
return (final: MenuItemCheckboxProps) => {
|
|
37
|
+
const mergedProps = mergeProps(menuItem.props, final);
|
|
38
|
+
const checkmarkXml = `
|
|
39
|
+
<svg>
|
|
40
|
+
<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' />
|
|
41
|
+
</svg>`;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Slots.root {...mergedProps}>
|
|
45
|
+
<Slots.checkmark xml={checkmarkXml} />
|
|
46
|
+
{mergedProps.content && <Slots.content>{mergedProps.content}</Slots.content>}
|
|
47
|
+
</Slots.root>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
};
|
|
@@ -14,7 +14,41 @@ import { useMenuListContext } from '../context/menuListContext';
|
|
|
14
14
|
const defaultAccessibilityActions = [{ name: 'Toggle' }];
|
|
15
15
|
|
|
16
16
|
export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
|
|
17
|
-
|
|
17
|
+
const { name } = props;
|
|
18
|
+
const context = useMenuListContext();
|
|
19
|
+
const checked = context.checked?.[name];
|
|
20
|
+
const onCheckedChange = context.onCheckedChange;
|
|
21
|
+
|
|
22
|
+
const toggleChecked = React.useCallback(
|
|
23
|
+
(e: InteractionEvent) => {
|
|
24
|
+
onCheckedChange(e, name, !checked);
|
|
25
|
+
},
|
|
26
|
+
[checked, name, onCheckedChange],
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return useMenuCheckboxInteraction(props, toggleChecked);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getAccessibilityState = memoize(getAccessibilityStateWorker);
|
|
33
|
+
function getAccessibilityStateWorker(disabled: boolean, checked: boolean, accessibilityState?: AccessibilityState) {
|
|
34
|
+
if (accessibilityState) {
|
|
35
|
+
return { disabled, checked, ...accessibilityState };
|
|
36
|
+
}
|
|
37
|
+
return { disabled, checked };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create interactivity and accessibility props to be passed into the inner render.
|
|
42
|
+
* This logic is shared between Checkbox and Radio versions of MenuItem.
|
|
43
|
+
*
|
|
44
|
+
* @param props Props passed into the outer compoennt
|
|
45
|
+
* @param toggleCallback Function to be called when item is toggled
|
|
46
|
+
* @returns Props and additional state needed to render the component
|
|
47
|
+
*/
|
|
48
|
+
export const useMenuCheckboxInteraction = (
|
|
49
|
+
props: MenuItemCheckboxProps,
|
|
50
|
+
toggleCallback: (e: InteractionEvent) => void,
|
|
51
|
+
): MenuItemCheckboxState => {
|
|
18
52
|
const defaultComponentRef = React.useRef(null);
|
|
19
53
|
const {
|
|
20
54
|
accessibilityActions,
|
|
@@ -27,32 +61,25 @@ export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheck
|
|
|
27
61
|
} = props;
|
|
28
62
|
const context = useMenuListContext();
|
|
29
63
|
const checked = context.checked?.[name];
|
|
30
|
-
const onCheckedChange = context.onCheckedChange;
|
|
31
64
|
|
|
32
|
-
const toggleChecked = React.useCallback(
|
|
33
|
-
(e: InteractionEvent) => {
|
|
34
|
-
onCheckedChange(e, name, !checked);
|
|
35
|
-
},
|
|
36
|
-
[checked, name, onCheckedChange],
|
|
37
|
-
);
|
|
38
65
|
// Ensure focus is placed on checkbox after click
|
|
39
|
-
const toggleCheckedWithFocus = useOnPressWithFocus(componentRef,
|
|
66
|
+
const toggleCheckedWithFocus = useOnPressWithFocus(componentRef, toggleCallback);
|
|
40
67
|
|
|
41
68
|
const pressable = useAsPressable({ onPress: toggleCheckedWithFocus, ...rest });
|
|
42
69
|
const buttonRef = useViewCommandFocus(componentRef);
|
|
43
70
|
|
|
44
|
-
const onKeyProps = useKeyProps(
|
|
71
|
+
const onKeyProps = useKeyProps(toggleCallback, ' ');
|
|
45
72
|
const accessibilityActionsProp = accessibilityActions
|
|
46
73
|
? [...defaultAccessibilityActions, ...accessibilityActions]
|
|
47
74
|
: defaultAccessibilityActions;
|
|
48
75
|
const onAccessibilityActionProp = React.useCallback(
|
|
49
76
|
(event: AccessibilityActionEvent) => {
|
|
50
77
|
if (event.nativeEvent.actionName === 'Toggle') {
|
|
51
|
-
|
|
78
|
+
toggleCallback(event);
|
|
52
79
|
}
|
|
53
80
|
onAccessibilityAction && onAccessibilityAction(event);
|
|
54
81
|
},
|
|
55
|
-
[
|
|
82
|
+
[toggleCallback, onAccessibilityAction],
|
|
56
83
|
);
|
|
57
84
|
|
|
58
85
|
const state = {
|
|
@@ -78,11 +105,3 @@ export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheck
|
|
|
78
105
|
state: state,
|
|
79
106
|
};
|
|
80
107
|
};
|
|
81
|
-
|
|
82
|
-
const getAccessibilityState = memoize(getAccessibilityStateWorker);
|
|
83
|
-
function getAccessibilityStateWorker(disabled: boolean, checked: boolean, accessibilityState?: AccessibilityState) {
|
|
84
|
-
if (accessibilityState) {
|
|
85
|
-
return { disabled, checked, ...accessibilityState };
|
|
86
|
-
}
|
|
87
|
-
return { disabled, checked };
|
|
88
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { UseSlots } from '@fluentui-react-native/framework';
|
|
2
|
+
import { useMenuItemRadio } from './useMenuItemRadio';
|
|
3
|
+
import { MenuItemCheckbox, menuItemFinalRender } from '../MenuItemCheckbox/MenuItemCheckbox';
|
|
4
|
+
import { MenuItemCheckboxProps, MenuItemCheckboxType } from '../MenuItemCheckbox/MenuItemCheckbox.types';
|
|
5
|
+
|
|
6
|
+
export const menuItemRadioName = 'MenuItemRadio';
|
|
7
|
+
|
|
8
|
+
export const MenuItemRadio = MenuItemCheckbox.compose({
|
|
9
|
+
displayName: menuItemRadioName,
|
|
10
|
+
useRender: (userProps: MenuItemCheckboxProps, useSlots: UseSlots<MenuItemCheckboxType>) => {
|
|
11
|
+
const menuItem = useMenuItemRadio(userProps);
|
|
12
|
+
const Slots = useSlots(userProps, (layer): boolean => menuItem.state[layer]);
|
|
13
|
+
|
|
14
|
+
return menuItemFinalRender(menuItem, Slots);
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
|
|
3
|
+
import { useMenuListContext } from '../context/menuListContext';
|
|
4
|
+
import { MenuItemCheckboxProps, MenuItemCheckboxState } from '../MenuItemCheckbox/MenuItemCheckbox.types';
|
|
5
|
+
import { useMenuCheckboxInteraction } from '../MenuItemCheckbox/useMenuItemCheckbox';
|
|
6
|
+
|
|
7
|
+
export const useMenuItemRadio = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
|
|
8
|
+
const { name } = props;
|
|
9
|
+
const context = useMenuListContext();
|
|
10
|
+
const checked = context.checked?.[name];
|
|
11
|
+
const selectRadio = context.selectRadio;
|
|
12
|
+
|
|
13
|
+
const toggleChecked = React.useCallback(
|
|
14
|
+
(e: InteractionEvent) => {
|
|
15
|
+
selectRadio(e, name, !checked);
|
|
16
|
+
},
|
|
17
|
+
[checked, name, selectRadio],
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
return useMenuCheckboxInteraction(props, toggleChecked);
|
|
21
|
+
};
|
|
@@ -17,6 +17,7 @@ export interface MenuListProps extends Omit<IViewProps, 'onPress'> {
|
|
|
17
17
|
|
|
18
18
|
export interface MenuListState extends MenuListProps {
|
|
19
19
|
isCheckedControlled: boolean;
|
|
20
|
+
selectRadio?: (e: InteractionEvent, name: string, isChecked: boolean) => void;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface MenuListSlotProps {
|
|
@@ -9,20 +9,25 @@ export const useMenuList = (_props: MenuListProps): MenuListState => {
|
|
|
9
9
|
// MenuList v2 needs to be able to be standalone, but this is not in scope for v1
|
|
10
10
|
// Assuming that checked information will come from parent Menu
|
|
11
11
|
const isCheckedControlled = typeof context.checked !== 'undefined';
|
|
12
|
-
const [checked, onCheckedChange] = useMenuCheckedState(isCheckedControlled, context);
|
|
12
|
+
const [checked, onCheckedChange, selectRadio] = useMenuCheckedState(isCheckedControlled, context);
|
|
13
13
|
|
|
14
14
|
return {
|
|
15
15
|
...context,
|
|
16
16
|
isCheckedControlled,
|
|
17
17
|
checked,
|
|
18
18
|
onCheckedChange,
|
|
19
|
+
selectRadio,
|
|
19
20
|
};
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
const useMenuCheckedState = (
|
|
23
24
|
isControlled: boolean,
|
|
24
25
|
props: MenuListProps,
|
|
25
|
-
): [
|
|
26
|
+
): [
|
|
27
|
+
Record<string, boolean>,
|
|
28
|
+
(e: InteractionEvent, name: string, isChecked: boolean) => void,
|
|
29
|
+
(e: InteractionEvent, name: string, isChecked: boolean) => void,
|
|
30
|
+
] => {
|
|
26
31
|
const { defaultChecked, onCheckedChange, checked } = props;
|
|
27
32
|
const initialState = defaultChecked ?? checked ?? {};
|
|
28
33
|
const [checkedInternal, setCheckedInternal] = React.useState<Record<string, boolean>>(initialState);
|
|
@@ -45,5 +50,19 @@ const useMenuCheckedState = (
|
|
|
45
50
|
[isControlled, state, onCheckedChange, setCheckedInternal],
|
|
46
51
|
);
|
|
47
52
|
|
|
48
|
-
|
|
53
|
+
const selectRadio = React.useCallback(
|
|
54
|
+
(e: InteractionEvent, name: string, isChecked: boolean) => {
|
|
55
|
+
if (!isControlled) {
|
|
56
|
+
const updatedChecked = { [name]: true };
|
|
57
|
+
setCheckedInternal(updatedChecked);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (onCheckedChange) {
|
|
61
|
+
onCheckedChange(e, name, isChecked);
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
[isControlled, onCheckedChange, setCheckedInternal],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return [state, setChecked, selectRadio];
|
|
49
68
|
};
|
|
@@ -16,6 +16,9 @@ export const MenuPopover = stagedComponent((props: MenuPopoverProps) => {
|
|
|
16
16
|
target={state.triggerRef}
|
|
17
17
|
onDismiss={state.onDismiss}
|
|
18
18
|
dismissBehaviors={state.dismissBehaviors}
|
|
19
|
+
setInitialFocus={state.setInitialFocus}
|
|
20
|
+
directionalHint={state.directionalHint}
|
|
21
|
+
doNotTakePointerCapture={state.doNotTakePointerCapture}
|
|
19
22
|
>
|
|
20
23
|
{children}
|
|
21
24
|
</Callout>
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { IViewProps } from '@fluentui-react-native/adapters';
|
|
2
|
-
import { DismissBehaviors } from '@fluentui-react-native/callout';
|
|
2
|
+
import { DirectionalHint, DismissBehaviors } from '@fluentui-react-native/callout';
|
|
3
3
|
|
|
4
4
|
export const menuPopoverName = 'MenuPopover';
|
|
5
5
|
|
|
6
6
|
export interface MenuPopoverProps extends Omit<IViewProps, 'onPress'> {}
|
|
7
7
|
|
|
8
8
|
export interface MenuPopoverState {
|
|
9
|
+
directionalHint?: DirectionalHint;
|
|
9
10
|
dismissBehaviors: DismissBehaviors[];
|
|
11
|
+
doNotTakePointerCapture: boolean;
|
|
10
12
|
onDismiss: () => void;
|
|
13
|
+
setInitialFocus: boolean;
|
|
11
14
|
triggerRef: React.RefObject<React.Component>;
|
|
12
15
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { I18nManager, Platform } from 'react-native';
|
|
3
|
+
import { DirectionalHint, DismissBehaviors } from '@fluentui-react-native/callout';
|
|
3
4
|
import { useMenuContext } from '../context/menuContext';
|
|
4
5
|
import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types';
|
|
5
6
|
|
|
@@ -12,6 +13,24 @@ export const useMenuPopover = (_props: MenuPopoverProps): MenuPopoverState => {
|
|
|
12
13
|
const dismissBehaviors = context.isControlled
|
|
13
14
|
? (['preventDismissOnKeyDown', 'preventDismissOnClickOutside'] as DismissBehaviors[])
|
|
14
15
|
: undefined;
|
|
16
|
+
const directionalHint = getDirectionalHint(context.isSubmenu, I18nManager.isRTL);
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
// Initial focus behavior differs per platform, Windows platforms move focus
|
|
19
|
+
// automatically onto first element of Callout
|
|
20
|
+
const setInitialFocus = Platform.OS === ('win32' as any) || Platform.OS === 'windows';
|
|
21
|
+
const doNotTakePointerCapture = context.openOnHover;
|
|
22
|
+
|
|
23
|
+
return { triggerRef, onDismiss, directionalHint, dismissBehaviors, doNotTakePointerCapture, setInitialFocus };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getDirectionalHint = (isSubmenu: boolean, isRtl: boolean): DirectionalHint | undefined => {
|
|
27
|
+
if (!isSubmenu) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isRtl) {
|
|
32
|
+
return 'leftTopEdge';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return 'rightTopEdge';
|
|
17
36
|
};
|
|
@@ -2,9 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import { stagedComponent } from '@fluentui-react-native/framework';
|
|
3
3
|
import { menuTriggerName, MenuTriggerProps } from './MenuTrigger.types';
|
|
4
4
|
import { useMenuTrigger } from './useMenuTrigger';
|
|
5
|
+
import { MenuTriggerProvider } from '../context/menuTriggerContext';
|
|
5
6
|
|
|
6
7
|
export const MenuTrigger = stagedComponent((props: MenuTriggerProps) => {
|
|
7
|
-
const
|
|
8
|
+
const menuTrigger = useMenuTrigger(props);
|
|
8
9
|
|
|
9
10
|
return (_rest: MenuTriggerProps, children: React.ReactNode) => {
|
|
10
11
|
const childrenArray = React.Children.toArray(children) as React.ReactElement[];
|
|
@@ -16,9 +17,9 @@ export const MenuTrigger = stagedComponent((props: MenuTriggerProps) => {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const child = childrenArray[0];
|
|
19
|
-
const revised = React.cloneElement(child,
|
|
20
|
+
const revised = React.cloneElement(child, menuTrigger.props);
|
|
20
21
|
|
|
21
|
-
return
|
|
22
|
+
return <MenuTriggerProvider value={menuTrigger.hasSubmenu}>{revised}</MenuTriggerProvider>;
|
|
22
23
|
};
|
|
23
24
|
});
|
|
24
25
|
MenuTrigger.displayName = menuTriggerName;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import
|
|
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<
|
|
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
|
-
|
|
12
|
+
onClick?: (e: InteractionEvent) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MenuTriggerState {
|
|
16
|
+
props: MenuTriggerProps;
|
|
17
|
+
hasSubmenu: boolean;
|
|
18
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useMenuContext } from '../context/menuContext';
|
|
2
2
|
import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
|
|
3
|
-
import { MenuTriggerProps } from './MenuTrigger.types';
|
|
3
|
+
import { MenuTriggerProps, MenuTriggerState } from './MenuTrigger.types';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
4
5
|
|
|
5
|
-
export const useMenuTrigger = (_props: MenuTriggerProps) => {
|
|
6
|
+
export const useMenuTrigger = (_props: MenuTriggerProps): MenuTriggerState => {
|
|
6
7
|
const context = useMenuContext();
|
|
7
8
|
|
|
8
9
|
const setOpen = context.setOpen;
|
|
@@ -10,15 +11,29 @@ export const useMenuTrigger = (_props: MenuTriggerProps) => {
|
|
|
10
11
|
const openOnHover = context.openOnHover;
|
|
11
12
|
const triggerRef = context.triggerRef;
|
|
12
13
|
|
|
14
|
+
const delayHover = Platform.select({
|
|
15
|
+
macos: 100,
|
|
16
|
+
default: 500, // win32
|
|
17
|
+
});
|
|
18
|
+
|
|
13
19
|
const onHoverIn = (e: InteractionEvent) => {
|
|
14
20
|
if (openOnHover) {
|
|
15
21
|
setOpen(e, true /* isOpen */);
|
|
16
22
|
}
|
|
17
23
|
};
|
|
18
24
|
|
|
25
|
+
const onHoverOut = (e: InteractionEvent) => {
|
|
26
|
+
if (openOnHover) {
|
|
27
|
+
setOpen(e, false /* isOpen */);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
19
31
|
const onClick = (e: InteractionEvent) => {
|
|
20
32
|
setOpen(e, !open);
|
|
21
33
|
};
|
|
22
34
|
|
|
23
|
-
return {
|
|
35
|
+
return {
|
|
36
|
+
props: { onClick, onHoverIn, onHoverOut, componentRef: triggerRef, delayHoverIn: delayHover, delayHoverOut: delayHover },
|
|
37
|
+
hasSubmenu: context.isSubmenu,
|
|
38
|
+
};
|
|
24
39
|
};
|
|
@@ -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);
|
package/src/index.ts
CHANGED
|
@@ -3,5 +3,6 @@ export { MenuTrigger } from './MenuTrigger/MenuTrigger';
|
|
|
3
3
|
export { MenuPopover } from './MenuPopover/MenuPopover';
|
|
4
4
|
export { MenuItem } from './MenuItem/MenuItem';
|
|
5
5
|
export { MenuItemCheckbox } from './MenuItemCheckbox/MenuItemCheckbox';
|
|
6
|
+
export { MenuItemRadio } from './MenuItemRadio/MenuItemRadio';
|
|
6
7
|
export { MenuList } from './MenuList/MenuList';
|
|
7
8
|
export { MenuDivider } from './MenuDivider/MenuDivider';
|