Merge branch 'develop'

This commit is contained in:
Linus Flood
2024-11-07 10:20:12 +01:00
261 changed files with 5132 additions and 2106 deletions
@@ -33,12 +33,12 @@ export default function Checkbox({
>
{({ isSelected }) => (
<>
<div className={styles.checkboxContainer}>
<div className={styles.checkbox}>
<span className={styles.checkboxContainer}>
<span className={styles.checkbox}>
{isSelected && <CheckIcon color="white" />}
</div>
</span>
{children}
</div>
</span>
{fieldState.error ? (
<Caption className={styles.error} fontOnly>
<InfoCircleIcon color="red" />
@@ -26,7 +26,12 @@ interface TextCardProps extends BaseCardProps {
text: React.ReactNode
}
export type CardProps = ListCardProps | TextCardProps
interface CleanCardProps extends BaseCardProps {
list?: never
text?: never
}
export type CardProps = ListCardProps | TextCardProps | CleanCardProps
export type CheckboxProps =
| Omit<ListCardProps, "type">
@@ -34,6 +39,7 @@ export type CheckboxProps =
export type RadioProps =
| Omit<ListCardProps, "type">
| Omit<TextCardProps, "type">
| Omit<CleanCardProps, "type">
export interface ListProps extends Pick<ListCardProps, "declined"> {
list?: ListCardProps["list"]
@@ -18,3 +18,12 @@
.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);
}
@@ -2,6 +2,7 @@ import type { RegisterOptions } from "react-hook-form"
export const enum DateName {
date = "date",
day = "day",
month = "month",
year = "year",
}
+91 -43
View File
@@ -1,6 +1,6 @@
"use client"
import { parseDate } from "@internationalized/date"
import { useState } from "react"
import { useEffect } from "react"
import { DateInput, DatePicker, Group } from "react-aria-components"
import { useController, useFormContext, useWatch } from "react-hook-form"
import { useIntl } from "react-intl"
@@ -8,8 +8,11 @@ import { useIntl } from "react-intl"
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"
import ErrorMessage from "../ErrorMessage"
import { DateName } from "./date"
import styles from "./date.module.css"
@@ -20,51 +23,75 @@ import type { DateProps } from "./date"
export default function DateSelect({ name, registerOptions = {} }: DateProps) {
const intl = useIntl()
const currentValue = useWatch({ name })
const { control, setValue, trigger } = useFormContext()
const { field } = useController({
const { control, setValue, formState, watch } = useFormContext()
const { field, fieldState } = useController({
control,
name,
rules: registerOptions,
})
const currentYear = new Date().getFullYear()
const currentDateValue = useWatch({ name })
const year = watch(DateName.year)
const month = watch(DateName.month)
const day = watch(DateName.day)
const lang = useLang()
const months = rangeArray(1, 12).map((month) => ({
value: month,
label: `${month}`,
label: getLocalizedMonthName(month, lang),
}))
const currentYear = new Date().getFullYear()
const years = rangeArray(1900, currentYear - 18)
.reverse()
.map((year) => ({ value: year, label: year.toString() }))
// Ensure the user can't select a date that doesn't exist.
const daysInMonth = dt(currentValue).daysInMonth()
// Calculate available days based on selected year and month
const daysInMonth = getDaysInMonth(
year ? Number(year) : null,
month ? Number(month) - 1 : null
)
const days = rangeArray(1, daysInMonth).map((day) => ({
value: day,
label: `${day}`,
}))
function createOnSelect(selector: DateName) {
/**
* Months are 0 index based and therefore we
* must subtract by 1 to get the selected month
*/
return (select: Key) => {
if (selector === DateName.month) {
select = Number(select) - 1
}
const newDate = dt(currentValue).set(selector, Number(select))
setValue(name, newDate.format("YYYY-MM-DD"))
trigger(name)
}
}
const dayLabel = intl.formatMessage({ id: "Day" })
const monthLabel = intl.formatMessage({ id: "Month" })
const yearLabel = intl.formatMessage({ id: "Year" })
useEffect(() => {
if (formState.isSubmitting) return
if (month && day) {
const maxDays = getDaysInMonth(
year ? Number(year) : null,
Number(month) - 1
)
const adjustedDay = Number(day) > maxDays ? maxDays : Number(day)
if (adjustedDay !== Number(day)) {
setValue(DateName.day, adjustedDay)
}
}
if (year && month && day) {
const newDate = dt()
.year(Number(year))
.month(Number(month) - 1)
.date(Number(day))
if (newDate.isValid()) {
setValue(name, newDate.format("YYYY-MM-DD"), {
shouldDirty: true,
shouldTouch: true,
shouldValidate: true,
})
}
}
}, [year, month, day, setValue, name, formState.isSubmitting])
let dateValue = null
try {
/**
@@ -72,7 +99,9 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
* date, but we can't check isNan since
* we recieve the date as "1999-01-01"
*/
dateValue = dt(currentValue).isValid() ? parseDate(currentValue) : null
dateValue = dt(currentDateValue).isValid()
? parseDate(currentDateValue)
: null
} catch (error) {
console.warn("Known error for parse date in DateSelect: ", error)
}
@@ -81,6 +110,7 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
<DatePicker
aria-label={intl.formatMessage({ id: "Select date of birth" })}
isRequired={!!registerOptions.required}
isInvalid={!formState.isValid}
name={name}
ref={field.ref}
value={dateValue}
@@ -92,57 +122,60 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
switch (segment.type) {
case "day":
return (
<div className={styles.day}>
<div
className={`${styles.day} ${fieldState.invalid ? styles.invalid : ""}`}
>
<Select
aria-label={dayLabel}
items={days}
label={dayLabel}
name={DateName.date}
onSelect={createOnSelect(DateName.date)}
placeholder="DD"
name={DateName.day}
onSelect={(key: Key) =>
setValue(DateName.day, Number(key))
}
placeholder={dayLabel}
required
tabIndex={3}
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)
case "month":
return (
<div className={styles.month}>
<div
className={`${styles.month} ${fieldState.invalid ? styles.invalid : ""}`}
>
<Select
aria-label={monthLabel}
items={months}
label={monthLabel}
name={DateName.month}
onSelect={createOnSelect(DateName.month)}
placeholder="MM"
onSelect={(key: Key) =>
setValue(DateName.month, Number(key))
}
placeholder={monthLabel}
required
tabIndex={2}
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
)
case "year":
return (
<div className={styles.year}>
<div
className={`${styles.year} ${fieldState.invalid ? styles.invalid : ""}`}
>
<Select
aria-label={yearLabel}
items={years}
label={yearLabel}
name={DateName.year}
onSelect={createOnSelect(DateName.year)}
placeholder="YYYY"
onSelect={(key: Key) =>
setValue(DateName.year, Number(key))
}
placeholder={yearLabel}
required
tabIndex={1}
defaultSelectedKey={
segment.isPlaceholder ? undefined : segment.value
}
value={segment.isPlaceholder ? undefined : segment.value}
/>
</div>
@@ -154,6 +187,21 @@ export default function DateSelect({ name, registerOptions = {} }: DateProps) {
}}
</DateInput>
</Group>
<ErrorMessage errors={formState.errors} name={field.name} />
</DatePicker>
)
}
function getDaysInMonth(year: number | null, month: number | null): number {
if (month === null) {
return 31
}
// If month is February and no year selected, return minimum.
if (month === 1 && !year) {
return 28
}
const yearToUse = year ?? new Date().getFullYear()
return dt(`${yearToUse}-${month + 1}-01`).daysInMonth()
}
@@ -58,8 +58,14 @@ export default function Phone({
forceDialCode: true,
value: phone,
onChange: (value) => {
setValue(name, value.phone)
trigger(name)
// 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, "")
}
},
})