diff --git a/app/hooks/useAnimateListEntry.tsx b/app/hooks/useAnimateListEntry.tsx new file mode 100644 index 000000000..e6180d448 --- /dev/null +++ b/app/hooks/useAnimateListEntry.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { spring } from "react-flip-toolkit"; + +export function useAnimateListEntry(className: string) { + const containerRef = React.useRef(null); + React.useEffect(() => { + const squares = [ + ...containerRef.current!.querySelectorAll(className), + ] as HTMLElement[]; + squares.forEach((el, i) => { + spring({ + config: "wobbly", + values: { + translateY: [-10, 0], + opacity: [0, 1], + }, + onUpdate: (value) => { + const { translateY, opacity } = value as { + translateY: number; + opacity: number; + }; + el.style.opacity = String(opacity); + el.style.transform = `translateY(${translateY}px)`; + }, + delay: i * 25, + }); + }); + }, [className]); + + return containerRef; +} diff --git a/app/routes/badges.tsx b/app/routes/badges.tsx index 4d23593e6..2f40e6752 100644 --- a/app/routes/badges.tsx +++ b/app/routes/badges.tsx @@ -8,6 +8,7 @@ import type { All } from "~/db/models/badges.server"; import styles from "~/styles/badges.css"; import { BORZOIC_TWITTER, FAQ_PAGE } from "~/utils/urls"; import { Trans, useTranslation } from "react-i18next"; +import { useAnimateListEntry } from "~/hooks/useAnimateListEntry"; export const links: LinksFunction = () => { return [{ rel: "stylesheet", href: styles }]; @@ -29,9 +30,11 @@ export default function BadgesPageLayout() { const { t } = useTranslation("badges"); const data = useLoaderData(); + const containerRef = useAnimateListEntry(".badges__nav-link"); + return (
-
+
{data.badges.map((badge) => ( diff --git a/app/styles/badges.css b/app/styles/badges.css index b51cdc1e2..3c83167af 100644 --- a/app/styles/badges.css +++ b/app/styles/badges.css @@ -17,6 +17,10 @@ gap: var(--s-2); } +.badges__nav-link { + opacity: 0; +} + .badges__nav-link.active { display: none; }