feat(SW-1509): simplified date of birth component to work with new select

added animated labels to new select
This commit is contained in:
Christian Andolf
2025-04-14 10:56:17 +02:00
parent 57cd2f6a7f
commit e04342110a
4 changed files with 80 additions and 118 deletions

View File

@@ -1,35 +1,15 @@
.container {
display: grid;
display: flex;
gap: var(--Spacing-x2);
grid-template-areas: "year month day";
grid-template-columns: 1fr 1fr 1fr;
width: var(--width);
user-select: none;
}
@media (max-width: 350px) {
@media screen and (width < 400px) {
.container {
display: flex;
flex-direction: column;
}
}
.day {
grid-area: day;
}
.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);
.segment {
flex: 1;
}

View File

@@ -1,13 +1,13 @@
"use client"
import { parseDate } from "@internationalized/date"
import { useEffect } from "react"
import { DateInput, DatePicker, Group, type Key } from "react-aria-components"
import { useController, useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
import { Select } from "@scandic-hotels/design-system/Select"
import { dt } from "@/lib/dt"
import Select from "@/components/TempDesignSystem/Select"
import useLang from "@/hooks/useLang"
import { getLocalizedMonthName } from "@/utils/dateFormatting"
import { rangeArray } from "@/utils/rangeArray"
@@ -125,87 +125,45 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
}, [setValue, formState.isSubmitting, dateValue, day, month, year])
return (
<DatePicker
aria-label={intl.formatMessage({
defaultMessage: "Select date of birth",
})}
isRequired={!!registerOptions.required}
isInvalid={!formState.isValid}
name={name}
ref={field.ref}
value={dateValue}
data-testid={name}
>
<Group>
<DateInput className={styles.container}>
{(segment) => {
switch (segment.type) {
case "day":
return (
<div
className={`${styles.day} ${fieldState.invalid ? styles.invalid : ""}`}
>
<Select
aria-label={dayLabel}
items={days}
label={dayLabel}
name={DateName.day}
onSelect={(key: Key) =>
setValue(DateName.day, Number(key))
}
required
tabIndex={3}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)
case "month":
return (
<div
className={`${styles.month} ${fieldState.invalid ? styles.invalid : ""}`}
>
<Select
aria-label={monthLabel}
items={months}
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>
<>
<div className={styles.container}>
<div className={styles.segment}>
<Select
items={days}
label={dayLabel}
name={DateName.day}
onSelectionChange={(key) => setValue(DateName.day, Number(key))}
isInvalid={fieldState.invalid}
isRequired
enableFiltering
defaultSelectedKey={day}
/>
</div>
<div className={styles.segment}>
<Select
items={months}
label={monthLabel}
name={DateName.month}
onSelectionChange={(key) => setValue(DateName.month, Number(key))}
isRequired
enableFiltering
defaultSelectedKey={month}
/>
</div>
<div className={styles.segment}>
<Select
items={years}
label={yearLabel}
name={DateName.year}
onSelectionChange={(key) => setValue(DateName.year, Number(key))}
isRequired
enableFiltering
defaultSelectedKey={year}
/>
</div>
</div>
<ErrorMessage errors={formState.errors} name={field.name} />
</DatePicker>
</>
)
}

View File

@@ -15,6 +15,7 @@ import { SelectFilter } from './SelectFilter'
import type { SelectProps, SelectFilterProps } from './types'
import styles from './select.module.css'
import { useState } from 'react'
export function Select({
name,
@@ -25,6 +26,8 @@ export function Select({
itemIcon,
...props
}: SelectProps | SelectFilterProps) {
const [isOpen, setIsOpen] = useState(false)
if ('enableFiltering' in props) {
return (
<SelectFilter
@@ -45,6 +48,7 @@ export function Select({
name={name}
aria-label={label}
isDisabled={isDisabled}
onOpenChange={setIsOpen}
{...props}
>
<Button className={cx(styles.inner, styles.button)}>
@@ -62,18 +66,16 @@ export function Select({
<>
<Typography
variant={
selectedText
selectedText || isOpen
? 'Label/xsRegular'
: 'Body/Paragraph/mdRegular'
}
>
<span className={styles.label}>{label}</span>
</Typography>
{selectedText ? (
<Typography variant="Body/Paragraph/mdRegular">
<span>{selectedText}</span>
</Typography>
) : null}
<Typography variant="Body/Paragraph/mdRegular">
<span className={styles.selectedText}>{selectedText}</span>
</Typography>
</>
)
}}

View File

@@ -7,8 +7,14 @@
&[data-required] .label::after {
content: ' *';
}
&[data-open] .chevron {
rotate: -90deg;
&[data-open] {
.chevron {
rotate: -90deg;
}
.selectedText {
min-height: 18px;
}
}
&[data-focused] {
border-color: var(--Border-Interactive-Focus);
@@ -18,7 +24,7 @@
outline: none;
}
.input {
position: unset;
min-height: 18px;
}
.label {
color: var(--Text-Interactive-Focus);
@@ -66,14 +72,25 @@
}
.input {
position: absolute;
height: 0;
padding: 0;
width: 100%;
&[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 {
display: flex;
flex-direction: column;
@@ -81,6 +98,7 @@
flex: 1;
justify-content: center;
height: 100%;
position: relative;
&:has(.input) {
cursor: text;
@@ -94,6 +112,8 @@
.label {
color: var(--Text-Interactive-Placeholder);
white-space: nowrap;
transition: font-size 150ms ease;
}
.popover {
@@ -107,6 +127,8 @@
padding: var(--Space-x2);
outline: none;
min-width: 280px;
scrollbar-color: var(--Icon-Interactive-Disabled);
scrollbar-width: thin;
}
.listBox {