diff --git a/src/components/InternationalPhoneCodeSelect.tsx b/src/components/InternationalPhoneCodeSelect.tsx
new file mode 100644
index 0000000000..77f3ffe0ae
--- /dev/null
+++ b/src/components/InternationalPhoneCodeSelect.tsx
@@ -0,0 +1,71 @@
+import {Fragment, useMemo} from 'react'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {getCountriesWithTelephoneCodes} from '#/lib/international-telephone-codes'
+import {useGeolocationStatus} from '#/state/geolocation'
+import {atoms as a} from '#/alf'
+import * as Select from '#/components/Select'
+
+/**
+ * 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} = useGeolocationStatus()
+ const items = useMemo(() => {
+ const telCountryMap = getCountriesWithTelephoneCodes(i18n)
+
+ return (
+ Object.entries(telCountryMap)
+ .map(([value, {name, code}]) => ({
+ value,
+ name,
+ code,
+ label: `${name} ${code}`,
+ }))
+ // boost the default value to the top
+ .sort((a, b) =>
+ a.value === location.countryCode
+ ? -1
+ : b.value === location.countryCode
+ ? 1
+ : 0,
+ )
+ )
+ }, [i18n, location])
+
+ return (
+
+
+
+
+
+ (
+
+
+
+ {item.name}
+
+ {item.code}
+
+
+ {item.value === location?.countryCode && }
+
+ )}
+ />
+
+ )
+}
diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx
index a723e52370..0794c9f586 100644
--- a/src/components/Select/index.tsx
+++ b/src/components/Select/index.tsx
@@ -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,
@@ -162,15 +162,14 @@ export function Content({
}
function ContentInner({
+ label,
items,
renderItem,
valueExtractor,
...context
}: ContentProps & 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 +178,26 @@ function ContentInner({
[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
+ 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,
+ ]}>
+
+ {label ?? _(msg`Select an option`)}
+
{children}
@@ -273,20 +266,46 @@ export function Item({children, value, label, style}: ItemProps) {
)
}
-export function ItemText({children}: ItemTextProps) {
+export function ItemText({children, style}: ItemTextProps) {
const {selected} = useItemContext()
- const t = useTheme()
- // eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return (
-
- {children}
-
+
+ {children}
+
)
}
-export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
- const {selected} = useItemContext()
+export function ItemIndicator({icon: Icon}: ItemIndicatorProps) {
+ const {selected, focused, hovered} = useItemContext()
- return {selected && }
+ if (Icon) {
+ return {selected && }
+ }
+
+ return (
+
+ )
+}
+
+export function Separator() {
+ const t = useTheme()
+
+ return (
+
+ )
}
diff --git a/src/components/Select/index.web.tsx b/src/components/Select/index.web.tsx
index 995f9d412e..71d0ae7013 100644
--- a/src/components/Select/index.web.tsx
+++ b/src/components/Select/index.web.tsx
@@ -1,4 +1,11 @@
-import {createContext, forwardRef, Fragment, useContext, useMemo} from 'react'
+import {
+ createContext,
+ type CSSProperties,
+ forwardRef,
+ Fragment,
+ useContext,
+ useMemo,
+} from 'react'
import {View} from 'react-native'
import {Select as RadixSelect} from 'radix-ui'
@@ -16,6 +23,7 @@ import {
type IconProps,
type ItemIndicatorProps,
type ItemProps,
+ type ItemTextProps,
type RadixPassThroughTriggerProps,
type RootProps,
type TriggerProps,
@@ -278,7 +286,13 @@ export function Item({ref, value, style, children}: ItemProps) {
)
}
-export const ItemText = RadixSelect.ItemText
+export const ItemText = function ItemText({children, style}: ItemTextProps) {
+ return (
+
+ {children}
+
+ )
+}
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
return (
@@ -294,3 +308,20 @@ export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
)
}
+
+export function Separator() {
+ const t = useTheme()
+
+ return (
+
+ )
+}
diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts
index 0e58c197be..33a91bbedb 100644
--- a/src/components/Select/types.ts
+++ b/src/components/Select/types.ts
@@ -137,6 +137,12 @@ export type ValueProps = {
export type IconProps = TextStyleProp
export type ContentProps = {
+ /**
+ * 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 +186,7 @@ export type ItemProps = {
export type ItemTextProps = {
children: React.ReactNode
+ style?: StyleProp
}
export type ItemIndicatorProps = {
diff --git a/src/components/forms/Toggle/index.tsx b/src/components/forms/Toggle/index.tsx
index 60fa50478a..819008f34f 100644
--- a/src/components/forms/Toggle/index.tsx
+++ b/src/components/forms/Toggle/index.tsx
@@ -503,9 +503,23 @@ export function Switch() {
}
export function Radio() {
+ const props = useContext(ItemContext)
+
+ return
+}
+
+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 (