diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx new file mode 100644 index 00000000..9e9888ad --- /dev/null +++ b/src/view/com/util/forms/NativeDropdown.web.tsx @@ -0,0 +1,241 @@ +import React from 'react' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import {Pressable, StyleSheet, View, Text} from 'react-native' +import {IconProp} from '@fortawesome/fontawesome-svg-core' +import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' +import {usePalette} from 'lib/hooks/usePalette' +import {useTheme} from 'lib/ThemeContext' +import {HITSLOP_10} from 'lib/constants' + +// Custom Dropdown Menu Components +// == +export const DropdownMenuRoot = DropdownMenu.Root +export const DropdownMenuContent = DropdownMenu.Content + +type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']> +export const DropdownMenuItem = (props: ItemProps & {testID?: string}) => { + const theme = useTheme() + const [focused, setFocused] = React.useState(false) + const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001' + + return ( + { + setFocused(true) + }} + onBlur={() => { + setFocused(false) + }} + /> + ) +} + +// Types for Dropdown Menu and Items +export type DropdownItem = { + label: string | 'separator' + onPress?: () => void + testID?: string + icon?: { + ios: MenuItemCommonProps['ios'] + android: string + web: IconProp + } +} +type Props = { + items: DropdownItem[] + testID?: string + accessibilityLabel?: string + accessibilityHint?: string +} + +export function NativeDropdown({ + items, + children, + testID, + accessibilityLabel, + accessibilityHint, +}: React.PropsWithChildren) { + const pal = usePalette('default') + const theme = useTheme() + const dropDownBackgroundColor = + theme.colorScheme === 'dark' ? pal.btn : pal.view + const [open, setOpen] = React.useState(false) + const buttonRef = React.useRef(null) + const menuRef = React.useRef(null) + const {borderColor: separatorColor} = + theme.colorScheme === 'dark' ? pal.borderDark : pal.border + + React.useEffect(() => { + function clickHandler(e: MouseEvent) { + const t = e.target + + if (!open) return + if (!t) return + if (!buttonRef.current || !menuRef.current) return + + if ( + t !== buttonRef.current && + !buttonRef.current.contains(t as Node) && + t !== menuRef.current && + !menuRef.current.contains(t as Node) + ) { + // prevent clicking through to links beneath dropdown + // only applies to mobile web + e.preventDefault() + e.stopPropagation() + + // close menu + setOpen(false) + } + } + + function keydownHandler(e: KeyboardEvent) { + if (e.key === 'Escape' && open) { + setOpen(false) + } + } + + document.addEventListener('click', clickHandler, true) + window.addEventListener('keydown', keydownHandler, true) + return () => { + document.removeEventListener('click', clickHandler, true) + window.removeEventListener('keydown', keydownHandler, true) + } + }, [open, setOpen]) + + return ( + setOpen(o)}> + e.preventDefault()}> + } + testID={testID} + accessibilityRole="button" + accessibilityLabel={accessibilityLabel} + accessibilityHint={accessibilityHint} + onPress={() => setOpen(o => !o)} + hitSlop={HITSLOP_10}> + {children} + + + + + + {items.map((item, index) => { + if (item.label === 'separator') { + return ( + + ) + } + if (index > 1 && items[index - 1].label === 'separator') { + return ( + + + + {item.label} + + {item.icon && ( + + )} + + + ) + } + return ( + + + {item.label} + + {item.icon && ( + + )} + + ) + })} + + + + ) +} + +const getKey = (label: string, index: number, id?: string) => { + if (id) { + return id + } + return `${label}_${index}` +} + +const styles = StyleSheet.create({ + separator: { + height: 1, + marginTop: 4, + marginBottom: 4, + }, + content: { + backgroundColor: '#f0f0f0', + borderRadius: 8, + paddingTop: 4, + paddingBottom: 4, + paddingLeft: 4, + paddingRight: 4, + marginTop: 6, + + // @ts-ignore web only -prf + boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px', + }, + item: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + columnGap: 20, + // @ts-ignore -web + cursor: 'pointer', + paddingTop: 8, + paddingBottom: 8, + paddingLeft: 12, + paddingRight: 12, + borderRadius: 8, + }, + itemTitle: { + fontSize: 16, + fontWeight: '500', + paddingRight: 10, + }, +})