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:
parent
72f72d86b2
commit
524cefcbad
|
@ -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",
|
||||
|
|
|
@ -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 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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>}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
))}
|
||||
|
|
Loading…
Reference in New Issue
Block a user