UI Improvements for new booker (#8483)

CAL-1578: No layout shift when switching months (because of timeslots disappearing again)
CAL-1576: Font weight tweaks
Bigger sidebar on fullscreen booker layouts
CAL-1573: Slightly smaller padding around booker sections
CAL-1563: Prevent timezone select from resizing when typing in it
CAL-1561: Spacing improvements timeslots + button height
CAL-1560: Improve resize animation when changing from time slot picker to form
CAL-1549: Added overflow scroll to event description, removed icon from event description so we have more space, and added sexy scrollbars

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Jeroen Reumkens 2023-04-25 16:28:32 +02:00 committed by GitHub
parent 72f72d86b2
commit 524cefcbad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 55 additions and 68 deletions

View File

@ -465,7 +465,7 @@
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"all_booked_today": "All booked today.",
"all_booked_today": "All booked.",
"slots_load_fail": "Could not load the available time slots.",
"additional_guests": "Add guests",
"your_name": "Your name",

View File

@ -5,9 +5,7 @@
"logo": "icon.svg",
"url": "https://developers.facebook.com/docs/metapixel/",
"variant": "analytics",
"categories": [
"analytics"
],
"categories": ["analytics"],
"publisher": "regexyl",
"email": "info@regexyl.com",
"description": "Add Meta Pixel to your bookings page to measure, optimize and build audiences for your ad campaigns.",
@ -24,4 +22,4 @@
]
}
}
}
}

View File

@ -1,4 +1,3 @@
import type { MotionStyle } from "framer-motion";
import { LazyMotion, domAnimation, m, AnimatePresence } from "framer-motion";
import { useCallback, useEffect, useRef } from "react";
import StickyBox from "react-sticky-box";
@ -20,13 +19,11 @@ import { EventMeta } from "./components/EventMeta";
import { LargeCalendar } from "./components/LargeCalendar";
import { LargeViewHeader } from "./components/LargeViewHeader";
import { BookerSection } from "./components/Section";
import { fadeInUp, fadeInLeft, resizeAnimationConfig } from "./config";
import { fadeInLeft, resizeAnimationConfig } from "./config";
import { useBookerStore, useInitializeBookerStore } from "./store";
import type { BookerLayout, BookerProps } from "./types";
import { useEvent } from "./utils/event";
const LargeLayouts = ["large_calendar", "large_timeslots"];
const useBrandColors = ({ brandColor, darkBrandColor }: { brandColor?: string; darkBrandColor?: string }) => {
const brandTheme = useGetBrandingColours({
lightVal: brandColor,
@ -46,10 +43,7 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
const event = useEvent();
const [layout, setLayout] = useBookerStore((state) => [state.layout, state.setLayout], shallow);
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
const [selectedDate, setSelectedDate] = useBookerStore(
(state) => [state.selectedDate, state.setSelectedDate],
shallow
);
const selectedDate = useBookerStore((state) => state.selectedDate);
const [selectedTimeslot, setSelectedTimeslot] = useBookerStore(
(state) => [state.selectedTimeslot, state.setSelectedTimeslot],
shallow
@ -124,27 +118,23 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
</div>
)}
<div className="flex h-full w-full flex-col items-center">
<m.div
layout
// Passing the default animation styles here as the styles, makes sure that there's no initial loading state
// where there's no styles applied yet (meaning there wouldn't be a grid + widths), which would cause
// the layout to jump around on load.
style={resizeAnimationConfig.small_calendar.default as MotionStyle}
animate={resizeAnimationConfig[layout]?.[bookerState] || resizeAnimationConfig[layout].default}
transition={{ ease: "easeInOut", duration: 0.4 }}
<div
style={resizeAnimationConfig[layout]?.[bookerState] || resizeAnimationConfig[layout].default}
className={classNames(
"[--booker-meta-width:280px] [--booker-main-width:480px] [--booker-timeslots-width:240px] lg:[--booker-timeslots-width:280px]",
"[--booker-main-width:480px] [--booker-timeslots-width:240px] lg:[--booker-timeslots-width:280px]",
"bg-muted grid max-w-full items-start overflow-clip dark:[color-scheme:dark] md:flex-row",
layout === "small_calendar" &&
"border-subtle mt-20 min-h-[450px] w-[calc(var(--booker-meta-width)+var(--booker-main-width))] rounded-md border",
layout !== "small_calendar" && "h-auto min-h-screen w-screen"
"border-subtle mt-20 min-h-[450px] w-[calc(var(--booker-meta-width)+var(--booker-main-width))] rounded-md border [--booker-meta-width:280px]",
layout !== "small_calendar" &&
"h-auto min-h-screen w-screen [--booker-meta-width:340px] lg:[--booker-meta-width:424px]",
"transition-[width] duration-300"
)}>
<AnimatePresence>
<StickyOnDesktop key="meta" className="relative z-10">
<BookerSection area="meta" className="max-w-screen w-full md:w-[var(--booker-meta-width)]">
<EventMeta />
{layout !== "small_calendar" && !(layout === "mobile" && bookerState === "booking") && (
<div className=" mt-auto p-6">
<div className=" mt-auto p-5">
<DatePicker />
</div>
)}
@ -154,8 +144,8 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
<BookerSection
key="book-event-form"
area="main"
className="border-subtle sticky top-0 ml-[-1px] h-full p-6 md:w-[var(--booker-main-width)] md:border-l"
{...fadeInUp}
className="border-subtle sticky top-0 ml-[-1px] h-full p-5 md:w-[var(--booker-main-width)] md:border-l"
{...fadeInLeft}
visible={bookerState === "booking" && layout !== "large_timeslots"}>
<BookEventForm onCancel={() => setSelectedTimeslot(null)} />
</BookerSection>
@ -164,9 +154,9 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
key="datepicker"
area="main"
visible={bookerState !== "booking" && layout === "small_calendar"}
{...fadeInUp}
{...fadeInLeft}
initial="visible"
className="md:border-subtle ml-[-1px] h-full flex-shrink p-6 md:border-l lg:w-[var(--booker-main-width)]">
className="md:border-subtle ml-[-1px] h-full flex-shrink p-5 md:border-l lg:w-[var(--booker-main-width)]">
<DatePicker />
</BookerSection>
@ -178,7 +168,7 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
(bookerState === "selecting_date" || bookerState === "selecting_time")
}
className="border-muted sticky top-0 ml-[-1px] h-full md:border-l"
{...fadeInUp}>
{...fadeInLeft}>
<LargeCalendar />
</BookerSection>
@ -190,8 +180,9 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
layout === "large_timeslots"
}
className={classNames(
"border-subtle flex h-full w-full flex-col p-6 pb-0 md:border-l",
layout === "small_calendar" && "h-full overflow-auto md:w-[var(--booker-timeslots-width)]",
"border-subtle flex h-full w-full flex-col p-5 pb-0 md:border-l",
layout === "small_calendar" &&
"scroll-bar h-full overflow-auto md:w-[var(--booker-timeslots-width)]",
layout !== "small_calendar" && "sticky top-0"
)}
ref={timeslotsRef}
@ -204,7 +195,7 @@ const BookerComponent = ({ username, eventSlug, month, rescheduleBooking }: Book
/>
</BookerSection>
</AnimatePresence>
</m.div>
</div>
<m.span
key="logo"

View File

@ -55,7 +55,7 @@ export const AvailableTimeSlots = ({ extraDays, limitHeight, seatsPerTimeslot }:
<div
className={classNames(
limitHeight && "flex-grow md:h-[400px]",
!limitHeight && "flex w-full flex-row gap-4 "
!limitHeight && "flex w-full flex-row gap-4"
)}>
{schedule.isLoading
? // Shows exact amount of days as skeleton.

View File

@ -33,22 +33,20 @@ export const DatePicker = () => {
if (!isLoadedClientSide) return null;
return (
<div className="mt-1">
<DatePickerComponent
isLoading={schedule.isLoading}
onChange={(date: Dayjs) => {
setSelectedDate(date.format("YYYY-MM-DD"));
}}
onMonthChange={(date: Dayjs) => {
setMonth(date.format("YYYY-MM"));
setSelectedDate(date.format("YYYY-MM-DD"));
}}
includedDates={nonEmptyScheduleDays}
locale={i18n.language}
browsingDate={month ? dayjs(month) : undefined}
selected={dayjs(selectedDate)}
weekStart={weekdayToWeekIndex(event?.data?.users?.[0]?.weekStart)}
/>
</div>
<DatePickerComponent
isLoading={schedule.isLoading}
onChange={(date: Dayjs) => {
setSelectedDate(date.format("YYYY-MM-DD"));
}}
onMonthChange={(date: Dayjs) => {
setMonth(date.format("YYYY-MM"));
setSelectedDate(date.format("YYYY-MM-DD"));
}}
includedDates={nonEmptyScheduleDays}
locale={i18n.language}
browsingDate={month ? dayjs(month) : undefined}
selected={dayjs(selectedDate)}
weekStart={weekdayToWeekIndex(event?.data?.users?.[0]?.weekStart)}
/>
);
};

View File

@ -36,7 +36,7 @@ export const EventMeta = () => {
<m.div {...fadeInUp} layout transition={{ ...fadeInUp.transition, delay: 0.3 }}>
<EventMembers schedulingType={event.schedulingType} users={event.users} profile={event.profile} />
<EventTitle className="mt-2 mb-8">{event?.title}</EventTitle>
<div className="space-y-4">
<div className="space-y-4 font-medium">
{rescheduleBooking && (
<EventMetaBlock icon={Calendar}>
{t("former_time")}
@ -65,13 +65,13 @@ export const EventMeta = () => {
)}
<EventDetails event={event} />
<EventMetaBlock
className="cursor-pointer [&_.current-timezone:before]:focus-within:opacity-100 [&_.current-timezone:before]:hover:opacity-100 [&_>svg]:mt-[4px]"
className="cursor-pointer [&_.current-timezone:before]:focus-within:opacity-100 [&_.current-timezone:before]:hover:opacity-100"
contentClassName="relative"
icon={Globe}>
{bookerState === "booking" ? (
<>{timezone}</>
) : (
<span className="current-timezone before:bg-subtle flex items-center justify-center before:absolute before:inset-0 before:left-[-30px] before:top-[-3px] before:bottom-[-3px] before:w-[calc(100%_+_35px)] before:rounded-md before:py-3 before:opacity-0 before:transition-opacity">
<span className="current-timezone before:bg-subtle -mt-[2px] flex h-6 items-center justify-center before:absolute before:inset-0 before:left-[-30px] before:top-[-3px] before:bottom-[-3px] before:w-[calc(100%_+_35px)] before:rounded-md before:py-3 before:opacity-0 before:transition-opacity">
<TimezoneSelect
menuPosition="fixed"
classNames={{

View File

@ -1,5 +1,3 @@
import type { TargetAndTransition } from "framer-motion";
import type { BookerLayout, BookerState } from "./types";
// Framer motion fade in animation configs.
@ -26,7 +24,7 @@ export const fadeInUp = {
type ResizeAnimationConfig = {
[key in BookerLayout]: {
[key in BookerState | "default"]?: TargetAndTransition;
[key in BookerState | "default"]?: React.CSSProperties;
};
};

View File

@ -39,7 +39,7 @@ export const AvailableTimes = ({
return (
<div className={classNames("text-default", className)}>
<header className="bg-muted before:bg-muted mb-8 flex w-full flex-row items-center md:flex-col md:items-start lg:flex-row lg:items-center">
<header className="bg-muted before:bg-muted mb-5 flex w-full flex-row items-center font-medium md:flex-col md:items-start lg:flex-row lg:items-center">
<span className={classNames(isLargeTimeslots && "w-full text-center")}>
<span className="text-emphasis font-semibold">
{nameOfDay(i18n.language, Number(date.format("d")), "short")}
@ -78,7 +78,7 @@ export const AvailableTimes = ({
data-testid="time"
data-time={slot.time}
onClick={() => onTimeSelect(slot.time)}
className="mb-3 flex h-auto min-h-[44px] w-full flex-col justify-center py-2"
className="mb-2 flex h-auto min-h-9 w-full flex-col justify-center py-2"
color="secondary">
{dayjs.utc(slot.time).tz(timezone).format(timeFormat)}
{bookingFull && <p className="text-sm">{t("booking_full")}</p>}

View File

@ -3,7 +3,7 @@ import React from "react";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Info, Clock, CheckSquare, RefreshCcw, CreditCard } from "@calcom/ui/components/icon";
import { Clock, CheckSquare, RefreshCcw, CreditCard } from "@calcom/ui/components/icon";
import type { PublicEvent } from "../../types";
import { EventDetailBlocks } from "../../types";
@ -31,7 +31,7 @@ type EventDetailCustomBlock = {
type EventDetailsProps = EventDetailsPropsBase & (EventDetailDefaultBlock | EventDetailCustomBlock);
interface EventMetaProps {
icon: React.FC<{ className: string }> | string;
icon?: React.FC<{ className: string }> | string;
children: React.ReactNode;
// Emphasises the text in the block. For now only
// applying in dark mode.
@ -80,7 +80,7 @@ export const EventMetaBlock = ({
className="mr-2 mt-[2px] h-4 w-4 flex-shrink-0 [filter:invert(0.5)_brightness(0.5)] dark:[filter:invert(1)_brightness(0.9)]"
/>
) : (
<Icon className="relative z-20 mr-2 mt-[2px] h-4 w-4 flex-shrink-0" />
<>{!!Icon && <Icon className="relative z-20 mr-2 mt-[2px] h-4 w-4 flex-shrink-0" />}</>
)}
<div className={classNames("relative z-10", contentClassName)}>{children}</div>
</div>
@ -115,7 +115,9 @@ export const EventDetails = ({ event, blocks = defaultEventDetailsBlocks }: Even
case EventDetailBlocks.DESCRIPTION:
if (!event.description) return null;
return (
<EventMetaBlock key={block} icon={Info} contentClassName="break-words max-w-full overflow-clip">
<EventMetaBlock
key={block}
contentClassName="break-words max-w-full max-h-[180px] scroll-bar pr-4">
<div dangerouslySetInnerHTML={{ __html: event.description }} />
</EventMetaBlock>
);

View File

@ -31,7 +31,7 @@ export const EventMembers = ({ schedulingType, users, profile }: EventMembersPro
return (
<>
<AvatarGroup size="sm" className="border-muted" items={avatars} />
<p className="text-subtle text-sm">
<p className="text-subtle text-sm font-semibold">
{users
.map((user) => user.name)
.filter((name) => name)

View File

@ -144,7 +144,7 @@ const Days = ({
<div key={`e-${idx}`} />
) : props.isLoading ? (
<button
className=" bg-muted text-muted opcaity-50 absolute top-0 left-0 right-0 bottom-0 mx-auto flex w-full items-center justify-center rounded-sm border-transparent text-center"
className=" bg-muted text-muted opcaity-50 absolute top-0 left-0 right-0 bottom-0 mx-auto flex w-full items-center justify-center rounded-sm border-transparent text-center font-medium"
key={`e-${idx}`}
disabled>
<SkeletonText className="h-4 w-5" />
@ -196,12 +196,12 @@ const DatePicker = ({
return (
<div className={className}>
<div className="mb-4 flex items-center justify-between text-xl font-light">
<div className="mb-1 flex items-center justify-between text-xl">
<span className="text-default w-1/2 text-base">
{browsingDate ? (
<>
<strong className="text-emphasis font-semibold">{month}</strong>{" "}
<span className="text-subtle">{browsingDate.format("YYYY")}</span>
<span className="text-subtle font-medium">{browsingDate.format("YYYY")}</span>
</>
) : (
<SkeletonText className="h-8 w-24" />
@ -235,7 +235,7 @@ const DatePicker = ({
</div>
<div className="border-subtle mb-2 grid grid-cols-7 gap-4 border-t border-b text-center md:mb-0 md:border-0">
{weekdayNames(locale, weekStart, "short").map((weekDay) => (
<div key={weekDay} className="text-emphasis my-4 text-xs uppercase tracking-widest">
<div key={weekDay} className="text-emphasis my-4 text-xs font-medium uppercase tracking-widest">
{weekDay}
</div>
))}