feat: add validation to enter details flow
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"use client"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { useEnterDetailsStore } from "@/stores/enter-details"
|
||||
|
||||
import { CheckIcon, ChevronDownIcon } from "@/components/Icons"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
|
||||
import styles from "./sectionAccordion.module.css"
|
||||
|
||||
import { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion"
|
||||
|
||||
export default function SectionAccordion({
|
||||
header,
|
||||
label,
|
||||
step,
|
||||
children,
|
||||
}: React.PropsWithChildren<SectionAccordionProps>) {
|
||||
const intl = useIntl()
|
||||
const [isComplete, setIsComplete] = useState(false)
|
||||
const currentStep = useEnterDetailsStore((state) => state.currentStep)
|
||||
const isValid = useEnterDetailsStore((state) => state.isValid[step])
|
||||
|
||||
const navigate = useEnterDetailsStore((state) => state.navigate)
|
||||
|
||||
const contentRef = useRef<HTMLDivElement>(null)
|
||||
const circleRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const isOpen = currentStep === step
|
||||
|
||||
useEffect(() => {
|
||||
const content = contentRef.current
|
||||
const circle = circleRef.current
|
||||
if (content) {
|
||||
if (isOpen) {
|
||||
content.style.maxHeight = `${content.scrollHeight}px`
|
||||
} else {
|
||||
content.style.maxHeight = "0"
|
||||
}
|
||||
}
|
||||
|
||||
if (circle) {
|
||||
if (isOpen) {
|
||||
circle.style.backgroundColor = `var(--UI-Text-Placeholder);`
|
||||
} else {
|
||||
circle.style.backgroundColor = `var(--Base-Surface-Subtle-Hover);`
|
||||
}
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
useEffect(() => {
|
||||
// We need to set the state on mount because of hydration errors
|
||||
setIsComplete(isValid)
|
||||
}, [isValid])
|
||||
|
||||
function onModify() {
|
||||
navigate(step)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.wrapper} data-open={isOpen}>
|
||||
<div className={styles.iconWrapper}>
|
||||
<div
|
||||
className={styles.circle}
|
||||
data-checked={isComplete}
|
||||
ref={circleRef}
|
||||
>
|
||||
{isComplete ? (
|
||||
<CheckIcon color="white" height="16" width="16" />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.main}>
|
||||
<header className={styles.headerContainer}>
|
||||
<div>
|
||||
<Footnote
|
||||
asChild
|
||||
textTransform="uppercase"
|
||||
color="uiTextPlaceholder"
|
||||
>
|
||||
<h2>{header}</h2>
|
||||
</Footnote>
|
||||
<Subtitle
|
||||
type="two"
|
||||
className={styles.selection}
|
||||
color="uiTextHighContrast"
|
||||
>
|
||||
{label}
|
||||
</Subtitle>
|
||||
</div>
|
||||
{isComplete && !isOpen && (
|
||||
<Button
|
||||
onClick={onModify}
|
||||
theme="base"
|
||||
size="small"
|
||||
variant="icon"
|
||||
intent="text"
|
||||
wrapping
|
||||
>
|
||||
{intl.formatMessage({ id: "Modify" })}{" "}
|
||||
<ChevronDownIcon color="burgundy" />
|
||||
</Button>
|
||||
)}
|
||||
</header>
|
||||
<div className={styles.content} ref={contentRef}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
.wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--Spacing-x3);
|
||||
|
||||
padding-top: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.wrapper:not(:last-child)::after {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
bottom: 0;
|
||||
top: var(--Spacing-x5);
|
||||
height: 100%;
|
||||
content: "";
|
||||
border-left: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
}
|
||||
|
||||
.wrapper:last-child .main {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x3);
|
||||
width: 100%;
|
||||
border-bottom: 1px solid var(--Primary-Light-On-Surface-Divider-subtle);
|
||||
padding-bottom: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.headerContainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.selection {
|
||||
font-weight: 450;
|
||||
font-size: var(--typography-Title-4-fontSize);
|
||||
}
|
||||
|
||||
.iconWrapper {
|
||||
position: relative;
|
||||
top: var(--Spacing-x1);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 100px;
|
||||
transition: background-color 0.4s;
|
||||
border: 2px solid var(--Base-Border-Inverted);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.circle[data-checked="true"] {
|
||||
background-color: var(--UI-Input-Controls-Fill-Selected);
|
||||
}
|
||||
|
||||
.wrapper[data-open="true"] .circle[data-checked="false"] {
|
||||
background-color: var(--UI-Text-Placeholder);
|
||||
}
|
||||
|
||||
.wrapper[data-open="false"] .circle[data-checked="false"] {
|
||||
background-color: var(--Base-Surface-Subtle-Hover);
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s ease-out;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.wrapper[data-open="true"] .content {
|
||||
max-height: 1000px;
|
||||
}
|
||||
Reference in New Issue
Block a user