fix(BOOK-711): Added isFloating prop to decide when the booking widget should have a border radius

Approved-by: Bianca Widstam
This commit is contained in:
Erik Tiekstra
2026-01-28 12:02:42 +00:00
parent 70838060e5
commit ead34c07ee
9 changed files with 74 additions and 45 deletions

View File

@@ -1,3 +1,8 @@
.formContent {
display: flex;
flex-direction: column;
}
.vouchersHeader { .vouchersHeader {
display: flex; display: flex;
gap: var(--Space-x15); gap: var(--Space-x15);
@@ -100,25 +105,25 @@
height: fit-content; height: fit-content;
} }
.input { @media screen and (min-width: 768px) and (max-width: 1366px) {
display: flex; .formContent.floating .voucherContainer {
flex-direction: column; border-bottom-left-radius: var(--Corner-Radius-lg);
border-bottom-right-radius: var(--Corner-Radius-lg);
}
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.input { .formContent {
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: row; flex-direction: row;
} }
.inputContainer { .inputContainer {
display: flex; display: flex;
flex: 2; flex: 2;
gap: var(--Space-x15); gap: var(--Space-x15);
} }
.voucherContainer {
border-radius: 0 0 var(--Corner-Radius-md) var(--Corner-Radius-md);
}
.rooms, .rooms,
.when, .when,
@@ -154,7 +159,7 @@
} }
@media screen and (min-width: 768px) and (max-width: 1366px) { @media screen and (min-width: 768px) and (max-width: 1366px) {
.input { .formContent {
flex-wrap: wrap; flex-wrap: wrap;
} }
.inputRow { .inputRow {
@@ -197,8 +202,8 @@
.inputContainer { .inputContainer {
margin-left: calc(-1 * var(--Space-x15)); margin-left: calc(-1 * var(--Space-x15));
} }
.input { .formContent {
gap: var(--Space-x15); gap: var(--Space-x2);
} }
.inputRow { .inputRow {
flex: 1; flex: 1;

View File

@@ -31,11 +31,13 @@ type BookingWidgetFormContentProps = {
formId: string formId: string
onSubmit: () => void onSubmit: () => void
isSearching: boolean isSearching: boolean
isFloating: boolean
} }
export default function FormContent({ export default function FormContent({
formId, formId,
onSubmit, onSubmit,
isSearching, isSearching,
isFloating,
}: BookingWidgetFormContentProps) { }: BookingWidgetFormContentProps) {
const intl = useIntl() const intl = useIntl()
const { const {
@@ -61,7 +63,7 @@ export default function FormContent({
const nights = dt(selectedDate.toDate).diff(dt(selectedDate.fromDate), "days") const nights = dt(selectedDate.toDate).diff(dt(selectedDate.fromDate), "days")
return ( return (
<div className={styles.input}> <div className={cx(styles.formContent, { [styles.floating]: isFloating })}>
<div className={styles.inputRow}> <div className={styles.inputRow}>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.where}> <div className={styles.where}>
@@ -170,7 +172,7 @@ export function BookingWidgetFormContentSkeleton() {
const intl = useIntl() const intl = useIntl()
return ( return (
<div className={styles.input}> <div className={styles.formContent}>
<div className={styles.inputRow}> <div className={styles.inputRow}>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.where}> <div className={styles.where}>

View File

@@ -32,9 +32,14 @@ const formId = "booking-widget"
type BookingWidgetFormProps = { type BookingWidgetFormProps = {
type?: BookingWidgetType type?: BookingWidgetType
isFloating: boolean
onClose: () => void onClose: () => void
} }
export default function Form({ type, onClose }: BookingWidgetFormProps) { export default function Form({
type,
isFloating,
onClose,
}: BookingWidgetFormProps) {
const router = useRouter() const router = useRouter()
const pathname = usePathname() const pathname = usePathname()
const lang = useLang() const lang = useLang()
@@ -106,6 +111,7 @@ export default function Form({ type, onClose }: BookingWidgetFormProps) {
formId={formId} formId={formId}
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
isSearching={isPending} isSearching={isPending}
isFloating={isFloating}
/> />
</FormRAC> </FormRAC>
</section> </section>

View File

@@ -1,6 +1,7 @@
"use client" "use client"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { cx } from "class-variance-authority"
import { useSearchParams } from "next/navigation" import { useSearchParams } from "next/navigation"
import { use, useEffect, useRef, useState } from "react" import { use, useEffect, useRef, useState } from "react"
import { FormProvider, useForm } from "react-hook-form" import { FormProvider, useForm } from "react-hook-form"
@@ -43,6 +44,7 @@ export type BookingWidgetClientProps = {
type?: BookingWidgetType type?: BookingWidgetType
data: BookingWidgetSearchData data: BookingWidgetSearchData
pageSettingsBookingCodePromise: Promise<string> | null pageSettingsBookingCodePromise: Promise<string> | null
isFloating?: boolean
} }
export const FOCUS_WIDGET = "focusWidget" export const FOCUS_WIDGET = "focusWidget"
@@ -50,6 +52,7 @@ export default function BookingWidgetClient({
type, type,
data, data,
pageSettingsBookingCodePromise, pageSettingsBookingCodePromise,
isFloating = false,
}: BookingWidgetClientProps) { }: BookingWidgetClientProps) {
const searchParams = useSearchParams() const searchParams = useSearchParams()
const focusWidget = searchParams.get(FOCUS_WIDGET) === "true" const focusWidget = searchParams.get(FOCUS_WIDGET) === "true"
@@ -245,10 +248,13 @@ export default function BookingWidgetClient({
<FormProvider {...methods}> <FormProvider {...methods}>
<section <section
ref={bookingWidgetRef} ref={bookingWidgetRef}
className={classNames} className={cx(classNames, { [styles.floating]: isFloating })}
data-booking-widget-open={isOpen} data-booking-widget-open={isOpen}
> >
<MobileToggleButton openMobileSearch={openMobileSearch} /> <MobileToggleButton
openMobileSearch={openMobileSearch}
isFloating={isFloating}
/>
<div className={styles.backdrop} onClick={closeMobileSearch} /> <div className={styles.backdrop} onClick={closeMobileSearch} />
<div className={formContainerClassNames}> <div className={formContainerClassNames}>
<button <button
@@ -258,7 +264,11 @@ export default function BookingWidgetClient({
> >
<MaterialIcon icon="close" /> <MaterialIcon icon="close" />
</button> </button>
<Form type={type} onClose={closeMobileSearch} /> <Form
type={type}
onClose={closeMobileSearch}
isFloating={isFloating}
/>
</div> </div>
</section> </section>
</FormProvider> </FormProvider>

View File

@@ -56,7 +56,11 @@ export function FloatingBookingWidgetClient(props: Props) {
ref={containerRef} ref={containerRef}
> >
<div className={styles.floatingBackground}> <div className={styles.floatingBackground}>
<BookingWidgetClient {...props} type="compact" /> <BookingWidgetClient
{...props}
type="compact"
isFloating={!stickyTop}
/>
</div> </div>
</div> </div>
) )

View File

@@ -14,8 +14,11 @@
/* To avoid this "flash" the styling is set to transparent) */ /* To avoid this "flash" the styling is set to transparent) */
/* It is a non-standard css proprty, so shouldn't have too much of an effect on accessibility. */ /* It is a non-standard css proprty, so shouldn't have too much of an effect on accessibility. */
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
&.floating {
border-radius: var(--Corner-Radius-md); border-radius: var(--Corner-Radius-md);
} }
}
.complete { .complete {
grid-template-columns: 1fr 36px; grid-template-columns: 1fr 36px;

View File

@@ -20,9 +20,11 @@ import type { BookingWidgetSchema } from "../Client"
type BookingWidgetToggleButtonProps = { type BookingWidgetToggleButtonProps = {
openMobileSearch: () => void openMobileSearch: () => void
isFloating: boolean
} }
export default function MobileToggleButton({ export default function MobileToggleButton({
openMobileSearch, openMobileSearch,
isFloating,
}: BookingWidgetToggleButtonProps) { }: BookingWidgetToggleButtonProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
@@ -99,7 +101,8 @@ export default function MobileToggleButton({
<Button <Button
className={cx( className={cx(
styles.mobileToggleButton, styles.mobileToggleButton,
locationAndDateIsSet ? styles.complete : styles.partial locationAndDateIsSet ? styles.complete : styles.partial,
{ [styles.floating]: isFloating }
)} )}
onPress={openMobileSearch} onPress={openMobileSearch}
> >

View File

@@ -1,4 +1,4 @@
.wrapper { .bookingWidgetContainer {
position: sticky; position: sticky;
z-index: var(--booking-widget-z-index); z-index: var(--booking-widget-z-index);
width: 100%; width: 100%;
@@ -14,7 +14,9 @@
z-index: 1; z-index: 1;
transform: translateY(0); transform: translateY(0);
visibility: visible; visibility: visible;
border-radius: var(--Corner-Radius-lg);
} }
@media screen and (max-width: 767px) { @media screen and (max-width: 767px) {
.backdrop { .backdrop {
position: fixed; position: fixed;
@@ -30,13 +32,16 @@
&:has([data-rooms-open="true"]) { &:has([data-rooms-open="true"]) {
z-index: var(--booking-widget-open-z-index); z-index: var(--booking-widget-open-z-index);
} }
&.floating .formContainer {
border-radius: var(--Corner-Radius-lg);
}
} }
.formContainer { .formContainer {
display: grid; display: grid;
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
background-color: var(--UI-Input-Controls-Surface-Normal); background-color: var(--UI-Input-Controls-Surface-Normal);
border-radius: 0;
gap: var(--Space-x3); gap: var(--Space-x3);
height: calc(100dvh - max(var(--sitewide-alert-sticky-height), 20px)); height: calc(100dvh - max(var(--sitewide-alert-sticky-height), 20px));
width: 100%; width: 100%;
@@ -53,18 +58,6 @@
} }
} }
.compact {
.formContainer {
border-radius: var(--Corner-Radius-lg);
}
}
@media screen and (max-width: 767px) {
.formContainer {
border-radius: var(--Corner-Radius-lg) var(--Corner-Radius-lg) 0 0;
}
}
.close { .close {
background: none; background: none;
border: none; border: none;
@@ -74,7 +67,7 @@
} }
@media screen and (min-width: 768px) { @media screen and (min-width: 768px) {
.wrapper { .bookingWidgetContainer {
top: 0; top: 0;
} }

View File

@@ -2,7 +2,9 @@ import { cva } from "class-variance-authority"
import styles from "./bookingWidget.module.css" import styles from "./bookingWidget.module.css"
export const bookingWidgetContainerVariants = cva(styles.wrapper, { export const bookingWidgetContainerVariants = cva(
styles.bookingWidgetContainer,
{
variants: { variants: {
type: { type: {
default: "", default: "",
@@ -13,7 +15,8 @@ export const bookingWidgetContainerVariants = cva(styles.wrapper, {
defaultVariants: { defaultVariants: {
type: "full", type: "full",
}, },
}) }
)
export const formContainerVariants = cva(styles.formContainer, { export const formContainerVariants = cva(styles.formContainer, {
variants: { variants: {