From 2362754356092c606deca1dc97dc9d3d2fcaa7c9 Mon Sep 17 00:00:00 2001 From: Jeroen Reumkens Date: Tue, 6 Jun 2023 15:55:27 +0200 Subject: [PATCH] feat: CAL-1674 Added new header layout for new booker. (#9227) * CAL-1674: Added new header layout for new booker. * Removed unneccesary bg style for toggle group, since it's already the default bg color. * Fixed types * Gave minwidth to booker header date title, to prevent arrows for switching weeks from jumping around. * Push small calendar down * Fixed overflow in small calendar view * Added booker modal padding underneath badges * Fixed grid area name causing broken animation. Also use setstyle directly instead of using animate for non animated properties. Performs better and removes some errors by framer motion for non animatable units like 'auto'. --------- Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> --- packages/features/bookings/Booker/Booker.tsx | 48 ++------ .../BookEventForm/BookFormAsModal.tsx | 2 +- .../bookings/Booker/components/Header.tsx | 113 ++++++++++++++++++ .../Booker/components/LargeViewHeader.tsx | 43 ------- .../bookings/Booker/components/Section.tsx | 1 + packages/features/bookings/Booker/config.ts | 50 +++++--- packages/features/bookings/Booker/types.ts | 2 +- 7 files changed, 159 insertions(+), 100 deletions(-) create mode 100644 packages/features/bookings/Booker/components/Header.tsx delete mode 100644 packages/features/bookings/Booker/components/LargeViewHeader.tsx 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";