about summary refs log tree commit diff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AppLanguageDropdown.tsx67
-rw-r--r--src/components/AppLanguageDropdown.web.tsx62
-rw-r--r--src/components/dialogs/Context.tsx7
-rw-r--r--src/components/dialogs/Signin.tsx99
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>
+  )
+}