167 lines
4.8 KiB
TypeScript
167 lines
4.8 KiB
TypeScript
"use client"
|
|
import "react-international-phone/style.css"
|
|
|
|
import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js"
|
|
import { TextField } from "react-aria-components"
|
|
import { useController, useFormContext, useWatch } from "react-hook-form"
|
|
import {
|
|
CountrySelector,
|
|
DialCodePreview,
|
|
type ParsedCountry,
|
|
usePhoneInput,
|
|
} from "react-international-phone"
|
|
import { useIntl } from "react-intl"
|
|
|
|
import { ChevronDownIcon } from "@/components/Icons"
|
|
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
|
|
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
|
|
import Label from "@/components/TempDesignSystem/Form/Label"
|
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
import useLang from "@/hooks/useLang"
|
|
|
|
import styles from "./phone.module.css"
|
|
|
|
import type { ChangeEvent } from "react"
|
|
|
|
import type {
|
|
LowerCaseCountryCode,
|
|
PhoneProps,
|
|
} from "@/types/components/form/phone"
|
|
import type { Lang } from "@/constants/languages"
|
|
|
|
export default function Phone({
|
|
ariaLabel = "Phone number input",
|
|
className = "",
|
|
disabled = false,
|
|
label,
|
|
name = "phoneNumber",
|
|
placeholder = "",
|
|
readOnly = false,
|
|
registerOptions = {
|
|
required: true,
|
|
},
|
|
}: PhoneProps) {
|
|
const intl = useIntl()
|
|
const lang = useLang()
|
|
const { control, setValue, trigger } = useFormContext()
|
|
const phone = useWatch({ name })
|
|
|
|
const { field, fieldState, formState } = useController({
|
|
control,
|
|
disabled,
|
|
name,
|
|
rules: registerOptions,
|
|
})
|
|
|
|
const defaultPhoneNumber = formState.defaultValues?.phoneNumber
|
|
|
|
// If defaultPhoneNumber exists and is valid, parse it to get the country code,
|
|
// otherwise set the default country from the lang.
|
|
const defaultCountry = isValidPhoneNumber(defaultPhoneNumber)
|
|
? parsePhoneNumber(defaultPhoneNumber).country?.toLowerCase()
|
|
: getDefaultCountryFromLang(lang)
|
|
|
|
const { country, handlePhoneValueChange, inputValue, setCountry } =
|
|
usePhoneInput({
|
|
defaultCountry,
|
|
disableDialCodeAndPrefix: true,
|
|
forceDialCode: true,
|
|
value: phone,
|
|
onChange: (value) => {
|
|
// If not checked trigger(name) forces validation on mount
|
|
// which shows error message before user even can see the form
|
|
if (value.inputValue) {
|
|
setValue(name, value.phone)
|
|
trigger(name)
|
|
} else {
|
|
setValue(name, "")
|
|
}
|
|
},
|
|
})
|
|
|
|
function handleSelectCountry(value: ParsedCountry) {
|
|
setCountry(value.iso2)
|
|
}
|
|
|
|
function handleChange(evt: ChangeEvent<HTMLInputElement>) {
|
|
handlePhoneValueChange(evt)
|
|
}
|
|
|
|
return (
|
|
<div className={`${styles.phone} ${className}`}>
|
|
<CountrySelector
|
|
disabled={readOnly}
|
|
dropdownArrowClassName={styles.arrow}
|
|
flagClassName={styles.flag}
|
|
onSelect={handleSelectCountry}
|
|
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
|
|
selectedCountry={country.iso2}
|
|
renderButtonWrapper={(props) => (
|
|
<button
|
|
{...props.rootProps}
|
|
className={styles.select}
|
|
tabIndex={0}
|
|
type="button"
|
|
data-testid="country-selector"
|
|
>
|
|
<Label required={!!registerOptions.required} size="small">
|
|
{intl.formatMessage({ id: "Country code" })}
|
|
</Label>
|
|
<span className={styles.selectContainer}>
|
|
{props.children}
|
|
<Body asChild fontOnly>
|
|
<DialCodePreview
|
|
className={styles.dialCode}
|
|
dialCode={country.dialCode}
|
|
prefix="+"
|
|
/>
|
|
</Body>
|
|
<ChevronDownIcon
|
|
className={styles.chevron}
|
|
color="grey80"
|
|
height={18}
|
|
width={18}
|
|
/>
|
|
</span>
|
|
</button>
|
|
)}
|
|
/>
|
|
<TextField
|
|
aria-label={ariaLabel}
|
|
defaultValue={field.value}
|
|
isDisabled={disabled ?? field.disabled}
|
|
isInvalid={fieldState.invalid}
|
|
isRequired={!!registerOptions?.required}
|
|
isReadOnly={readOnly}
|
|
name={field.name}
|
|
type="tel"
|
|
>
|
|
<AriaInputWithLabel
|
|
{...field}
|
|
id={field.name}
|
|
label={label}
|
|
onChange={handleChange}
|
|
placeholder={placeholder}
|
|
readOnly={readOnly}
|
|
required={!!registerOptions.required}
|
|
type="tel"
|
|
value={inputValue}
|
|
/>
|
|
<ErrorMessage errors={formState.errors} name={field.name} />
|
|
</TextField>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function getDefaultCountryFromLang(lang: Lang): LowerCaseCountryCode {
|
|
const countryMap: Record<Lang, LowerCaseCountryCode> = {
|
|
sv: "se",
|
|
da: "dk",
|
|
fi: "fi",
|
|
no: "no",
|
|
de: "de",
|
|
en: "se", // Default to Sweden for English
|
|
}
|
|
return countryMap[lang] || "se"
|
|
}
|