fix: refactor GuestRoomsPicker to avoid performance bugs
This commit is contained in:
committed by
Christel Westerberg
parent
3e62df27cc
commit
58678244fc
130
components/GuestsRoomsPicker/Dialog.tsx
Normal file
130
components/GuestsRoomsPicker/Dialog.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
"use client"
|
||||||
|
import { Dialog } from "react-aria-components"
|
||||||
|
import { useFormContext } from "react-hook-form"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
|
||||||
|
|
||||||
|
import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
|
||||||
|
import Button from "../TempDesignSystem/Button"
|
||||||
|
import Divider from "../TempDesignSystem/Divider"
|
||||||
|
import Subtitle from "../TempDesignSystem/Text/Subtitle"
|
||||||
|
import { Tooltip } from "../TempDesignSystem/Tooltip"
|
||||||
|
import AdultSelector from "./AdultSelector"
|
||||||
|
import ChildSelector from "./ChildSelector"
|
||||||
|
|
||||||
|
import styles from "./guests-rooms-picker.module.css"
|
||||||
|
|
||||||
|
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
|
||||||
|
|
||||||
|
export default function GuestsRoomsPickerDialog() {
|
||||||
|
const intl = useIntl()
|
||||||
|
const doneLabel = intl.formatMessage({ id: "Done" })
|
||||||
|
const roomLabel = intl.formatMessage({ id: "Room" })
|
||||||
|
const disabledBookingOptionsHeader = intl.formatMessage({
|
||||||
|
id: "Disabled booking options header",
|
||||||
|
})
|
||||||
|
const disabledBookingOptionsText = intl.formatMessage({
|
||||||
|
id: "Disabled adding room",
|
||||||
|
})
|
||||||
|
const addRoomLabel = intl.formatMessage({ id: "Add Room" })
|
||||||
|
|
||||||
|
const { getFieldState } = useFormContext<BookingWidgetSchema>()
|
||||||
|
|
||||||
|
const rooms = useGuestsRoomsStore((state) => state.rooms)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog className={styles.pickerContainer}>
|
||||||
|
{({ close }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<button type="button" className={styles.close}>
|
||||||
|
<CloseLargeIcon />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
<div className={styles.contentContainer}>
|
||||||
|
{rooms.map((room, index) => (
|
||||||
|
<div className={styles.roomContainer} key={index}>
|
||||||
|
<section className={styles.roomDetailsContainer}>
|
||||||
|
<Subtitle type="two" className={styles.roomHeading}>
|
||||||
|
{roomLabel} {index + 1}
|
||||||
|
</Subtitle>
|
||||||
|
<AdultSelector roomIndex={index} />
|
||||||
|
<ChildSelector roomIndex={index} />
|
||||||
|
</section>
|
||||||
|
<Divider color="primaryLightSubtle" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className={styles.addRoomMobileContainer}>
|
||||||
|
<Tooltip
|
||||||
|
heading={disabledBookingOptionsHeader}
|
||||||
|
text={disabledBookingOptionsText}
|
||||||
|
position="top"
|
||||||
|
arrow="left"
|
||||||
|
>
|
||||||
|
{rooms.length < 4 ? (
|
||||||
|
<Button
|
||||||
|
intent="text"
|
||||||
|
variant="icon"
|
||||||
|
wrapping
|
||||||
|
disabled
|
||||||
|
theme="base"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
{addRoomLabel}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer className={styles.footer}>
|
||||||
|
<div className={styles.hideOnMobile}>
|
||||||
|
<Tooltip
|
||||||
|
heading={disabledBookingOptionsHeader}
|
||||||
|
text={disabledBookingOptionsText}
|
||||||
|
position="top"
|
||||||
|
arrow="left"
|
||||||
|
>
|
||||||
|
{rooms.length < 4 ? (
|
||||||
|
<Button
|
||||||
|
intent="text"
|
||||||
|
variant="icon"
|
||||||
|
wrapping
|
||||||
|
disabled
|
||||||
|
theme="base"
|
||||||
|
>
|
||||||
|
<PlusCircleIcon />
|
||||||
|
{addRoomLabel}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onPress={close}
|
||||||
|
disabled={getFieldState("rooms").invalid}
|
||||||
|
className={styles.hideOnMobile}
|
||||||
|
intent="tertiary"
|
||||||
|
theme="base"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{doneLabel}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onPress={close}
|
||||||
|
disabled={getFieldState("rooms").invalid}
|
||||||
|
className={styles.hideOnDesktop}
|
||||||
|
intent="tertiary"
|
||||||
|
theme="base"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{doneLabel}
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import { useFormContext } from "react-hook-form"
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { useGuestsRoomsStore } from "@/stores/guests-rooms"
|
|
||||||
|
|
||||||
import { CloseLargeIcon, PlusCircleIcon, PlusIcon } from "../Icons"
|
|
||||||
import Button from "../TempDesignSystem/Button"
|
|
||||||
import Divider from "../TempDesignSystem/Divider"
|
|
||||||
import Subtitle from "../TempDesignSystem/Text/Subtitle"
|
|
||||||
import { Tooltip } from "../TempDesignSystem/Tooltip"
|
|
||||||
import AdultSelector from "./AdultSelector"
|
|
||||||
import ChildSelector from "./ChildSelector"
|
|
||||||
|
|
||||||
import styles from "./guests-rooms-picker.module.css"
|
|
||||||
|
|
||||||
import { BookingWidgetSchema } from "@/types/components/bookingWidget"
|
|
||||||
import { GuestsRoomsPickerProps } from "@/types/components/bookingWidget/guestsRoomsPicker"
|
|
||||||
|
|
||||||
export default function GuestsRoomsPicker({
|
|
||||||
closePicker,
|
|
||||||
}: GuestsRoomsPickerProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
const doneLabel = intl.formatMessage({ id: "Done" })
|
|
||||||
const roomLabel = intl.formatMessage({ id: "Room" })
|
|
||||||
const disabledBookingOptionsHeader = intl.formatMessage({
|
|
||||||
id: "Disabled booking options header",
|
|
||||||
})
|
|
||||||
const disabledBookingOptionsText = intl.formatMessage({
|
|
||||||
id: "Disabled adding room",
|
|
||||||
})
|
|
||||||
const addRoomLabel = intl.formatMessage({ id: "Add Room" })
|
|
||||||
|
|
||||||
const { getFieldState } = useFormContext<BookingWidgetSchema>()
|
|
||||||
|
|
||||||
const rooms = useGuestsRoomsStore((state) => state.rooms)
|
|
||||||
|
|
||||||
// Not in MVP
|
|
||||||
// const increaseRoom = useGuestsRoomsStore.use.increaseRoom()
|
|
||||||
// const decreaseRoom = useGuestsRoomsStore.use.decreaseRoom()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.pickerContainer}>
|
|
||||||
<header className={styles.header}>
|
|
||||||
<button type="button" className={styles.close} onClick={closePicker}>
|
|
||||||
<CloseLargeIcon />
|
|
||||||
</button>
|
|
||||||
</header>
|
|
||||||
<div className={styles.contentContainer}>
|
|
||||||
{rooms.map((room, index) => (
|
|
||||||
<div className={styles.roomContainer} key={index}>
|
|
||||||
<section className={styles.roomDetailsContainer}>
|
|
||||||
<Subtitle type="two" className={styles.roomHeading}>
|
|
||||||
{roomLabel} {index + 1}
|
|
||||||
</Subtitle>
|
|
||||||
<AdultSelector roomIndex={index} />
|
|
||||||
<ChildSelector roomIndex={index} />
|
|
||||||
</section>
|
|
||||||
{/* Not in MVP
|
|
||||||
{index > 0 ? (
|
|
||||||
<Button intent="text" onClick={() => decreaseRoom(index)}>
|
|
||||||
Remove Room
|
|
||||||
</Button>
|
|
||||||
) : null} */}
|
|
||||||
<Divider color="primaryLightSubtle" />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className={styles.addRoomMobileContainer}>
|
|
||||||
<Tooltip
|
|
||||||
heading={disabledBookingOptionsHeader}
|
|
||||||
text={disabledBookingOptionsText}
|
|
||||||
position="top"
|
|
||||||
arrow="left"
|
|
||||||
>
|
|
||||||
{rooms.length < 4 ? (
|
|
||||||
<Button
|
|
||||||
intent="text"
|
|
||||||
variant="icon"
|
|
||||||
wrapping
|
|
||||||
disabled
|
|
||||||
theme="base"
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
{addRoomLabel}
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<footer className={styles.footer}>
|
|
||||||
<div className={styles.hideOnMobile}>
|
|
||||||
<Tooltip
|
|
||||||
heading={disabledBookingOptionsHeader}
|
|
||||||
text={disabledBookingOptionsText}
|
|
||||||
position="top"
|
|
||||||
arrow="left"
|
|
||||||
>
|
|
||||||
{rooms.length < 4 ? (
|
|
||||||
<Button
|
|
||||||
intent="text"
|
|
||||||
variant="icon"
|
|
||||||
wrapping
|
|
||||||
disabled
|
|
||||||
theme="base"
|
|
||||||
>
|
|
||||||
<PlusCircleIcon />
|
|
||||||
{addRoomLabel}
|
|
||||||
</Button>
|
|
||||||
) : null}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={closePicker}
|
|
||||||
disabled={getFieldState("rooms").invalid}
|
|
||||||
className={styles.hideOnMobile}
|
|
||||||
intent="tertiary"
|
|
||||||
theme="base"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
{doneLabel}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={closePicker}
|
|
||||||
disabled={getFieldState("rooms").invalid}
|
|
||||||
className={styles.hideOnDesktop}
|
|
||||||
intent="tertiary"
|
|
||||||
theme="base"
|
|
||||||
size="large"
|
|
||||||
>
|
|
||||||
{doneLabel}
|
|
||||||
</Button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
.container {
|
.container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
&[data-isopen="true"] {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.roomContainer {
|
.roomContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -14,9 +11,6 @@
|
|||||||
gap: var(--Spacing-x2);
|
gap: var(--Spacing-x2);
|
||||||
padding-bottom: var(--Spacing-x1);
|
padding-bottom: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
.hideWrapper {
|
|
||||||
background-color: var(--Main-Grey-White);
|
|
||||||
}
|
|
||||||
.roomHeading {
|
.roomHeading {
|
||||||
margin-bottom: var(--Spacing-x1);
|
margin-bottom: var(--Spacing-x1);
|
||||||
}
|
}
|
||||||
@@ -39,33 +33,25 @@
|
|||||||
margin-top: var(--Spacing-x2);
|
margin-top: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1366px) {
|
.pickerContainer {
|
||||||
.hideWrapper {
|
--header-height: 72px;
|
||||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
--sticky-button-height: 140px;
|
||||||
bottom: 0;
|
background-color: var(--Main-Grey-White);
|
||||||
left: 0;
|
display: grid;
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
top: 100%;
|
|
||||||
transition: top 300ms ease;
|
|
||||||
z-index: 10002;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||||
|
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 1366px) {
|
||||||
.container[data-isopen="true"] .hideWrapper {
|
.container[data-isopen="true"] .hideWrapper {
|
||||||
top: 20px;
|
top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pickerContainer {
|
|
||||||
--header-height: 72px;
|
|
||||||
--sticky-button-height: 140px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-areas:
|
|
||||||
"header"
|
|
||||||
"content";
|
|
||||||
grid-template-rows: var(--header-height) calc(100dvh - var(--header-height));
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.contentContainer {
|
.contentContainer {
|
||||||
grid-area: content;
|
grid-area: content;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@@ -121,19 +107,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.hideWrapper {
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
|
||||||
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
left: calc((var(--Spacing-x1) + var(--Spacing-x2)) * -1);
|
|
||||||
max-width: calc(100vw - 20px);
|
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + var(--Spacing-x2) + 1px + var(--Spacing-x4));
|
|
||||||
width: 360px;
|
|
||||||
max-height: calc(100dvh - 77px - var(--Spacing-x6));
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react"
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
import { Button, DialogTrigger, Popover } from "react-aria-components"
|
||||||
import { useFormContext } from "react-hook-form"
|
import { useFormContext } from "react-hook-form"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ import { useGuestsRoomsStore } from "@/stores/guests-rooms"
|
|||||||
import { guestRoomsSchema } from "@/components/Forms/BookingWidget/schema"
|
import { guestRoomsSchema } from "@/components/Forms/BookingWidget/schema"
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
import GuestsRoomsPicker from "./GuestsRoomsPicker"
|
import Dialog from "./Dialog"
|
||||||
|
|
||||||
import styles from "./guests-rooms-picker.module.css"
|
import styles from "./guests-rooms-picker.module.css"
|
||||||
|
|
||||||
@@ -19,47 +20,16 @@ export default function GuestsRoomsPickerForm({
|
|||||||
name: string
|
name: string
|
||||||
}) {
|
}) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
|
||||||
const { setValue } = useFormContext()
|
|
||||||
const { rooms, adultCount, childCount, setIsValidated } = useGuestsRoomsStore(
|
|
||||||
(state) => ({
|
|
||||||
rooms: state.rooms,
|
|
||||||
adultCount: state.adultCount,
|
|
||||||
childCount: state.childCount,
|
|
||||||
setIsValidated: state.setIsValidated,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
|
||||||
function handleOnClick() {
|
|
||||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
|
||||||
}
|
|
||||||
const closePicker = useCallback(() => {
|
|
||||||
const guestRoomsValidData = guestRoomsSchema.safeParse(rooms)
|
|
||||||
if (guestRoomsValidData.success) {
|
|
||||||
setIsOpen(false)
|
|
||||||
setIsValidated(false)
|
|
||||||
setValue(name, guestRoomsValidData.data, { shouldValidate: true })
|
|
||||||
} else {
|
|
||||||
setIsValidated(true)
|
|
||||||
}
|
|
||||||
}, [rooms, name, setValue, setIsValidated, setIsOpen])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const { rooms, adultCount, childCount } = useGuestsRoomsStore((state) => ({
|
||||||
function handleClickOutside(evt: Event) {
|
rooms: state.rooms,
|
||||||
const target = evt.target as HTMLElement
|
adultCount: state.adultCount,
|
||||||
if (ref.current && target && !ref.current.contains(target)) {
|
childCount: state.childCount,
|
||||||
closePicker()
|
}))
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener("click", handleClickOutside)
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("click", handleClickOutside)
|
|
||||||
}
|
|
||||||
}, [closePicker])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container} data-isopen={isOpen} ref={ref}>
|
<DialogTrigger>
|
||||||
<button className={styles.btn} onClick={handleOnClick} type="button">
|
<Button className={styles.btn} type="button">
|
||||||
<Body className={styles.body} asChild>
|
<Body className={styles.body} asChild>
|
||||||
<span>
|
<span>
|
||||||
{intl.formatMessage(
|
{intl.formatMessage(
|
||||||
@@ -80,10 +50,10 @@ export default function GuestsRoomsPickerForm({
|
|||||||
: null}
|
: null}
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
</button>
|
</Button>
|
||||||
<div aria-modal className={styles.hideWrapper} role="dialog">
|
<Popover>
|
||||||
<GuestsRoomsPicker closePicker={closePicker} />
|
<Dialog />
|
||||||
</div>
|
</Popover>
|
||||||
</div>
|
</DialogTrigger>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,6 @@ export type GuestsRoom = {
|
|||||||
child: Child[]
|
child: Child[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GuestsRoomsPickerProps {
|
|
||||||
closePicker: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type GuestsRoomPickerProps = {
|
export type GuestsRoomPickerProps = {
|
||||||
index: number
|
index: number
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user