get it somewhat working on web

This commit is contained in:
Samuel Newman
2025-11-12 18:45:40 +02:00
parent c910a31ebd
commit 47aa6c5242
5 changed files with 60 additions and 38 deletions
+2 -1
View File
@@ -105,7 +105,8 @@ export type ButtonProps = Pick<
PressableComponent?: React.ComponentType<PressableProps> 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>({ const Context = React.createContext<VariantProps & ButtonState>({
hovered: false, hovered: false,
+39 -20
View File
@@ -6,9 +6,11 @@ import {useLingui} from '@lingui/react'
import {getCountriesWithTelephoneCodes} from '#/lib/international-telephone-codes' import {getCountriesWithTelephoneCodes} from '#/lib/international-telephone-codes'
import {isWeb} from '#/platform/detection' import {isWeb} from '#/platform/detection'
import {useGeolocationStatus} from '#/state/geolocation' import {useGeolocationStatus} from '#/state/geolocation'
import {atoms as a} from '#/alf' import {atoms as a, web} from '#/alf'
import * as Select from '#/components/Select' import * as Select from '#/components/Select'
export const DEFAULT_PHONE_COUNTRY = 'US'
/** /**
* Country picker for a phone number input * Country picker for a phone number input
* *
@@ -24,6 +26,9 @@ export function InternationalPhoneCodeSelect({
}) { }) {
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
const {location} = useGeolocationStatus() const {location} = useGeolocationStatus()
const defaultCountry = location?.countryCode || DEFAULT_PHONE_COUNTRY
const items = useMemo(() => { const items = useMemo(() => {
const telCountryMap = getCountriesWithTelephoneCodes(i18n) const telCountryMap = getCountriesWithTelephoneCodes(i18n)
@@ -37,22 +42,25 @@ export function InternationalPhoneCodeSelect({
unicodeFlag, unicodeFlag,
svgFlag, svgFlag,
})) }))
// boost the default value to the top // boost the default value to the top, then sort by name
.sort((a, b) => .sort((a, b) => {
a.value === location.countryCode if (a.value === defaultCountry) return -1
? -1 if (b.value === defaultCountry) return 1
: b.value === location.countryCode return a.name.localeCompare(b.name)
? 1 })
: 0,
)
) )
}, [i18n, location]) }, [i18n, defaultCountry])
return ( return (
<Select.Root value={value} onValueChange={onChange}> <Select.Root value={value} onValueChange={onChange}>
<Select.Trigger label={_(msg`Select telephone code`)}> <Select.Trigger label={_(msg`Select telephone code`)}>
<Select.ValueText placeholder={_(msg`Select telephone code`)}> <Select.ValueText placeholder="+...">
{item => item.unicodeFlag + ' ' + item.code} {selected => (
<>
<Flag {...selected} />
{selected.code}
</>
)}
</Select.ValueText> </Select.ValueText>
<Select.Icon /> <Select.Icon />
</Select.Trigger> </Select.Trigger>
@@ -64,17 +72,11 @@ export function InternationalPhoneCodeSelect({
<Select.Item value={item.value} label={item.label}> <Select.Item value={item.value} label={item.label}>
<Select.ItemIndicator /> <Select.ItemIndicator />
<Select.ItemText style={[a.flex_1]} emoji> <Select.ItemText style={[a.flex_1]} emoji>
{isWeb ? ( {isWeb ? <Flag {...item} /> : item.unicodeFlag + ' '}
<Image
source={item.svgFlag}
accessibilityIgnoresInvertColors
/>
) : (
item.unicodeFlag + ' '
)}
{item.name} {item.name}
</Select.ItemText> </Select.ItemText>
<Select.ItemText style={[a.text_right]}> <Select.ItemText style={[a.text_right]}>
{' '}
{item.code} {item.code}
</Select.ItemText> </Select.ItemText>
</Select.Item> </Select.Item>
@@ -85,3 +87,20 @@ export function InternationalPhoneCodeSelect({
</Select.Root> </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 + ' '
}
+4 -2
View File
@@ -122,10 +122,12 @@ export function ValueText({
const t = useTheme() const t = useTheme()
let text = value && children(value) let text = value && children(value)
if (typeof text !== 'string') text = placeholder if (!text) text = placeholder
return ( return (
<ButtonText style={[t.atoms.text, a.font_normal, style]}>{text}</ButtonText> <ButtonText style={[t.atoms.text, a.font_normal, style]} emoji>
{text}
</ButtonText>
) )
} }
+14 -14
View File
@@ -1,15 +1,9 @@
import { import {createContext, forwardRef, Fragment, useContext, useMemo} from 'react'
createContext,
type CSSProperties,
forwardRef,
Fragment,
useContext,
useMemo,
} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {Select as RadixSelect} from 'radix-ui' 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 {atoms as a} from '#/alf'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
@@ -105,7 +99,6 @@ export function Trigger({children, label}: TriggerProps) {
a.flex, a.flex,
a.relative, a.relative,
t.atoms.bg_contrast_50, t.atoms.bg_contrast_50,
a.w_full,
a.align_center, a.align_center,
a.gap_sm, a.gap_sm,
a.justify_between, a.justify_between,
@@ -153,6 +146,7 @@ export function Content<T>({
}: ContentProps<T>) { }: ContentProps<T>) {
const t = useTheme() const t = useTheme()
const selectedValue = useContext(SelectedValueContext) const selectedValue = useContext(SelectedValueContext)
const {reduceMotionEnabled} = useA11y()
const scrollBtnStyles: React.CSSProperties[] = [ const scrollBtnStyles: React.CSSProperties[] = [
a.absolute, a.absolute,
@@ -194,8 +188,11 @@ export function Content<T>({
<RadixSelect.Content <RadixSelect.Content
style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])} style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])}
position="popper" position="popper"
align="center"
sideOffset={5} sideOffset={5}
className="radix-select-content"> className="radix-select-content"
// prevent the keyboard shortcut for opening the composer
onKeyDown={evt => evt.stopPropagation()}>
<View <View
style={[ style={[
a.flex_1, a.flex_1,
@@ -203,6 +200,7 @@ export function Content<T>({
t.atoms.border_contrast_low, t.atoms.border_contrast_low,
a.rounded_sm, a.rounded_sm,
a.overflow_hidden, a.overflow_hidden,
!reduceMotionEnabled && a.zoom_fade_in,
]}> ]}>
<RadixSelect.ScrollUpButton style={flatten(up)}> <RadixSelect.ScrollUpButton style={flatten(up)}>
<ChevronUpIcon style={[t.atoms.text]} size="xs" /> <ChevronUpIcon style={[t.atoms.text]} size="xs" />
@@ -269,7 +267,7 @@ export function Item({ref, value, style, children}: ItemProps) {
t.atoms.text, t.atoms.text,
a.relative, a.relative,
a.flex, a.flex,
{minHeight: 25, paddingLeft: 30, paddingRight: 35}, {minHeight: 25, paddingLeft: 30, paddingRight: 8},
a.user_select_none, a.user_select_none,
a.align_center, a.align_center,
a.rounded_xs, a.rounded_xs,
@@ -288,8 +286,10 @@ export function Item({ref, value, style, children}: ItemProps) {
export const ItemText = function ItemText({children, style}: ItemTextProps) { export const ItemText = function ItemText({children, style}: ItemTextProps) {
return ( return (
<RadixSelect.ItemText style={flatten(style) as CSSProperties}> <RadixSelect.ItemText asChild>
{children} <Text style={flatten([style, web({pointerEvents: 'inherit'})])}>
{children}
</Text>
</RadixSelect.ItemText> </RadixSelect.ItemText>
) )
} }
+1 -1
View File
@@ -123,7 +123,7 @@ export type ValueProps = {
/** /**
* Only needed for native. Extracts the label from an item. Defaults to `item => item.label` * 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 placeholder?: string
style?: StyleProp<TextStyle> style?: StyleProp<TextStyle>
} }