122 lines
3.4 KiB
TypeScript
122 lines
3.4 KiB
TypeScript
"use client"
|
|
import { useState } from "react"
|
|
import {
|
|
Button,
|
|
ComboBox,
|
|
Input,
|
|
type Key,
|
|
ListBox,
|
|
ListBoxItem,
|
|
Popover,
|
|
} from "react-aria-components"
|
|
import { useController, useFormContext } from "react-hook-form"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import Label from "@/components/TempDesignSystem/Form/Label"
|
|
import SelectChevron from "@/components/TempDesignSystem/Form/SelectChevron"
|
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
|
|
import ErrorMessage from "../ErrorMessage"
|
|
import { countries } from "./countries"
|
|
|
|
import styles from "./country.module.css"
|
|
|
|
import type {
|
|
CountryPortalContainer,
|
|
CountryPortalContainerArgs,
|
|
CountryProps,
|
|
} from "./country"
|
|
|
|
export default function CountrySelect({
|
|
className = "",
|
|
label,
|
|
name = "country",
|
|
readOnly = false,
|
|
registerOptions = {},
|
|
}: CountryProps) {
|
|
const intl = useIntl()
|
|
const [rootDiv, setRootDiv] = useState<CountryPortalContainer>(undefined)
|
|
|
|
function setRef(node: CountryPortalContainerArgs) {
|
|
if (node) {
|
|
setRootDiv(node)
|
|
}
|
|
}
|
|
const { control, setValue } = useFormContext()
|
|
const { field, formState } = useController({
|
|
control,
|
|
name,
|
|
rules: registerOptions,
|
|
})
|
|
|
|
function handleChange(country: Key | null) {
|
|
setValue(name, country ?? "")
|
|
}
|
|
|
|
const selectCountryLabel = intl.formatMessage({ id: "Select a country" })
|
|
|
|
return (
|
|
<div className={`${styles.container} ${className}`} ref={setRef}>
|
|
<ComboBox
|
|
aria-label={intl.formatMessage({ id: "Select country of residence" })}
|
|
isReadOnly={readOnly}
|
|
isRequired={!!registerOptions?.required}
|
|
name={field.name}
|
|
onBlur={field.onBlur}
|
|
onSelectionChange={handleChange}
|
|
ref={field.ref}
|
|
selectedKey={field.value}
|
|
data-testid={name}
|
|
>
|
|
<div className={styles.comboBoxContainer}>
|
|
<Label
|
|
className={styles.label}
|
|
size="small"
|
|
required={!!registerOptions.required}
|
|
>
|
|
{label}
|
|
</Label>
|
|
<Body asChild fontOnly>
|
|
<Input
|
|
aria-label={selectCountryLabel}
|
|
className={styles.input}
|
|
placeholder={selectCountryLabel}
|
|
/>
|
|
</Body>
|
|
<Button className={styles.button}>
|
|
<SelectChevron />
|
|
</Button>
|
|
</div>
|
|
<ErrorMessage errors={formState.errors} name={name} />
|
|
<Popover
|
|
className={styles.popover}
|
|
placement="bottom"
|
|
shouldFlip={false}
|
|
shouldUpdatePosition={false}
|
|
/**
|
|
* react-aria uses portals to render Popover in body
|
|
* unless otherwise specified. We need it to be contained
|
|
* by this component to both access css variables assigned
|
|
* on the container as well as to not overflow it at any time.
|
|
*/
|
|
UNSTABLE_portalContainer={rootDiv}
|
|
>
|
|
<ListBox>
|
|
{countries.map((country, idx) => (
|
|
<Body asChild fontOnly key={`${country.code}-${idx}`}>
|
|
<ListBoxItem
|
|
aria-label={country.name}
|
|
className={styles.listBoxItem}
|
|
id={country.code}
|
|
>
|
|
{country.name}
|
|
</ListBoxItem>
|
|
</Body>
|
|
))}
|
|
</ListBox>
|
|
</Popover>
|
|
</ComboBox>
|
|
</div>
|
|
)
|
|
}
|