Add dismiss backdrop to native dropdowns (#4711)

zio/stable
dan 2024-07-01 18:45:15 +01:00 committed by GitHub
parent 1a037d3542
commit a9fe87b842
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 100 additions and 67 deletions

View File

@ -1,13 +1,15 @@
import React from 'react' import React from 'react'
import {Platform, Pressable, StyleSheet, View, ViewStyle} from 'react-native'
import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import * as DropdownMenu from 'zeego/dropdown-menu' import * as DropdownMenu from 'zeego/dropdown-menu'
import {Pressable, StyleSheet, Platform, View, ViewStyle} from 'react-native'
import {IconProp} from '@fortawesome/fontawesome-svg-core'
import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' import {MenuItemCommonProps} from 'zeego/lib/typescript/menu'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useTheme} from 'lib/ThemeContext'
import {HITSLOP_10} from 'lib/constants' import {HITSLOP_10} from 'lib/constants'
import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {isIOS, isWeb} from 'platform/detection'
import {Portal} from '#/components/Portal'
// Custom Dropdown Menu Components // Custom Dropdown Menu Components
// == // ==
@ -169,74 +171,105 @@ export function NativeDropdown({
}: React.PropsWithChildren<Props>) { }: React.PropsWithChildren<Props>) {
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const [isOpen, setIsOpen] = React.useState(false)
const dropDownBackgroundColor = const dropDownBackgroundColor =
theme.colorScheme === 'dark' ? pal.btn : pal.viewLight theme.colorScheme === 'dark' ? pal.btn : pal.viewLight
return ( return (
<DropdownMenuRoot> <>
<DropdownMenuTrigger {isIOS && isOpen && (
action="press" <Portal>
testID={testID} <Backdrop />
accessibilityLabel={accessibilityLabel} </Portal>
accessibilityHint={accessibilityHint}> )}
{children} <DropdownMenuRoot onOpenWillChange={setIsOpen}>
</DropdownMenuTrigger> <DropdownMenuTrigger
<DropdownMenuContent action="press"
style={[styles.content, dropDownBackgroundColor]} testID={testID}
loop> accessibilityLabel={accessibilityLabel}
{items.map((item, index) => { accessibilityHint={accessibilityHint}>
if (item.label === 'separator') { {children}
return ( </DropdownMenuTrigger>
<DropdownMenuSeparator <DropdownMenuContent
key={getKey(item.label, index, item.testID)} style={[styles.content, dropDownBackgroundColor]}
/> loop>
) {items.map((item, index) => {
} if (item.label === 'separator') {
if (index > 1 && items[index - 1].label === 'separator') { return (
return ( <DropdownMenuSeparator
<DropdownMenu.Group key={getKey(item.label, index, item.testID)}>
<DropdownMenuItem
key={getKey(item.label, index, item.testID)} key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}> />
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle> )
{item.icon && ( }
<DropdownMenuItemIcon if (index > 1 && items[index - 1].label === 'separator') {
ios={item.icon.ios} return (
// androidIconName={item.icon.android} TODO: Add custom android icon support, because these ones are based on https://developer.android.com/reference/android/R.drawable.html and they are ugly <DropdownMenu.Group
> key={getKey(item.label, index, item.testID)}>
<FontAwesomeIcon <DropdownMenuItem
icon={item.icon.web} key={getKey(item.label, index, item.testID)}
size={20} onSelect={item.onPress}>
style={[pal.text]} <DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
/> {item.icon && (
</DropdownMenuItemIcon> <DropdownMenuItemIcon
)} ios={item.icon.ios}
</DropdownMenuItem> // androidIconName={item.icon.android} TODO: Add custom android icon support, because these ones are based on https://developer.android.com/reference/android/R.drawable.html and they are ugly
</DropdownMenu.Group> >
<FontAwesomeIcon
icon={item.icon.web}
size={20}
style={[pal.text]}
/>
</DropdownMenuItemIcon>
)}
</DropdownMenuItem>
</DropdownMenu.Group>
)
}
return (
<DropdownMenuItem
key={getKey(item.label, index, item.testID)}
onSelect={item.onPress}>
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
{item.icon && (
<DropdownMenuItemIcon
ios={item.icon.ios}
// androidIconName={item.icon.android}
>
<FontAwesomeIcon
icon={item.icon.web}
size={20}
style={[pal.text]}
/>
</DropdownMenuItemIcon>
)}
</DropdownMenuItem>
) )
} })}
return ( </DropdownMenuContent>
<DropdownMenuItem </DropdownMenuRoot>
key={getKey(item.label, index, item.testID)} </>
onSelect={item.onPress}> )
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle> }
{item.icon && (
<DropdownMenuItemIcon function Backdrop() {
ios={item.icon.ios} // Not visible but it eats the click outside.
// androidIconName={item.icon.android} // Only necessary for iOS.
> return (
<FontAwesomeIcon <Pressable
icon={item.icon.web} accessibilityRole="button"
size={20} accessibilityLabel="Dialog backdrop"
style={[pal.text]} accessibilityHint="Press the backdrop to close the dialog"
/> style={{
</DropdownMenuItemIcon> top: 0,
)} left: 0,
</DropdownMenuItem> right: 0,
) bottom: 0,
})} position: 'absolute',
</DropdownMenuContent> }}
</DropdownMenuRoot> onPress={() => {
/* noop */
}}
/>
) )
} }