fix: handle stickyness when scrolling is locked
This commit is contained in:
@@ -38,11 +38,11 @@ export default function BookingWidgetClient({
|
|||||||
|
|
||||||
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
|
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
|
||||||
searchParams
|
searchParams
|
||||||
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
|
? getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
|
||||||
adults: "number",
|
adults: "number",
|
||||||
age: "number",
|
age: "number",
|
||||||
bed: "number",
|
bed: "number",
|
||||||
}) as BookingWidgetSearchParams)
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const getLocationObj = (destination: string): Location | undefined => {
|
const getLocationObj = (destination: string): Location | undefined => {
|
||||||
@@ -75,6 +75,16 @@ export default function BookingWidgetClient({
|
|||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
const defaultRoomsData = bookingWidgetSearchData?.room?.map((room) => ({
|
||||||
|
adults: room.adults,
|
||||||
|
child: room.child ?? [],
|
||||||
|
})) ?? [
|
||||||
|
{
|
||||||
|
adults: 1,
|
||||||
|
child: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const methods = useForm<BookingWidgetSchema>({
|
const methods = useForm<BookingWidgetSchema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
search: selectedLocation?.name ?? "",
|
search: selectedLocation?.name ?? "",
|
||||||
@@ -92,12 +102,7 @@ export default function BookingWidgetClient({
|
|||||||
bookingCode: "",
|
bookingCode: "",
|
||||||
redemption: false,
|
redemption: false,
|
||||||
voucher: false,
|
voucher: false,
|
||||||
rooms: bookingWidgetSearchData?.room ?? [
|
rooms: defaultRoomsData,
|
||||||
{
|
|
||||||
adults: 1,
|
|
||||||
child: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
shouldFocusError: false,
|
shouldFocusError: false,
|
||||||
mode: "all",
|
mode: "all",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
.formContainer {
|
.formContainer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 36px 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
background-color: var(--UI-Input-Controls-Surface-Normal);
|
background-color: var(--UI-Input-Controls-Surface-Normal);
|
||||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||||
gap: var(--Spacing-x3);
|
gap: var(--Spacing-x3);
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
justify-self: flex-end;
|
justify-self: flex-end;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wrapper[data-open="true"] + .backdrop {
|
.wrapper[data-open="true"] + .backdrop {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.container {
|
.container {
|
||||||
--header-height: 68px;
|
--header-height: 72px;
|
||||||
--sticky-button-height: 120px;
|
--sticky-button-height: 120px;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -11,12 +11,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
align-self: flex-start;
|
align-self: flex-end;
|
||||||
background-color: var(--Main-Grey-White);
|
background-color: var(--Main-Grey-White);
|
||||||
display: grid;
|
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
grid-template-columns: 1fr 24px;
|
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x2);
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
.hideWrapper {
|
.hideWrapper {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import type { Location } from "@/types/trpc/routers/hotel/locations"
|
|||||||
|
|
||||||
export const guestRoomSchema = z.object({
|
export const guestRoomSchema = z.object({
|
||||||
adults: z.number().default(1),
|
adults: z.number().default(1),
|
||||||
child: z.array(
|
child: z
|
||||||
z.object({
|
.array(
|
||||||
age: z.number().nonnegative(),
|
z.object({
|
||||||
bed: z.number(),
|
age: z.number().nonnegative(),
|
||||||
})
|
bed: z.number(),
|
||||||
),
|
})
|
||||||
|
)
|
||||||
|
.default([]),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const guestRoomsSchema = z.array(guestRoomSchema)
|
export const guestRoomsSchema = z.array(guestRoomSchema)
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ import {
|
|||||||
ChildInfoSelectorProps,
|
ChildInfoSelectorProps,
|
||||||
} from "@/types/components/bookingWidget/guestsRoomsPicker"
|
} from "@/types/components/bookingWidget/guestsRoomsPicker"
|
||||||
|
|
||||||
|
const ageList = Array.from(Array(13).keys()).map((age) => ({
|
||||||
|
label: age.toString(),
|
||||||
|
value: age,
|
||||||
|
}))
|
||||||
|
|
||||||
export default function ChildInfoSelector({
|
export default function ChildInfoSelector({
|
||||||
child = { age: -1, bed: -1 },
|
child = { age: -1, bed: -1 },
|
||||||
childrenInAdultsBed,
|
childrenInAdultsBed,
|
||||||
@@ -28,11 +33,6 @@ export default function ChildInfoSelector({
|
|||||||
const bedLabel = intl.formatMessage({ id: "Bed" })
|
const bedLabel = intl.formatMessage({ id: "Bed" })
|
||||||
const { setValue, formState } = useFormContext()
|
const { setValue, formState } = useFormContext()
|
||||||
|
|
||||||
const ageList = Array.from(Array(13).keys()).map((age) => ({
|
|
||||||
label: `${age}`,
|
|
||||||
value: age,
|
|
||||||
}))
|
|
||||||
|
|
||||||
function updateSelectedAge(age: number) {
|
function updateSelectedAge(age: number) {
|
||||||
setValue(`rooms.${roomIndex}.child.${index}.age`, age, {
|
setValue(`rooms.${roomIndex}.child.${index}.age`, age, {
|
||||||
shouldValidate: true,
|
shouldValidate: true,
|
||||||
@@ -74,7 +74,8 @@ export default function ChildInfoSelector({
|
|||||||
return availableBedTypes
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div key={index} className={styles.childInfoContainer}>
|
<div key={index} className={styles.childInfoContainer}>
|
||||||
@@ -110,12 +111,12 @@ export default function ChildInfoSelector({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* {isValidated && child.age < 0 ? (
|
{roomErrors ? (
|
||||||
<Caption color="red" className={styles.error}>
|
<Caption color="red" className={styles.error}>
|
||||||
<ErrorCircleIcon color="red" />
|
<ErrorCircleIcon color="red" />
|
||||||
{ageReqdErrMsg}
|
{ageReqdErrMsg}
|
||||||
</Caption>
|
</Caption>
|
||||||
) : null} */}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,65 +39,68 @@ export default function GuestsRoomsPickerDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<header className={styles.header}>
|
<section className={styles.contentWrapper}>
|
||||||
<button type="button" className={styles.close} onClick={onClose}>
|
<header className={styles.header}>
|
||||||
<CloseLargeIcon />
|
<button type="button" className={styles.close} onClick={onClose}>
|
||||||
</button>
|
<CloseLargeIcon />
|
||||||
</header>
|
</button>
|
||||||
<div className={styles.contentContainer}>
|
</header>
|
||||||
{rooms.map((room, index) => {
|
<div className={styles.contentContainer}>
|
||||||
const currentAdults = room.adults
|
{rooms.map((room, index) => {
|
||||||
const currentChildren = room.child
|
const currentAdults = room.adults
|
||||||
const childrenInAdultsBed = currentChildren.filter(
|
const currentChildren = room.child
|
||||||
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
|
const childrenInAdultsBed =
|
||||||
).length
|
currentChildren.filter(
|
||||||
|
(child) => child.bed === ChildBedMapEnum.IN_ADULTS_BED
|
||||||
|
).length ?? 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.roomContainer} key={index}>
|
<div className={styles.roomContainer} key={index}>
|
||||||
<section className={styles.roomDetailsContainer}>
|
<section className={styles.roomDetailsContainer}>
|
||||||
<Subtitle type="two" className={styles.roomHeading}>
|
<Subtitle type="two" className={styles.roomHeading}>
|
||||||
{roomLabel} {index + 1}
|
{roomLabel} {index + 1}
|
||||||
</Subtitle>
|
</Subtitle>
|
||||||
<AdultSelector
|
<AdultSelector
|
||||||
roomIndex={index}
|
roomIndex={index}
|
||||||
currentAdults={currentAdults}
|
currentAdults={currentAdults}
|
||||||
currentChildren={currentChildren}
|
currentChildren={currentChildren}
|
||||||
childrenInAdultsBed={childrenInAdultsBed}
|
childrenInAdultsBed={childrenInAdultsBed}
|
||||||
/>
|
/>
|
||||||
<ChildSelector
|
<ChildSelector
|
||||||
roomIndex={index}
|
roomIndex={index}
|
||||||
currentAdults={currentAdults}
|
currentAdults={currentAdults}
|
||||||
currentChildren={currentChildren}
|
currentChildren={currentChildren}
|
||||||
childrenInAdultsBed={childrenInAdultsBed}
|
childrenInAdultsBed={childrenInAdultsBed}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<Divider color="primaryLightSubtle" />
|
<Divider color="primaryLightSubtle" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<div className={styles.addRoomMobileContainer}>
|
<div className={styles.addRoomMobileContainer}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
heading={disabledBookingOptionsHeader}
|
heading={disabledBookingOptionsHeader}
|
||||||
text={disabledBookingOptionsText}
|
text={disabledBookingOptionsText}
|
||||||
position="top"
|
position="top"
|
||||||
arrow="left"
|
arrow="left"
|
||||||
>
|
>
|
||||||
{rooms.length < 4 ? (
|
{rooms.length < 4 ? (
|
||||||
<Button
|
<Button
|
||||||
intent="text"
|
intent="text"
|
||||||
variant="icon"
|
variant="icon"
|
||||||
wrapping
|
wrapping
|
||||||
disabled
|
disabled
|
||||||
theme="base"
|
theme="base"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
{addRoomLabel}
|
{addRoomLabel}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</section>
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<div className={styles.hideOnMobile}>
|
<div className={styles.hideOnMobile}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
|||||||
@@ -3,15 +3,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pickerContainerMobile {
|
.pickerContainerMobile {
|
||||||
|
--header-height: 72px;
|
||||||
|
--sticky-button-height: 140px;
|
||||||
background-color: var(--Main-Grey-White);
|
background-color: var(--Main-Grey-White);
|
||||||
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
border-radius: var(--Corner-radius-Large) var(--Corner-radius-Large) 0 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 20px;
|
||||||
transition: top 300ms ease;
|
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 {
|
.pickerContainerDesktop {
|
||||||
@@ -44,7 +53,6 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--Spacing-x1);
|
gap: var(--Spacing-x1);
|
||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
margin-top: var(--Spacing-x2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 1366px) {
|
@media screen and (max-width: 1366px) {
|
||||||
@@ -55,7 +63,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
background-color: var(--Main-Grey-White);
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-area: header;
|
grid-area: header;
|
||||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||||
@@ -83,11 +90,10 @@
|
|||||||
rgba(255, 255, 255, 0) 7.5%,
|
rgba(255, 255, 255, 0) 7.5%,
|
||||||
#ffffff 82.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;
|
position: sticky;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .hideOnMobile {
|
.footer .hideOnMobile {
|
||||||
@@ -103,9 +109,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1367px) {
|
@media screen and (min-width: 1367px) {
|
||||||
.pickerContainerMobisse {
|
.pickerContainerMobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.contentWrapper {
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentContainer {
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.triggerMobile {
|
.triggerMobile {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -117,24 +131,27 @@
|
|||||||
.pickerContainerDesktop {
|
.pickerContainerDesktop {
|
||||||
--header-height: 72px;
|
--header-height: 72px;
|
||||||
--sticky-button-height: 140px;
|
--sticky-button-height: 140px;
|
||||||
|
|
||||||
background-color: var(--Main-Grey-White);
|
background-color: var(--Main-Grey-White);
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
border-radius: var(--Corner-radius-Large);
|
||||||
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
max-width: calc(100vw - 20px);
|
max-width: calc(100vw - 20px);
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||||
|
|
||||||
width: 360px;
|
width: 360px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pickerContainerDesktop:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
|
padding-top: var(--Spacing-x2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer .hideOnDesktop,
|
.footer .hideOnDesktop,
|
||||||
|
|||||||
@@ -20,22 +20,36 @@ import { GuestsRoom } from "@/types/components/bookingWidget/guestsRoomsPicker"
|
|||||||
|
|
||||||
export default function GuestsRoomsPickerForm() {
|
export default function GuestsRoomsPickerForm() {
|
||||||
const { watch } = useFormContext()
|
const { watch } = useFormContext()
|
||||||
|
|
||||||
const rooms = watch("rooms") as GuestsRoom[]
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<DialogTrigger>
|
<DialogTrigger>
|
||||||
<Trigger rooms={rooms} className={styles.triggerMobile} />
|
<Trigger rooms={rooms} className={styles.triggerMobile} />
|
||||||
<Modal className="my-modal">
|
<Modal>
|
||||||
<Dialog className={styles.pickerContainerMobile}>
|
<Dialog className={styles.pickerContainerMobile}>
|
||||||
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Modal>
|
</Modal>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogTrigger>
|
<DialogTrigger onOpenChange={setOverflowClip}>
|
||||||
<Trigger rooms={rooms} className={styles.triggerDesktop} />
|
<Trigger rooms={rooms} className={styles.triggerDesktop} />
|
||||||
<Popover placement="bottom start" offset={22}>
|
<Popover placement="bottom start" offset={36}>
|
||||||
<Dialog className={styles.pickerContainerDesktop}>
|
<Dialog className={styles.pickerContainerDesktop}>
|
||||||
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
{({ close }) => <PickerForm rooms={rooms} onClose={close} />}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
Reference in New Issue
Block a user