feat(WEB-162): final design edit profile page

This commit is contained in:
Simon Emanuelsson
2024-06-18 08:15:57 +02:00
committed by Christel Westerberg
parent 5f3e417593
commit d84efcbb0f
81 changed files with 1538 additions and 711 deletions

View File

@@ -1,35 +1,46 @@
.container {
--select-border: 2px solid var(--UI-Grey-60);
--select-width: min(28rem, 100%);
position: relative;
}
.comboBoxContainer {
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-radius: var(--Corner-radius-Medium);
border-style: solid;
border-width: 1px;
display: grid;
grid-template-areas: "content";
width: var(--select-width);
gap: var(--Spacing-x-half);
grid-template-areas:
"label chevron"
"input chevron";
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
}
.label {
grid-area: label;
}
.input {
background-color: var(--Main-Grey-White);
border: var(--select-border);
border-radius: var(--Corner-radius-Small);
grid-area: content;
height: 40px;
padding: var(--Spacing-x1) var(--Spacing-x2);
width: var(--select-width);
border: none;
grid-area: input;
height: 18px;
padding: 0;
}
.input,
.listBoxItem {
color: var(--UI-Grey-60);
color: var(--Main-Grey-100);
}
.button {
background: none;
border: none;
cursor: pointer;
grid-area: content;
grid-area: chevron;
height: 100%;
justify-self: flex-end;
padding-left: 0;
@@ -38,13 +49,27 @@
.popover {
background-color: var(--Main-Grey-White);
border: var(--select-border);
border-radius: var(--Corner-radius-Small);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
left: 0px;
max-height: 400px;
overflow: auto;
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x1);
width: var(--select-width);
padding: var(--Spacing-x2);
top: calc(60px + var(--Spacing-x1));
width: 100%;
}
.listBoxItem {
padding: 0 var(--Spacing-x1);
}
padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1)
var(--Spacing-x2);
}
.listBoxItem[data-selected="true"],
.listBoxItem:hover {
background-color: var(--Scandic-Blue-00);
border-radius: var(--Corner-radius-Medium);
cursor: pointer;
outline: none;
}

View File

@@ -1,6 +1,11 @@
import type { RegisterOptions } from "react-hook-form"
export type CountryProps = {
label: string
name?: string
placeholder?: string
registerOptions?: RegisterOptions
}
export type CountryPortalContainer = HTMLDivElement | undefined
export type CountryPortalContainerArgs = HTMLDivElement | null

View File

@@ -1,6 +1,6 @@
"use client"
import { ErrorMessage } from "@hookform/error-message"
import { useRef } from "react"
import { useState } from "react"
import {
Button,
ComboBox,
@@ -14,21 +14,33 @@ import {
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 SelectChevron from "../SelectChevron"
import { countries } from "./countries"
import styles from "./country.module.css"
import type { CountryProps } from "./country"
import type {
CountryPortalContainer,
CountryPortalContainerArgs,
CountryProps,
} from "./country"
export default function CountrySelect({
label,
name = "country",
registerOptions,
registerOptions = {},
}: CountryProps) {
const { formatMessage } = useIntl()
const divRef = useRef<HTMLDivElement>(null)
const [rootDiv, setRootDiv] = useState<CountryPortalContainer>(undefined)
function setRef(node: CountryPortalContainerArgs) {
if (node) {
setRootDiv(node)
}
}
const { control, setValue } = useFormContext()
const { field } = useController({
control,
@@ -43,7 +55,7 @@ export default function CountrySelect({
const selectCountryLabel = formatMessage({ id: "Select a country" })
return (
<div className={styles.container} ref={divRef}>
<div className={styles.container} ref={setRef}>
<ComboBox
aria-label={formatMessage({ id: "Select country of residence" })}
className={styles.select}
@@ -55,6 +67,13 @@ export default function CountrySelect({
selectedKey={field.value}
>
<div className={styles.comboBoxContainer}>
<Label
className={styles.label}
size="small"
required={!!registerOptions.required}
>
{label}
</Label>
<Body asChild fontOnly>
<Input
aria-label={selectCountryLabel}
@@ -73,13 +92,14 @@ export default function CountrySelect({
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={divRef.current ?? undefined}
UNSTABLE_portalContainer={rootDiv}
>
<ListBox>
{countries.map((country, idx) => (

View File

@@ -1,20 +1,9 @@
/* Leaving, will most likely get deleted */
.container {
--border: 2px solid var(--some-black-color, #757575);
--radius: 0.4rem;
--width: min(28rem, 100%);
--width-day: 6rem;
--width-month: 6rem;
--width-year: 8rem;
display: grid;
gap: 0.8rem;
grid-template-areas: "day month year";
grid-template-columns: min(--width-day, 1fr) min(--width-month, 1fr) min(
--width-year,
2fr
);
gap: var(--Spacing-x2);
grid-template-areas: "year month day";
grid-template-columns: 1fr 1fr 1fr;
width: var(--width);
}

View File

@@ -1,6 +1,4 @@
import type { Control, RegisterOptions } from "react-hook-form"
import type { EditProfileSchema } from "@/components/MyProfile/Profile/Edit/Form/schema"
import type { RegisterOptions } from "react-hook-form"
export const enum DateName {
date = "date",
@@ -9,7 +7,6 @@ export const enum DateName {
}
export interface DateProps
extends React.SelectHTMLAttributes<HTMLSelectElement> {
control: Control<EditProfileSchema>
name: keyof EditProfileSchema
registerOptions: RegisterOptions<EditProfileSchema>
name: string
registerOptions: RegisterOptions
}

View File

@@ -11,9 +11,9 @@ import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Select from "@/components/TempDesignSystem/Select"
import { rangeArray } from "@/utils/rangeArray"
import Select from "../Select"
import { DateName } from "./date"
import styles from "./date.module.css"
@@ -23,14 +23,10 @@ import type { Key } from "react-aria-components"
import type { DateProps } from "./date"
/** TODO: Get selecting with Enter-key to work */
export default function DateSelect({
control,
name,
registerOptions,
}: DateProps) {
export default function DateSelect({ name, registerOptions }: DateProps) {
const { formatMessage } = useIntl()
const d = useWatch({ name })
const { setValue } = useFormContext()
const { control, setValue } = useFormContext()
const { field } = useController({
control,
name,

View File

@@ -1,4 +1,7 @@
.message {
align-items: center;
color: var(--Scandic-Red-60);
margin: var(--Spacing-x-half) 0 0;
}
display: flex;
gap: var(--Spacing-x-half);
margin: var(--Spacing-x1) 0 0;
}

View File

@@ -1,6 +1,7 @@
import { ErrorMessage as RHFErrorMessage } from "@hookform/error-message"
import Body from "@/components/TempDesignSystem/Text/Body"
import { InfoCircleIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./error.module.css"
@@ -15,9 +16,10 @@ export default function ErrorMessage<T>({
errors={errors}
name={name}
render={({ message }) => (
<Body className={styles.message} fontOnly>
<Caption className={styles.message} fontOnly>
<InfoCircleIcon color="red" />
{message}
</Body>
</Caption>
)}
/>
)

View File

@@ -1,8 +1,13 @@
"use client"
import { Input as AriaInput, TextField } from "react-aria-components"
import { useController } from "react-hook-form"
import {
Input as AriaInput,
Label as AriaLabel,
TextField,
} from "react-aria-components"
import { useController, useFormContext } from "react-hook-form"
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./input.module.css"
@@ -11,17 +16,24 @@ import type { InputProps } from "./input"
export default function Input({
"aria-label": ariaLabel,
control,
disabled,
label,
name,
placeholder,
registerOptions,
placeholder = "",
registerOptions = {},
required = false,
type = "text",
}: InputProps) {
const { control } = useFormContext()
const rules = {
...registerOptions,
required:
"required" in registerOptions ? !!registerOptions.required : required,
}
const { field, fieldState, formState } = useController({
control,
name,
rules: registerOptions,
rules,
})
return (
@@ -32,18 +44,24 @@ export default function Input({
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
type={type}
>
<Body asChild fontOnly>
<AriaInput
className={styles.input}
placeholder={placeholder}
ref={field.ref}
/>
</Body>
<ErrorMessage errors={formState.errors} name={name} />
<AriaLabel className={styles.container} htmlFor={field.name}>
<Body asChild fontOnly>
<AriaInput
className={styles.input}
id={field.name}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
placeholder={placeholder}
ref={field.ref}
required={rules.required}
/>
</Body>
<Label required={rules.required}>{label}</Label>
</AriaLabel>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
)
}

View File

@@ -1,8 +1,44 @@
.input {
border: 2px solid var(--UI-Grey-60);
border-radius: var(--Corner-radius-Small);
color: var(--UI-Grey-60);
height: 40px;
.container {
align-content: center;
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
display: grid;
gap: var(--Spacing-x-half);
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
width: min(280px, 100%);
}
transition: border-color 200ms ease;
}
.container:has(.input:not(:focus):placeholder-shown) {
gap: 0;
grid-template-rows: 1fr;
}
.container:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
}
.input:focus,
.input:focus:placeholder-shown {
height: 18px;
outline: none;
}

View File

@@ -1,10 +1,8 @@
import { Control, RegisterOptions } from "react-hook-form"
import { EditProfileSchema } from "@/components/MyProfile/Profile/Edit/Form/schema"
import type { RegisterOptions } from "react-hook-form"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
control: Control<EditProfileSchema>
name: keyof EditProfileSchema
registerOptions?: RegisterOptions<EditProfileSchema>
label: string
name: string
registerOptions?: RegisterOptions
}

View File

@@ -0,0 +1,20 @@
import { labelVariants } from "./variants"
import type { LabelProps } from "./label"
export default function Label({
children,
className,
required,
size,
}: LabelProps) {
const classNames = labelVariants({
className,
size,
})
return (
<span className={classNames}>
{children} {required ? "*" : ""}
</span>
)
}

View File

@@ -0,0 +1,30 @@
.label {
color: var(--UI-Grey-60);
font-family: "fira sans";
font-weight: 400;
letter-spacing: 0.03px;
line-height: 120%;
text-align: left;
}
span.small {
display: block;
font-size: 12px;
}
span.regular {
font-size: 16px;
order: 1;
transition: font-size 200ms ease 100ms;
}
input:focus ~ .label,
input:not(:placeholder-shown) ~ .label {
display: block;
font-size: 12px;
}
input:placeholder-shown ~ .label {
align-self: center;
grid-row: 1/-1;
}

View File

@@ -0,0 +1,9 @@
import { labelVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
export interface LabelProps
extends React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>,
VariantProps<typeof labelVariants> {
required?: boolean
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./label.module.css"
export const labelVariants = cva(styles.label, {
variants: {
size: {
small: styles.small,
regular: styles.regular,
},
},
defaultVariants: {
size: "regular",
},
})

View File

@@ -1,99 +1,137 @@
"use client"
import "react-international-phone/style.css"
import { useCallback, useEffect, useRef } from "react"
import { parsePhoneNumber } from "libphonenumber-js"
import {
Input as AriaInput,
Label as AriaLabel,
TextField,
} from "react-aria-components"
import { useController, useFormContext, useWatch } from "react-hook-form"
import {
defaultCountries,
getCountry,
PhoneInput,
type PhoneInputRefType,
CountrySelector,
DialCodePreview,
ParsedCountry,
usePhoneInput,
} from "react-international-phone"
import { useIntl } from "react-intl"
import { ChevronDownIcon } from "@/components/Icons"
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./phone.module.css"
import type { ChangeEvent } from "react"
import type { PhoneProps } from "./phone"
export default function Phone({
countrySelectName = "country",
ariaLabel = "Phone number input",
disabled = false,
label,
name = "phoneNumber",
placeholder = "",
registerOptions = {
required: true,
},
}: PhoneProps) {
const phoneRef = useRef<PhoneInputRefType>(null)
const { control, formState } = useFormContext()
const countryValue = useWatch({ name: countrySelectName })
const defaultCountry = getCountry({
countries: defaultCountries,
field: "iso2",
value: String(countryValue).toLowerCase(),
})
/**
* Holds the previous selected country to be able to update
* countrycode based on country select field.
* Since PhoneInput inputs the countrys dialcode (country code) upon
* selection, we need to check if the current value is just
* the previously selected countrys dialcode number.
*/
const prevSelectedCountry = useRef<string | undefined>(countryValue)
const { field } = useController({
const { formatMessage } = useIntl()
const { control, setValue } = useFormContext()
const phone = useWatch({ name })
const { field, fieldState, formState } = useController({
control,
disabled,
name,
rules: registerOptions,
})
const handleCountrySelectForPhone = useCallback((country: string) => {
const selectedCountry = getCountry({
countries: defaultCountries,
field: "iso2",
value: country.toLowerCase(),
const { country, handlePhoneValueChange, inputValue, setCountry } =
usePhoneInput({
defaultCountry:
parsePhoneNumber(
formState.defaultValues?.phoneNumber
).country?.toLowerCase() ?? "sv",
disableCountryGuess: true,
forceDialCode: true,
value: phone,
})
if (selectedCountry) {
phoneRef.current?.setCountry(selectedCountry.iso2)
prevSelectedCountry.current = country.toLowerCase()
}
}, [])
function handleSelectCountry(value: ParsedCountry) {
setCountry(value.iso2)
}
useEffect(() => {
if (countryValue) {
if (field.value) {
if (prevSelectedCountry.current) {
if (prevSelectedCountry.current !== countryValue) {
const selectedCountryPrev = getCountry({
countries: defaultCountries,
field: "iso2",
value: prevSelectedCountry.current.toLowerCase(),
})
if (
field.value.replace("+", "") === selectedCountryPrev?.dialCode
) {
handleCountrySelectForPhone(countryValue)
}
}
} else {
handleCountrySelectForPhone(countryValue)
}
} else {
handleCountrySelectForPhone(countryValue)
}
}
}, [countryValue, field.value, handleCountrySelectForPhone])
function handleChange(evt: ChangeEvent<HTMLInputElement>) {
handlePhoneValueChange(evt)
setValue(name, evt.target.value)
}
return (
<div className={styles.phone}>
<PhoneInput
{...field}
className={styles.input}
defaultCountry={defaultCountry?.iso2 ?? "se"}
placeholder={placeholder}
<CountrySelector
dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag}
onSelect={handleSelectCountry}
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
ref={phoneRef}
selectedCountry={country.iso2}
renderButtonWrapper={(props) => (
<button
{...props.rootProps}
className={styles.select}
tabIndex={0}
type="button"
>
<Label required={!!registerOptions.required} size="small">
{formatMessage({ id: "Country code" })}
</Label>
<div 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}
/>
</div>
</button>
)}
/>
<TextField
aria-label={ariaLabel}
defaultValue={field.value}
isDisabled={disabled ?? field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
name={field.name}
type="tel"
>
<AriaLabel className={styles.inputContainer} htmlFor={field.name}>
<Body asChild fontOnly>
<AriaInput
className={styles.input}
id={field.name}
name={field.name}
onBlur={field.onBlur}
onChange={handleChange}
placeholder={placeholder}
ref={field.ref}
required={!!registerOptions.required}
value={inputValue}
/>
</Body>
<Label required={!!registerOptions.required}>{label}</Label>
</AriaLabel>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
<ErrorMessage errors={formState.errors} name={name} />
</div>
)

View File

@@ -1,26 +1,130 @@
.phone {
--react-international-phone-border-color: var(--UI-Grey-60);
--react-international-phone-border-radius: var(--Corner-radius-Small);
--react-international-phone-font-size: var(
--typography-Body-Regular-fontSize
);
--react-international-phone-height: 40px;
--react-international-phone-text-color: var(--UI-Grey-60);
}
.phone :global(.react-international-phone-input-container) {
display: grid;
/* r-i-p sets their width dynamically and doesn't respect the width property of its parent */
grid-template-columns: 470px minmax(203px, 1fr);
width: min(280px, 100%);
gap: var(--Spacing-x2);
grid-template-columns: max(164px) 1fr;
--react-international-phone-background-color: var(--Main-Grey-White);
--react-international-phone-border-color: var(--Scandic-Beige-40);
--react-international-phone-dropdown-preferred-list-divider-color: var(
--Scandic-Brand-Pale-Peach
);
--react-international-phone-selected-dropdown-item-background-color: var(
--Scandic-Blue-00
);
--react-international-phone-text-color: var(--Main-Grey-100);
--react-international-phone-dropdown-preferred-list-divider-margin: 8px;
--react-international-phone-height: 60px;
--react-international-phone-dropdown-top: calc(
var(--react-international-phone-height) + var(--Spacing-x1)
);
}
/* react-international-phone only exposes variables to change border-color */
.phone :global(.react-international-phone-country-selector-button),
.phone :global(.react-international-phone-input) {
border-width: 2px;
.phone:has(.input:active, .input:focus) {
--react-international-phone-border-color: var(--Scandic-Blue-90);
}
.phone :global(.react-international-phone-input) {
.phone :global(.react-international-phone-country-selector-dropdown) {
background: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Medium);
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.08);
gap: var(--Spacing-x1);
outline: none;
padding: var(--Spacing-x2);
}
.phone
:global(.react-international-phone-country-selector-dropdown__list-item) {
border-radius: var(--Corner-radius-Medium);
padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1)
var(--Spacing-x-one-and-half);
}
.phone
:global(.react-international-phone-country-selector-button__button-content) {
align-self: center;
}
.inputContainer,
.select {
align-content: center;
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
display: grid;
gap: var(--Spacing-x-half);
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
transition: border-color 200ms ease;
}
.select {
width: 100%;
}
.select[aria-expanded="true"] .chevron {
transform: rotate(180deg);
}
.selectContainer {
background-color: var(--Main-Grey-White);
border: none;
display: grid;
gap: var(--Spacing-x1);
grid-template-columns: auto 1fr auto;
height: 18px;
justify-content: flex-start;
order: 2;
}
.arrow {
display: none;
}
.flag {
height: 18px;
margin: 0;
width: 18px;
}
.select .dialCode {
border: none;
color: var(--Main-Grey-100);
line-height: 1;
justify-self: flex-start;
padding: 0;
}
.inputContainer:has(.input:not(:focus):placeholder-shown) {
gap: 0;
grid-template-rows: 1fr;
}
.inputContainer:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.input {
background: none;
border: none;
color: var(--Main-Grey-100);
height: 18px;
margin: 0;
order: 2;
overflow: visible;
padding: 0;
}
.input:not(:active, :focus):placeholder-shown {
height: 0px;
}
.input:focus,
.input:focus:placeholder-shown {
height: 18px;
outline: none;
}

View File

@@ -1,7 +1,9 @@
import type { RegisterOptions } from "react-hook-form"
export type PhoneProps = {
countrySelectName?: string
ariaLabel?: string
disabled?: boolean
label: string
name?: string
placeholder?: string
registerOptions?: RegisterOptions

View File

@@ -1,94 +1,35 @@
"use client"
import { useState } from "react"
import {
Button,
type Key,
Label,
ListBox,
ListBoxItem,
Popover,
Select as ReactAriaSelect,
SelectValue,
} from "react-aria-components"
import { useController, useFormContext } from "react-hook-form"
import Body from "../../Text/Body"
import Footnote from "../../Text/Footnote"
import SelectChevron from "../SelectChevron"
import ReactAriaSelect from "@/components/TempDesignSystem/Select"
import styles from "./select.module.css"
import type { SelectPortalContainer, SelectProps } from "./select"
import type { SelectProps } from "./select"
export default function Select({
"aria-label": ariaLabel,
items,
label,
onSelect,
name,
placeholder,
value,
defaultSelectedKey,
registerOptions = {},
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(null)
function setRef(node: SelectPortalContainer) {
if (node) {
setRootDiv(node)
}
}
function handleOnSelect(key: Key) {
onSelect(key)
}
const { control } = useFormContext()
const { field } = useController({
control,
name,
rules: registerOptions,
})
return (
<div className={styles.container} ref={setRef}>
<ReactAriaSelect
defaultSelectedKey={defaultSelectedKey}
aria-label={ariaLabel}
className={styles.select}
onSelectionChange={handleOnSelect}
placeholder={placeholder}
selectedKey={value as Key}
>
<Body asChild fontOnly>
<Button className={styles.input}>
<div className={styles.inputContentWrapper}>
<Footnote asChild fontOnly>
<Label className={styles.label}>{label}</Label>
</Footnote>
<SelectValue />
</div>
<SelectChevron />
</Button>
</Body>
<Body asChild fontOnly>
<Popover
className={styles.popover}
placement="bottom"
shouldFlip={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 ?? undefined}
>
<ListBox className={styles.listBox}>
{items.map((item) => (
<ListBoxItem
aria-label={String(item)}
className={styles.listBoxItem}
id={item.value}
key={item.label}
>
{item.label}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Body>
</ReactAriaSelect>
</div>
<ReactAriaSelect
defaultSelectedKey={field.value}
disabled={field.disabled}
items={items}
label={label}
name={field.name}
onBlur={field.onBlur}
onSelect={field.onChange}
placeholder={placeholder}
value={field.value}
/>
)
}

View File

@@ -1,72 +0,0 @@
.container {
position: relative;
}
.label {
color: var(--Base-Text-UI-Placeholder);
}
.select {
border: 1px solid var(--Base-Border-Normal);
border-radius: var(--Corner-radius-Medium);
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
}
.select[data-focused="true"] {
border: 1px solid var(--Scandic-Blue-90);
outline: none;
}
.input {
align-items: center;
background-color: var(--Scandic-Opacity-White-100);
border: none;
border-radius: var(--Corner-radius-Medium);
color: var(--Base-Text-UI-High-contrast);
display: flex;
gap: var(--Spacing-x-half);
height: 56px;
outline: none;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
text-align: left;
}
.inputContentWrapper {
align-items: flex-start;
display: flex;
flex-direction: column;
gap: var(--Spacing-x-half);
flex: 1 0 0;
}
.popover {
background-color: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Medium);
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.08);
display: inline-flex;
flex-direction: column;
gap: var(--Spacing-x1);
overflow: auto;
width: 100%;
}
.listBox {
display: flex;
flex-direction: column;
gap: var(--Spacing-x1);
}
.listBoxItem {
padding: var(--Spacing-x2);
}
.listBoxItem[data-focused="true"] {
background: var(--UI-Input-Controls-Surface-Hover, var(--Scandic-Blue-00));
outline: none;
}
.listBoxItem[data-selected="true"] {
font-weight: 500;
}

View File

@@ -1,13 +1,12 @@
import type { Key } from "react-aria-components"
import type { RegisterOptions } from "react-hook-form"
import type { SelectProps as ReactAriaSelectProps } from "@/components/TempDesignSystem/Select/select"
export interface SelectProps
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onSelect"> {
items: { label: string; value: Key }[]
label: string
name: string
onSelect: (key: Key) => void
placeholder?: string
defaultSelectedKey?: Key
extends Omit<
React.SelectHTMLAttributes<HTMLSelectElement>,
"name" | "onSelect"
>,
Omit<ReactAriaSelectProps, "onSelect" | "ref" | "value"> {
registerOptions?: RegisterOptions
}
export type SelectPortalContainer = HTMLDivElement | null

View File

@@ -5,7 +5,7 @@ import styles from "./chevron.module.css"
export default function SelectChevron() {
return (
<span aria-hidden="true" className={styles.chevron}>
<ChevronDownIcon height={24} width={24} />
<ChevronDownIcon color="grey80" />
</span>
)
}