️ 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 { useTranslation } from "next-i18next";
import posthog from "posthog-js";
@ -97,7 +97,7 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
</div>
<AnimatePresence initial={false}>
{isSharingVisible ? (
<motion.div
<m.div
initial={{
opacity: 0,
scale: 0.8,
@ -122,13 +122,13 @@ export const AdminControls = (props: { children?: React.ReactNode }) => {
setIsSharingVisible(false);
}}
/>
</motion.div>
</m.div>
) : null}
</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}
{props.children}
</motion.div>
</m.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 Link from "next/link";
import * as React from "react";
@ -14,7 +14,7 @@ const CookieConsentPopover: React.VoidFunctionComponent = () => {
return ReactDOM.createPortal(
<AnimatePresence>
{visible ? (
<motion.div
<m.div
variants={{
enter: {
opacity: 1,
@ -54,7 +54,7 @@ const CookieConsentPopover: React.VoidFunctionComponent = () => {
OK
</button>
</div>
</motion.div>
</m.div>
) : null}
</AnimatePresence>,
getPortal(),

View file

@ -1,5 +1,5 @@
/* 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 * as React from "react";
@ -46,13 +46,13 @@ const Hero: React.VoidFunctionComponent = () => {
<UserAvatarProvider seed="mock" names={names}>
<DayjsProvider>
<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"
initial={{ opacity: 0, width: 100, scale: 1.1, x: 480 }}
animate={{ opacity: 1, x: 381 }}
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"
initial={{
opacity: 0,
@ -65,15 +65,15 @@ const Hero: React.VoidFunctionComponent = () => {
>
{t("perfect")} 🤩
<ScribbleArrow className="absolute -right-8 top-3 text-slate-400" />
</motion.div>
<motion.div
</m.div>
<m.div
className="rounded-lg"
transition={{ type: "spring", delay: 0.5 }}
initial={{ opacity: 0, translateY: -100 }}
animate={{ opacity: 1, translateY: 0 }}
>
<PollDemo />
</motion.div>
</m.div>
</div>
</DayjsProvider>
</UserAvatarProvider>

View file

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

View file

@ -1,23 +1,42 @@
import { AnimatePresence, domAnimation, LazyMotion, m } from "framer-motion";
import { useRouter } from "next/router";
import React from "react";
import { DayjsProvider } from "@/utils/dayjs";
import { NextPageWithLayout } from "../../types";
import ModalProvider from "../modal/modal-provider";
import { UserProvider } from "../user-provider";
import { MobileNavigation } from "./standard-layout/mobile-navigation";
const StandardLayout: React.VoidFunctionComponent<{
children?: React.ReactNode;
}> = ({ children, ...rest }) => {
const router = useRouter();
return (
<UserProvider>
<DayjsProvider>
<div className={"bg-pattern relative min-h-full"} {...rest}>
<MobileNavigation />
<div className="mx-auto max-w-4xl">{children}</div>
</div>
</DayjsProvider>
</UserProvider>
<LazyMotion features={domAnimation}>
<UserProvider>
<DayjsProvider>
<ModalProvider>
<div className={"bg-pattern relative min-h-full"} {...rest}>
<MobileNavigation />
<div className="mx-auto max-w-4xl">
<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 { AnimatePresence, motion } from "framer-motion";
import { AnimatePresence, m } from "framer-motion";
import * as React from "react";
import X from "@/components/icons/x.svg";
@ -47,19 +47,19 @@ const Modal: React.VoidFunctionComponent<ModalProps> = ({
if (overlayClosable) onCancel?.();
}}
>
<motion.div
<m.div
transition={{ duration: 0.5 }}
className="flex min-h-screen items-center justify-center"
>
<Dialog.Overlay
as={motion.div}
as={m.div}
transition={{ duration: 0.5 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-0 bg-slate-900/25"
/>
<motion.div
<m.div
transition={{ duration: 0.1 }}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
@ -116,8 +116,8 @@ const Modal: React.VoidFunctionComponent<ModalProps> = ({
</div>
) : null}
</div>
</motion.div>
</motion.div>
</m.div>
</m.div>
</Dialog>
) : null}
</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 * as React from "react";
import { useMeasure } from "react-use";
@ -191,7 +191,7 @@ const Poll: React.VoidFunctionComponent = () => {
{shouldShowNewParticipantForm &&
!poll.closed &&
!editingParticipantId ? (
<motion.div
<m.div
variants={{
hidden: { height: 0, y: -50, opacity: 0 },
visible: { height: "auto", y: 0, opacity: 1 },
@ -211,7 +211,7 @@ const Poll: React.VoidFunctionComponent = () => {
});
}}
/>
</motion.div>
</m.div>
) : null}
</AnimatePresence>
{participants.map((participant, i) => {
@ -247,7 +247,7 @@ const Poll: React.VoidFunctionComponent = () => {
</div>
<AnimatePresence initial={false}>
{shouldShowNewParticipantForm || editingParticipantId ? (
<motion.div
<m.div
variants={{
hidden: { height: 0, y: 30, opacity: 0 },
visible: { height: "auto", y: 0, opacity: 1 },
@ -281,7 +281,7 @@ const Poll: React.VoidFunctionComponent = () => {
{shouldShowNewParticipantForm ? t("continue") : t("save")}
</Button>
</div>
</motion.div>
</m.div>
) : null}
</AnimatePresence>
</div>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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