Merged in feat/SW-1509-new-select-usage-in-forms (pull request #1787)
feat(SW-1509): simplified date of birth component to work with new select Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -1,35 +1,15 @@
|
|||||||
.container {
|
.container {
|
||||||
display: grid;
|
display: flex;
|
||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
grid-template-areas: "year month day";
|
user-select: none;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
width: var(--width);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 350px) {
|
@media screen and (width < 400px) {
|
||||||
.container {
|
.container {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.day {
|
.segment {
|
||||||
grid-area: day;
|
flex: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.month {
|
|
||||||
grid-area: month;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year {
|
|
||||||
grid-area: year;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Handle this in Select component.
|
|
||||||
- out of scope for now.
|
|
||||||
*/
|
|
||||||
.day.invalid > div > div,
|
|
||||||
.month.invalid > div > div,
|
|
||||||
.year.invalid > div > div {
|
|
||||||
border-color: var(--Scandic-Red-60);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { parseDate } from "@internationalized/date"
|
import { parseDate } from "@internationalized/date"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { DateInput, DatePicker, Group, type Key } from "react-aria-components"
|
|
||||||
import { useController, useFormContext, useWatch } from "react-hook-form"
|
import { useController, useFormContext, useWatch } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Select } from "@scandic-hotels/design-system/Select"
|
||||||
|
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
import Select from "@/components/TempDesignSystem/Select"
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { getLocalizedMonthName } from "@/utils/dateFormatting"
|
import { getLocalizedMonthName } from "@/utils/dateFormatting"
|
||||||
import { rangeArray } from "@/utils/rangeArray"
|
import { rangeArray } from "@/utils/rangeArray"
|
||||||
@@ -22,13 +22,13 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
|
|||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
const { control, setValue, formState, watch } = useFormContext()
|
const { control, setValue, formState, watch } = useFormContext()
|
||||||
const { field, fieldState } = useController({
|
const { field } = useController({
|
||||||
control,
|
control,
|
||||||
name,
|
name,
|
||||||
rules: registerOptions,
|
rules: registerOptions,
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentDateValue = useWatch({ name })
|
const currentDateValue: string = useWatch({ name })
|
||||||
const year = watch(DateName.year)
|
const year = watch(DateName.year)
|
||||||
const month = watch(DateName.month)
|
const month = watch(DateName.month)
|
||||||
const day = watch(DateName.day)
|
const day = watch(DateName.day)
|
||||||
@@ -61,16 +61,6 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
|
|||||||
label: `${day}`,
|
label: `${day}`,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const dayLabel = intl.formatMessage({
|
|
||||||
defaultMessage: "Day",
|
|
||||||
})
|
|
||||||
const monthLabel = intl.formatMessage({
|
|
||||||
defaultMessage: "Month",
|
|
||||||
})
|
|
||||||
const yearLabel = intl.formatMessage({
|
|
||||||
defaultMessage: "Year",
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formState.isSubmitting) return
|
if (formState.isSubmitting) return
|
||||||
|
|
||||||
@@ -125,87 +115,50 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
|
|||||||
}, [setValue, formState.isSubmitting, dateValue, day, month, year])
|
}, [setValue, formState.isSubmitting, dateValue, day, month, year])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatePicker
|
<>
|
||||||
aria-label={intl.formatMessage({
|
<div className={styles.container}>
|
||||||
defaultMessage: "Select date of birth",
|
<div className={styles.segment}>
|
||||||
})}
|
<Select
|
||||||
isRequired={!!registerOptions.required}
|
items={days}
|
||||||
isInvalid={!formState.isValid}
|
label={intl.formatMessage({
|
||||||
name={name}
|
defaultMessage: "Day",
|
||||||
ref={field.ref}
|
})}
|
||||||
value={dateValue}
|
name={DateName.day}
|
||||||
data-testid={name}
|
onSelectionChange={(key) => setValue(DateName.day, Number(key))}
|
||||||
>
|
isRequired
|
||||||
<Group>
|
enableFiltering
|
||||||
<DateInput className={styles.container}>
|
defaultSelectedKey={dateValue?.day}
|
||||||
{(segment) => {
|
/>
|
||||||
switch (segment.type) {
|
</div>
|
||||||
case "day":
|
<div className={styles.segment}>
|
||||||
return (
|
<Select
|
||||||
<div
|
items={months}
|
||||||
className={`${styles.day} ${fieldState.invalid ? styles.invalid : ""}`}
|
label={intl.formatMessage({
|
||||||
>
|
defaultMessage: "Month",
|
||||||
<Select
|
})}
|
||||||
aria-label={dayLabel}
|
name={DateName.month}
|
||||||
items={days}
|
onSelectionChange={(key) => setValue(DateName.month, Number(key))}
|
||||||
label={dayLabel}
|
isRequired
|
||||||
name={DateName.day}
|
enableFiltering
|
||||||
onSelect={(key: Key) =>
|
defaultSelectedKey={dateValue?.month}
|
||||||
setValue(DateName.day, Number(key))
|
/>
|
||||||
}
|
</div>
|
||||||
required
|
<div className={styles.segment}>
|
||||||
tabIndex={3}
|
<Select
|
||||||
value={segment.isPlaceholder ? undefined : segment.value}
|
items={years}
|
||||||
/>
|
label={intl.formatMessage({
|
||||||
</div>
|
defaultMessage: "Year",
|
||||||
)
|
})}
|
||||||
case "month":
|
name={DateName.year}
|
||||||
return (
|
onSelectionChange={(key) => setValue(DateName.year, Number(key))}
|
||||||
<div
|
isRequired
|
||||||
className={`${styles.month} ${fieldState.invalid ? styles.invalid : ""}`}
|
enableFiltering
|
||||||
>
|
defaultSelectedKey={dateValue?.year}
|
||||||
<Select
|
/>
|
||||||
aria-label={monthLabel}
|
</div>
|
||||||
items={months}
|
</div>
|
||||||
label={monthLabel}
|
|
||||||
name={DateName.month}
|
|
||||||
onSelect={(key: Key) =>
|
|
||||||
setValue(DateName.month, Number(key))
|
|
||||||
}
|
|
||||||
required
|
|
||||||
tabIndex={2}
|
|
||||||
value={segment.isPlaceholder ? undefined : segment.value}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
case "year":
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`${styles.year} ${fieldState.invalid ? styles.invalid : ""}`}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
aria-label={yearLabel}
|
|
||||||
items={years}
|
|
||||||
label={yearLabel}
|
|
||||||
name={DateName.year}
|
|
||||||
onSelect={(key: Key) =>
|
|
||||||
setValue(DateName.year, Number(key))
|
|
||||||
}
|
|
||||||
required
|
|
||||||
tabIndex={1}
|
|
||||||
value={segment.isPlaceholder ? undefined : segment.value}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
/** DateInput forces return of ReactElement */
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
</DateInput>
|
|
||||||
</Group>
|
|
||||||
<ErrorMessage errors={formState.errors} name={field.name} />
|
<ErrorMessage errors={formState.errors} name={field.name} />
|
||||||
</DatePicker>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { SelectFilter } from './SelectFilter'
|
|||||||
import type { SelectProps, SelectFilterProps } from './types'
|
import type { SelectProps, SelectFilterProps } from './types'
|
||||||
|
|
||||||
import styles from './select.module.css'
|
import styles from './select.module.css'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
export function Select({
|
export function Select({
|
||||||
name,
|
name,
|
||||||
@@ -25,6 +26,8 @@ export function Select({
|
|||||||
itemIcon,
|
itemIcon,
|
||||||
...props
|
...props
|
||||||
}: SelectProps | SelectFilterProps) {
|
}: SelectProps | SelectFilterProps) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
if ('enableFiltering' in props) {
|
if ('enableFiltering' in props) {
|
||||||
return (
|
return (
|
||||||
<SelectFilter
|
<SelectFilter
|
||||||
@@ -45,6 +48,7 @@ export function Select({
|
|||||||
name={name}
|
name={name}
|
||||||
aria-label={label}
|
aria-label={label}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
onOpenChange={setIsOpen}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<Button className={cx(styles.inner, styles.button)}>
|
<Button className={cx(styles.inner, styles.button)}>
|
||||||
@@ -62,18 +66,16 @@ export function Select({
|
|||||||
<>
|
<>
|
||||||
<Typography
|
<Typography
|
||||||
variant={
|
variant={
|
||||||
selectedText
|
selectedText || isOpen
|
||||||
? 'Label/xsRegular'
|
? 'Label/xsRegular'
|
||||||
: 'Body/Paragraph/mdRegular'
|
: 'Body/Paragraph/mdRegular'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className={styles.label}>{label}</span>
|
<span className={styles.label}>{label}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
{selectedText ? (
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
<Typography variant="Body/Paragraph/mdRegular">
|
<span className={styles.selectedText}>{selectedText}</span>
|
||||||
<span>{selectedText}</span>
|
</Typography>
|
||||||
</Typography>
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,8 +7,14 @@
|
|||||||
&[data-required] .label::after {
|
&[data-required] .label::after {
|
||||||
content: ' *';
|
content: ' *';
|
||||||
}
|
}
|
||||||
&[data-open] .chevron {
|
&[data-open] {
|
||||||
rotate: -90deg;
|
.chevron {
|
||||||
|
rotate: -90deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedText {
|
||||||
|
min-height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&[data-focused] {
|
&[data-focused] {
|
||||||
border-color: var(--Border-Interactive-Focus);
|
border-color: var(--Border-Interactive-Focus);
|
||||||
@@ -18,7 +24,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.input {
|
.input {
|
||||||
position: unset;
|
min-height: 18px;
|
||||||
}
|
}
|
||||||
.label {
|
.label {
|
||||||
color: var(--Text-Interactive-Focus);
|
color: var(--Text-Interactive-Focus);
|
||||||
@@ -66,14 +72,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
position: absolute;
|
height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
&[value]:not([value='']) {
|
&[value]:not([value='']) {
|
||||||
position: unset;
|
min-height: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.selectedText {
|
||||||
|
min-height: 0;
|
||||||
|
transition: min-height 150ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedText:not(:empty) {
|
||||||
|
min-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
.displayText {
|
.displayText {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -81,6 +98,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:has(.input) {
|
&:has(.input) {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
@@ -94,6 +112,8 @@
|
|||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: var(--Text-Interactive-Placeholder);
|
color: var(--Text-Interactive-Placeholder);
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: font-size 150ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.popover {
|
.popover {
|
||||||
@@ -107,6 +127,8 @@
|
|||||||
padding: var(--Space-x2);
|
padding: var(--Space-x2);
|
||||||
outline: none;
|
outline: none;
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
|
scrollbar-color: var(--Icon-Interactive-Disabled);
|
||||||
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.listBox {
|
.listBox {
|
||||||
|
|||||||
Reference in New Issue
Block a user