️ Lazy load animation library to help reduce bundle size (#502)

This commit is contained in:
Luke Vella 2023-02-10 09:24:01 +00:00 committed by GitHub
parent c2c000f770
commit 696cd44ba1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 115 additions and 83 deletions

View file

@ -1,4 +1,4 @@
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import posthog from "posthog-js"; import posthog from "posthog-js";
@ -97,7 +97,7 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
</div> </div>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{isSharingVisible ? ( {isSharingVisible ? (
<motion.div <m.div
initial={{ initial={{
opacity: 0, opacity: 0,
scale: 0.8, scale: 0.8,
@ -122,13 +122,13 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
setIsSharingVisible(false); setIsSharingVisible(false);
}} }}
/> />
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
<motion.div className="relative z-10 space-y-4" layout="position"> <m.div className="relative z-10 space-y-4" layout="position">
{poll.verified === false ? <UnverifiedPollNotice /> : null} {poll.verified === false ? <UnverifiedPollNotice /> : null}
{props.children} {props.children}
</motion.div> </m.div>
</div> </div>
); );
}; };

View file

@ -1,4 +1,4 @@
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import Link from "next/link"; import Link from "next/link";
import * as React from "react"; import * as React from "react";
@ -14,7 +14,7 @@ const CookieConsentPopover: React.VoidFunctionComponent = () => {
return ReactDOM.createPortal( return ReactDOM.createPortal(
<AnimatePresence> <AnimatePresence>
{visible ? ( {visible ? (
<motion.div <m.div
variants={{ variants={{
enter: { enter: {
opacity: 1, opacity: 1,
@ -54,7 +54,7 @@ const CookieConsentPopover: React.VoidFunctionComponent = () => {
OK OK
</button> </button>
</div> </div>
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence>, </AnimatePresence>,
getPortal(), getPortal(),

View file

@ -1,5 +1,5 @@
/* eslint-disable @next/next/no-html-link-for-pages */ /* eslint-disable @next/next/no-html-link-for-pages */
import { motion } from "framer-motion"; import { m } from "framer-motion";
import { Trans, useTranslation } from "next-i18next"; import { Trans, useTranslation } from "next-i18next";
import * as React from "react"; import * as React from "react";
@ -46,13 +46,13 @@ const Hero: React.VoidFunctionComponent = () => {
<UserAvatarProvider seed="mock" names={names}> <UserAvatarProvider seed="mock" names={names}>
<DayjsProvider> <DayjsProvider>
<div className="relative inline-block"> <div className="relative inline-block">
<motion.div <m.div
className="absolute z-20 h-full rounded-2xl border-4 border-primary-500 bg-primary-200/10 shadow-md" className="absolute z-20 h-full rounded-2xl border-4 border-primary-500 bg-primary-200/10 shadow-md"
initial={{ opacity: 0, width: 100, scale: 1.1, x: 480 }} initial={{ opacity: 0, width: 100, scale: 1.1, x: 480 }}
animate={{ opacity: 1, x: 381 }} animate={{ opacity: 1, x: 381 }}
transition={{ type: "spring", delay: 1 }} transition={{ type: "spring", delay: 1 }}
/> />
<motion.div <m.div
className="absolute z-20 rounded-full bg-primary-500 py-1 px-3 text-sm text-slate-100" className="absolute z-20 rounded-full bg-primary-500 py-1 px-3 text-sm text-slate-100"
initial={{ initial={{
opacity: 0, opacity: 0,
@ -65,15 +65,15 @@ const Hero: React.VoidFunctionComponent = () => {
> >
{t("perfect")} 🤩 {t("perfect")} 🤩
<ScribbleArrow className="absolute -right-8 top-3 text-slate-400" /> <ScribbleArrow className="absolute -right-8 top-3 text-slate-400" />
</motion.div> </m.div>
<motion.div <m.div
className="rounded-lg" className="rounded-lg"
transition={{ type: "spring", delay: 0.5 }} transition={{ type: "spring", delay: 0.5 }}
initial={{ opacity: 0, translateY: -100 }} initial={{ opacity: 0, translateY: -100 }}
animate={{ opacity: 1, translateY: 0 }} animate={{ opacity: 1, translateY: 0 }}
> >
<PollDemo /> <PollDemo />
</motion.div> </m.div>
</div> </div>
</DayjsProvider> </DayjsProvider>
</UserAvatarProvider> </UserAvatarProvider>

View file

@ -1,4 +1,5 @@
import clsx from "clsx"; import clsx from "clsx";
import { domAnimation, LazyMotion } from "framer-motion";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { Trans, useTranslation } from "next-i18next"; import { Trans, useTranslation } from "next-i18next";
@ -62,33 +63,35 @@ const PageLayout: React.VoidFunctionComponent<PageLayoutProps> = ({
}) => { }) => {
const { t } = useTranslation("homepage"); const { t } = useTranslation("homepage");
return ( return (
<div className="bg-pattern min-h-full overflow-x-hidden"> <LazyMotion features={domAnimation}>
<div className="mx-auto flex max-w-7xl items-center py-8 px-8"> <div className="bg-pattern min-h-full overflow-x-hidden">
<div className="grow"> <div className="mx-auto flex max-w-7xl items-center py-8 px-8">
<div className="relative inline-block"> <div className="grow">
<Link className="inline-block rounded" href="/"> <div className="relative inline-block">
<Logo className="w-40 text-primary-500" alt="Rallly" /> <Link className="inline-block rounded" href="/">
</Link> <Logo className="w-40 text-primary-500" alt="Rallly" />
<span className="absolute -bottom-6 right-0 text-sm text-slate-400 transition-colors"> </Link>
<Trans t={t} i18nKey="3Ls" components={{ e: <em /> }} /> <span className="absolute -bottom-6 right-0 text-sm text-slate-400 transition-colors">
</span> <Trans t={t} i18nKey="3Ls" components={{ e: <em /> }} />
</span>
</div>
</div> </div>
<Menu className="hidden items-center space-x-8 sm:flex" />
<Popover>
<PopoverTrigger asChild={true}>
<button className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2 sm:hidden">
<DotsVertical className="w-5" />
</button>
</PopoverTrigger>
<PopoverContent align="end">
<Menu className="flex flex-col space-y-2 p-2" />
</PopoverContent>
</Popover>
</div> </div>
<Menu className="hidden items-center space-x-8 sm:flex" /> <div className="md:min-h-[calc(100vh-460px)]">{children}</div>
<Popover> <Footer />
<PopoverTrigger asChild={true}>
<button className="text-gray-400 transition-colors hover:text-primary-500 hover:no-underline hover:underline-offset-2 sm:hidden">
<DotsVertical className="w-5" />
</button>
</PopoverTrigger>
<PopoverContent align="end">
<Menu className="flex flex-col space-y-2 p-2" />
</PopoverContent>
</Popover>
</div> </div>
<div className="md:min-h-[calc(100vh-460px)]">{children}</div> </LazyMotion>
<Footer />
</div>
); );
}; };

View file

@ -1,23 +1,42 @@
import { AnimatePresence, domAnimation, LazyMotion, m } from "framer-motion";
import { useRouter } from "next/router";
import React from "react"; import React from "react";
import { DayjsProvider } from "@/utils/dayjs"; import { DayjsProvider } from "@/utils/dayjs";
import { NextPageWithLayout } from "../../types"; import { NextPageWithLayout } from "../../types";
import ModalProvider from "../modal/modal-provider";
import { UserProvider } from "../user-provider"; import { UserProvider } from "../user-provider";
import { MobileNavigation } from "./standard-layout/mobile-navigation"; import { MobileNavigation } from "./standard-layout/mobile-navigation";
const StandardLayout: React.VoidFunctionComponent<{ const StandardLayout: React.VoidFunctionComponent<{
children?: React.ReactNode; children?: React.ReactNode;
}> = ({ children, ...rest }) => { }> = ({ children, ...rest }) => {
const router = useRouter();
return ( return (
<UserProvider> <LazyMotion features={domAnimation}>
<DayjsProvider> <UserProvider>
<div className={"bg-pattern relative min-h-full"} {...rest}> <DayjsProvider>
<MobileNavigation /> <ModalProvider>
<div className="mx-auto max-w-4xl">{children}</div> <div className={"bg-pattern relative min-h-full"} {...rest}>
</div> <MobileNavigation />
</DayjsProvider> <div className="mx-auto max-w-4xl">
</UserProvider> <AnimatePresence initial={false} exitBeforeEnter={true}>
<m.div
key={router.asPath}
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -50 }}
>
{children}
</m.div>
</AnimatePresence>
</div>
</div>
</ModalProvider>
</DayjsProvider>
</UserProvider>
</LazyMotion>
); );
}; };

View file

@ -1,5 +1,5 @@
import { Dialog } from "@headlessui/react"; import { Dialog } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import * as React from "react"; import * as React from "react";
import X from "@/components/icons/x.svg"; import X from "@/components/icons/x.svg";
@ -47,19 +47,19 @@ const Modal: React.VoidFunctionComponent<ModalProps> = ({
if (overlayClosable) onCancel?.(); if (overlayClosable) onCancel?.();
}} }}
> >
<motion.div <m.div
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
className="flex min-h-screen items-center justify-center" className="flex min-h-screen items-center justify-center"
> >
<Dialog.Overlay <Dialog.Overlay
as={motion.div} as={m.div}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="fixed inset-0 z-0 bg-slate-900/25" className="fixed inset-0 z-0 bg-slate-900/25"
/> />
<motion.div <m.div
transition={{ duration: 0.1 }} transition={{ duration: 0.1 }}
initial={{ opacity: 0, scale: 0.9 }} initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }} animate={{ opacity: 1, scale: 1 }}
@ -116,8 +116,8 @@ const Modal: React.VoidFunctionComponent<ModalProps> = ({
</div> </div>
) : null} ) : null}
</div> </div>
</motion.div> </m.div>
</motion.div> </m.div>
</Dialog> </Dialog>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>

View file

@ -1,4 +1,4 @@
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import { Trans, useTranslation } from "next-i18next"; import { Trans, useTranslation } from "next-i18next";
import * as React from "react"; import * as React from "react";
import { useMeasure } from "react-use"; import { useMeasure } from "react-use";
@ -191,7 +191,7 @@ const Poll: React.VoidFunctionComponent = () => {
{shouldShowNewParticipantForm && {shouldShowNewParticipantForm &&
!poll.closed && !poll.closed &&
!editingParticipantId ? ( !editingParticipantId ? (
<motion.div <m.div
variants={{ variants={{
hidden: { height: 0, y: -50, opacity: 0 }, hidden: { height: 0, y: -50, opacity: 0 },
visible: { height: "auto", y: 0, opacity: 1 }, visible: { height: "auto", y: 0, opacity: 1 },
@ -211,7 +211,7 @@ const Poll: React.VoidFunctionComponent = () => {
}); });
}} }}
/> />
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
{participants.map((participant, i) => { {participants.map((participant, i) => {
@ -247,7 +247,7 @@ const Poll: React.VoidFunctionComponent = () => {
</div> </div>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{shouldShowNewParticipantForm || editingParticipantId ? ( {shouldShowNewParticipantForm || editingParticipantId ? (
<motion.div <m.div
variants={{ variants={{
hidden: { height: 0, y: 30, opacity: 0 }, hidden: { height: 0, y: 30, opacity: 0 },
visible: { height: "auto", y: 0, opacity: 1 }, visible: { height: "auto", y: 0, opacity: 1 },
@ -281,7 +281,7 @@ const Poll: React.VoidFunctionComponent = () => {
{shouldShowNewParticipantForm ? t("continue") : t("save")} {shouldShowNewParticipantForm ? t("continue") : t("save")}
</Button> </Button>
</div> </div>
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
</div> </div>

View file

@ -1,5 +1,5 @@
import clsx from "clsx"; import clsx from "clsx";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import React from "react"; import React from "react";
import { usePollContext } from "./poll-context"; import { usePollContext } from "./poll-context";
@ -16,7 +16,7 @@ const ControlledScrollArea: React.VoidFunctionComponent<{
style={{ width: availableSpace, maxWidth: availableSpace }} style={{ width: availableSpace, maxWidth: availableSpace }}
> >
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
<motion.div <m.div
className="flex h-full" className="flex h-full"
transition={{ transition={{
type: "spring", type: "spring",
@ -26,7 +26,7 @@ const ControlledScrollArea: React.VoidFunctionComponent<{
animate={{ x: scrollPosition * -1 }} animate={{ x: scrollPosition * -1 }}
> >
{children} {children}
</motion.div> </m.div>
</AnimatePresence> </AnimatePresence>
</div> </div>
); );

View file

@ -1,5 +1,5 @@
import { Listbox } from "@headlessui/react"; import { Listbox } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import * as React from "react"; import * as React from "react";
import { FormProvider, useForm } from "react-hook-form"; import { FormProvider, useForm } from "react-hook-form";
@ -131,7 +131,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
<ChevronDown className="h-5 shrink-0" /> <ChevronDown className="h-5 shrink-0" />
</Listbox.Button> </Listbox.Button>
<Listbox.Options <Listbox.Options
as={motion.div} as={m.div}
transition={{ transition={{
duration: 0.1, duration: 0.1,
}} }}
@ -257,7 +257,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
/> />
<AnimatePresence> <AnimatePresence>
{isEditing ? ( {isEditing ? (
<motion.div <m.div
variants={{ variants={{
hidden: { opacity: 0, y: -100, height: 0 }, hidden: { opacity: 0, y: -100, height: 0 },
visible: { opacity: 1, y: 0, height: "auto" }, visible: { opacity: 1, y: 0, height: "auto" },
@ -281,7 +281,7 @@ const MobilePoll: React.VoidFunctionComponent = () => {
{selectedParticipantId ? t("save") : t("continue")} {selectedParticipantId ? t("save") : t("continue")}
</Button> </Button>
</div> </div>
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
</form> </form>

View file

@ -1,6 +1,6 @@
import { Participant, VoteType } from "@prisma/client"; import { Participant, VoteType } from "@prisma/client";
import clsx from "clsx"; import clsx from "clsx";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import * as React from "react"; import * as React from "react";
@ -33,7 +33,7 @@ const CollapsibleContainer: React.VoidFunctionComponent<{
return ( return (
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{expanded ? ( {expanded ? (
<motion.div <m.div
variants={{ variants={{
collapsed: { collapsed: {
width: 0, width: 0,
@ -50,7 +50,7 @@ const CollapsibleContainer: React.VoidFunctionComponent<{
className={className} className={className}
> >
{children} {children}
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
); );
@ -61,14 +61,14 @@ const PopInOut: React.VoidFunctionComponent<{
className?: string; className?: string;
}> = ({ children, className }) => { }> = ({ children, className }) => {
return ( return (
<motion.div <m.div
initial={{ scale: 0 }} initial={{ scale: 0 }}
animate={{ scale: 1 }} animate={{ scale: 1 }}
exit={{ scale: 0 }} exit={{ scale: 0 }}
className={clsx(className)} className={clsx(className)}
> >
{children} {children}
</motion.div> </m.div>
); );
}; };
@ -83,7 +83,7 @@ const PollOptionVoteSummary: React.VoidFunctionComponent<{ optionId: string }> =
participantsWhoVotedYes.length + participantsWhoVotedIfNeedBe.length === participantsWhoVotedYes.length + participantsWhoVotedIfNeedBe.length ===
0; 0;
return ( return (
<motion.div <m.div
transition={{ transition={{
duration: 0.1, duration: 0.1,
}} }}
@ -145,7 +145,7 @@ const PollOptionVoteSummary: React.VoidFunctionComponent<{ optionId: string }> =
</div> </div>
)} )}
</div> </div>
</motion.div> </m.div>
); );
}; };
@ -228,7 +228,7 @@ const PollOption: React.VoidFunctionComponent<PollOptionProps> = ({
<div className="mr-3 shrink-0 grow">{children}</div> <div className="mr-3 shrink-0 grow">{children}</div>
<AnimatePresence initial={false}> <AnimatePresence initial={false}>
{editable ? null : ( {editable ? null : (
<motion.button <m.button
exit={{ opacity: 0, x: -10 }} exit={{ opacity: 0, x: -10 }}
type="button" type="button"
onTouchStart={(e) => e.stopPropagation()} onTouchStart={(e) => e.stopPropagation()}
@ -249,7 +249,7 @@ const PollOption: React.VoidFunctionComponent<PollOptionProps> = ({
}, },
)} )}
/> />
</motion.button> </m.button>
)} )}
</AnimatePresence> </AnimatePresence>
<div className="mx-3"> <div className="mx-3">

View file

@ -1,4 +1,4 @@
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import * as React from "react"; import * as React from "react";
import { usePrevious } from "react-use"; import { usePrevious } from "react-use";
@ -23,7 +23,7 @@ export const ScoreSummary: React.VoidFunctionComponent<PopularityScoreProps> =
> >
<CheckCircle className="inline-block h-4 text-slate-300 transition-opacity" /> <CheckCircle className="inline-block h-4 text-slate-300 transition-opacity" />
<AnimatePresence initial={false} exitBeforeEnter={true}> <AnimatePresence initial={false} exitBeforeEnter={true}>
<motion.span <m.span
transition={{ transition={{
duration: 0.1, duration: 0.1,
}} }}
@ -40,7 +40,7 @@ export const ScoreSummary: React.VoidFunctionComponent<PopularityScoreProps> =
className="relative" className="relative"
> >
{score} {score}
</motion.span> </m.span>
</AnimatePresence> </AnimatePresence>
</div> </div>
); );

View file

@ -1,4 +1,4 @@
import { motion } from "framer-motion"; import { m } from "framer-motion";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import posthog from "posthog-js"; import posthog from "posthog-js";
import * as React from "react"; import * as React from "react";
@ -16,7 +16,7 @@ export interface UserDetailsProps {
email?: string; email?: string;
} }
const MotionButton = motion(Button); const MotionButton = m(Button);
export const UserDetails: React.VoidFunctionComponent<UserDetailsProps> = ({ export const UserDetails: React.VoidFunctionComponent<UserDetailsProps> = ({
userId, userId,

View file

@ -12,7 +12,7 @@ import {
useRole, useRole,
} from "@floating-ui/react-dom-interactions"; } from "@floating-ui/react-dom-interactions";
import clsx from "clsx"; import clsx from "clsx";
import { AnimatePresence, motion } from "framer-motion"; import { AnimatePresence, m } from "framer-motion";
import * as React from "react"; import * as React from "react";
import { preventWidows } from "@/utils/prevent-widows"; import { preventWidows } from "@/utils/prevent-widows";
@ -104,7 +104,7 @@ const Tooltip: React.VoidFunctionComponent<TooltipProps> = ({
<FloatingPortal> <FloatingPortal>
<AnimatePresence> <AnimatePresence>
{open ? ( {open ? (
<motion.div <m.div
className="z-30 rounded-md bg-slate-700 px-3 py-2 text-slate-200 shadow-md" className="z-30 rounded-md bg-slate-700 px-3 py-2 text-slate-200 shadow-md"
initial="hidden" initial="hidden"
transition={{ transition={{
@ -140,7 +140,7 @@ const Tooltip: React.VoidFunctionComponent<TooltipProps> = ({
}} }}
/> />
{typeof content === "string" ? preventWidows(content) : content} {typeof content === "string" ? preventWidows(content) : content}
</motion.div> </m.div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
</FloatingPortal> </FloatingPortal>

View file

@ -4,6 +4,7 @@ import "~/style.css";
import { Inter, Noto_Sans_Mono } from "@next/font/google"; import { Inter, Noto_Sans_Mono } from "@next/font/google";
import { inject } from "@vercel/analytics"; import { inject } from "@vercel/analytics";
import { domAnimation, LazyMotion, m } from "framer-motion";
import { NextPage } from "next"; import { NextPage } from "next";
import { AppProps } from "next/app"; import { AppProps } from "next/app";
import Head from "next/head"; import Head from "next/head";
@ -15,7 +16,6 @@ import { Toaster } from "react-hot-toast";
import Maintenance from "@/components/maintenance"; import Maintenance from "@/components/maintenance";
import { useCrispChat } from "../components/crisp-chat"; import { useCrispChat } from "../components/crisp-chat";
import ModalProvider from "../components/modal/modal-provider";
import { NextPageWithLayout } from "../types"; import { NextPageWithLayout } from "../types";
import { absoluteUrl } from "../utils/absolute-url"; import { absoluteUrl } from "../utils/absolute-url";
import { UserSession } from "../utils/auth"; import { UserSession } from "../utils/auth";
@ -89,7 +89,17 @@ const MyApp: NextPage<AppPropsWithLayout> = ({ Component, pageProps }) => {
--font-noto: ${noto.style.fontFamily}; --font-noto: ${noto.style.fontFamily};
} }
`}</style> `}</style>
<ModalProvider>{getLayout(<Component {...pageProps} />)}</ModalProvider> <LazyMotion features={domAnimation}>
{getLayout(
<m.div
initial={{ opacity: 0, y: -50 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 50 }}
>
<Component {...pageProps} />
</m.div>,
)}
</LazyMotion>
</> </>
); );
}; };