mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at session/schema 249 lines 7.2 kB view raw
1import React from 'react' 2import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 3import * as DropdownMenu from '@radix-ui/react-dropdown-menu' 4import {Pressable, StyleSheet, View, Text, ViewStyle} from 'react-native' 5import {IconProp} from '@fortawesome/fontawesome-svg-core' 6import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' 7import {usePalette} from 'lib/hooks/usePalette' 8import {useTheme} from 'lib/ThemeContext' 9import {HITSLOP_10} from 'lib/constants' 10 11// Custom Dropdown Menu Components 12// == 13export const DropdownMenuRoot = DropdownMenu.Root 14export const DropdownMenuContent = DropdownMenu.Content 15 16type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']> 17export const DropdownMenuItem = (props: ItemProps & {testID?: string}) => { 18 const theme = useTheme() 19 const [focused, setFocused] = React.useState(false) 20 const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001' 21 22 return ( 23 <DropdownMenu.Item 24 className="nativeDropdown-item" 25 {...props} 26 style={StyleSheet.flatten([ 27 styles.item, 28 focused && {backgroundColor: backgroundColor}, 29 ])} 30 onFocus={() => { 31 setFocused(true) 32 }} 33 onBlur={() => { 34 setFocused(false) 35 }} 36 /> 37 ) 38} 39 40// Types for Dropdown Menu and Items 41export type DropdownItem = { 42 label: string | 'separator' 43 onPress?: () => void 44 testID?: string 45 icon?: { 46 ios: MenuItemCommonProps['ios'] 47 android: string 48 web: IconProp 49 } 50} 51type Props = { 52 items: DropdownItem[] 53 testID?: string 54 accessibilityLabel?: string 55 accessibilityHint?: string 56 triggerStyle?: ViewStyle 57} 58 59export function NativeDropdown({ 60 items, 61 children, 62 testID, 63 accessibilityLabel, 64 accessibilityHint, 65 triggerStyle, 66}: React.PropsWithChildren<Props>) { 67 const pal = usePalette('default') 68 const theme = useTheme() 69 const dropDownBackgroundColor = 70 theme.colorScheme === 'dark' ? pal.btn : pal.view 71 const [open, setOpen] = React.useState(false) 72 const buttonRef = React.useRef<HTMLButtonElement>(null) 73 const menuRef = React.useRef<HTMLDivElement>(null) 74 const {borderColor: separatorColor} = 75 theme.colorScheme === 'dark' ? pal.borderDark : pal.border 76 77 React.useEffect(() => { 78 function clickHandler(e: MouseEvent) { 79 const t = e.target 80 81 if (!open) return 82 if (!t) return 83 if (!buttonRef.current || !menuRef.current) return 84 85 if ( 86 t !== buttonRef.current && 87 !buttonRef.current.contains(t as Node) && 88 t !== menuRef.current && 89 !menuRef.current.contains(t as Node) 90 ) { 91 // prevent clicking through to links beneath dropdown 92 // only applies to mobile web 93 e.preventDefault() 94 e.stopPropagation() 95 96 // close menu 97 setOpen(false) 98 } 99 } 100 101 function keydownHandler(e: KeyboardEvent) { 102 if (e.key === 'Escape' && open) { 103 setOpen(false) 104 } 105 } 106 107 document.addEventListener('click', clickHandler, true) 108 window.addEventListener('keydown', keydownHandler, true) 109 return () => { 110 document.removeEventListener('click', clickHandler, true) 111 window.removeEventListener('keydown', keydownHandler, true) 112 } 113 }, [open, setOpen]) 114 115 return ( 116 <DropdownMenuRoot open={open} onOpenChange={o => setOpen(o)}> 117 <DropdownMenu.Trigger asChild onPointerDown={e => e.preventDefault()}> 118 <Pressable 119 ref={buttonRef as unknown as React.Ref<View>} 120 testID={testID} 121 accessibilityRole="button" 122 accessibilityLabel={accessibilityLabel} 123 accessibilityHint={accessibilityHint} 124 onPress={() => setOpen(o => !o)} 125 hitSlop={HITSLOP_10} 126 style={triggerStyle}> 127 {children} 128 </Pressable> 129 </DropdownMenu.Trigger> 130 131 <DropdownMenu.Portal> 132 <DropdownMenu.Content 133 ref={menuRef} 134 style={ 135 StyleSheet.flatten([ 136 styles.content, 137 dropDownBackgroundColor, 138 ]) as React.CSSProperties 139 } 140 loop> 141 {items.map((item, index) => { 142 if (item.label === 'separator') { 143 return ( 144 <DropdownMenu.Separator 145 key={getKey(item.label, index, item.testID)} 146 style={ 147 StyleSheet.flatten([ 148 styles.separator, 149 {backgroundColor: separatorColor}, 150 ]) as React.CSSProperties 151 } 152 /> 153 ) 154 } 155 if (index > 1 && items[index - 1].label === 'separator') { 156 return ( 157 <DropdownMenu.Group 158 key={getKey(item.label, index, item.testID)}> 159 <DropdownMenuItem 160 key={getKey(item.label, index, item.testID)} 161 onSelect={item.onPress}> 162 <Text 163 selectable={false} 164 style={[pal.text, styles.itemTitle]}> 165 {item.label} 166 </Text> 167 {item.icon && ( 168 <FontAwesomeIcon 169 icon={item.icon.web} 170 size={20} 171 color={pal.colors.textLight} 172 /> 173 )} 174 </DropdownMenuItem> 175 </DropdownMenu.Group> 176 ) 177 } 178 return ( 179 <DropdownMenuItem 180 key={getKey(item.label, index, item.testID)} 181 onSelect={item.onPress}> 182 <Text selectable={false} style={[pal.text, styles.itemTitle]}> 183 {item.label} 184 </Text> 185 {item.icon && ( 186 <FontAwesomeIcon 187 icon={item.icon.web} 188 size={20} 189 color={pal.colors.textLight} 190 /> 191 )} 192 </DropdownMenuItem> 193 ) 194 })} 195 </DropdownMenu.Content> 196 </DropdownMenu.Portal> 197 </DropdownMenuRoot> 198 ) 199} 200 201const getKey = (label: string, index: number, id?: string) => { 202 if (id) { 203 return id 204 } 205 return `${label}_${index}` 206} 207 208const styles = StyleSheet.create({ 209 separator: { 210 height: 1, 211 marginTop: 4, 212 marginBottom: 4, 213 }, 214 content: { 215 backgroundColor: '#f0f0f0', 216 borderRadius: 8, 217 paddingTop: 4, 218 paddingBottom: 4, 219 paddingLeft: 4, 220 paddingRight: 4, 221 marginTop: 6, 222 223 // @ts-ignore web only -prf 224 boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px', 225 }, 226 item: { 227 display: 'flex', 228 flexDirection: 'row', 229 justifyContent: 'space-between', 230 alignItems: 'center', 231 columnGap: 20, 232 // @ts-ignore -web 233 cursor: 'pointer', 234 paddingTop: 8, 235 paddingBottom: 8, 236 paddingLeft: 12, 237 paddingRight: 12, 238 borderRadius: 8, 239 fontFamily: 240 '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif', 241 outline: 0, 242 border: 0, 243 }, 244 itemTitle: { 245 fontSize: 16, 246 fontWeight: '500', 247 paddingRight: 10, 248 }, 249})