diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Country/CountryCombobox.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Country/CountryCombobox.tsx new file mode 100644 index 000000000..dc57b7b8e --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Form/Country/CountryCombobox.tsx @@ -0,0 +1,168 @@ +"use client" + +import { type SyntheticEvent, useMemo, useState } from "react" +import { + Button, + ComboBox, + Input, + Label, + ListBox, + ListBoxItem, + Popover, + useFilter, +} from "react-aria-components" +import { useController } from "react-hook-form" +import { useIntl } from "react-intl" + +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import { countries } from "@/constants/countries" + +import useLang from "@/hooks/useLang" + +import ErrorMessage from "../ErrorMessage" + +import styles from "./country.module.css" + +import type { CountryProps } from "./country" + +const prioCountryCode = ["DE", "DK", "FI", "NO", "SE"] + +export default function CountryCombobox({ + // hack used since chrome does not respect autocomplete="off" + autoComplete = "nope", + className = "", + label, + name = "country", + readOnly = false, + registerOptions = {}, +}: CountryProps) { + const lang = useLang() + const intl = useIntl() + + const { startsWith } = useFilter({ sensitivity: "base" }) + const [filterValue, setFilterValue] = useState("") + const { field, formState, fieldState } = useController({ + name, + rules: registerOptions, + }) + + const items = useMemo(() => { + function mapCountry(country: (typeof countries)[number]) { + return { + value: country.code, + label: + intl.formatDisplayName(country.code, { type: "region" }) || + country.name, + } + } + + const collator = new Intl.Collator(lang) + const prioCountries = countries + .filter((c) => prioCountryCode.includes(c.code)) + .map(mapCountry) + .filter((item) => startsWith(item.label, filterValue)) + .sort((a, b) => collator.compare(a.label, b.label)) + + const restCountries = countries + .filter((c) => !prioCountryCode.includes(c.code)) + .map(mapCountry) + .filter((item) => startsWith(item.label, filterValue)) + .sort((a, b) => collator.compare(a.label, b.label)) + + return [...prioCountries, ...restCountries] + }, [filterValue, intl, lang, startsWith]) + + function handleOnInput(evt: SyntheticEvent) { + setFilterValue(evt.currentTarget.value) + const isAutoCompleteEvent = !("inputType" in evt.nativeEvent) + if (isAutoCompleteEvent) { + const { value } = evt.currentTarget + const cc = countries.find((c) => c.name === value || c.code === value) + if (cc) { + field.onChange(cc.code) + } + } + } + + return ( +
+ field.onChange(c ?? "")} + selectedKey={field.value} + menuTrigger="focus" + > + + + + {items.map((item) => ( + + {({ isSelected }) => ( + + {item.label} + + )} + + ))} + + + + +
+ ) +} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Country/CountrySelect.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Country/CountrySelect.tsx new file mode 100644 index 000000000..ab376647e --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Form/Country/CountrySelect.tsx @@ -0,0 +1,75 @@ +"use client" + +import { useMemo } from "react" +import { useController } from "react-hook-form" +import { useIntl } from "react-intl" + +import { Select } from "@scandic-hotels/design-system/Select" + +import { countries } from "@/constants/countries" + +import useLang from "@/hooks/useLang" + +import ErrorMessage from "../ErrorMessage" + +import type { CountryProps } from "./country" + +const prioCountryCode = ["DE", "DK", "FI", "NO", "SE"] + +export default function CountrySelect({ + className = "", + label, + name = "country", + readOnly = false, + registerOptions = {}, +}: CountryProps) { + const lang = useLang() + const intl = useIntl() + + const { field, formState, fieldState } = useController({ + name, + rules: registerOptions, + }) + + const items = useMemo(() => { + function mapCountry(country: (typeof countries)[number]) { + return { + value: country.code, + label: + intl.formatDisplayName(country.code, { type: "region" }) || + country.name, + } + } + + const collator = new Intl.Collator(lang) + const prioCountries = countries + .filter((c) => prioCountryCode.includes(c.code)) + .map(mapCountry) + .sort((a, b) => collator.compare(a.label, b.label)) + + const restCountries = countries + .filter((c) => !prioCountryCode.includes(c.code)) + .map(mapCountry) + .sort((a, b) => collator.compare(a.label, b.label)) + + return [...prioCountries, ...restCountries] + }, [intl, lang]) + + return ( +
+ - - - - - - - {items.map((item) => ( - - {({ isSelected }) => ( - - {item.label} - - )} - - ))} - - - - -
+ return isDesktop ? ( + + ) : ( + ) } diff --git a/apps/scandic-web/components/TempDesignSystem/Form/Date/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/Date/index.tsx index 2f8d27b2c..5d57c889e 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/Date/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/Date/index.tsx @@ -3,6 +3,7 @@ import { parseDate } from "@internationalized/date" import { useEffect } from "react" import { useController, useFormContext, useWatch } from "react-hook-form" import { useIntl } from "react-intl" +import { useMediaQuery } from "usehooks-ts" import { Select } from "@scandic-hotels/design-system/Select" @@ -20,6 +21,9 @@ import styles from "./date.module.css" export default function DateSelect({ name, registerOptions = {} }: DateProps) { const intl = useIntl() const lang = useLang() + const isDesktop = useMediaQuery("(min-width: 768px)", { + initializeWithValue: false, + }) const { control, setValue, formState, watch } = useFormContext() const { field, fieldState } = useController({ @@ -126,7 +130,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) { name={DateName.day} onSelectionChange={(key) => setValue(DateName.day, Number(key))} isRequired - enableFiltering + enableFiltering={isDesktop} isInvalid={fieldState.invalid} onBlur={field.onBlur} defaultSelectedKey={dateValue?.day} @@ -142,7 +146,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) { name={DateName.month} onSelectionChange={(key) => setValue(DateName.month, Number(key))} isRequired - enableFiltering + enableFiltering={isDesktop} isInvalid={fieldState.invalid} onBlur={field.onBlur} defaultSelectedKey={dateValue?.month} @@ -158,7 +162,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) { name={DateName.year} onSelectionChange={(key) => setValue(DateName.year, Number(key))} isRequired - enableFiltering + enableFiltering={isDesktop} isInvalid={fieldState.invalid} onBlur={field.onBlur} defaultSelectedKey={dateValue?.year} diff --git a/packages/design-system/lib/components/Select/Select.tsx b/packages/design-system/lib/components/Select/Select.tsx index deb37342a..409b6d9de 100644 --- a/packages/design-system/lib/components/Select/Select.tsx +++ b/packages/design-system/lib/components/Select/Select.tsx @@ -28,7 +28,7 @@ export function Select({ }: SelectProps | SelectFilterProps) { const [isOpen, setIsOpen] = useState(false) - if ('enableFiltering' in props) { + if (props.enableFiltering) { return ( { name: string label: string onSelectionChange?: (key: Key | null) => void + enableFiltering?: false } export interface SelectItemProps extends ComponentProps { @@ -32,5 +33,5 @@ export interface SelectFilterProps extends ComponentProps { name: string label: string onSelectionChange?: (key: Key | null) => void - enableFiltering: boolean + enableFiltering: true }