feat(LOY-169): use new filter select for country select
standardised colors on error messages fixed error validation on date field removed duplicates in country list
This commit is contained in:
@@ -1,33 +1,19 @@
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
import {
|
||||
Button,
|
||||
ComboBox,
|
||||
Input,
|
||||
type Key,
|
||||
ListBox,
|
||||
ListBoxItem,
|
||||
Popover,
|
||||
} from "react-aria-components"
|
||||
|
||||
import { useMemo, useState } from "react"
|
||||
import { useFilter } from "react-aria-components"
|
||||
import { useController, useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Select } from "@scandic-hotels/design-system/Select"
|
||||
|
||||
import { countries } from "@/constants/countries"
|
||||
|
||||
import Label from "@/components/TempDesignSystem/Form/Label"
|
||||
import SelectChevron from "@/components/TempDesignSystem/Form/SelectChevron"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import ErrorMessage from "../ErrorMessage"
|
||||
|
||||
import styles from "./country.module.css"
|
||||
|
||||
import type {
|
||||
CountryPortalContainer,
|
||||
CountryPortalContainerArgs,
|
||||
CountryProps,
|
||||
} from "./country"
|
||||
import type { CountryProps } from "./country"
|
||||
|
||||
export default function CountrySelect({
|
||||
className = "",
|
||||
@@ -38,104 +24,45 @@ export default function CountrySelect({
|
||||
}: CountryProps) {
|
||||
const lang = useLang()
|
||||
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({
|
||||
const { startsWith } = useFilter({ sensitivity: "base" })
|
||||
const [filterValue, setFilterValue] = useState("")
|
||||
const { control } = useFormContext()
|
||||
const { field, formState, fieldState } = useController({
|
||||
control,
|
||||
name,
|
||||
rules: registerOptions,
|
||||
})
|
||||
|
||||
function handleChange(country: Key | null) {
|
||||
setValue(name, country ?? "")
|
||||
}
|
||||
|
||||
const selectCountryLabel = intl.formatMessage({
|
||||
defaultMessage: "Select a country",
|
||||
})
|
||||
const collator = new Intl.Collator(lang)
|
||||
const items = useMemo(() => {
|
||||
const collator = new Intl.Collator(lang)
|
||||
return countries
|
||||
.map((country) => ({
|
||||
value: country.code,
|
||||
label:
|
||||
intl.formatDisplayName(country.code, { type: "region" }) ||
|
||||
country.name,
|
||||
}))
|
||||
.filter((item) => startsWith(item.label, filterValue))
|
||||
.sort((a, b) => collator.compare(a.label, b.label))
|
||||
}, [filterValue, intl, lang, startsWith])
|
||||
|
||||
return (
|
||||
<div className={`${styles.container} ${className}`} ref={setRef}>
|
||||
<ComboBox
|
||||
aria-label={intl.formatMessage({
|
||||
defaultMessage: "Select country of residence",
|
||||
})}
|
||||
isReadOnly={readOnly}
|
||||
isRequired={!!registerOptions?.required}
|
||||
<div className={className}>
|
||||
<Select
|
||||
label={label}
|
||||
items={items}
|
||||
enableFiltering
|
||||
isRequired={Boolean(registerOptions?.required)}
|
||||
isInvalid={fieldState.invalid}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
onSelectionChange={handleChange}
|
||||
ref={field.ref}
|
||||
selectedKey={field.value}
|
||||
defaultSelectedKey={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) => ({
|
||||
...country,
|
||||
localizedDisplayName:
|
||||
intl.formatDisplayName(country.code, { type: "region" }) ||
|
||||
country.name,
|
||||
}))
|
||||
.sort((a, b) =>
|
||||
collator.compare(a.localizedDisplayName, b.localizedDisplayName)
|
||||
)
|
||||
.map((country, idx) => {
|
||||
return (
|
||||
<Body asChild fontOnly key={`${country.code}-${idx}`}>
|
||||
<ListBoxItem
|
||||
aria-label={country.name}
|
||||
className={styles.listBoxItem}
|
||||
id={country.code}
|
||||
>
|
||||
{country.localizedDisplayName}
|
||||
</ListBoxItem>
|
||||
</Body>
|
||||
)
|
||||
})}
|
||||
</ListBox>
|
||||
</Popover>
|
||||
</ComboBox>
|
||||
isReadOnly={readOnly}
|
||||
onInputChange={setFilterValue}
|
||||
/>
|
||||
<ErrorMessage errors={formState.errors} name={name} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user