mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
150 lines
4.1 KiB
TypeScript
150 lines
4.1 KiB
TypeScript
import * as React from 'react';
|
|
import { useCallback, useMemo, useState } from 'react';
|
|
import { StyleSheet, TextStyle, View, Text, ScrollView, useWindowDimensions } from 'react-native';
|
|
import { themeStyle } from '../global-style';
|
|
import { Menu, MenuOption as MenuOptionComponent, MenuOptions, MenuTrigger } from 'react-native-popup-menu';
|
|
import AccessibleView from '../accessibility/AccessibleView';
|
|
import debounce from '../../utils/debounce';
|
|
|
|
interface MenuOptionDivider {
|
|
isDivider: true;
|
|
}
|
|
|
|
interface MenuOptionButton {
|
|
key?: string;
|
|
isDivider?: false|undefined;
|
|
disabled?: boolean;
|
|
onPress: ()=> void;
|
|
title: string;
|
|
}
|
|
|
|
export type MenuOptionType = MenuOptionDivider|MenuOptionButton;
|
|
|
|
interface Props {
|
|
themeId: number;
|
|
options: MenuOptionType[];
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
const useStyles = (themeId: number) => {
|
|
const { height: windowHeight } = useWindowDimensions();
|
|
|
|
return useMemo(() => {
|
|
const theme = themeStyle(themeId);
|
|
|
|
const contextMenuItemTextBase: TextStyle = {
|
|
flex: 1,
|
|
textAlignVertical: 'center',
|
|
paddingLeft: theme.marginLeft,
|
|
paddingRight: theme.marginRight,
|
|
paddingTop: theme.itemMarginTop,
|
|
paddingBottom: theme.itemMarginBottom,
|
|
color: theme.color,
|
|
backgroundColor: theme.backgroundColor,
|
|
fontSize: theme.fontSize,
|
|
};
|
|
|
|
return StyleSheet.create({
|
|
divider: {
|
|
borderBottomWidth: 1,
|
|
borderColor: theme.dividerColor,
|
|
backgroundColor: '#0000ff',
|
|
},
|
|
contextMenu: {
|
|
backgroundColor: theme.backgroundColor2,
|
|
},
|
|
contextMenuItem: {
|
|
backgroundColor: theme.backgroundColor,
|
|
},
|
|
contextMenuItemText: {
|
|
...contextMenuItemTextBase,
|
|
},
|
|
contextMenuItemTextDisabled: {
|
|
...contextMenuItemTextBase,
|
|
opacity: 0.5,
|
|
},
|
|
menuContentScroller: {
|
|
maxHeight: windowHeight - 50,
|
|
},
|
|
contextMenuButton: {
|
|
padding: 0,
|
|
},
|
|
});
|
|
}, [themeId, windowHeight]);
|
|
};
|
|
|
|
const MenuComponent: React.FC<Props> = props => {
|
|
const styles = useStyles(props.themeId);
|
|
|
|
const menuOptionComponents: React.ReactNode[] = [];
|
|
|
|
// When undefined/null: Don't auto-focus anything.
|
|
const [refocusCounter, setRefocusCounter] = useState<number|undefined>(undefined);
|
|
|
|
let key = 0;
|
|
let isFirst = true;
|
|
for (const option of props.options) {
|
|
if (option.isDivider === true) {
|
|
menuOptionComponents.push(
|
|
<View key={`menuOption_divider_${key++}`} style={styles.divider} />,
|
|
);
|
|
} else {
|
|
const canAutoFocus = isFirst;
|
|
menuOptionComponents.push(
|
|
<MenuOptionComponent value={option.onPress} key={`menuOption_${option.key ?? key++}`} style={styles.contextMenuItem} disabled={!!option.disabled}>
|
|
<AccessibleView refocusCounter={canAutoFocus ? refocusCounter : undefined}>
|
|
<Text
|
|
style={option.disabled ? styles.contextMenuItemTextDisabled : styles.contextMenuItemText}
|
|
disabled={!!option.disabled}
|
|
>{option.title}</Text>
|
|
</AccessibleView>
|
|
</MenuOptionComponent>,
|
|
);
|
|
|
|
isFirst = false;
|
|
}
|
|
}
|
|
|
|
const onMenuItemSelect = useCallback((value: unknown) => {
|
|
if (typeof value === 'function') {
|
|
value();
|
|
}
|
|
setRefocusCounter(undefined);
|
|
}, []);
|
|
|
|
// debounce: If the menu is focused during its transition animation, it briefly
|
|
// appears to be in the wrong place. As such, add a brief delay before focusing.
|
|
const onMenuOpen = useMemo(() => debounce(() => {
|
|
setRefocusCounter(counter => (counter ?? 0) + 1);
|
|
}, 200), []);
|
|
|
|
// Resetting the refocus counter to undefined causes the menu to not be focused immediately
|
|
// after opening.
|
|
const onMenuClose = useCallback(() => {
|
|
setRefocusCounter(undefined);
|
|
}, []);
|
|
|
|
return (
|
|
<Menu
|
|
onSelect={onMenuItemSelect}
|
|
onClose={onMenuClose}
|
|
onOpen={onMenuOpen}
|
|
style={styles.contextMenu}
|
|
>
|
|
<MenuTrigger style={styles.contextMenuButton} testID='screen-header-menu-trigger'>
|
|
{props.children}
|
|
</MenuTrigger>
|
|
<MenuOptions>
|
|
<ScrollView
|
|
style={styles.menuContentScroller}
|
|
aria-modal={true}
|
|
accessibilityViewIsModal={true}
|
|
testID={`menu-content-${refocusCounter ? 'refocusing' : ''}`}
|
|
>{menuOptionComponents}</ScrollView>
|
|
</MenuOptions>
|
|
</Menu>
|
|
);
|
|
};
|
|
|
|
export default MenuComponent;
|