fix: handle stickyness when scrolling is locked
This commit is contained in:
@@ -38,11 +38,11 @@ export default function BookingWidgetClient({
|
||||
|
||||
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
|
||||
searchParams
|
||||
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
|
||||
? getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
|
||||
adults: "number",
|
||||
age: "number",
|
||||
bed: "number",
|
||||
}) as BookingWidgetSearchParams)
|
||||
})
|
||||
: undefined
|
||||
|
||||
const getLocationObj = (destination: string): Location | undefined => {
|
||||
@@ -75,6 +75,16 @@ export default function BookingWidgetClient({
|
||||
)
|
||||
: undefined
|
||||
|
||||
const defaultRoomsData = bookingWidgetSearchData?.room?.map((room) => ({
|
||||
adults: room.adults,
|
||||
child: room.child ?? [],
|
||||
})) ?? [
|
||||
{
|
||||
adults: 1,
|
||||
child: [],
|
||||
},
|
||||
]
|
||||
|
||||
const methods = useForm<BookingWidgetSchema>({
|
||||
defaultValues: {
|
||||
search: selectedLocation?.name ?? "",
|
||||
@@ -92,12 +102,7 @@ export default function BookingWidgetClient({
|
||||
bookingCode: "",
|
||||
redemption: false,
|
||||
voucher: false,
|
||||
rooms: bookingWidgetSearchData?.room ?? [
|
||||
{
|
||||
adults: 1,
|
||||
child: [],
|
||||
},
|
||||
],
|
||||
rooms: defaultRoomsData,
|
||||
},
|
||||
shouldFocusError: false,
|
||||
mode: "all",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
.formContainer {
|
||||
display: grid;
|
||||
grid-template-rows: 36px 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
background-color: var(--UI-Input-Controls-Surface-Normal);
|
||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||
gap: var(--Spacing-x3);
|
||||
@@ -30,6 +30,7 @@
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
justify-self: flex-end;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wrapper[data-open="true"] + .backdrop {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.container {
|
||||
--header-height: 68px;
|
||||
--header-height: 72px;
|
||||
--sticky-button-height: 120px;
|
||||
|
||||
display: grid;
|
||||
@@ -11,12 +11,10 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
align-self: flex-start;
|
||||
align-self: flex-end;
|
||||
background-color: var(--Main-Grey-White);
|
||||
display: grid;
|
||||
grid-area: header;
|
||||
grid-template-columns: 1fr 24px;
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x2);
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
.hideWrapper {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
|
||||
@@ -4,12 +4,14 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
|
||||
|
||||
export const guestRoomSchema = z.object({
|
||||
adults: z.number().default(1),
|
||||
child: z.array(
|
||||
z.object({
|
||||
age: z.number().nonnegative(),
|
||||
bed: z.number(),
|
||||
})
|
||||
),
|
||||
child: z
|
||||
.array(
|
||||
z.object({
|
||||
age: z.number().nonnegative(),
|
||||
bed: z.number(),
|
||||
})
|
||||
)
|
||||
.default([]),
|
||||
})
|
||||
|
||||
export const guestRoomsSchema = z.array(guestRoomSchema)
|
||||
|
||||
@@ -15,6 +15,11 @@ import {
|
||||
ChildInfoSelectorProps,
|
||||
} from "@/types/components/bookingWidget/guestsRoomsPicker"
|
||||
|
||||
const ageList = Array.from(Array(13).keys()).map((age) => ({
|
||||
label: age.toString(),
|
||||
value: age,
|
||||
}))
|
||||
|
||||
export default function ChildInfoSelector({
|
||||
child = { age: -1, bed: -1 },
|
||||
childrenInAdultsBed,
|
||||
@@ -28,11 +33,6 @@ export default function ChildInfoSelector({
|
||||
const bedLabel = intl.formatMessage({ id: "Bed" })
|
||||
const { setValue, formState } = useFormContext()
|
||||
|
||||
const ageList = Array.from(Array(13).keys()).map((age) => ({
|
||||
label: `${age}`,
|
||||
value: age,
|
||||
}))
|
||||
|
||||
function updateSelectedAge(age: number) {
|
||||
setValue(`rooms.${roomIndex}.child.${index}.age`, age, {
|
||||
shouldValidate: true,
|
||||
@@ -74,7 +74,8 @@ export default function ChildInfoSelector({
|
||||
return availableBedTypes
|
||||
}
|
||||
|
||||
console.log("ALL TJHE ERORRORS", formState.errors)
|
||||
//@ts-expect-error: formState is typed with FormValues
|
||||
const roomErrors = formState.errors.rooms?.[roomIndex]?.child?.[index]
|
||||
return (
|
||||
<>
|
||||
<div key={index} className={styles.childInfoContainer}>
|
||||
@@ -110,12 +111,12 @@ export default function ChildInfoSelector({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* {isValidated && child.age < 0 ? (
|
||||
{roomErrors ? (
|
||||
<Caption color="red" className={styles.error}>
|
||||
<ErrorCircleIcon color="red" />
|
||||
{ageReqdErrMsg}
|
||||
</Caption>
|
||||
) : null} */}
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,65 +39,68 @@ export default function GuestsRoomsPickerDialog({
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<button type="button" className={styles.close} onClick={onClose}>
|
||||
<CloseLargeIcon />
|
||||
</button>
|
||||
</header>
|
||||
<div className={styles.contentContainer}>
|
||||
{rooms.map((room, index) => {
|
||||
const currentAdults = room.adults
|
||||
const currentChildren = room.child
|
||||
const childrenInAdultsBed = currentChildren.filter(
|
||||
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
|
||||
).length
|
||||
<section className={styles.contentWrapper}>
|
||||
<header className={styles.header}>
|
||||
<button type="button" className={styles.close} onClick={onClose}>
|
||||
<CloseLargeIcon />
|
||||
</button>
|
||||
</header>
|
||||
<div className={styles.contentContainer}>
|
||||
{rooms.map((room, index) => {
|
||||
const currentAdults = room.adults
|
||||
const currentChildren = room.child
|
||||
const childrenInAdultsBed =
|
||||
currentChildren.filter(
|
||||
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
|
||||
).length ?? 0
|
||||
|
||||
return (
|
||||
<div className={styles.roomContainer} key={index}>
|
||||
<section className={styles.roomDetailsContainer}>
|
||||
<Subtitle type="two" className={styles.roomHeading}>
|
||||
{roomLabel} {index + 1}
|
||||
</Subtitle>
|
||||
<AdultSelector
|
||||
roomIndex={index}
|
||||
currentAdults={currentAdults}
|
||||
currentChildren={currentChildren}
|
||||
childrenInAdultsBed={childrenInAdultsBed}
|
||||
/>
|
||||
<ChildSelector
|
||||
roomIndex={index}
|
||||
currentAdults={currentAdults}
|
||||
currentChildren={currentChildren}
|
||||
childrenInAdultsBed={childrenInAdultsBed}
|
||||
/>
|
||||
</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>
|
||||
return (
|
||||
<div className={styles.roomContainer} key={index}>
|
||||
<section className={styles.roomDetailsContainer}>
|
||||
<Subtitle type="two" className={styles.roomHeading}>
|
||||
{roomLabel} {index + 1}
|
||||
</Subtitle>
|
||||
<AdultSelector
|
||||
roomIndex={index}
|
||||
currentAdults={currentAdults}
|
||||
currentChildren={currentChildren}
|
||||
childrenInAdultsBed={childrenInAdultsBed}
|
||||
/>
|
||||
<ChildSelector
|
||||
roomIndex={index}
|
||||
currentAdults={currentAdults}
|
||||
currentChildren={currentChildren}
|
||||
childrenInAdultsBed={childrenInAdultsBed}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.hideOnMobile}>
|
||||
<Tooltip
|
||||
|
||||
@@ -3,15 +3,24 @@
|
||||
}
|
||||
|
||||
.pickerContainerMobile {
|
||||
--header-height: 72px;
|
||||
--sticky-button-height: 140px;
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
top: 20px;
|
||||
transition: top 300ms ease;
|
||||
z-index: 1000;
|
||||
z-index: 100;
|
||||
}
|
||||
.contentWrapper {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"header"
|
||||
"content";
|
||||
grid-template-rows: var(--header-height) calc(100dvh - var(--header-height));
|
||||
}
|
||||
|
||||
.pickerContainerDesktop {
|
||||
@@ -44,7 +53,6 @@
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: auto;
|
||||
margin-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1366px) {
|
||||
@@ -55,7 +63,6 @@
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--Main-Grey-White);
|
||||
display: grid;
|
||||
grid-area: header;
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||
@@ -83,11 +90,10 @@
|
||||
rgba(255, 255, 255, 0) 7.5%,
|
||||
#ffffff 82.5%
|
||||
);
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2) var(--Spacing-x7);
|
||||
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x7);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.footer .hideOnMobile {
|
||||
@@ -103,9 +109,17 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.pickerContainerMobisse {
|
||||
.pickerContainerMobile {
|
||||
display: none;
|
||||
}
|
||||
.contentWrapper {
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
overflow-y: visible;
|
||||
}
|
||||
|
||||
.triggerMobile {
|
||||
display: none;
|
||||
}
|
||||
@@ -117,24 +131,27 @@
|
||||
.pickerContainerDesktop {
|
||||
--header-height: 72px;
|
||||
--sticky-button-height: 140px;
|
||||
|
||||
background-color: var(--Main-Grey-White);
|
||||
display: grid;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.pickerContainerDesktop:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-template-columns: auto auto;
|
||||
padding-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.footer .hideOnDesktop,
|
||||
|
||||
@@ -20,22 +20,36 @@ import { GuestsRoom } from "@/types/components/bookingWidget/guestsRoomsPicker"
|
||||
|
||||
export default function GuestsRoomsPickerForm() {
|
||||
const { watch } = useFormContext()
|
||||
|
||||
const rooms = watch("rooms") as GuestsRoom[]
|
||||
|
||||
const htmlElement =
|
||||
typeof window !== "undefined" ? document.querySelector("body") : null
|
||||
//isOpen is the 'old state', so isOpen === true means "The modal is open and WILL be closed".
|
||||
function setOverflowClip(isOpen: boolean) {
|
||||
if (htmlElement) {
|
||||
if (isOpen) {
|
||||
htmlElement.style.overflow = "visible"
|
||||
} else {
|
||||
// !important needed to override 'overflow: hidden' set by react-aria.
|
||||
// 'overflow: hidden' does not work in combination with other sticky positioned elements, which clip does.
|
||||
htmlElement.style.overflow = "clip !important"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogTrigger>
|
||||
<Trigger rooms={rooms} className={styles.triggerMobile} />
|
||||
<Modal className="my-modal">
|
||||
<Modal>
|
||||
<Dialog className={styles.pickerContainerMobile}>
|
||||
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
<DialogTrigger>
|
||||
<DialogTrigger onOpenChange={setOverflowClip}>
|
||||
<Trigger rooms={rooms} className={styles.triggerDesktop} />
|
||||
<Popover placement="bottom start" offset={22}>
|
||||
<Popover placement="bottom start" offset={36}>
|
||||
<Dialog className={styles.pickerContainerDesktop}>
|
||||
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
||||
</Dialog>
|
||||
|
||||
Reference in New Issue
Block a user