diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx index 0372b4574f..703012ebbc 100644 --- a/packages/features/bookings/Booker/Booker.tsx +++ b/packages/features/bookings/Booker/Booker.tsx @@ -1,26 +1,24 @@ import { LazyMotion, domAnimation, m, AnimatePresence } from "framer-motion"; import dynamic from "next/dynamic"; -import { useCallback, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; import StickyBox from "react-sticky-box"; import { shallow } from "zustand/shallow"; import classNames from "@calcom/lib/classNames"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import useMediaQuery from "@calcom/lib/hooks/useMediaQuery"; -import { ToggleGroup } from "@calcom/ui"; -import { Calendar, Columns, Grid } from "@calcom/ui/components/icon"; import { AvailableTimeSlots } from "./components/AvailableTimeSlots"; import { BookEventForm } from "./components/BookEventForm"; import { BookFormAsModal } from "./components/BookEventForm/BookFormAsModal"; import { EventMeta } from "./components/EventMeta"; +import { Header } from "./components/Header"; import { LargeCalendar } from "./components/LargeCalendar"; -import { LargeViewHeader } from "./components/LargeViewHeader"; import { BookerSection } from "./components/Section"; import { Away, NotFound } from "./components/Unavailable"; import { fadeInLeft, getBookerSizeClassNames, useBookerResizeAnimation } from "./config"; import { useBookerStore, useInitializeBookerStore } from "./store"; -import type { BookerLayout, BookerProps } from "./types"; +import type { BookerProps } from "./types"; import { useEvent } from "./utils/event"; import { useBrandColors } from "./utils/use-brand-colors"; @@ -52,7 +50,6 @@ const BookerComponent = ({ shallow ); const extraDays = layout === "large_timeslots" ? (isTablet ? 2 : 4) : 0; - const onLayoutToggle = useCallback((newLayout: BookerLayout) => setLayout(newLayout), [setLayout]); const animationScope = useBookerResizeAnimation(layout, bookerState); @@ -98,46 +95,22 @@ const BookerComponent = ({ return ( <> - {/* - If we would render this on mobile, it would unset the mobile variant, - since that's not a valid option, so it would set the layout to null. - */} - {!isMobile && ( -
- , - tooltip: t("switch_monthly"), - }, - { - value: "large_calendar", - label: , - tooltip: t("switch_weekly"), - }, - { - value: "large_timeslots", - label: , - tooltip: t("switch_multiday"), - }, - ]} - /> -
- )}
- + +
+ + @@ -196,7 +169,6 @@ const BookerComponent = ({ )} ref={timeslotsRef} {...fadeInLeft}> - {layout === "large_timeslots" && }

{t("confirm_your_details")}

-
+
{parsedSelectedTimeslot.format("LLL")} diff --git a/packages/features/bookings/Booker/components/Header.tsx b/packages/features/bookings/Booker/components/Header.tsx new file mode 100644 index 0000000000..f6864aad4e --- /dev/null +++ b/packages/features/bookings/Booker/components/Header.tsx @@ -0,0 +1,113 @@ +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { useCallback } from "react"; +import { shallow } from "zustand/shallow"; + +import dayjs from "@calcom/dayjs"; +import { useLocale } from "@calcom/lib/hooks/useLocale"; +import { Button, ButtonGroup, ToggleGroup } from "@calcom/ui"; +import { Calendar, Columns, Grid } from "@calcom/ui/components/icon"; + +import { TimeFormatToggle } from "../../components/TimeFormatToggle"; +import { useBookerStore } from "../store"; +import type { BookerLayout } from "../types"; + +export function Header({ extraDays, isMobile }: { extraDays: number; isMobile: boolean }) { + const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow); + const selectedDateString = useBookerStore((state) => state.selectedDate); + const addToSelectedDate = useBookerStore((state) => state.addToSelectedDate); + const isSmallCalendar = layout === "small_calendar"; + const selectedDate = dayjs(selectedDateString); + + const onLayoutToggle = useCallback( + (newLayout: string) => setLayout(newLayout as BookerLayout), + [setLayout] + ); + + if (isMobile) return null; + + // In month view we only show the layout toggle. + if (isSmallCalendar) { + return ( +
+ +
+ ); + } + + return ( +
+
+

+ {selectedDate.format("MMM D")}-{selectedDate.add(extraDays, "days").format("D")},{" "} + {selectedDate.format("YYYY")} +

+ +
+
+ +
+ +
+ {/* + This second layout toggle is hidden, but needed to reserve the correct spot in the DIV + for the fixed toggle above to fit into. If we wouldn't make it fixed in this view, the transition + would be really weird, because the element is positioned fixed in the month view, and then + when switching layouts wouldn't anymmore, causing it to animate from the center to the top right, + while it actuall already was on place. That's why we have this element twice. + */} +
+ +
+
+
+ ); +} + +const LayoutToggle = ({ + onLayoutToggle, + layout, +}: { + onLayoutToggle: (layout: string) => void; + layout: string; +}) => { + const { t } = useLocale(); + + return ( + , + tooltip: t("switch_monthly"), + }, + { + value: "large_calendar", + label: , + tooltip: t("switch_weekly"), + }, + { + value: "large_timeslots", + label: , + tooltip: t("switch_multiday"), + }, + ]} + /> + ); +}; diff --git a/packages/features/bookings/Booker/components/LargeViewHeader.tsx b/packages/features/bookings/Booker/components/LargeViewHeader.tsx deleted file mode 100644 index a44d42577e..0000000000 --- a/packages/features/bookings/Booker/components/LargeViewHeader.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { ChevronLeft, ChevronRight } from "lucide-react"; - -import dayjs from "@calcom/dayjs"; -import { Button, ButtonGroup } from "@calcom/ui"; - -import { useBookerStore } from "../store"; - -export function LargeViewHeader({ extraDays }: { extraDays: number }) { - const layout = useBookerStore((state) => state.layout); - const selectedDateString = useBookerStore((state) => state.selectedDate); - const addToSelectedDate = useBookerStore((state) => state.addToSelectedDate); - const isLargeTimeslots = layout === "large_timeslots"; - const selectedDate = dayjs(selectedDateString); - - if (!isLargeTimeslots) return null; - - return ( -
-

- {selectedDate.format("MMM D")}-{selectedDate.add(extraDays, "days").format("D")},{" "} - {selectedDate.format("YYYY")} -

-
- -
-
- ); -} diff --git a/packages/features/bookings/Booker/components/Section.tsx b/packages/features/bookings/Booker/components/Section.tsx index 4c1a039d1a..34069f29b3 100644 --- a/packages/features/bookings/Booker/components/Section.tsx +++ b/packages/features/bookings/Booker/components/Section.tsx @@ -34,6 +34,7 @@ const gridAreaClassNameMap: { [key in BookerAreas]: string } = { main: "[grid-area:main]", meta: "[grid-area:meta]", timeslots: "[grid-area:timeslots]", + header: "[grid-area:header]", }; /** diff --git a/packages/features/bookings/Booker/config.ts b/packages/features/bookings/Booker/config.ts index c659800c61..2465ba4ae5 100644 --- a/packages/features/bookings/Booker/config.ts +++ b/packages/features/bookings/Booker/config.ts @@ -50,10 +50,12 @@ export const resizeAnimationConfig: ResizeAnimationConfig = { minHeight: "0px", gridTemplateAreas: ` "meta" + "header" "main" "timeslots" `, gridTemplateColumns: "100%", + gridTemplateRows: "auto auto auto auto", }, }, small_calendar: { @@ -61,31 +63,49 @@ export const resizeAnimationConfig: ResizeAnimationConfig = { width: "calc(var(--booker-meta-width) + var(--booker-main-width))", minHeight: "450px", height: "auto", - gridTemplateAreas: `"meta main"`, + gridTemplateAreas: ` + "meta main main" + "meta main main" + `, gridTemplateColumns: "var(--booker-meta-width) var(--booker-main-width)", + gridTemplateRows: "auto", }, selecting_time: { width: "calc(var(--booker-meta-width) + var(--booker-main-width) + var(--booker-timeslots-width))", minHeight: "450px", height: "auto", - gridTemplateAreas: `"meta main timeslots"`, + gridTemplateAreas: ` + "meta main timeslots" + "meta main timeslots" + `, gridTemplateColumns: "var(--booker-meta-width) 1fr var(--booker-timeslots-width)", + gridTemplateRows: "auto", }, }, large_calendar: { default: { width: "100vw", + minHeight: "450px", height: "100vh", - gridTemplateAreas: `"meta main"`, + gridTemplateAreas: ` + "meta header header" + "meta main main" + `, gridTemplateColumns: "var(--booker-meta-width) 1fr", + gridTemplateRows: "70px auto", }, }, large_timeslots: { default: { width: "100vw", + minHeight: "450px", height: "100vh", - gridTemplateAreas: `"meta main"`, + gridTemplateAreas: ` + "meta header header" + "meta main main" + `, gridTemplateColumns: "var(--booker-meta-width) 1fr", + gridTemplateRows: "70px auto", }, }, }; @@ -134,28 +154,24 @@ export const useBookerResizeAnimation = (layout: BookerLayout, state: BookerStat // Width is animated by the css class instead of via framer motion, // because css is better at animating the calcs, framer motion might // make some mistakes in that. - width: animationConfig?.width || "auto", gridTemplateAreas: animationConfig?.gridTemplateAreas, + width: animationConfig?.width || "auto", gridTemplateColumns: animationConfig?.gridTemplateColumns, + gridTemplateRows: animationConfig?.gridTemplateRows, minHeight: animationConfig?.minHeight, }; // We don't animate if users has set prefers-reduced-motion, // or when the layout is mobile. if (prefersReducedMotion || layout === "mobile") { - animate( - animationScope.current, - { - ...animatedProperties, - ...nonAnimatedProperties, - }, - { - duration: 0, - } - ); + const styles = { ...nonAnimatedProperties, ...animatedProperties }; + Object.keys(styles).forEach((property) => { + animationScope.current.style[property] = styles[property as keyof typeof styles]; + }); } else { - animate(animationScope.current, nonAnimatedProperties, { - duration: 0, + Object.keys(nonAnimatedProperties).forEach((property) => { + animationScope.current.style[property] = + nonAnimatedProperties[property as keyof typeof nonAnimatedProperties]; }); animate(animationScope.current, animatedProperties, { duration: 0.5, diff --git a/packages/features/bookings/Booker/types.ts b/packages/features/bookings/Booker/types.ts index 54687bd12c..221b920460 100644 --- a/packages/features/bookings/Booker/types.ts +++ b/packages/features/bookings/Booker/types.ts @@ -43,4 +43,4 @@ export interface BookerProps { export type BookerState = "loading" | "selecting_date" | "selecting_time" | "booking"; export type BookerLayout = "small_calendar" | "large_timeslots" | "large_calendar" | "mobile"; -export type BookerAreas = "calendar" | "timeslots" | "main" | "meta"; +export type BookerAreas = "calendar" | "timeslots" | "main" | "meta" | "header";