Telephone country code select (#9473)
* add telephone code select * add flags * run svgo on flags * get it somewhat working on web * get web closed state working * verify country code we get from geo * trim down names to shorter common versions * add labels to other selects * make international tel codes a static object * add component to storybook * Update src/lib/international-telephone-codes.ts Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * update to new geo hook * use Intl.DisplayNames, add polyfill * use in rather than keys().includes() --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
This commit is contained in:
@@ -66,6 +66,7 @@ export function AppLanguageDropdown() {
|
||||
)}
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
label={_(msg`Select language`)}
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
|
||||
@@ -105,7 +105,8 @@ export type ButtonProps = Pick<
|
||||
PressableComponent?: React.ComponentType<PressableProps>
|
||||
}
|
||||
|
||||
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
|
||||
export type ButtonTextProps = TextProps &
|
||||
VariantProps & {disabled?: boolean; emoji?: boolean}
|
||||
|
||||
const Context = React.createContext<VariantProps & ButtonState>({
|
||||
hovered: false,
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {Fragment, useMemo} from 'react'
|
||||
import {Image} from 'expo-image'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {
|
||||
getDefaultCountry,
|
||||
INTERNATIONAL_TELEPHONE_CODES,
|
||||
} from '#/lib/international-telephone-codes'
|
||||
import {regionName} from '#/locale/helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {atoms as a, web} from '#/alf'
|
||||
import * as Select from '#/components/Select'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
|
||||
/**
|
||||
* Country picker for a phone number input
|
||||
*
|
||||
* Pro tip: you can use `location?.countryCode` from `useGeolocationStatus()`
|
||||
* to set a default value.
|
||||
*/
|
||||
export function InternationalPhoneCodeSelect({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value?: string
|
||||
onChange: (value: string) => void
|
||||
}) {
|
||||
const {_, i18n} = useLingui()
|
||||
const location = useGeolocation()
|
||||
|
||||
const defaultCountry = useMemo(() => {
|
||||
return getDefaultCountry(location)
|
||||
}, [location])
|
||||
|
||||
const items = useMemo(() => {
|
||||
return (
|
||||
Object.entries(INTERNATIONAL_TELEPHONE_CODES)
|
||||
.map(([value, {code, unicodeFlag, svgFlag}]) => {
|
||||
const name = regionName(value, i18n.locale)
|
||||
|
||||
return {
|
||||
value,
|
||||
name,
|
||||
code,
|
||||
label: `${name} ${code}`,
|
||||
unicodeFlag,
|
||||
svgFlag,
|
||||
}
|
||||
})
|
||||
// boost the default value to the top, then sort by name
|
||||
.sort((a, b) => {
|
||||
if (a.value === defaultCountry) return -1
|
||||
if (b.value === defaultCountry) return 1
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
)
|
||||
}, [i18n.locale, defaultCountry])
|
||||
|
||||
const selected = useMemo(() => {
|
||||
return items.find(item => item.value === value)
|
||||
}, [value, items])
|
||||
|
||||
return (
|
||||
<Select.Root value={value} onValueChange={onChange}>
|
||||
<Select.Trigger label={_(msg`Select telephone code`)}>
|
||||
<Select.ValueText placeholder="+..." webOverrideValue={selected}>
|
||||
{selected => (
|
||||
<>
|
||||
<Flag {...selected} />
|
||||
{selected.code}
|
||||
</>
|
||||
)}
|
||||
</Select.ValueText>
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
label={_(msg`Country code`)}
|
||||
items={items}
|
||||
renderItem={item => (
|
||||
<Fragment key={item.value}>
|
||||
<Select.Item value={item.value} label={item.label}>
|
||||
<Select.ItemIndicator />
|
||||
<Select.ItemText style={[a.flex_1]} emoji>
|
||||
{isWeb ? <Flag {...item} /> : item.unicodeFlag + ' '}
|
||||
{item.name}
|
||||
</Select.ItemText>
|
||||
<Select.ItemText style={[a.text_right]}>
|
||||
{' '}
|
||||
{item.code}
|
||||
</Select.ItemText>
|
||||
</Select.Item>
|
||||
{item.value === defaultCountry && <Select.Separator />}
|
||||
</Fragment>
|
||||
)}
|
||||
/>
|
||||
</Select.Root>
|
||||
)
|
||||
}
|
||||
|
||||
function Flag({unicodeFlag, svgFlag}: {unicodeFlag: string; svgFlag: any}) {
|
||||
if (isWeb) {
|
||||
return (
|
||||
<Image
|
||||
source={svgFlag}
|
||||
style={[
|
||||
a.rounded_2xs,
|
||||
{height: 13, aspectRatio: 4 / 3, marginRight: 6},
|
||||
web({verticalAlign: 'bottom'}),
|
||||
]}
|
||||
accessibilityIgnoresInvertColors
|
||||
/>
|
||||
)
|
||||
}
|
||||
return unicodeFlag + ' '
|
||||
}
|
||||
@@ -13,10 +13,12 @@ export function LanguageSelect({
|
||||
label: l.name,
|
||||
value: l.code2,
|
||||
})),
|
||||
label,
|
||||
}: {
|
||||
value?: string
|
||||
onChange: (value: string) => void
|
||||
items?: {label: string; value: string}[]
|
||||
label?: string
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
|
||||
@@ -37,6 +39,7 @@ export function LanguageSelect({
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
label={label}
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useTheme} from '#/alf'
|
||||
@@ -15,9 +15,9 @@ import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||
import {ChevronTopBottom_Stroke2_Corner0_Rounded as ChevronUpDownIcon} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
import {BaseRadio} from '../forms/Toggle'
|
||||
import {
|
||||
type ContentProps,
|
||||
type IconProps,
|
||||
@@ -122,10 +122,12 @@ export function ValueText({
|
||||
const t = useTheme()
|
||||
|
||||
let text = value && children(value)
|
||||
if (typeof text !== 'string') text = placeholder
|
||||
if (!text) text = placeholder
|
||||
|
||||
return (
|
||||
<ButtonText style={[t.atoms.text, a.font_normal, style]}>{text}</ButtonText>
|
||||
<ButtonText style={[t.atoms.text, a.font_normal, style]} emoji>
|
||||
{text}
|
||||
</ButtonText>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -162,15 +164,14 @@ export function Content<T>({
|
||||
}
|
||||
|
||||
function ContentInner<T>({
|
||||
label,
|
||||
items,
|
||||
renderItem,
|
||||
valueExtractor,
|
||||
...context
|
||||
}: ContentProps<T> & ContextType) {
|
||||
const control = Dialog.useDialogContext()
|
||||
|
||||
const {_} = useLingui()
|
||||
const [headerHeight, setHeaderHeight] = useState(50)
|
||||
const [headerHeight, setHeaderHeight] = useState(61)
|
||||
|
||||
const render = useCallback(
|
||||
({item, index}: {item: T; index: number}) => {
|
||||
@@ -179,33 +180,26 @@ function ContentInner<T>({
|
||||
[renderItem, context.value],
|
||||
)
|
||||
|
||||
const doneButton = useCallback(
|
||||
() => (
|
||||
<Button
|
||||
label={_(msg`Done`)}
|
||||
onPress={() => control.close()}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="ghost"
|
||||
style={[a.rounded_full]}>
|
||||
<ButtonText style={[a.text_md]}>
|
||||
<Trans>Done</Trans>
|
||||
</ButtonText>
|
||||
</Button>
|
||||
),
|
||||
[control, _],
|
||||
)
|
||||
|
||||
return (
|
||||
<Context.Provider value={context}>
|
||||
<Dialog.Header
|
||||
renderRight={doneButton}
|
||||
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
|
||||
style={[a.absolute, a.top_0, a.left_0, a.right_0, a.z_10]}>
|
||||
<Dialog.HeaderText>
|
||||
<Trans>Select an option</Trans>
|
||||
style={[
|
||||
a.absolute,
|
||||
a.top_0,
|
||||
a.left_0,
|
||||
a.right_0,
|
||||
a.z_10,
|
||||
a.pt_3xl,
|
||||
a.pb_sm,
|
||||
a.border_b_0,
|
||||
]}>
|
||||
<Dialog.HeaderText
|
||||
style={[a.flex_1, a.px_xl, a.text_left, a.font_bold, a.text_2xl]}>
|
||||
{label ?? _(msg`Select an option`)}
|
||||
</Dialog.HeaderText>
|
||||
</Dialog.Header>
|
||||
<Dialog.Handle />
|
||||
<Dialog.InnerFlatList
|
||||
headerOffset={headerHeight}
|
||||
data={items}
|
||||
@@ -258,11 +252,12 @@ export function Item({children, value, label, style}: ItemProps) {
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.pl_md,
|
||||
a.px_xl,
|
||||
(focused || pressed) && t.atoms.bg_contrast_25,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
a.py_md,
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
@@ -273,20 +268,48 @@ export function Item({children, value, label, style}: ItemProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export function ItemText({children}: ItemTextProps) {
|
||||
export function ItemText({children, style, emoji}: ItemTextProps) {
|
||||
const {selected} = useItemContext()
|
||||
const t = useTheme()
|
||||
|
||||
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
|
||||
return (
|
||||
<View style={[a.flex_1, a.py_md, a.border_b, t.atoms.border_contrast_low]}>
|
||||
<Text style={[a.text_md, selected && a.font_semi_bold]}>{children}</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[a.text_md, selected && a.font_semi_bold, style]}
|
||||
emoji={emoji}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
|
||||
const {selected} = useItemContext()
|
||||
export function ItemIndicator({icon: Icon}: ItemIndicatorProps) {
|
||||
const {selected, focused, hovered} = useItemContext()
|
||||
|
||||
return <View style={{width: 24}}>{selected && <Icon size="md" />}</View>
|
||||
if (Icon) {
|
||||
return <View style={{width: 24}}>{selected && <Icon size="md" />}</View>
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseRadio
|
||||
selected={selected}
|
||||
focused={focused}
|
||||
hovered={hovered}
|
||||
isInvalid={false}
|
||||
disabled={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Separator() {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_low,
|
||||
a.mx_xl,
|
||||
a.my_xs,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import {createContext, forwardRef, Fragment, useContext, useMemo} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {Select as RadixSelect} from 'radix-ui'
|
||||
|
||||
import {flatten, useTheme} from '#/alf'
|
||||
import {useA11y} from '#/state/a11y'
|
||||
import {flatten, useTheme, web} 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'
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
type IconProps,
|
||||
type ItemIndicatorProps,
|
||||
type ItemProps,
|
||||
type ItemTextProps,
|
||||
type RadixPassThroughTriggerProps,
|
||||
type RootProps,
|
||||
type TriggerProps,
|
||||
@@ -97,7 +99,6 @@ export function Trigger({children, label}: TriggerProps) {
|
||||
a.flex,
|
||||
a.relative,
|
||||
t.atoms.bg_contrast_50,
|
||||
a.w_full,
|
||||
a.align_center,
|
||||
a.gap_sm,
|
||||
a.justify_between,
|
||||
@@ -121,10 +122,21 @@ export function Trigger({children, label}: TriggerProps) {
|
||||
}
|
||||
}
|
||||
|
||||
export function ValueText({children: _, style, ...props}: ValueProps) {
|
||||
export function ValueText({
|
||||
children,
|
||||
webOverrideValue,
|
||||
style,
|
||||
...props
|
||||
}: ValueProps) {
|
||||
let content
|
||||
|
||||
if (webOverrideValue && children) {
|
||||
content = children(webOverrideValue)
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style={style}>
|
||||
<RadixSelect.Value {...props} />
|
||||
<RadixSelect.Value {...props}>{content}</RadixSelect.Value>
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -145,6 +157,7 @@ export function Content<T>({
|
||||
}: ContentProps<T>) {
|
||||
const t = useTheme()
|
||||
const selectedValue = useContext(SelectedValueContext)
|
||||
const {reduceMotionEnabled} = useA11y()
|
||||
|
||||
const scrollBtnStyles: React.CSSProperties[] = [
|
||||
a.absolute,
|
||||
@@ -186,8 +199,11 @@ export function Content<T>({
|
||||
<RadixSelect.Content
|
||||
style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])}
|
||||
position="popper"
|
||||
align="center"
|
||||
sideOffset={5}
|
||||
className="radix-select-content">
|
||||
className="radix-select-content"
|
||||
// prevent the keyboard shortcut for opening the composer
|
||||
onKeyDown={evt => evt.stopPropagation()}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
@@ -195,6 +211,7 @@ export function Content<T>({
|
||||
t.atoms.border_contrast_low,
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
!reduceMotionEnabled && a.zoom_fade_in,
|
||||
]}>
|
||||
<RadixSelect.ScrollUpButton style={flatten(up)}>
|
||||
<ChevronUpIcon style={[t.atoms.text]} size="xs" />
|
||||
@@ -261,7 +278,7 @@ export function Item({ref, value, style, children}: ItemProps) {
|
||||
t.atoms.text,
|
||||
a.relative,
|
||||
a.flex,
|
||||
{minHeight: 25, paddingLeft: 30, paddingRight: 35},
|
||||
{minHeight: 25, paddingLeft: 30, paddingRight: 8},
|
||||
a.user_select_none,
|
||||
a.align_center,
|
||||
a.rounded_xs,
|
||||
@@ -278,7 +295,15 @@ export function Item({ref, value, style, children}: ItemProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export const ItemText = RadixSelect.ItemText
|
||||
export const ItemText = function ItemText({children, style}: ItemTextProps) {
|
||||
return (
|
||||
<RadixSelect.ItemText asChild>
|
||||
<Text style={flatten([style, web({pointerEvents: 'inherit'})])}>
|
||||
{children}
|
||||
</Text>
|
||||
</RadixSelect.ItemText>
|
||||
)
|
||||
}
|
||||
|
||||
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
|
||||
return (
|
||||
@@ -294,3 +319,20 @@ export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
|
||||
</RadixSelect.ItemIndicator>
|
||||
)
|
||||
}
|
||||
|
||||
export function Separator() {
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<RadixSelect.Separator
|
||||
style={flatten([
|
||||
{
|
||||
height: 1,
|
||||
backgroundColor: t.atoms.border_contrast_low.borderColor,
|
||||
},
|
||||
a.my_xs,
|
||||
a.w_full,
|
||||
])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -123,9 +123,16 @@ export type ValueProps = {
|
||||
/**
|
||||
* Only needed for native. Extracts the label from an item. Defaults to `item => item.label`
|
||||
*/
|
||||
children?: (value: any) => string
|
||||
children?: (value: any) => React.ReactNode
|
||||
placeholder?: string
|
||||
style?: StyleProp<TextStyle>
|
||||
/**
|
||||
* By default, web just extracts the component from inside the dropdown and portals it in here.
|
||||
* If you want to override this, pass a value that will then be rendered via `children(value)`
|
||||
*
|
||||
* @platform web
|
||||
*/
|
||||
webOverrideValue?: any
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -137,6 +144,12 @@ export type ValueProps = {
|
||||
export type IconProps = TextStyleProp
|
||||
|
||||
export type ContentProps<T> = {
|
||||
/**
|
||||
* Label at the top of the sheet on native.
|
||||
*
|
||||
* @default "Select an option"
|
||||
*/
|
||||
label?: string
|
||||
/**
|
||||
* 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
|
||||
@@ -180,6 +193,8 @@ export type ItemProps = {
|
||||
|
||||
export type ItemTextProps = {
|
||||
children: React.ReactNode
|
||||
style?: StyleProp<TextStyle>
|
||||
emoji?: boolean
|
||||
}
|
||||
|
||||
export type ItemIndicatorProps = {
|
||||
|
||||
@@ -289,6 +289,7 @@ function Inner() {
|
||||
<Trans>Your preferred language</Trans>
|
||||
</TextField.LabelText>
|
||||
<LanguageSelect
|
||||
label={_(msg`Preferred language`)}
|
||||
value={language}
|
||||
onChange={value => {
|
||||
setLanguage(value)
|
||||
|
||||
@@ -503,9 +503,23 @@ export function Switch() {
|
||||
}
|
||||
|
||||
export function Radio() {
|
||||
const props = useContext(ItemContext)
|
||||
|
||||
return <BaseRadio {...props} />
|
||||
}
|
||||
|
||||
export function BaseRadio({
|
||||
hovered,
|
||||
focused,
|
||||
selected,
|
||||
disabled,
|
||||
isInvalid,
|
||||
}: Pick<
|
||||
ItemState,
|
||||
'hovered' | 'focused' | 'selected' | 'disabled' | 'isInvalid'
|
||||
>) {
|
||||
const t = useTheme()
|
||||
const {selected, hovered, focused, disabled, isInvalid} =
|
||||
useContext(ItemContext)
|
||||
|
||||
const {baseStyles, baseHoverStyles, indicatorStyles} =
|
||||
createSharedToggleStyles({
|
||||
theme: t,
|
||||
@@ -515,6 +529,7 @@ export function Radio() {
|
||||
disabled,
|
||||
isInvalid,
|
||||
})
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+42
-1
@@ -57,7 +57,7 @@ function getLocalizedLanguage(
|
||||
|
||||
export function languageName(language: Language, appLang: string): string {
|
||||
// if Intl.DisplayNames is unavailable on the target, display the English name
|
||||
if (!(Intl as any).DisplayNames) {
|
||||
if (!Intl.DisplayNames) {
|
||||
return language.name
|
||||
}
|
||||
|
||||
@@ -279,3 +279,44 @@ export function findSupportedAppLanguage(languageTags: (string | undefined)[]) {
|
||||
}
|
||||
return AppLanguage.en
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets region name for a given country code and language.
|
||||
*
|
||||
* Falls back to English if unavailable/error, and if that fails, returns the country code.
|
||||
*
|
||||
* Intl.DisplayNames is widely available + has been polyfilled on native
|
||||
*/
|
||||
export function regionName(countryCode: string, appLang: string): string {
|
||||
const translatedName = getLocalizedRegionName(countryCode, appLang)
|
||||
|
||||
if (translatedName) {
|
||||
return translatedName
|
||||
}
|
||||
|
||||
// Fallback: get English name. Needed for i.e. Esperanto
|
||||
const englishName = getLocalizedRegionName(countryCode, 'en')
|
||||
if (englishName) {
|
||||
return englishName
|
||||
}
|
||||
|
||||
// Final fallback: return country code
|
||||
return countryCode
|
||||
}
|
||||
|
||||
function getLocalizedRegionName(
|
||||
countryCode: string,
|
||||
appLang: string,
|
||||
): string | undefined {
|
||||
try {
|
||||
const allNames = new Intl.DisplayNames([appLang], {
|
||||
type: 'region',
|
||||
fallback: 'none',
|
||||
})
|
||||
|
||||
return allNames.of(countryCode)
|
||||
} catch (err) {
|
||||
console.warn('Error getting localized region name:', err)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
import '@formatjs/intl-locale/polyfill-force'
|
||||
import '@formatjs/intl-pluralrules/polyfill-force'
|
||||
import '@formatjs/intl-numberformat/polyfill-force'
|
||||
import '@formatjs/intl-displaynames/polyfill-force'
|
||||
import '@formatjs/intl-pluralrules/locale-data/en'
|
||||
import '@formatjs/intl-numberformat/locale-data/en'
|
||||
import '@formatjs/intl-displaynames/locale-data/en'
|
||||
|
||||
import {useEffect} from 'react'
|
||||
import {i18n} from '@lingui/core'
|
||||
@@ -64,6 +66,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/an'),
|
||||
import('@formatjs/intl-numberformat/locale-data/es'),
|
||||
import('@formatjs/intl-displaynames/locale-data/es'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -72,6 +75,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ast'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ast'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ast'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -80,6 +84,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ca'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ca'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ca'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -88,6 +93,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/cy'),
|
||||
import('@formatjs/intl-numberformat/locale-data/cy'),
|
||||
import('@formatjs/intl-displaynames/locale-data/cy'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -96,6 +102,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/da'),
|
||||
import('@formatjs/intl-numberformat/locale-data/da'),
|
||||
import('@formatjs/intl-displaynames/locale-data/da'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -104,6 +111,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/de'),
|
||||
import('@formatjs/intl-numberformat/locale-data/de'),
|
||||
import('@formatjs/intl-displaynames/locale-data/de'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -112,6 +120,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/el'),
|
||||
import('@formatjs/intl-numberformat/locale-data/el'),
|
||||
import('@formatjs/intl-displaynames/locale-data/el'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -120,6 +129,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/en'),
|
||||
import('@formatjs/intl-numberformat/locale-data/en-GB'),
|
||||
import('@formatjs/intl-displaynames/locale-data/en-GB'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -128,6 +138,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/eo'),
|
||||
import('@formatjs/intl-numberformat/locale-data/eo'),
|
||||
import('@formatjs/intl-displaynames/locale-data/eo'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -136,6 +147,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/es'),
|
||||
import('@formatjs/intl-numberformat/locale-data/es'),
|
||||
import('@formatjs/intl-displaynames/locale-data/es'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -144,6 +156,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/eu'),
|
||||
import('@formatjs/intl-numberformat/locale-data/eu'),
|
||||
import('@formatjs/intl-displaynames/locale-data/eu'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -152,6 +165,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/fi'),
|
||||
import('@formatjs/intl-numberformat/locale-data/fi'),
|
||||
import('@formatjs/intl-displaynames/locale-data/fi'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -160,6 +174,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/fr'),
|
||||
import('@formatjs/intl-numberformat/locale-data/fr'),
|
||||
import('@formatjs/intl-displaynames/locale-data/fr'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -168,6 +183,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/fy'),
|
||||
import('@formatjs/intl-numberformat/locale-data/fy'),
|
||||
import('@formatjs/intl-displaynames/locale-data/fy'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -176,6 +192,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ga'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ga'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ga'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -184,6 +201,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/gd'),
|
||||
import('@formatjs/intl-numberformat/locale-data/gd'),
|
||||
import('@formatjs/intl-displaynames/locale-data/gd'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -192,6 +210,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/gl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/gl'),
|
||||
import('@formatjs/intl-displaynames/locale-data/gl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -200,6 +219,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/hi'),
|
||||
import('@formatjs/intl-numberformat/locale-data/hi'),
|
||||
import('@formatjs/intl-displaynames/locale-data/hi'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -208,6 +228,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/hu'),
|
||||
import('@formatjs/intl-numberformat/locale-data/hu'),
|
||||
import('@formatjs/intl-displaynames/locale-data/hu'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -216,6 +237,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ia'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ia'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ia'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -224,6 +246,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/id'),
|
||||
import('@formatjs/intl-numberformat/locale-data/id'),
|
||||
import('@formatjs/intl-displaynames/locale-data/id'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -232,6 +255,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/it'),
|
||||
import('@formatjs/intl-numberformat/locale-data/it'),
|
||||
import('@formatjs/intl-displaynames/locale-data/it'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -240,6 +264,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ja'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ja'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ja'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -248,6 +273,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/km'),
|
||||
import('@formatjs/intl-numberformat/locale-data/km'),
|
||||
import('@formatjs/intl-displaynames/locale-data/km'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -256,11 +282,17 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ko'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ko'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ko'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.ne: {
|
||||
i18n.loadAndActivate({locale, messages: messagesNe})
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ne'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ne'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ne'),
|
||||
])
|
||||
break
|
||||
}
|
||||
case AppLanguage.nl: {
|
||||
@@ -268,6 +300,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/nl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/nl'),
|
||||
import('@formatjs/intl-displaynames/locale-data/nl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -276,6 +309,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/pl'),
|
||||
import('@formatjs/intl-numberformat/locale-data/pl'),
|
||||
import('@formatjs/intl-displaynames/locale-data/pl'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -284,6 +318,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/pt'),
|
||||
import('@formatjs/intl-numberformat/locale-data/pt'),
|
||||
import('@formatjs/intl-displaynames/locale-data/pt'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -292,6 +327,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/pt-PT'),
|
||||
import('@formatjs/intl-numberformat/locale-data/pt-PT'),
|
||||
import('@formatjs/intl-displaynames/locale-data/pt-PT'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -300,6 +336,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ro'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ro'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ro'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -308,6 +345,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/ru'),
|
||||
import('@formatjs/intl-numberformat/locale-data/ru'),
|
||||
import('@formatjs/intl-displaynames/locale-data/ru'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -316,6 +354,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/sv'),
|
||||
import('@formatjs/intl-numberformat/locale-data/sv'),
|
||||
import('@formatjs/intl-displaynames/locale-data/sv'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -324,6 +363,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/th'),
|
||||
import('@formatjs/intl-numberformat/locale-data/th'),
|
||||
import('@formatjs/intl-displaynames/locale-data/th'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -332,6 +372,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/tr'),
|
||||
import('@formatjs/intl-numberformat/locale-data/tr'),
|
||||
import('@formatjs/intl-displaynames/locale-data/tr'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -340,6 +381,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/uk'),
|
||||
import('@formatjs/intl-numberformat/locale-data/uk'),
|
||||
import('@formatjs/intl-displaynames/locale-data/uk'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -348,6 +390,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/vi'),
|
||||
import('@formatjs/intl-numberformat/locale-data/vi'),
|
||||
import('@formatjs/intl-displaynames/locale-data/vi'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -356,6 +399,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/zh'),
|
||||
import('@formatjs/intl-numberformat/locale-data/zh'),
|
||||
import('@formatjs/intl-displaynames/locale-data/zh'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -364,6 +408,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/zh'),
|
||||
import('@formatjs/intl-numberformat/locale-data/zh'),
|
||||
import('@formatjs/intl-displaynames/locale-data/zh'),
|
||||
])
|
||||
break
|
||||
}
|
||||
@@ -372,6 +417,7 @@ export async function dynamicActivate(locale: AppLanguage) {
|
||||
await Promise.all([
|
||||
import('@formatjs/intl-pluralrules/locale-data/zh'),
|
||||
import('@formatjs/intl-numberformat/locale-data/zh'),
|
||||
import('@formatjs/intl-displaynames/locale-data/zh'),
|
||||
])
|
||||
break
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
label={_(msg`App language`)}
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
@@ -133,6 +134,7 @@ export function LanguageSettingsScreen({}: Props) {
|
||||
<Select.Icon />
|
||||
</Select.Trigger>
|
||||
<Select.Content
|
||||
label={_(msg`Primary language`)}
|
||||
renderItem={({label, value}) => (
|
||||
<Select.Item value={value} label={label}>
|
||||
<Select.ItemIndicator />
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import {type TextInput, View} from 'react-native'
|
||||
|
||||
import {getDefaultCountry} from '#/lib/international-telephone-codes'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {DateField, LabelText} from '#/components/forms/DateField'
|
||||
@@ -9,7 +10,9 @@ import * as TextField from '#/components/forms/TextField'
|
||||
import * as Toggle from '#/components/forms/Toggle'
|
||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
||||
import {InternationalPhoneCodeSelect} from '#/components/InternationalPhoneCodeSelect'
|
||||
import {H1, H3} from '#/components/Typography'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
|
||||
export function Forms() {
|
||||
const [toggleGroupAValues, setToggleGroupAValues] = React.useState(['a'])
|
||||
@@ -23,6 +26,11 @@ export function Forms() {
|
||||
const [value, setValue] = React.useState('')
|
||||
const [date, setDate] = React.useState('2001-01-01')
|
||||
|
||||
const location = useGeolocation()
|
||||
const [telCode, setTelCode] = React.useState(() =>
|
||||
getDefaultCountry(location),
|
||||
)
|
||||
|
||||
const inputRef = React.useRef<TextInput>(null)
|
||||
|
||||
return (
|
||||
@@ -119,6 +127,20 @@ export function Forms() {
|
||||
label="Input"
|
||||
/>
|
||||
</View>
|
||||
|
||||
<H3>InternationalPhoneCodeSelect</H3>
|
||||
|
||||
<View style={[a.flex_row, a.gap_sm, a.align_center]}>
|
||||
<View>
|
||||
<InternationalPhoneCodeSelect
|
||||
value={telCode}
|
||||
onChange={setTelCode}
|
||||
/>
|
||||
</View>
|
||||
<View style={[a.flex_1]}>
|
||||
<TextField.Input label="Phone number" />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={[a.gap_md, a.align_start, a.w_full]}>
|
||||
|
||||
Reference in New Issue
Block a user