Skip to content

Commit c6f0fea

Browse files
authored
Add ScrollView to Menu (#2353)
1 parent 90a983f commit c6f0fea

File tree

6 files changed

+134
-14
lines changed

6 files changed

+134
-14
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import React, { useState } from 'react';
2+
import { ButtonV1 as Button } from '@fluentui/react-native';
3+
import { Menu, MenuItem, MenuTrigger, MenuPopover, MenuList } from '@fluentui-react-native/menu';
4+
import { Stack } from '@fluentui-react-native/stack';
5+
import { stackStyle } from '../Common/styles';
6+
import { TextInput, StyleSheet, View } from 'react-native';
7+
import { commonTestStyles as commonStyles } from '../Common/styles';
8+
9+
const styles = StyleSheet.create({
10+
menuContainerRow: { flexDirection: 'row' },
11+
menuMargin: { marginRight: 20 },
12+
});
13+
14+
export const MenuScrollView: React.FunctionComponent = () => {
15+
const [data, setData] = useState([
16+
<MenuItem key={1}>MenuItem 1</MenuItem>,
17+
<MenuItem key={2}>MenuItem 2</MenuItem>,
18+
<MenuItem key={3}>MenuItem 3</MenuItem>,
19+
<MenuItem key={4}>MenuItem 4</MenuItem>,
20+
<MenuItem key={5}>MenuItem 5</MenuItem>,
21+
]);
22+
23+
const insertOnClick = React.useCallback(() => {
24+
data.push(<MenuItem key={Math.random()}>MenuItem</MenuItem>);
25+
const newList = [...data];
26+
setData(newList);
27+
}, [data]);
28+
29+
const popOnClick = React.useCallback(() => {
30+
if (data.length > 0) {
31+
data.pop();
32+
const newList = [...data];
33+
setData(newList);
34+
}
35+
}, [data]);
36+
37+
const [maxHeight, setMaxHeight] = useState<number>(800);
38+
const menuItems = [];
39+
40+
for (let i = 0; i < 40; i++) {
41+
menuItems.push(<MenuItem key={i}>MenuItem {i}</MenuItem>);
42+
}
43+
44+
return (
45+
<Stack style={stackStyle}>
46+
<View style={styles.menuContainerRow}>
47+
<TextInput
48+
accessibilityLabel="Max height for menu"
49+
style={[commonStyles.textBox, styles.menuMargin]}
50+
placeholder="Max height for menu"
51+
blurOnSubmit={true}
52+
onSubmitEditing={(e) => {
53+
setMaxHeight(parseInt(e.nativeEvent.text));
54+
}}
55+
/>
56+
<Menu>
57+
<MenuTrigger>
58+
<Button>Custom height</Button>
59+
</MenuTrigger>
60+
<MenuPopover maxHeight={maxHeight}>
61+
<MenuList>{menuItems}</MenuList>
62+
</MenuPopover>
63+
</Menu>
64+
</View>
65+
<View style={styles.menuContainerRow}>
66+
<Button appearance="subtle" onClick={insertOnClick}>
67+
Add new menu item
68+
</Button>
69+
<Button appearance="subtle" onClick={popOnClick}>
70+
Remove last menu item
71+
</Button>
72+
<Menu>
73+
<MenuTrigger>
74+
<Button>Mutable Menu</Button>
75+
</MenuTrigger>
76+
<MenuPopover>
77+
<MenuList>{data}</MenuList>
78+
</MenuPopover>
79+
</Menu>
80+
</View>
81+
<Menu>
82+
<MenuTrigger>
83+
<Button>Height: 100</Button>
84+
</MenuTrigger>
85+
<MenuPopover maxHeight={100}>
86+
<MenuList>
87+
<MenuItem>MenuItem 1</MenuItem>
88+
<MenuItem>MenuItem 2</MenuItem>
89+
<MenuItem>MenuItem 3</MenuItem>
90+
<MenuItem>MenuItem 4</MenuItem>
91+
<MenuItem>MenuItem 5</MenuItem>
92+
</MenuList>
93+
</MenuPopover>
94+
</Menu>
95+
</Stack>
96+
);
97+
};

apps/fluent-tester/src/TestComponents/Menu/MenuTest.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import * as React from 'react';
1+
import React from 'react';
22
import { ButtonV1 as Button } from '@fluentui/react-native';
33
import {
44
Menu,
@@ -16,9 +16,10 @@ import { MENU_TESTPAGE } from './consts';
1616
import { Test, TestSection, PlatformStatus } from '../Test';
1717
import { TextV1 as Text } from '@fluentui-react-native/text';
1818
import { E2EMenuTest } from './E2EMenuTest';
19-
import { StyleSheet } from 'react-native';
2019
import { MenuTriggerHoverCallback, MenuTriggerOnClickCallback } from './MenuTriggerCallbacks';
2120
import { MenuTriggerChildRef } from './MenuRefs';
21+
import { StyleSheet } from 'react-native';
22+
import { MenuScrollView } from './MenuScrollView';
2223

2324
const MenuDefault: React.FunctionComponent = () => {
2425
return (
@@ -269,6 +270,10 @@ const menuSections: TestSection[] = [
269270
name: 'Menu Submenu',
270271
component: MenuSubMenu,
271272
},
273+
{
274+
name: 'Menu with ScrollView',
275+
component: MenuScrollView,
276+
},
272277
{
273278
name: 'Menu Trigger onClick Override',
274279
component: MenuTriggerOnClickCallback,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Add ScrollView to Menu (macOS)",
4+
"packageName": "@fluentui-react-native/menu",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Add ScrollView to Menu (macOS)",
4+
"packageName": "@fluentui-react-native/tester",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/components/Menu/src/MenuList/MenuList.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @jsx withSlots */
22
import React from 'react';
3-
import { Platform, View } from 'react-native';
3+
import { Platform, ScrollView, View } from 'react-native';
44
import { compose, mergeProps, stagedComponent, UseSlots, withSlots } from '@fluentui-react-native/framework';
55
import { menuListName, MenuListProps, MenuListType } from './MenuList.types';
66
import { stylingSettings } from './MenuList.styling';
@@ -34,6 +34,7 @@ export const MenuList = compose<MenuListType>({
3434
...stylingSettings,
3535
slots: {
3636
root: MenuStack,
37+
...(Platform.OS === 'macos' && { scrollView: ScrollView }),
3738
...(Platform.OS === 'macos' && { focusZone: FocusZone }),
3839
},
3940
useRender: (userProps: MenuListProps, useSlots: UseSlots<MenuListType>) => {
@@ -51,17 +52,18 @@ export const MenuList = compose<MenuListType>({
5152
const content =
5253
Platform.OS === 'macos' ? (
5354
<Slots.root>
54-
<Slots.focusZone
55-
componentRef={focusZoneRef}
56-
focusZoneDirection={'vertical'}
57-
defaultTabbableElement={focusZoneRef}
58-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
59-
// @ts-ignore FocusZone takes ViewProps, but that isn't defined on it's type.
60-
enableFocusRing={false}
61-
forceFocusMacOS={true}
62-
>
63-
{children}
64-
</Slots.focusZone>
55+
<Slots.scrollView>
56+
<Slots.focusZone
57+
componentRef={focusZoneRef}
58+
focusZoneDirection={'vertical'}
59+
defaultTabbableElement={focusZoneRef}
60+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
61+
// @ts-ignore FocusZone takes ViewProps, but that isn't defined on it's type.
62+
enableFocusRing={false}
63+
>
64+
{children}
65+
</Slots.focusZone>
66+
</Slots.scrollView>
6567
</Slots.root>
6668
) : (
6769
<Slots.root>{children}</Slots.root>

packages/components/Menu/src/MenuList/MenuList.types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FocusZoneProps } from '@fluentui-react-native/focus-zone';
33
import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
44
import { IBackgroundColorTokens, LayoutTokens } from '@fluentui-react-native/tokens';
55
import React from 'react';
6+
import { ScrollViewProps } from 'react-native';
67

78
export const menuListName = 'MenuList';
89

@@ -51,6 +52,7 @@ export interface MenuListState extends Omit<MenuListProps, 'checked' | 'onChecke
5152
export interface MenuListSlotProps {
5253
root: React.PropsWithRef<IViewProps> & { gap?: number };
5354
focusZone?: FocusZoneProps; // macOS only
55+
scrollView?: ScrollViewProps; // macOS only
5456
}
5557

5658
export interface MenuListType {

0 commit comments

Comments
 (0)