From 973538d246a3f76550611e438152f1a6cad75f49 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 6 May 2025 20:27:05 +0300 Subject: 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 --- .../src/BottomSheetNativeComponent.tsx | 5 +- package.json | 1 - src/alf/atoms.ts | 3 + src/components/AppLanguageDropdown.tsx | 87 ++++--- src/components/AppLanguageDropdown.web.tsx | 83 ------ src/components/Dialog/shared.tsx | 11 +- src/components/Select/index.tsx | 289 +++++++++++++++++++++ src/components/Select/index.web.tsx | 280 ++++++++++++++++++++ src/components/Select/types.ts | 185 +++++++++++++ src/screens/Settings/LanguageSettings.tsx | 185 ++++--------- src/screens/Signup/index.tsx | 8 +- src/style.css | 8 + src/view/com/auth/SplashScreen.tsx | 4 +- src/view/com/auth/SplashScreen.web.tsx | 4 +- src/view/com/composer/videos/SubtitleDialog.tsx | 29 ++- src/view/shell/NavSignupCard.tsx | 2 +- src/view/shell/desktop/RightNav.tsx | 2 +- yarn.lock | 13 - 18 files changed, 899 insertions(+), 300 deletions(-) delete mode 100644 src/components/AppLanguageDropdown.web.tsx create mode 100644 src/components/Select/index.tsx create mode 100644 src/components/Select/index.web.tsx create mode 100644 src/components/Select/types.ts diff --git a/modules/bottom-sheet/src/BottomSheetNativeComponent.tsx b/modules/bottom-sheet/src/BottomSheetNativeComponent.tsx index 869b89e3d..d367ac300 100644 --- a/modules/bottom-sheet/src/BottomSheetNativeComponent.tsx +++ b/modules/bottom-sheet/src/BottomSheetNativeComponent.tsx @@ -30,7 +30,10 @@ const NativeView: React.ComponentType< const NativeModule = requireNativeModule('BottomSheet') -const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16 +const isIOS15 = + Platform.OS === 'ios' && + // semvar - can be 3 segments, so can't use Number(Platform.Version) + Number(Platform.Version.split('.').at(0)) < 16 export class BottomSheetNativeComponent extends React.Component< BottomSheetViewProps, diff --git a/package.json b/package.json index 0d96e688c..5d308958e 100644 --- a/package.json +++ b/package.json @@ -193,7 +193,6 @@ "react-native-keyboard-controller": "^1.17.1", "react-native-mmkv": "^2.12.2", "react-native-pager-view": "6.7.1", - "react-native-picker-select": "^9.3.1", "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", "react-native-reanimated": "~3.17.5", diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index c60af4862..68aa3cc88 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -204,6 +204,9 @@ export const atoms = { flex_grow: { flexGrow: 1, }, + flex_grow_0: { + flexGrow: 0, + }, flex_shrink: { flexShrink: 1, }, diff --git a/src/components/AppLanguageDropdown.tsx b/src/components/AppLanguageDropdown.tsx index de2e50fc8..9837ce5ce 100644 --- a/src/components/AppLanguageDropdown.tsx +++ b/src/components/AppLanguageDropdown.tsx @@ -1,17 +1,19 @@ import React from 'react' -import {View} from 'react-native' -import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' import {sanitizeAppLanguageSetting} from '#/locale/helpers' import {APP_LANGUAGES} from '#/locale/languages' import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {resetPostsFeedQueries} from '#/state/queries/post-feed' -import {atoms as a, useTheme, ViewStyleProp} from '#/alf' -import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {atoms as a, platform, useTheme} from '#/alf' +import * as Select from '#/components/Select' +import {Button} from './Button' -export function AppLanguageDropdown(_props: ViewStyleProp) { +export function AppLanguageDropdown() { const t = useTheme() + const {_} = useLingui() const queryClient = useQueryClient() const langPrefs = useLanguagePrefs() @@ -19,7 +21,7 @@ export function AppLanguageDropdown(_props: ViewStyleProp) { const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) const onChangeAppLanguage = React.useCallback( - (value: Parameters[0]) => { + (value: string) => { if (!value) return if (sanitizedLang !== value) { setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) @@ -32,43 +34,48 @@ export function AppLanguageDropdown(_props: ViewStyleProp) { ) return ( - - Boolean(l.code2)).map(l => ({ + + + {({props}) => ( + + )} + + ( + + + {label} + + )} + items={APP_LANGUAGES.map(l => ({ label: l.name, value: l.code2, - key: l.code2, }))} - useNativeAndroidPickerStyle={false} - style={{ - inputAndroid: { - color: t.atoms.text_contrast_medium.color, - fontSize: 16, - paddingRight: 12 + 4, - }, - inputIOS: { - color: t.atoms.text.color, - fontSize: 16, - paddingRight: 12 + 4, - }, - }} /> - - - - - + ) } diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx deleted file mode 100644 index d51b53ac0..000000000 --- a/src/components/AppLanguageDropdown.web.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' -import {View} from 'react-native' -import {useQueryClient} from '@tanstack/react-query' - -import {sanitizeAppLanguageSetting} from '#/locale/helpers' -import {APP_LANGUAGES} from '#/locale/languages' -import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' -import {resetPostsFeedQueries} from '#/state/queries/post-feed' -import {atoms as a, useTheme, ViewStyleProp} from '#/alf' -import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' -import {Text} from '#/components/Typography' - -export function AppLanguageDropdown({style}: ViewStyleProp) { - const t = useTheme() - - const queryClient = useQueryClient() - const langPrefs = useLanguagePrefs() - const setLangPrefs = useLanguagePrefsApi() - - const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) - - const onChangeAppLanguage = React.useCallback( - (ev: React.ChangeEvent) => { - const value = ev.target.value - - if (!value) return - if (sanitizedLang !== value) { - setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) - } - - // reset feeds to refetch content - resetPostsFeedQueries(queryClient) - }, - [sanitizedLang, setLangPrefs, queryClient], - ) - - return ( - - - - {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} - - - - - - - ) -} diff --git a/src/components/Dialog/shared.tsx b/src/components/Dialog/shared.tsx index 44a4f6b0b..eec47b2ba 100644 --- a/src/components/Dialog/shared.tsx +++ b/src/components/Dialog/shared.tsx @@ -1,5 +1,11 @@ import React from 'react' -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' +import { + LayoutChangeEvent, + StyleProp, + TextStyle, + View, + ViewStyle, +} from 'react-native' import {atoms as a, useTheme} from '#/alf' import {Text} from '#/components/Typography' @@ -9,15 +15,18 @@ export function Header({ renderRight, children, style, + onLayout, }: { renderLeft?: () => React.ReactNode renderRight?: () => React.ReactNode children?: React.ReactNode style?: StyleProp + onLayout?: (event: LayoutChangeEvent) => void }) { const t = useTheme() return ( + +const Context = createContext(null) + +const ValueTextContext = createContext< + [any, React.Dispatch>] +>([undefined, () => {}]) + +function useSelectContext() { + const ctx = useContext(Context) + if (!ctx) { + throw new Error('Select components must must be used within a Select.Root') + } + return ctx +} + +export function Root({children, value, onValueChange, disabled}: RootProps) { + const control = Dialog.useDialogControl() + const valueTextCtx = useState() + + const ctx = useMemo( + () => ({ + control, + value, + onValueChange, + disabled, + }), + [control, value, onValueChange, disabled], + ) + return ( + + + {children} + + + ) +} + +export function Trigger({children, label}: TriggerProps) { + const {control} = useSelectContext() + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + const { + state: pressed, + onIn: onPressIn, + onOut: onPressOut, + } = useInteractionState() + + if (typeof children === 'function') { + return children({ + isNative: true, + control, + state: { + hovered: false, + focused, + pressed, + }, + props: { + onPress: control.open, + onFocus, + onBlur, + onPressIn, + onPressOut, + accessibilityLabel: label, + }, + }) + } else { + return ( + + ) + } +} + +export function ValueText({ + placeholder, + children = value => value.label, + style, +}: ValueProps) { + const [value] = useContext(ValueTextContext) + const t = useTheme() + + let text = value && children(value) + if (typeof text !== 'string') text = placeholder + + return ( + {text} + ) +} + +export function Icon({}: IconProps) { + return +} + +export function Content({ + items, + valueExtractor = defaultItemValueExtractor, + ...props +}: ContentProps) { + const {control, ...context} = useSelectContext() + const [, setValue] = useContext(ValueTextContext) + + useLayoutEffect(() => { + const item = items.find(item => valueExtractor(item) === context.value) + if (item) { + setValue(item) + } + }, [items, context.value, valueExtractor, setValue]) + + return ( + + + + ) +} + +function ContentInner({ + items, + renderItem, + valueExtractor, + ...context +}: ContentProps & ContextType) { + const control = Dialog.useDialogContext() + + const {_} = useLingui() + const [headerHeight, setHeaderHeight] = useState(50) + + const render = useCallback( + ({item, index}: {item: T; index: number}) => { + return renderItem(item, index, context.value) + }, + [renderItem, context.value], + ) + + const doneButton = useCallback( + () => ( + + ), + [control, _], + ) + + return ( + + setHeaderHeight(evt.nativeEvent.layout.height)} + style={[a.absolute, a.top_0, a.left_0, a.right_0, a.z_10]}> + + Select an option + + + + + ) +} + +function defaultItemValueExtractor(item: any) { + return item.value +} + +const ItemContext = createContext<{ + selected: boolean + hovered: boolean + focused: boolean + pressed: boolean +}>({ + selected: false, + hovered: false, + focused: false, + pressed: false, +}) + +export function useItemContext() { + return useContext(ItemContext) +} + +export function Item({children, value, label, style}: ItemProps) { + const t = useTheme() + const control = Dialog.useDialogContext() + const {value: selected, onValueChange} = useSelectContext() + + return ( + + ) +} + +export function ItemText({children}: ItemTextProps) { + const {selected} = useItemContext() + const t = useTheme() + + // eslint-disable-next-line bsky-internal/avoid-unwrapped-text + return ( + + {children} + + ) +} + +export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { + const {selected} = useItemContext() + + return {selected && } +} diff --git a/src/components/Select/index.web.tsx b/src/components/Select/index.web.tsx new file mode 100644 index 000000000..e9d26631c --- /dev/null +++ b/src/components/Select/index.web.tsx @@ -0,0 +1,280 @@ +import {createContext, forwardRef, useContext, useMemo} from 'react' +import {View} from 'react-native' +import {Select as RadixSelect} from 'radix-ui' + +import {flatten, useTheme} from '#/alf' +import {atoms as a} from '#/alf' +import {useInteractionState} from '#/components/hooks/useInteractionState' +import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' +import { + ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon, + ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon, +} from '#/components/icons/Chevron' +import {Text} from '#/components/Typography' +import { + type ContentProps, + type IconProps, + type ItemIndicatorProps, + type ItemProps, + type RadixPassThroughTriggerProps, + type RootProps, + type TriggerProps, + type ValueProps, +} from './types' + +const SelectedValueContext = createContext(null) + +export function Root(props: RootProps) { + return ( + + + + ) +} + +const RadixTriggerPassThrough = forwardRef( + ( + props: { + children: ( + props: RadixPassThroughTriggerProps & { + ref: React.Ref + }, + ) => React.ReactNode + }, + ref, + ) => { + // @ts-expect-error Radix provides no types of this stuff + + return props.children?.({...props, ref}) + }, +) +RadixTriggerPassThrough.displayName = 'RadixTriggerPassThrough' + +export function Trigger({children, label}: TriggerProps) { + const t = useTheme() + const { + state: hovered, + onIn: onMouseEnter, + onOut: onMouseLeave, + } = useInteractionState() + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + + if (typeof children === 'function') { + return ( + + + {props => + children({ + isNative: false, + state: { + hovered, + focused, + pressed: false, + }, + props: { + ...props, + onFocus: onFocus, + onBlur: onBlur, + onMouseEnter, + onMouseLeave, + accessibilityLabel: label, + }, + }) + } + + + ) + } else { + return ( + + {children} + + ) + } +} + +export function ValueText({children: _, style, ...props}: ValueProps) { + return ( + + + + ) +} + +export function Icon({style}: IconProps) { + const t = useTheme() + return ( + + + + ) +} + +export function Content({items, renderItem}: ContentProps) { + const t = useTheme() + const selectedValue = useContext(SelectedValueContext) + + const scrollBtnStyles: React.CSSProperties[] = [ + a.absolute, + a.flex, + a.align_center, + a.justify_center, + a.rounded_sm, + a.z_10, + ] + const up: React.CSSProperties[] = [ + ...scrollBtnStyles, + a.pt_sm, + a.pb_lg, + { + top: 0, + left: 0, + right: 0, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + background: `linear-gradient(to bottom, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, + }, + ] + const down: React.CSSProperties[] = [ + ...scrollBtnStyles, + a.pt_lg, + a.pb_sm, + { + bottom: 0, + left: 0, + right: 0, + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + background: `linear-gradient(to top, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`, + }, + ] + + return ( + + + + + + + + {items.map((item, index) => renderItem(item, index, selectedValue))} + + + + + + + + ) +} + +const ItemContext = createContext<{ + hovered: boolean + focused: boolean + pressed: boolean + selected: boolean +}>({ + hovered: false, + focused: false, + pressed: false, + selected: false, +}) + +export function useItemContext() { + return useContext(ItemContext) +} + +export function Item({ref, value, style, children}: ItemProps) { + const t = useTheme() + const { + state: hovered, + onIn: onMouseEnter, + onOut: onMouseLeave, + } = useInteractionState() + const selected = useContext(SelectedValueContext) === value + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() + const ctx = useMemo( + () => ({hovered, focused, pressed: false, selected}), + [hovered, focused, selected], + ) + return ( + + {children} + + ) +} + +export const ItemText = RadixSelect.ItemText + +export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) { + return ( + + + + ) +} diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts new file mode 100644 index 000000000..5c1b80a3b --- /dev/null +++ b/src/components/Select/types.ts @@ -0,0 +1,185 @@ +import { + type AccessibilityProps, + type StyleProp, + type TextStyle, + type ViewStyle, +} from 'react-native' + +import {type TextStyleProp} from '#/alf' +import {type DialogControlProps} from '#/components/Dialog' +import {type Props as SVGIconProps} from '#/components/icons/common' + +export type RootProps = { + children?: React.ReactNode + value?: string + onValueChange?: (value: string) => void + disabled?: boolean + /** + * @platform web + */ + defaultValue?: string + /** + * @platform web + */ + open?: boolean + /** + * @platform web + */ + defaultOpen?: boolean + /** + * @platform web + */ + onOpenChange?(open: boolean): void + /** + * @platform web + */ + name?: string + /** + * @platform web + */ + autoComplete?: string + /** + * @platform web + */ + required?: boolean +} + +export type RadixPassThroughTriggerProps = { + id: string + type: 'button' + disabled: boolean + ['data-disabled']: boolean + ['data-state']: string + ['aria-controls']?: string + ['aria-haspopup']?: boolean + ['aria-expanded']?: AccessibilityProps['aria-expanded'] + onPress: () => void +} + +export type TriggerProps = { + children: React.ReactNode | ((props: TriggerChildProps) => React.ReactNode) + label: string +} + +export type TriggerChildProps = + | { + isNative: true + control: DialogControlProps + state: { + /** + * Web only, `false` on native + */ + hovered: false + focused: boolean + pressed: boolean + } + /** + * We don't necessarily know what these will be spread on to, so we + * should add props one-by-one. + * + * On web, these properties are applied to a parent `Pressable`, so this + * object is empty. + */ + props: { + onPress: () => void + onFocus: () => void + onBlur: () => void + onPressIn: () => void + onPressOut: () => void + accessibilityLabel: string + } + } + | { + isNative: false + state: { + hovered: boolean + focused: boolean + /** + * Native only, `false` on web + */ + pressed: false + } + props: RadixPassThroughTriggerProps & { + onPress: () => void + onFocus: () => void + onBlur: () => void + onMouseEnter: () => void + onMouseLeave: () => void + accessibilityLabel: string + } + } + +/* + * For use within the `Select.Trigger` component. + * Shows the currently selected value. You can also + * provide a placeholder to show when no value is selected. + * + * If you're passing items of a different shape than {value: string, label: string}, + * you'll need to pass a function to `children` that extracts the label from an item. + */ +export type ValueProps = { + /** + * Only needed for native. Extracts the label from an item. Defaults to `item => item.label` + */ + children?: (value: any) => string + placeholder?: string + style?: StyleProp +} + +/* + * Icon for use within the `Select.Trigger` component. + * Changes based on platform - chevron down on web, up/down chevrons on native + * + * `style` prop is web only + */ +export type IconProps = TextStyleProp + +export type ContentProps = { + /** + * Items to render. Recommended to be in the form {value: string, label: string} - if not, + * you need to provide a `valueExtractor` function to extract the value from an item and + * customise the `Select.ValueText` component. + */ + items: T[] + /** + * Renders an item. You should probably use the `Select.Item` component. + * + * @example + * ```tsx + * renderItem={({label, value}) => ( + * + * + * {label} + * + * )} + * ``` + */ + renderItem: ( + item: T, + index: number, + selectedValue?: string | null, + ) => React.ReactElement + /* + * Extracts the value from an item. Defaults to `item => item.value` + */ + valueExtractor?: (item: T) => string +} + +/* + * An item within the select dropdown + */ +export type ItemProps = { + ref?: React.Ref + value: string + label: string + children: React.ReactNode + style?: StyleProp +} + +export type ItemTextProps = { + children: React.ReactNode +} + +export type ItemIndicatorProps = { + icon?: React.ComponentType +} diff --git a/src/screens/Settings/LanguageSettings.tsx b/src/screens/Settings/LanguageSettings.tsx index 7266dda4a..d035e6592 100644 --- a/src/screens/Settings/LanguageSettings.tsx +++ b/src/screens/Settings/LanguageSettings.tsx @@ -1,23 +1,30 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages' -import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' +import { + type CommonNavigatorParams, + type NativeStackScreenProps, +} from '#/lib/routes/types' import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers' import {useModalControls} from '#/state/modals' import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' -import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Layout from '#/components/Layout' +import * as Select from '#/components/Select' import {Text} from '#/components/Typography' import * as SettingsList from './components/SettingsList' +const DEDUPED_LANGUAGES = LANGUAGES.filter( + (lang, i, arr) => + lang.code2 && arr.findIndex(l => l.code2 === lang.code2) === i, +) + type Props = NativeStackScreenProps export function LanguageSettingsScreen({}: Props) { const {_} = useLingui() @@ -32,7 +39,7 @@ export function LanguageSettingsScreen({}: Props) { }, [openModal]) const onChangePrimaryLanguage = useCallback( - (value: Parameters[0]) => { + (value: string) => { if (!value) return if (langPrefs.primaryLanguage !== value) { setLangPrefs.setPrimaryLanguage(value) @@ -42,7 +49,7 @@ export function LanguageSettingsScreen({}: Props) { ) const onChangeAppLanguage = useCallback( - (value: Parameters[0]) => { + (value: string) => { if (!value) return if (langPrefs.appLanguage !== value) { setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) @@ -85,79 +92,26 @@ export function LanguageSettingsScreen({}: Props) { Select which language to use for the app's user interface. - - Boolean(l.code2)).map(l => ({ + + + + + + ( + + + {label} + + )} + items={APP_LANGUAGES.map(l => ({ label: l.name, value: l.code2, - key: l.code2, }))} - style={{ - inputAndroid: { - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - inputIOS: { - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - inputWeb: { - flex: 1, - width: '100%', - cursor: 'pointer', - // @ts-ignore web only - '-moz-appearance': 'none', - '-webkit-appearance': 'none', - appearance: 'none', - outline: 0, - borderWidth: 0, - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - fontFamily: 'inherit', - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - }} /> - - - - - + @@ -171,77 +125,26 @@ export function LanguageSettingsScreen({}: Props) { Select your preferred language for translations in your feed. - - Boolean(l.code2)).map(l => ({ + + + + + + ( + + + {label} + + )} + items={DEDUPED_LANGUAGES.map(l => ({ label: languageName(l, langPrefs.appLanguage), value: l.code2, - key: l.code2 + l.code3, }))} - style={{ - inputAndroid: { - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - inputIOS: { - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - inputWeb: { - flex: 1, - width: '100%', - cursor: 'pointer', - // @ts-ignore web only - '-moz-appearance': 'none', - '-webkit-appearance': 'none', - appearance: 'none', - outline: 0, - borderWidth: 0, - backgroundColor: t.atoms.bg_contrast_25.backgroundColor, - color: t.atoms.text.color, - fontSize: 14, - fontFamily: 'inherit', - letterSpacing: 0.5, - fontWeight: a.font_bold.fontWeight, - paddingHorizontal: 14, - paddingVertical: 8, - borderRadius: a.rounded_xs.borderRadius, - }, - }} /> - - - - - + diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index c98040010..03f4e2cdd 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -184,10 +184,14 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { + style={[a.w_full, a.py_lg, a.flex_row, a.gap_md, a.align_center]}> + style={[ + a.flex_1, + t.atoms.text_contrast_medium, + !gtMobile && a.text_md, + ]}> Having trouble?{' '} - + + + diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index 30ebb391f..f3488e485 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -154,9 +154,11 @@ function Footer() { a.absolute, a.inset_0, {top: 'auto'}, - a.p_xl, + a.px_xl, + a.py_lg, a.border_t, a.flex_row, + a.align_center, a.flex_wrap, a.gap_xl, a.flex_1, diff --git a/src/view/com/composer/videos/SubtitleDialog.tsx b/src/view/com/composer/videos/SubtitleDialog.tsx index 13d1b7ce5..298e70896 100644 --- a/src/view/com/composer/videos/SubtitleDialog.tsx +++ b/src/view/com/composer/videos/SubtitleDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useState} from 'react' -import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' -import RNPickerSelect from 'react-native-picker-select' +import {Keyboard, type StyleProp, View, type ViewStyle} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -240,19 +239,21 @@ function SubtitleFileRow({ numberOfLines={1}> {file.name} - ({ - label: `${lang.name} (${langCode(lang)})`, - value: langCode(lang), - }))} - style={{viewContainer: {maxWidth: 200, flex: 1}}} - /> + onChange={evt => handleValueChange(evt.target.value)} + style={{maxWidth: 200, flex: 1}}> + + {otherLanguages.map(lang => ( + + ))} + diff --git a/src/view/shell/NavSignupCard.tsx b/src/view/shell/NavSignupCard.tsx index e32c24dc4..000f5824c 100644 --- a/src/view/shell/NavSignupCard.tsx +++ b/src/view/shell/NavSignupCard.tsx @@ -65,7 +65,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => { - + ) diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 18ce42ee8..26795e0fd 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -137,7 +137,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { {!hasSession && leftNavMinimal && ( - + )} diff --git a/yarn.lock b/yarn.lock index 747087e83..cc321e65f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14285,11 +14285,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== -lodash.isobject@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" - integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA== - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -16840,14 +16835,6 @@ react-native-pager-view@6.7.1: resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.7.1.tgz#60d52dedbcc92ee7037a13287ebeed5f74e49df7" integrity sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw== -react-native-picker-select@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-9.3.1.tgz#8a2ad51c286fcd54ef60fb883842ec1895c15003" - integrity sha512-o621HcsKJfJkpYeP/PZQiZTKbf8W7FT08niLFL0v1pGkIQyak5IfzfinV2t+/l1vktGwAH2Tt29LrP/Hc5fk3A== - dependencies: - lodash.isequal "^4.5.0" - lodash.isobject "^3.0.2" - react-native-progress@bluesky-social/react-native-progress: version "5.0.0" resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4" -- cgit 1.4.1