169 lines
5.4 KiB
TypeScript
169 lines
5.4 KiB
TypeScript
import { cubicBezier, useAnimate } from "framer-motion";
|
|
import { useReducedMotion } from "framer-motion";
|
|
import { useEffect } from "react";
|
|
|
|
import type { BookerLayout, BookerState } from "./types";
|
|
|
|
// Framer motion fade in animation configs.
|
|
export const fadeInLeft = {
|
|
variants: {
|
|
visible: { opacity: 1, x: 0 },
|
|
hidden: { opacity: 0, x: 20 },
|
|
},
|
|
initial: "hidden",
|
|
exit: "hidden",
|
|
animate: "visible",
|
|
transition: { ease: "easeInOut", delay: 0.1 },
|
|
};
|
|
export const fadeInUp = {
|
|
variants: {
|
|
visible: { opacity: 1, y: 0 },
|
|
hidden: { opacity: 0, y: 20 },
|
|
},
|
|
initial: "hidden",
|
|
exit: "hidden",
|
|
animate: "visible",
|
|
transition: { ease: "easeInOut", delay: 0.1 },
|
|
};
|
|
|
|
type ResizeAnimationConfig = {
|
|
[key in BookerLayout]: {
|
|
[key in BookerState | "default"]?: React.CSSProperties;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* This configuration is used to animate the grid container for the booker.
|
|
* The object is structured as following:
|
|
*
|
|
* The root property of the object: is the name of the layout
|
|
* (mobile, small_calendar, large_calendar, large_timeslots)
|
|
*
|
|
* The values of these properties are objects that define the animation for each state of the booker.
|
|
* The animation have the same properties as you could pass to the animate prop of framer-motion:
|
|
* @see: https://www.framer.com/motion/animation/
|
|
*/
|
|
export const resizeAnimationConfig: ResizeAnimationConfig = {
|
|
mobile: {
|
|
default: {
|
|
width: "100%",
|
|
minHeight: "0px",
|
|
gridTemplateAreas: `
|
|
"meta"
|
|
"main"
|
|
"timeslots"
|
|
`,
|
|
gridTemplateColumns: "100%",
|
|
},
|
|
},
|
|
small_calendar: {
|
|
default: {
|
|
width: "calc(var(--booker-meta-width) + var(--booker-main-width))",
|
|
minHeight: "450px",
|
|
height: "auto",
|
|
gridTemplateAreas: `"meta main"`,
|
|
gridTemplateColumns: "var(--booker-meta-width) var(--booker-main-width)",
|
|
},
|
|
selecting_time: {
|
|
width: "calc(var(--booker-meta-width) + var(--booker-main-width) + var(--booker-timeslots-width))",
|
|
minHeight: "450px",
|
|
height: "auto",
|
|
gridTemplateAreas: `"meta main timeslots"`,
|
|
gridTemplateColumns: "var(--booker-meta-width) 1fr var(--booker-timeslots-width)",
|
|
},
|
|
},
|
|
large_calendar: {
|
|
default: {
|
|
width: "100vw",
|
|
height: "100vh",
|
|
gridTemplateAreas: `"meta main"`,
|
|
gridTemplateColumns: "var(--booker-meta-width) 1fr",
|
|
},
|
|
},
|
|
large_timeslots: {
|
|
default: {
|
|
width: "100vw",
|
|
height: "100vh",
|
|
gridTemplateAreas: `"meta main"`,
|
|
gridTemplateColumns: "var(--booker-meta-width) 1fr",
|
|
},
|
|
},
|
|
};
|
|
|
|
export const getBookerSizeClassNames = (layout: BookerLayout, bookerState: BookerState) => {
|
|
return [
|
|
// Size settings are abstracted on their own lines purely for readbility.
|
|
// General sizes, used always
|
|
"[--booker-timeslots-width:240px] lg:[--booker-timeslots-width:280px]",
|
|
// Small calendar defaults
|
|
layout === "small_calendar" && "[--booker-meta-width:240px]",
|
|
// Meta column get's wider in booking view to fit the full date on a single row in case
|
|
// of a multi occurance event. Also makes form less wide, which also looks better.
|
|
layout === "small_calendar" &&
|
|
bookerState === "booking" &&
|
|
"[--booker-main-width:420px] lg:[--booker-meta-width:340px]",
|
|
// Smaller meta when not in booking view.
|
|
layout === "small_calendar" &&
|
|
bookerState !== "booking" &&
|
|
"[--booker-main-width:480px] lg:[--booker-meta-width:280px]",
|
|
// Fullscreen view settings.
|
|
layout !== "small_calendar" &&
|
|
"[--booker-main-width:480px] [--booker-meta-width:340px] lg:[--booker-meta-width:424px]",
|
|
];
|
|
};
|
|
|
|
/**
|
|
* This hook returns a ref that should be set on the booker element.
|
|
* Based on that ref this hook animates the size of the booker element with framer motion.
|
|
* It also takes into account the prefers-reduced-motion setting, to not animate when that's set.
|
|
*/
|
|
export const useBookerResizeAnimation = (layout: BookerLayout, state: BookerState) => {
|
|
const prefersReducedMotion = useReducedMotion();
|
|
const [animationScope, animate] = useAnimate();
|
|
|
|
useEffect(() => {
|
|
const animationConfig = resizeAnimationConfig[layout][state] || resizeAnimationConfig[layout].default;
|
|
|
|
if (!animationScope.current) return;
|
|
|
|
const animatedProperties = {
|
|
height: animationConfig?.height || "auto",
|
|
};
|
|
|
|
const nonAnimatedProperties = {
|
|
// 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,
|
|
gridTemplateColumns: animationConfig?.gridTemplateColumns,
|
|
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,
|
|
}
|
|
);
|
|
} else {
|
|
animate(animationScope.current, nonAnimatedProperties, {
|
|
duration: 0,
|
|
});
|
|
animate(animationScope.current, animatedProperties, {
|
|
duration: 0.5,
|
|
ease: cubicBezier(0.4, 0, 0.2, 1),
|
|
});
|
|
}
|
|
}, [animate, animationScope, layout, prefersReducedMotion, state]);
|
|
|
|
return animationScope;
|
|
};
|