forked from microsoft/fluentui-react-native
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMenuList.tsx
More file actions
122 lines (106 loc) · 5.09 KB
/
MenuList.tsx
File metadata and controls
122 lines (106 loc) · 5.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/** @jsxRuntime classic */
/** @jsx withSlots */
import React from 'react';
import { Platform, ScrollView, View } from 'react-native';
import type { IViewProps } from '@fluentui-react-native/adapters';
import { FocusZone } from '@fluentui-react-native/focus-zone';
import type { UseSlots } from '@fluentui-react-native/framework';
import { compose, mergeProps, stagedComponent, withSlots } from '@fluentui-react-native/framework';
import { stylingSettings } from './MenuList.styling';
import type { MenuListProps, MenuListState, MenuListType } from './MenuList.types';
import { menuListName } from './MenuList.types';
import { useMenuList } from './useMenuList';
import { useMenuListContextValue } from './useMenuListContextValue';
import { MenuListProvider } from '../context/menuListContext';
const MenuStack = stagedComponent((props: React.PropsWithRef<IViewProps> & { gap?: number }) => {
const { gap, ...rest } = props;
return (final: React.PropsWithRef<IViewProps> & { gap?: number }, children: React.ReactNode) => {
if (gap && gap > 0 && children) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - GH:1684, fix typing error
children = React.Children.map(children, (child: React.ReactChild, index: number) => {
if (React.isValidElement(child) && index > 0) {
return React.cloneElement(child, mergeProps(child.props, { style: { marginTop: gap } }));
}
return child;
});
}
return <View {...mergeProps(rest, final)}>{children}</View>;
};
});
MenuStack.displayName = 'MenuStack';
const shouldHaveFocusZone = ['macos', 'win32'].includes(Platform.OS as string);
const focusLandsOnContainer = Platform.OS === 'macos';
const hasCircularNavigation = Platform.OS === ('win32' as any);
export const menuListLookup = (layer: string, state: MenuListState, userProps: MenuListProps): boolean => {
return state[layer] || userProps[layer] || layer === 'hasMaxHeight';
};
export const MenuList = compose<MenuListType>({
displayName: menuListName,
...stylingSettings,
slots: {
root: MenuStack,
scrollView: ScrollView,
focusZone: shouldHaveFocusZone ? FocusZone : React.Fragment,
},
useRender: (userProps: MenuListProps, useSlots: UseSlots<MenuListType>) => {
const menuList = useMenuList(userProps);
const menuListContextValue = useMenuListContextValue(menuList);
const Slots = useSlots(menuList.props, (layer) => menuListLookup(layer, menuList, userProps));
return (_final: MenuListProps, children: React.ReactNode) => {
const itemCount = React.Children.toArray(children).filter(
(child) => React.isValidElement(child) && (child as any).type.displayName !== 'MenuDivider',
).length;
let itemPosition = 0;
const childrenWithSet = React.Children.toArray(children).map((child) => {
if (React.isValidElement(child)) {
if ((child as any).type.displayName !== 'MenuDivider') {
itemPosition++;
}
return React.cloneElement(
child as React.ReactElement<unknown, string | React.JSXElementConstructor<any>>,
{
accessibilityPositionInSet: child.props.accessibilityPositionInSet ?? itemPosition, // win32
accessibilitySetSize: child.props.accessibilitySetSize ?? itemCount, //win32
...(child.props.tooltip && { alwaysShowToolTip: true }),
} as any,
);
}
return child;
});
const shouldHaveScrollView = Platform.OS === 'macos' || menuList.hasMaxHeight || menuList.hasMaxWidth;
const ScrollViewWrapper = shouldHaveScrollView ? Slots.scrollView : React.Fragment;
const hasMenuGroupChild = React.Children.toArray(children).some(
(child) => child && (child as any).type && (child as any).type.displayName === 'MenuGroup',
);
// On win32, tab navigation should only be enabled when we have menu groups.
const hasTabNavigation = Platform.OS === ('win32' as any) && hasMenuGroupChild;
const content = (
<Slots.root>
<ScrollViewWrapper
// avoid error that fires when props are passed into React.fragment
{...(shouldHaveScrollView && {
showsVerticalScrollIndicator: menuList.hasMaxHeight,
showsHorizontalScrollIndicator: menuList.hasMaxWidth,
})}
>
<Slots.focusZone
// avoid error that fires when props are passed into React.fragment
{...(shouldHaveFocusZone && {
componentRef: focusLandsOnContainer && menuList.focusZoneRef,
focusZoneDirection: 'vertical',
defaultTabbableElement: focusLandsOnContainer && menuList.focusZoneRef,
enableFocusRing: false,
isCircularNavigation: hasCircularNavigation,
tabKeyNavigation: hasTabNavigation ? 'Normal' : 'None',
})}
>
{childrenWithSet}
</Slots.focusZone>
</ScrollViewWrapper>
</Slots.root>
);
return <MenuListProvider value={menuListContextValue}>{content}</MenuListProvider>;
};
},
});