Files
web/components/TempDesignSystem/Form/Country/index.tsx
2025-02-14 14:20:54 +01:00

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>
)
}