fix: handle stickyness when scrolling is locked

This commit is contained in:
Christel Westerberg
2024-11-13 14:43:06 +01:00
parent ca3819f7cc
commit 7a3194f978
9 changed files with 141 additions and 100 deletions

View File

@@ -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",

View File

@@ -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 {

View File

@@ -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;

View File

@@ -38,7 +38,7 @@
.hideWrapper {
bottom: 0;
left: 0;
overflow: auto;
overflow: hidden;
position: fixed;
right: 0;
top: 100%;

View File

@@ -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)

View File

@@ -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}
</>
)
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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>