diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/AppLanguageDropdown.tsx | 67 | ||||
-rw-r--r-- | src/components/AppLanguageDropdown.web.tsx | 62 | ||||
-rw-r--r-- | src/components/dialogs/Context.tsx | 7 | ||||
-rw-r--r-- | src/components/dialogs/Signin.tsx | 99 |
4 files changed, 233 insertions, 2 deletions
diff --git a/src/components/AppLanguageDropdown.tsx b/src/components/AppLanguageDropdown.tsx new file mode 100644 index 000000000..dea9e66fb --- /dev/null +++ b/src/components/AppLanguageDropdown.tsx @@ -0,0 +1,67 @@ +import React from 'react' +import {View} from 'react-native' +import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' + +import {sanitizeAppLanguageSetting} from '#/locale/helpers' +import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {atoms as a, useTheme} from '#/alf' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' + +export function AppLanguageDropdown() { + const t = useTheme() + + const langPrefs = useLanguagePrefs() + const setLangPrefs = useLanguagePrefsApi() + const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) + + const onChangeAppLanguage = React.useCallback( + (value: Parameters<PickerSelectProps['onValueChange']>[0]) => { + if (!value) return + if (sanitizedLang !== value) { + setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) + } + }, + [sanitizedLang, setLangPrefs], + ) + + return ( + <View style={a.relative}> + <RNPickerSelect + placeholder={{}} + value={sanitizedLang} + onValueChange={onChangeAppLanguage} + items={APP_LANGUAGES.filter(l => Boolean(l.code2)).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, + }, + }} + /> + + <View + style={[ + a.absolute, + a.inset_0, + {left: 'auto'}, + {pointerEvents: 'none'}, + a.align_center, + a.justify_center, + ]}> + <ChevronDown fill={t.atoms.text.color} size="xs" /> + </View> + </View> + ) +} diff --git a/src/components/AppLanguageDropdown.web.tsx b/src/components/AppLanguageDropdown.web.tsx new file mode 100644 index 000000000..302a30ca6 --- /dev/null +++ b/src/components/AppLanguageDropdown.web.tsx @@ -0,0 +1,62 @@ +import React from 'react' +import {View} from 'react-native' + +import {sanitizeAppLanguageSetting} from '#/locale/helpers' +import {APP_LANGUAGES} from '#/locale/languages' +import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' +import {atoms as a, useTheme} from '#/alf' +import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' +import {Text} from '#/components/Typography' + +export function AppLanguageDropdown() { + const t = useTheme() + + const langPrefs = useLanguagePrefs() + const setLangPrefs = useLanguagePrefsApi() + + const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) + + const onChangeAppLanguage = React.useCallback( + (ev: React.ChangeEvent<HTMLSelectElement>) => { + const value = ev.target.value + + if (!value) return + if (sanitizedLang !== value) { + setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) + } + }, + [sanitizedLang, setLangPrefs], + ) + + return ( + <View style={[a.flex_row, a.gap_sm, a.align_center, a.flex_shrink]}> + <Text aria-hidden={true} style={t.atoms.text_contrast_medium}> + {APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name} + </Text> + <ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_shrink} /> + + <select + value={sanitizedLang} + onChange={onChangeAppLanguage} + style={{ + cursor: 'pointer', + MozAppearance: 'none', + WebkitAppearance: 'none', + appearance: 'none', + position: 'absolute', + inset: 0, + width: '100%', + color: 'transparent', + background: 'transparent', + border: 0, + padding: 0, + }}> + {APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ( + <option key={l.code2} value={l.code2}> + {l.name} + </option> + ))} + </select> + </View> + ) +} diff --git a/src/components/dialogs/Context.tsx b/src/components/dialogs/Context.tsx index 87bd5c2ed..c9dff9a99 100644 --- a/src/components/dialogs/Context.tsx +++ b/src/components/dialogs/Context.tsx @@ -6,10 +6,12 @@ type Control = Dialog.DialogOuterProps['control'] type ControlsContext = { mutedWordsDialogControl: Control + signinDialogControl: Control } const ControlsContext = React.createContext({ mutedWordsDialogControl: {} as Control, + signinDialogControl: {} as Control, }) export function useGlobalDialogsControlContext() { @@ -18,9 +20,10 @@ export function useGlobalDialogsControlContext() { export function Provider({children}: React.PropsWithChildren<{}>) { const mutedWordsDialogControl = Dialog.useDialogControl() + const signinDialogControl = Dialog.useDialogControl() const ctx = React.useMemo<ControlsContext>( - () => ({mutedWordsDialogControl}), - [mutedWordsDialogControl], + () => ({mutedWordsDialogControl, signinDialogControl}), + [mutedWordsDialogControl, signinDialogControl], ) return ( diff --git a/src/components/dialogs/Signin.tsx b/src/components/dialogs/Signin.tsx new file mode 100644 index 000000000..488eb5c73 --- /dev/null +++ b/src/components/dialogs/Signin.tsx @@ -0,0 +1,99 @@ +import React from 'react' +import {View} from 'react-native' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {isNative} from '#/platform/detection' +import {useLoggedOutViewControls} from '#/state/shell/logged-out' +import {useCloseAllActiveElements} from '#/state/util' +import {Logo} from '#/view/icons/Logo' +import {Logotype} from '#/view/icons/Logotype' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {useGlobalDialogsControlContext} from '#/components/dialogs/Context' +import {Text} from '#/components/Typography' + +export function SigninDialog() { + const {signinDialogControl: control} = useGlobalDialogsControlContext() + return ( + <Dialog.Outer control={control}> + <Dialog.Handle /> + <SigninDialogInner control={control} /> + </Dialog.Outer> + ) +} + +function SigninDialogInner({}: {control: Dialog.DialogOuterProps['control']}) { + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + const {requestSwitchToAccount} = useLoggedOutViewControls() + const closeAllActiveElements = useCloseAllActiveElements() + + const showSignIn = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'none'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + const showCreateAccount = React.useCallback(() => { + closeAllActiveElements() + requestSwitchToAccount({requestedAccount: 'new'}) + }, [requestSwitchToAccount, closeAllActiveElements]) + + return ( + <Dialog.ScrollableInner + label={_(msg`Sign into Bluesky or create a new account`)} + style={[gtMobile ? {width: 'auto', maxWidth: 420} : a.w_full]}> + <View> + <View + style={[ + a.flex_row, + a.align_center, + a.justify_center, + a.gap_sm, + a.pb_lg, + ]}> + <Logo width={36} /> + <View style={{paddingTop: 6}}> + <Logotype width={120} fill={t.atoms.text.color} /> + </View> + </View> + + <Text style={[a.text_lg, a.text_center, t.atoms.text, a.pb_2xl]}> + <Trans> + Sign in or create your account to join the conversation! + </Trans> + </Text> + + <View style={[a.flex_col, a.gap_md]}> + <Button + variant="solid" + color="primary" + size="large" + onPress={showCreateAccount} + label={_(msg`Create an account`)}> + <ButtonText> + <Trans>Create an account</Trans> + </ButtonText> + </Button> + + <Button + variant="solid" + color="secondary" + size="large" + onPress={showSignIn} + label={_(msg`Sign in`)}> + <ButtonText> + <Trans>Sign in</Trans> + </ButtonText> + </Button> + </View> + + {isNative && <View style={{height: 10}} />} + </View> + + <Dialog.Close /> + </Dialog.ScrollableInner> + ) +} |