Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

New `Select` component (#8323)

* radix select component on web

* native implementation (wip)

* fix sheet height/padding

* tone down web styles

* react 19 cleanup

* replace primary language select

* change style on native

* get auto placeholder working

* more style tweaks

* replace app language dropdown

* replace rnpickerselect with native select

* rm react-native-picker-select dependency

* rm placeholder, since a value is always selected

* docblock for renderItem

* add more docblocks

* add style prop to item

* pass selectedValue through renderItem

* fix context

* Style overflow buttons

---------

Co-authored-by: Eric Bailey <git@esb.lol>

authored by

Samuel Newman
Eric Bailey
and committed by
GitHub
973538d2 25f8506c

+899 -300
+4 -1
modules/bottom-sheet/src/BottomSheetNativeComponent.tsx
··· 30 30 31 31 const NativeModule = requireNativeModule('BottomSheet') 32 32 33 - const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16 33 + const isIOS15 = 34 + Platform.OS === 'ios' && 35 + // semvar - can be 3 segments, so can't use Number(Platform.Version) 36 + Number(Platform.Version.split('.').at(0)) < 16 34 37 35 38 export class BottomSheetNativeComponent extends React.Component< 36 39 BottomSheetViewProps,
-1
package.json
··· 193 193 "react-native-keyboard-controller": "^1.17.1", 194 194 "react-native-mmkv": "^2.12.2", 195 195 "react-native-pager-view": "6.7.1", 196 - "react-native-picker-select": "^9.3.1", 197 196 "react-native-progress": "bluesky-social/react-native-progress", 198 197 "react-native-qrcode-styled": "^0.3.3", 199 198 "react-native-reanimated": "~3.17.5",
+3
src/alf/atoms.ts
··· 204 204 flex_grow: { 205 205 flexGrow: 1, 206 206 }, 207 + flex_grow_0: { 208 + flexGrow: 0, 209 + }, 207 210 flex_shrink: { 208 211 flexShrink: 1, 209 212 },
+47 -40
src/components/AppLanguageDropdown.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 3 - import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' 2 + import {msg} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 4 import {useQueryClient} from '@tanstack/react-query' 5 5 6 6 import {sanitizeAppLanguageSetting} from '#/locale/helpers' 7 7 import {APP_LANGUAGES} from '#/locale/languages' 8 8 import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' 9 9 import {resetPostsFeedQueries} from '#/state/queries/post-feed' 10 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 11 - import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' 10 + import {atoms as a, platform, useTheme} from '#/alf' 11 + import * as Select from '#/components/Select' 12 + import {Button} from './Button' 12 13 13 - export function AppLanguageDropdown(_props: ViewStyleProp) { 14 + export function AppLanguageDropdown() { 14 15 const t = useTheme() 16 + const {_} = useLingui() 15 17 16 18 const queryClient = useQueryClient() 17 19 const langPrefs = useLanguagePrefs() ··· 19 21 const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) 20 22 21 23 const onChangeAppLanguage = React.useCallback( 22 - (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { 24 + (value: string) => { 23 25 if (!value) return 24 26 if (sanitizedLang !== value) { 25 27 setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) ··· 32 34 ) 33 35 34 36 return ( 35 - <View style={a.relative}> 36 - <RNPickerSelect 37 - darkTheme={t.scheme === 'dark'} 38 - placeholder={{}} 39 - value={sanitizedLang} 40 - onValueChange={onChangeAppLanguage} 41 - items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ 37 + <Select.Root 38 + value={sanitizeAppLanguageSetting(langPrefs.appLanguage)} 39 + onValueChange={onChangeAppLanguage}> 40 + <Select.Trigger label={_(msg`Change app language`)}> 41 + {({props}) => ( 42 + <Button 43 + {...props} 44 + label={props.accessibilityLabel} 45 + size={platform({ 46 + web: 'tiny', 47 + native: 'small', 48 + })} 49 + variant="ghost" 50 + color="secondary" 51 + style={[ 52 + a.pr_xs, 53 + a.pl_sm, 54 + platform({ 55 + web: [{alignSelf: 'flex-start'}, a.gap_sm], 56 + native: [a.gap_xs], 57 + }), 58 + ]}> 59 + <Select.ValueText 60 + placeholder={_(msg`Select an app language`)} 61 + style={[t.atoms.text_contrast_medium]} 62 + /> 63 + <Select.Icon style={[t.atoms.text_contrast_medium]} /> 64 + </Button> 65 + )} 66 + </Select.Trigger> 67 + <Select.Content 68 + renderItem={({label, value}) => ( 69 + <Select.Item value={value} label={label}> 70 + <Select.ItemIndicator /> 71 + <Select.ItemText>{label}</Select.ItemText> 72 + </Select.Item> 73 + )} 74 + items={APP_LANGUAGES.map(l => ({ 42 75 label: l.name, 43 76 value: l.code2, 44 - key: l.code2, 45 77 }))} 46 - useNativeAndroidPickerStyle={false} 47 - style={{ 48 - inputAndroid: { 49 - color: t.atoms.text_contrast_medium.color, 50 - fontSize: 16, 51 - paddingRight: 12 + 4, 52 - }, 53 - inputIOS: { 54 - color: t.atoms.text.color, 55 - fontSize: 16, 56 - paddingRight: 12 + 4, 57 - }, 58 - }} 59 78 /> 60 - 61 - <View 62 - style={[ 63 - a.absolute, 64 - a.inset_0, 65 - {left: 'auto'}, 66 - {pointerEvents: 'none'}, 67 - a.align_center, 68 - a.justify_center, 69 - ]}> 70 - <ChevronDown fill={t.atoms.text.color} size="xs" /> 71 - </View> 72 - </View> 79 + </Select.Root> 73 80 ) 74 81 }
-83
src/components/AppLanguageDropdown.web.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 3 - import {useQueryClient} from '@tanstack/react-query' 4 - 5 - import {sanitizeAppLanguageSetting} from '#/locale/helpers' 6 - import {APP_LANGUAGES} from '#/locale/languages' 7 - import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' 8 - import {resetPostsFeedQueries} from '#/state/queries/post-feed' 9 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 10 - import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' 11 - import {Text} from '#/components/Typography' 12 - 13 - export function AppLanguageDropdown({style}: ViewStyleProp) { 14 - const t = useTheme() 15 - 16 - const queryClient = useQueryClient() 17 - const langPrefs = useLanguagePrefs() 18 - const setLangPrefs = useLanguagePrefsApi() 19 - 20 - const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) 21 - 22 - const onChangeAppLanguage = React.useCallback( 23 - (ev: React.ChangeEvent<HTMLSelectElement>) => { 24 - const value = ev.target.value 25 - 26 - if (!value) return 27 - if (sanitizedLang !== value) { 28 - setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) 29 - } 30 - 31 - // reset feeds to refetch content 32 - resetPostsFeedQueries(queryClient) 33 - }, 34 - [sanitizedLang, setLangPrefs, queryClient], 35 - ) 36 - 37 - return ( 38 - <View 39 - style={[ 40 - // We don't have hitSlop here to increase the tap region, 41 - // alternative is negative margins. 42 - {height: 32, marginVertical: -((32 - 14) / 2)}, 43 - style, 44 - ]}> 45 - <View 46 - style={[ 47 - a.flex_row, 48 - a.gap_sm, 49 - a.align_center, 50 - a.flex_shrink, 51 - a.h_full, 52 - t.atoms.bg, 53 - ]}> 54 - <Text aria-hidden={true} style={t.atoms.text_contrast_medium}> 55 - {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} 56 - </Text> 57 - <ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_0} /> 58 - </View> 59 - 60 - <select 61 - value={sanitizedLang} 62 - onChange={onChangeAppLanguage} 63 - style={{ 64 - fontSize: a.text_sm.fontSize, 65 - letterSpacing: a.text_sm.letterSpacing, 66 - cursor: 'pointer', 67 - position: 'absolute', 68 - inset: 0, 69 - opacity: 0, 70 - color: t.atoms.text.color, 71 - background: t.atoms.bg.backgroundColor, 72 - padding: 4, 73 - maxWidth: '100%', 74 - }}> 75 - {APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ( 76 - <option key={l.code2} value={l.code2}> 77 - {l.name} 78 - </option> 79 - ))} 80 - </select> 81 - </View> 82 - ) 83 - }
+10 -1
src/components/Dialog/shared.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' 2 + import { 3 + LayoutChangeEvent, 4 + StyleProp, 5 + TextStyle, 6 + View, 7 + ViewStyle, 8 + } from 'react-native' 3 9 4 10 import {atoms as a, useTheme} from '#/alf' 5 11 import {Text} from '#/components/Typography' ··· 9 15 renderRight, 10 16 children, 11 17 style, 18 + onLayout, 12 19 }: { 13 20 renderLeft?: () => React.ReactNode 14 21 renderRight?: () => React.ReactNode 15 22 children?: React.ReactNode 16 23 style?: StyleProp<ViewStyle> 24 + onLayout?: (event: LayoutChangeEvent) => void 17 25 }) { 18 26 const t = useTheme() 19 27 return ( 20 28 <View 29 + onLayout={onLayout} 21 30 style={[ 22 31 a.relative, 23 32 a.w_full,
+289
src/components/Select/index.tsx
··· 1 + import { 2 + createContext, 3 + useCallback, 4 + useContext, 5 + useLayoutEffect, 6 + useMemo, 7 + useState, 8 + } from 'react' 9 + import {View} from 'react-native' 10 + import {msg, Trans} from '@lingui/macro' 11 + import {useLingui} from '@lingui/react' 12 + 13 + import {useTheme} from '#/alf' 14 + import {atoms as a} from '#/alf' 15 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 16 + import * as Dialog from '#/components/Dialog' 17 + import {useInteractionState} from '#/components/hooks/useInteractionState' 18 + import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 19 + import {ChevronTopBottom_Stroke2_Corner0_Rounded as ChevronUpDownIcon} from '#/components/icons/Chevron' 20 + import {Text} from '#/components/Typography' 21 + import { 22 + type ContentProps, 23 + type IconProps, 24 + type ItemIndicatorProps, 25 + type ItemProps, 26 + type ItemTextProps, 27 + type RootProps, 28 + type TriggerProps, 29 + type ValueProps, 30 + } from './types' 31 + 32 + type ContextType = { 33 + control: Dialog.DialogControlProps 34 + } & Pick<RootProps, 'value' | 'onValueChange' | 'disabled'> 35 + 36 + const Context = createContext<ContextType | null>(null) 37 + 38 + const ValueTextContext = createContext< 39 + [any, React.Dispatch<React.SetStateAction<any>>] 40 + >([undefined, () => {}]) 41 + 42 + function useSelectContext() { 43 + const ctx = useContext(Context) 44 + if (!ctx) { 45 + throw new Error('Select components must must be used within a Select.Root') 46 + } 47 + return ctx 48 + } 49 + 50 + export function Root({children, value, onValueChange, disabled}: RootProps) { 51 + const control = Dialog.useDialogControl() 52 + const valueTextCtx = useState<any>() 53 + 54 + const ctx = useMemo( 55 + () => ({ 56 + control, 57 + value, 58 + onValueChange, 59 + disabled, 60 + }), 61 + [control, value, onValueChange, disabled], 62 + ) 63 + return ( 64 + <Context.Provider value={ctx}> 65 + <ValueTextContext.Provider value={valueTextCtx}> 66 + {children} 67 + </ValueTextContext.Provider> 68 + </Context.Provider> 69 + ) 70 + } 71 + 72 + export function Trigger({children, label}: TriggerProps) { 73 + const {control} = useSelectContext() 74 + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 75 + const { 76 + state: pressed, 77 + onIn: onPressIn, 78 + onOut: onPressOut, 79 + } = useInteractionState() 80 + 81 + if (typeof children === 'function') { 82 + return children({ 83 + isNative: true, 84 + control, 85 + state: { 86 + hovered: false, 87 + focused, 88 + pressed, 89 + }, 90 + props: { 91 + onPress: control.open, 92 + onFocus, 93 + onBlur, 94 + onPressIn, 95 + onPressOut, 96 + accessibilityLabel: label, 97 + }, 98 + }) 99 + } else { 100 + return ( 101 + <Button 102 + label={label} 103 + onPress={control.open} 104 + style={[a.flex_1, a.justify_between]} 105 + color="secondary" 106 + size="small" 107 + variant="solid"> 108 + <>{children}</> 109 + </Button> 110 + ) 111 + } 112 + } 113 + 114 + export function ValueText({ 115 + placeholder, 116 + children = value => value.label, 117 + style, 118 + }: ValueProps) { 119 + const [value] = useContext(ValueTextContext) 120 + const t = useTheme() 121 + 122 + let text = value && children(value) 123 + if (typeof text !== 'string') text = placeholder 124 + 125 + return ( 126 + <ButtonText style={[t.atoms.text, a.font_normal, style]}>{text}</ButtonText> 127 + ) 128 + } 129 + 130 + export function Icon({}: IconProps) { 131 + return <ButtonIcon icon={ChevronUpDownIcon} /> 132 + } 133 + 134 + export function Content<T>({ 135 + items, 136 + valueExtractor = defaultItemValueExtractor, 137 + ...props 138 + }: ContentProps<T>) { 139 + const {control, ...context} = useSelectContext() 140 + const [, setValue] = useContext(ValueTextContext) 141 + 142 + useLayoutEffect(() => { 143 + const item = items.find(item => valueExtractor(item) === context.value) 144 + if (item) { 145 + setValue(item) 146 + } 147 + }, [items, context.value, valueExtractor, setValue]) 148 + 149 + return ( 150 + <Dialog.Outer control={control}> 151 + <ContentInner 152 + control={control} 153 + items={items} 154 + valueExtractor={valueExtractor} 155 + {...props} 156 + {...context} 157 + /> 158 + </Dialog.Outer> 159 + ) 160 + } 161 + 162 + function ContentInner<T>({ 163 + items, 164 + renderItem, 165 + valueExtractor, 166 + ...context 167 + }: ContentProps<T> & ContextType) { 168 + const control = Dialog.useDialogContext() 169 + 170 + const {_} = useLingui() 171 + const [headerHeight, setHeaderHeight] = useState(50) 172 + 173 + const render = useCallback( 174 + ({item, index}: {item: T; index: number}) => { 175 + return renderItem(item, index, context.value) 176 + }, 177 + [renderItem, context.value], 178 + ) 179 + 180 + const doneButton = useCallback( 181 + () => ( 182 + <Button 183 + label={_(msg`Done`)} 184 + onPress={() => control.close()} 185 + size="small" 186 + color="primary" 187 + variant="ghost" 188 + style={[a.rounded_full]}> 189 + <ButtonText style={[a.text_md]}> 190 + <Trans>Done</Trans> 191 + </ButtonText> 192 + </Button> 193 + ), 194 + [control, _], 195 + ) 196 + 197 + return ( 198 + <Context.Provider value={context}> 199 + <Dialog.Header 200 + renderRight={doneButton} 201 + onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)} 202 + style={[a.absolute, a.top_0, a.left_0, a.right_0, a.z_10]}> 203 + <Dialog.HeaderText> 204 + <Trans>Select an option</Trans> 205 + </Dialog.HeaderText> 206 + </Dialog.Header> 207 + <Dialog.InnerFlatList 208 + headerOffset={headerHeight} 209 + data={items} 210 + renderItem={render} 211 + keyExtractor={valueExtractor} 212 + /> 213 + </Context.Provider> 214 + ) 215 + } 216 + 217 + function defaultItemValueExtractor(item: any) { 218 + return item.value 219 + } 220 + 221 + const ItemContext = createContext<{ 222 + selected: boolean 223 + hovered: boolean 224 + focused: boolean 225 + pressed: boolean 226 + }>({ 227 + selected: false, 228 + hovered: false, 229 + focused: false, 230 + pressed: false, 231 + }) 232 + 233 + export function useItemContext() { 234 + return useContext(ItemContext) 235 + } 236 + 237 + export function Item({children, value, label, style}: ItemProps) { 238 + const t = useTheme() 239 + const control = Dialog.useDialogContext() 240 + const {value: selected, onValueChange} = useSelectContext() 241 + 242 + return ( 243 + <Button 244 + role="listitem" 245 + label={label} 246 + style={[a.flex_1]} 247 + onPress={() => { 248 + control.close(() => { 249 + onValueChange?.(value) 250 + }) 251 + }}> 252 + {({hovered, focused, pressed}) => ( 253 + <ItemContext.Provider 254 + value={{selected: value === selected, hovered, focused, pressed}}> 255 + <View 256 + style={[ 257 + a.flex_1, 258 + a.pl_md, 259 + (focused || pressed) && t.atoms.bg_contrast_25, 260 + a.flex_row, 261 + a.align_center, 262 + a.gap_sm, 263 + style, 264 + ]}> 265 + {children} 266 + </View> 267 + </ItemContext.Provider> 268 + )} 269 + </Button> 270 + ) 271 + } 272 + 273 + export function ItemText({children}: ItemTextProps) { 274 + const {selected} = useItemContext() 275 + const t = useTheme() 276 + 277 + // eslint-disable-next-line bsky-internal/avoid-unwrapped-text 278 + return ( 279 + <View style={[a.flex_1, a.py_md, a.border_b, t.atoms.border_contrast_low]}> 280 + <Text style={[a.text_md, selected && a.font_bold]}>{children}</Text> 281 + </View> 282 + ) 283 + } 284 + 285 + export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { 286 + const {selected} = useItemContext() 287 + 288 + return <View style={{width: 24}}>{selected && <Icon size="md" />}</View> 289 + }
+280
src/components/Select/index.web.tsx
··· 1 + import {createContext, forwardRef, useContext, useMemo} from 'react' 2 + import {View} from 'react-native' 3 + import {Select as RadixSelect} from 'radix-ui' 4 + 5 + import {flatten, useTheme} from '#/alf' 6 + import {atoms as a} from '#/alf' 7 + import {useInteractionState} from '#/components/hooks/useInteractionState' 8 + import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 9 + import { 10 + ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon, 11 + ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon, 12 + } from '#/components/icons/Chevron' 13 + import {Text} from '#/components/Typography' 14 + import { 15 + type ContentProps, 16 + type IconProps, 17 + type ItemIndicatorProps, 18 + type ItemProps, 19 + type RadixPassThroughTriggerProps, 20 + type RootProps, 21 + type TriggerProps, 22 + type ValueProps, 23 + } from './types' 24 + 25 + const SelectedValueContext = createContext<string | undefined | null>(null) 26 + 27 + export function Root(props: RootProps) { 28 + return ( 29 + <SelectedValueContext.Provider value={props.value}> 30 + <RadixSelect.Root {...props} /> 31 + </SelectedValueContext.Provider> 32 + ) 33 + } 34 + 35 + const RadixTriggerPassThrough = forwardRef( 36 + ( 37 + props: { 38 + children: ( 39 + props: RadixPassThroughTriggerProps & { 40 + ref: React.Ref<any> 41 + }, 42 + ) => React.ReactNode 43 + }, 44 + ref, 45 + ) => { 46 + // @ts-expect-error Radix provides no types of this stuff 47 + 48 + return props.children?.({...props, ref}) 49 + }, 50 + ) 51 + RadixTriggerPassThrough.displayName = 'RadixTriggerPassThrough' 52 + 53 + export function Trigger({children, label}: TriggerProps) { 54 + const t = useTheme() 55 + const { 56 + state: hovered, 57 + onIn: onMouseEnter, 58 + onOut: onMouseLeave, 59 + } = useInteractionState() 60 + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 61 + 62 + if (typeof children === 'function') { 63 + return ( 64 + <RadixSelect.Trigger asChild> 65 + <RadixTriggerPassThrough> 66 + {props => 67 + children({ 68 + isNative: false, 69 + state: { 70 + hovered, 71 + focused, 72 + pressed: false, 73 + }, 74 + props: { 75 + ...props, 76 + onFocus: onFocus, 77 + onBlur: onBlur, 78 + onMouseEnter, 79 + onMouseLeave, 80 + accessibilityLabel: label, 81 + }, 82 + }) 83 + } 84 + </RadixTriggerPassThrough> 85 + </RadixSelect.Trigger> 86 + ) 87 + } else { 88 + return ( 89 + <RadixSelect.Trigger 90 + onFocus={onFocus} 91 + onBlur={onBlur} 92 + onMouseEnter={onMouseEnter} 93 + onMouseLeave={onMouseLeave} 94 + style={flatten([ 95 + a.flex, 96 + a.relative, 97 + t.atoms.bg_contrast_25, 98 + a.rounded_sm, 99 + a.w_full, 100 + {maxWidth: 400}, 101 + a.align_center, 102 + a.gap_sm, 103 + a.justify_between, 104 + a.py_sm, 105 + a.px_md, 106 + { 107 + outline: 0, 108 + borderWidth: 2, 109 + borderStyle: 'solid', 110 + borderColor: focused 111 + ? t.palette.primary_500 112 + : hovered 113 + ? t.palette.contrast_100 114 + : t.palette.contrast_25, 115 + }, 116 + ])}> 117 + {children} 118 + </RadixSelect.Trigger> 119 + ) 120 + } 121 + } 122 + 123 + export function ValueText({children: _, style, ...props}: ValueProps) { 124 + return ( 125 + <Text style={style}> 126 + <RadixSelect.Value {...props} /> 127 + </Text> 128 + ) 129 + } 130 + 131 + export function Icon({style}: IconProps) { 132 + const t = useTheme() 133 + return ( 134 + <RadixSelect.Icon> 135 + <ChevronDownIcon style={[t.atoms.text, style]} size="xs" /> 136 + </RadixSelect.Icon> 137 + ) 138 + } 139 + 140 + export function Content<T>({items, renderItem}: ContentProps<T>) { 141 + const t = useTheme() 142 + const selectedValue = useContext(SelectedValueContext) 143 + 144 + const scrollBtnStyles: React.CSSProperties[] = [ 145 + a.absolute, 146 + a.flex, 147 + a.align_center, 148 + a.justify_center, 149 + a.rounded_sm, 150 + a.z_10, 151 + ] 152 + const up: React.CSSProperties[] = [ 153 + ...scrollBtnStyles, 154 + a.pt_sm, 155 + a.pb_lg, 156 + { 157 + top: 0, 158 + left: 0, 159 + right: 0, 160 + borderBottomLeftRadius: 0, 161 + borderBottomRightRadius: 0, 162 + background: `linear-gradient(to bottom, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 163 + }, 164 + ] 165 + const down: React.CSSProperties[] = [ 166 + ...scrollBtnStyles, 167 + a.pt_lg, 168 + a.pb_sm, 169 + { 170 + bottom: 0, 171 + left: 0, 172 + right: 0, 173 + borderBottomLeftRadius: 0, 174 + borderBottomRightRadius: 0, 175 + background: `linear-gradient(to top, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, 176 + }, 177 + ] 178 + 179 + return ( 180 + <RadixSelect.Portal> 181 + <RadixSelect.Content 182 + style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])} 183 + position="popper" 184 + sideOffset={5} 185 + className="radix-select-content"> 186 + <View 187 + style={[ 188 + a.flex_1, 189 + a.border, 190 + t.atoms.border_contrast_low, 191 + a.rounded_sm, 192 + ]}> 193 + <RadixSelect.ScrollUpButton style={flatten(up)}> 194 + <ChevronUpIcon style={[t.atoms.text]} size="xs" /> 195 + </RadixSelect.ScrollUpButton> 196 + <RadixSelect.Viewport style={flatten([a.p_xs])}> 197 + {items.map((item, index) => renderItem(item, index, selectedValue))} 198 + </RadixSelect.Viewport> 199 + <RadixSelect.ScrollDownButton style={flatten(down)}> 200 + <ChevronDownIcon style={[t.atoms.text]} size="xs" /> 201 + </RadixSelect.ScrollDownButton> 202 + </View> 203 + </RadixSelect.Content> 204 + </RadixSelect.Portal> 205 + ) 206 + } 207 + 208 + const ItemContext = createContext<{ 209 + hovered: boolean 210 + focused: boolean 211 + pressed: boolean 212 + selected: boolean 213 + }>({ 214 + hovered: false, 215 + focused: false, 216 + pressed: false, 217 + selected: false, 218 + }) 219 + 220 + export function useItemContext() { 221 + return useContext(ItemContext) 222 + } 223 + 224 + export function Item({ref, value, style, children}: ItemProps) { 225 + const t = useTheme() 226 + const { 227 + state: hovered, 228 + onIn: onMouseEnter, 229 + onOut: onMouseLeave, 230 + } = useInteractionState() 231 + const selected = useContext(SelectedValueContext) === value 232 + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() 233 + const ctx = useMemo( 234 + () => ({hovered, focused, pressed: false, selected}), 235 + [hovered, focused, selected], 236 + ) 237 + return ( 238 + <RadixSelect.Item 239 + ref={ref} 240 + value={value} 241 + onMouseEnter={onMouseEnter} 242 + onMouseLeave={onMouseLeave} 243 + onFocus={onFocus} 244 + onBlur={onBlur} 245 + style={flatten([ 246 + a.relative, 247 + a.flex, 248 + {minHeight: 25, paddingLeft: 30, paddingRight: 35}, 249 + a.user_select_none, 250 + a.align_center, 251 + a.rounded_xs, 252 + a.py_2xs, 253 + a.text_sm, 254 + {outline: 0}, 255 + (hovered || focused) && {backgroundColor: t.palette.primary_50}, 256 + selected && [a.font_bold], 257 + a.transition_color, 258 + style, 259 + ])}> 260 + <ItemContext.Provider value={ctx}>{children}</ItemContext.Provider> 261 + </RadixSelect.Item> 262 + ) 263 + } 264 + 265 + export const ItemText = RadixSelect.ItemText 266 + 267 + export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { 268 + return ( 269 + <RadixSelect.ItemIndicator 270 + style={flatten([ 271 + a.absolute, 272 + {left: 0, width: 30}, 273 + a.flex, 274 + a.align_center, 275 + a.justify_center, 276 + ])}> 277 + <Icon size="sm" /> 278 + </RadixSelect.ItemIndicator> 279 + ) 280 + }
+185
src/components/Select/types.ts
··· 1 + import { 2 + type AccessibilityProps, 3 + type StyleProp, 4 + type TextStyle, 5 + type ViewStyle, 6 + } from 'react-native' 7 + 8 + import {type TextStyleProp} from '#/alf' 9 + import {type DialogControlProps} from '#/components/Dialog' 10 + import {type Props as SVGIconProps} from '#/components/icons/common' 11 + 12 + export type RootProps = { 13 + children?: React.ReactNode 14 + value?: string 15 + onValueChange?: (value: string) => void 16 + disabled?: boolean 17 + /** 18 + * @platform web 19 + */ 20 + defaultValue?: string 21 + /** 22 + * @platform web 23 + */ 24 + open?: boolean 25 + /** 26 + * @platform web 27 + */ 28 + defaultOpen?: boolean 29 + /** 30 + * @platform web 31 + */ 32 + onOpenChange?(open: boolean): void 33 + /** 34 + * @platform web 35 + */ 36 + name?: string 37 + /** 38 + * @platform web 39 + */ 40 + autoComplete?: string 41 + /** 42 + * @platform web 43 + */ 44 + required?: boolean 45 + } 46 + 47 + export type RadixPassThroughTriggerProps = { 48 + id: string 49 + type: 'button' 50 + disabled: boolean 51 + ['data-disabled']: boolean 52 + ['data-state']: string 53 + ['aria-controls']?: string 54 + ['aria-haspopup']?: boolean 55 + ['aria-expanded']?: AccessibilityProps['aria-expanded'] 56 + onPress: () => void 57 + } 58 + 59 + export type TriggerProps = { 60 + children: React.ReactNode | ((props: TriggerChildProps) => React.ReactNode) 61 + label: string 62 + } 63 + 64 + export type TriggerChildProps = 65 + | { 66 + isNative: true 67 + control: DialogControlProps 68 + state: { 69 + /** 70 + * Web only, `false` on native 71 + */ 72 + hovered: false 73 + focused: boolean 74 + pressed: boolean 75 + } 76 + /** 77 + * We don't necessarily know what these will be spread on to, so we 78 + * should add props one-by-one. 79 + * 80 + * On web, these properties are applied to a parent `Pressable`, so this 81 + * object is empty. 82 + */ 83 + props: { 84 + onPress: () => void 85 + onFocus: () => void 86 + onBlur: () => void 87 + onPressIn: () => void 88 + onPressOut: () => void 89 + accessibilityLabel: string 90 + } 91 + } 92 + | { 93 + isNative: false 94 + state: { 95 + hovered: boolean 96 + focused: boolean 97 + /** 98 + * Native only, `false` on web 99 + */ 100 + pressed: false 101 + } 102 + props: RadixPassThroughTriggerProps & { 103 + onPress: () => void 104 + onFocus: () => void 105 + onBlur: () => void 106 + onMouseEnter: () => void 107 + onMouseLeave: () => void 108 + accessibilityLabel: string 109 + } 110 + } 111 + 112 + /* 113 + * For use within the `Select.Trigger` component. 114 + * Shows the currently selected value. You can also 115 + * provide a placeholder to show when no value is selected. 116 + * 117 + * If you're passing items of a different shape than {value: string, label: string}, 118 + * you'll need to pass a function to `children` that extracts the label from an item. 119 + */ 120 + export type ValueProps = { 121 + /** 122 + * Only needed for native. Extracts the label from an item. Defaults to `item => item.label` 123 + */ 124 + children?: (value: any) => string 125 + placeholder?: string 126 + style?: StyleProp<TextStyle> 127 + } 128 + 129 + /* 130 + * Icon for use within the `Select.Trigger` component. 131 + * Changes based on platform - chevron down on web, up/down chevrons on native 132 + * 133 + * `style` prop is web only 134 + */ 135 + export type IconProps = TextStyleProp 136 + 137 + export type ContentProps<T> = { 138 + /** 139 + * Items to render. Recommended to be in the form {value: string, label: string} - if not, 140 + * you need to provide a `valueExtractor` function to extract the value from an item and 141 + * customise the `Select.ValueText` component. 142 + */ 143 + items: T[] 144 + /** 145 + * Renders an item. You should probably use the `Select.Item` component. 146 + * 147 + * @example 148 + * ```tsx 149 + * renderItem={({label, value}) => ( 150 + * <Select.Item value={value} label={label}> 151 + * <Select.ItemIndicator /> 152 + * <Select.ItemText>{label}</Select.ItemText> 153 + * </Select.Item> 154 + * )} 155 + * ``` 156 + */ 157 + renderItem: ( 158 + item: T, 159 + index: number, 160 + selectedValue?: string | null, 161 + ) => React.ReactElement 162 + /* 163 + * Extracts the value from an item. Defaults to `item => item.value` 164 + */ 165 + valueExtractor?: (item: T) => string 166 + } 167 + 168 + /* 169 + * An item within the select dropdown 170 + */ 171 + export type ItemProps = { 172 + ref?: React.Ref<HTMLDivElement> 173 + value: string 174 + label: string 175 + children: React.ReactNode 176 + style?: StyleProp<ViewStyle> 177 + } 178 + 179 + export type ItemTextProps = { 180 + children: React.ReactNode 181 + } 182 + 183 + export type ItemIndicatorProps = { 184 + icon?: React.ComponentType<SVGIconProps> 185 + }
+44 -141
src/screens/Settings/LanguageSettings.tsx
··· 1 1 import {useCallback, useMemo} from 'react' 2 2 import {View} from 'react-native' 3 - import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' 4 3 import {msg, Trans} from '@lingui/macro' 5 4 import {useLingui} from '@lingui/react' 6 5 7 6 import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 7 + import { 8 + type CommonNavigatorParams, 9 + type NativeStackScreenProps, 10 + } from '#/lib/routes/types' 9 11 import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers' 10 12 import {useModalControls} from '#/state/modals' 11 13 import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' 12 14 import {atoms as a, useTheme, web} from '#/alf' 13 15 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 14 16 import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 15 - import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron' 16 17 import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 17 18 import * as Layout from '#/components/Layout' 19 + import * as Select from '#/components/Select' 18 20 import {Text} from '#/components/Typography' 19 21 import * as SettingsList from './components/SettingsList' 20 22 23 + const DEDUPED_LANGUAGES = LANGUAGES.filter( 24 + (lang, i, arr) => 25 + lang.code2 && arr.findIndex(l => l.code2 === lang.code2) === i, 26 + ) 27 + 21 28 type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> 22 29 export function LanguageSettingsScreen({}: Props) { 23 30 const {_} = useLingui() ··· 32 39 }, [openModal]) 33 40 34 41 const onChangePrimaryLanguage = useCallback( 35 - (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { 42 + (value: string) => { 36 43 if (!value) return 37 44 if (langPrefs.primaryLanguage !== value) { 38 45 setLangPrefs.setPrimaryLanguage(value) ··· 42 49 ) 43 50 44 51 const onChangeAppLanguage = useCallback( 45 - (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { 52 + (value: string) => { 46 53 if (!value) return 47 54 if (langPrefs.appLanguage !== value) { 48 55 setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) ··· 85 92 Select which language to use for the app's user interface. 86 93 </Trans> 87 94 </Text> 88 - <View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}> 89 - <RNPickerSelect 90 - darkTheme={t.scheme === 'dark'} 91 - placeholder={{}} 92 - value={sanitizeAppLanguageSetting(langPrefs.appLanguage)} 93 - onValueChange={onChangeAppLanguage} 94 - items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ 95 + <Select.Root 96 + value={sanitizeAppLanguageSetting(langPrefs.appLanguage)} 97 + onValueChange={onChangeAppLanguage}> 98 + <Select.Trigger label={_(msg`Select app language`)}> 99 + <Select.ValueText /> 100 + <Select.Icon /> 101 + </Select.Trigger> 102 + <Select.Content 103 + renderItem={({label, value}) => ( 104 + <Select.Item value={value} label={label}> 105 + <Select.ItemIndicator /> 106 + <Select.ItemText>{label}</Select.ItemText> 107 + </Select.Item> 108 + )} 109 + items={APP_LANGUAGES.map(l => ({ 95 110 label: l.name, 96 111 value: l.code2, 97 - key: l.code2, 98 112 }))} 99 - style={{ 100 - inputAndroid: { 101 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 102 - color: t.atoms.text.color, 103 - fontSize: 14, 104 - letterSpacing: 0.5, 105 - fontWeight: a.font_bold.fontWeight, 106 - paddingHorizontal: 14, 107 - paddingVertical: 8, 108 - borderRadius: a.rounded_xs.borderRadius, 109 - }, 110 - inputIOS: { 111 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 112 - color: t.atoms.text.color, 113 - fontSize: 14, 114 - letterSpacing: 0.5, 115 - fontWeight: a.font_bold.fontWeight, 116 - paddingHorizontal: 14, 117 - paddingVertical: 8, 118 - borderRadius: a.rounded_xs.borderRadius, 119 - }, 120 - inputWeb: { 121 - flex: 1, 122 - width: '100%', 123 - cursor: 'pointer', 124 - // @ts-ignore web only 125 - '-moz-appearance': 'none', 126 - '-webkit-appearance': 'none', 127 - appearance: 'none', 128 - outline: 0, 129 - borderWidth: 0, 130 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 131 - color: t.atoms.text.color, 132 - fontSize: 14, 133 - fontFamily: 'inherit', 134 - letterSpacing: 0.5, 135 - fontWeight: a.font_bold.fontWeight, 136 - paddingHorizontal: 14, 137 - paddingVertical: 8, 138 - borderRadius: a.rounded_xs.borderRadius, 139 - }, 140 - }} 141 113 /> 142 - 143 - <View 144 - style={[ 145 - a.absolute, 146 - t.atoms.bg_contrast_25, 147 - a.rounded_xs, 148 - a.pointer_events_none, 149 - a.align_center, 150 - a.justify_center, 151 - { 152 - top: 1, 153 - right: 1, 154 - bottom: 1, 155 - width: 40, 156 - }, 157 - ]}> 158 - <ChevronDownIcon style={[t.atoms.text]} /> 159 - </View> 160 - </View> 114 + </Select.Root> 161 115 </View> 162 116 </SettingsList.Group> 163 117 <SettingsList.Divider /> ··· 171 125 Select your preferred language for translations in your feed. 172 126 </Trans> 173 127 </Text> 174 - <View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}> 175 - <RNPickerSelect 176 - darkTheme={t.scheme === 'dark'} 177 - placeholder={{}} 178 - value={langPrefs.primaryLanguage} 179 - onValueChange={onChangePrimaryLanguage} 180 - items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ 128 + <Select.Root 129 + value={langPrefs.primaryLanguage} 130 + onValueChange={onChangePrimaryLanguage}> 131 + <Select.Trigger label={_(msg`Select primary language`)}> 132 + <Select.ValueText /> 133 + <Select.Icon /> 134 + </Select.Trigger> 135 + <Select.Content 136 + renderItem={({label, value}) => ( 137 + <Select.Item value={value} label={label}> 138 + <Select.ItemIndicator /> 139 + <Select.ItemText>{label}</Select.ItemText> 140 + </Select.Item> 141 + )} 142 + items={DEDUPED_LANGUAGES.map(l => ({ 181 143 label: languageName(l, langPrefs.appLanguage), 182 144 value: l.code2, 183 - key: l.code2 + l.code3, 184 145 }))} 185 - style={{ 186 - inputAndroid: { 187 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 188 - color: t.atoms.text.color, 189 - fontSize: 14, 190 - letterSpacing: 0.5, 191 - fontWeight: a.font_bold.fontWeight, 192 - paddingHorizontal: 14, 193 - paddingVertical: 8, 194 - borderRadius: a.rounded_xs.borderRadius, 195 - }, 196 - inputIOS: { 197 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 198 - color: t.atoms.text.color, 199 - fontSize: 14, 200 - letterSpacing: 0.5, 201 - fontWeight: a.font_bold.fontWeight, 202 - paddingHorizontal: 14, 203 - paddingVertical: 8, 204 - borderRadius: a.rounded_xs.borderRadius, 205 - }, 206 - inputWeb: { 207 - flex: 1, 208 - width: '100%', 209 - cursor: 'pointer', 210 - // @ts-ignore web only 211 - '-moz-appearance': 'none', 212 - '-webkit-appearance': 'none', 213 - appearance: 'none', 214 - outline: 0, 215 - borderWidth: 0, 216 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 217 - color: t.atoms.text.color, 218 - fontSize: 14, 219 - fontFamily: 'inherit', 220 - letterSpacing: 0.5, 221 - fontWeight: a.font_bold.fontWeight, 222 - paddingHorizontal: 14, 223 - paddingVertical: 8, 224 - borderRadius: a.rounded_xs.borderRadius, 225 - }, 226 - }} 227 146 /> 228 - 229 - <View 230 - style={{ 231 - position: 'absolute', 232 - top: 1, 233 - right: 1, 234 - bottom: 1, 235 - width: 40, 236 - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, 237 - borderRadius: a.rounded_xs.borderRadius, 238 - pointerEvents: 'none', 239 - alignItems: 'center', 240 - justifyContent: 'center', 241 - }}> 242 - <ChevronDownIcon style={t.atoms.text} /> 243 - </View> 244 - </View> 147 + </Select.Root> 245 148 </View> 246 149 </SettingsList.Group> 247 150 <SettingsList.Divider />
+6 -2
src/screens/Signup/index.tsx
··· 184 184 <Divider /> 185 185 186 186 <View 187 - style={[a.w_full, a.py_lg, a.flex_row, a.gap_lg, a.align_center]}> 187 + style={[a.w_full, a.py_lg, a.flex_row, a.gap_md, a.align_center]}> 188 188 <AppLanguageDropdown /> 189 189 <Text 190 - style={[t.atoms.text_contrast_medium, !gtMobile && a.text_md]}> 190 + style={[ 191 + a.flex_1, 192 + t.atoms.text_contrast_medium, 193 + !gtMobile && a.text_md, 194 + ]}> 191 195 <Trans>Having trouble?</Trans>{' '} 192 196 <InlineLinkText 193 197 label={_(msg`Contact support`)}
+8
src/style.css
··· 325 325 background-color: var(--backgroundLight); 326 326 border-color: transparent; 327 327 } 328 + 329 + /* #/components/Select/index.web.tsx */ 330 + .radix-select-content { 331 + box-shadow: 0px 6px 24px -10px rgba(22, 23, 24, 0.25), 332 + 0px 6px 12px -12px rgba(22, 23, 24, 0.15); 333 + min-width: var(--radix-select-trigger-width); 334 + max-height: var(--radix-select-content-available-height); 335 + }
+3 -1
src/view/com/auth/SplashScreen.tsx
··· 78 78 a.justify_center, 79 79 a.align_center, 80 80 ]}> 81 - <AppLanguageDropdown /> 81 + <View> 82 + <AppLanguageDropdown /> 83 + </View> 82 84 </View> 83 85 <View style={{height: insets.bottom}} /> 84 86 </ErrorBoundary>
+3 -1
src/view/com/auth/SplashScreen.web.tsx
··· 154 154 a.absolute, 155 155 a.inset_0, 156 156 {top: 'auto'}, 157 - a.p_xl, 157 + a.px_xl, 158 + a.py_lg, 158 159 a.border_t, 159 160 a.flex_row, 161 + a.align_center, 160 162 a.flex_wrap, 161 163 a.gap_xl, 162 164 a.flex_1,
+15 -14
src/view/com/composer/videos/SubtitleDialog.tsx
··· 1 1 import {useCallback, useState} from 'react' 2 - import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' 3 - import RNPickerSelect from 'react-native-picker-select' 2 + import {Keyboard, type StyleProp, View, type ViewStyle} from 'react-native' 4 3 import {msg, Trans} from '@lingui/macro' 5 4 import {useLingui} from '@lingui/react' 6 5 ··· 240 239 numberOfLines={1}> 241 240 {file.name} 242 241 </Text> 243 - <RNPickerSelect 244 - placeholder={{ 245 - label: _(msg`Select language...`), 246 - value: '', 247 - }} 242 + <select 248 243 value={language} 249 - onValueChange={handleValueChange} 250 - items={otherLanguages.map(lang => ({ 251 - label: `${lang.name} (${langCode(lang)})`, 252 - value: langCode(lang), 253 - }))} 254 - style={{viewContainer: {maxWidth: 200, flex: 1}}} 255 - /> 244 + onChange={evt => handleValueChange(evt.target.value)} 245 + style={{maxWidth: 200, flex: 1}}> 246 + <option value="" disabled selected hidden> 247 + {/* eslint-disable-next-line bsky-internal/avoid-unwrapped-text */} 248 + <Trans>Select language...</Trans> 249 + </option> 250 + {otherLanguages.map(lang => ( 251 + <option key={langCode(lang)} value={langCode(lang)}> 252 + {/* eslint-disable-next-line bsky-internal/avoid-unwrapped-text */} 253 + {`${lang.name} (${langCode(lang)})`} 254 + </option> 255 + ))} 256 + </select> 256 257 </View> 257 258 </View> 258 259
+1 -1
src/view/shell/NavSignupCard.tsx
··· 65 65 </View> 66 66 67 67 <View style={[a.mt_md, a.w_full, {height: 32}]}> 68 - <AppLanguageDropdown style={{marginTop: 0}} /> 68 + <AppLanguageDropdown /> 69 69 </View> 70 70 </View> 71 71 )
+1 -1
src/view/shell/desktop/RightNav.tsx
··· 137 137 138 138 {!hasSession && leftNavMinimal && ( 139 139 <View style={[a.w_full, {height: 32}]}> 140 - <AppLanguageDropdown style={{marginTop: 0}} /> 140 + <AppLanguageDropdown /> 141 141 </View> 142 142 )} 143 143 </View>
-13
yarn.lock
··· 14285 14285 resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" 14286 14286 integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== 14287 14287 14288 - lodash.isobject@^3.0.2: 14289 - version "3.0.2" 14290 - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" 14291 - integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== 14292 - 14293 14288 lodash.memoize@^4.1.2: 14294 14289 version "4.1.2" 14295 14290 resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" ··· 16839 16834 version "6.7.1" 16840 16835 resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.7.1.tgz#60d52dedbcc92ee7037a13287ebeed5f74e49df7" 16841 16836 integrity sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw== 16842 - 16843 - react-native-picker-select@^9.3.1: 16844 - version "9.3.1" 16845 - resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-9.3.1.tgz#8a2ad51c286fcd54ef60fb883842ec1895c15003" 16846 - integrity sha512-o621HcsKJfJkpYeP/PZQiZTKbf8W7FT08niLFL0v1pGkIQyak5IfzfinV2t+/l1vktGwAH2Tt29LrP/Hc5fk3A== 16847 - dependencies: 16848 - lodash.isequal "^4.5.0" 16849 - lodash.isobject "^3.0.2" 16850 16837 16851 16838 react-native-progress@bluesky-social/react-native-progress: 16852 16839 version "5.0.0"