Instantly share code, notes, and snippets.
Created
June 24, 2024 09:45
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save samuelhorn/ca5c1c2668214864b4887ebb1bfcc7f1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use client"; | |
import clsx from "clsx"; | |
import { AnimatePresence, motion } from "framer-motion"; | |
import { useEffect, useState } from "react"; | |
import CloseIcon from "@/assets/svg/close.svg"; | |
import { usePrismicAnalytics } from "@/lib/context/analytics"; | |
import { asText, Content, isFilled } from "@prismicio/client"; | |
import { PrismicNextImage } from "@prismicio/next"; | |
import { PrismicLink } from "@prismicio/react"; | |
import type { SliceZoneContext } from "@/lib/types"; | |
type BannerProps = { | |
banners: Content.BannerDocument[]; | |
page?: string; | |
theme: SliceZoneContext["theme"]; | |
noBanner?: boolean; | |
}; | |
export const Banners = ({ banners, page, theme, noBanner }: BannerProps) => { | |
const { segmentCtaEvent } = usePrismicAnalytics(); | |
// Keep track of which banners a user has closed | |
const [closedBanners, setClosedBanners] = useState<string[]>([]); | |
// Keep track of whether the component has loaded | |
const [loaded, setLoaded] = useState(false); | |
useEffect(() => { | |
// Load the closed banners from users local storage | |
const storedBanners = localStorage.getItem("closedBanners"); | |
// If there are closed banners, set them in state | |
if (storedBanners) { | |
setClosedBanners(JSON.parse(storedBanners)); | |
} | |
// Set the component as loaded | |
setLoaded(true); | |
document.querySelector("#layoutComponent")?.classList.remove("opacity-0"); | |
}, []); | |
// If the component hasn't loaded, return null | |
if (!loaded) return null; | |
// When a user closes a banner, add it to the closedBanners array together with the previous closed banners and save it to local storage | |
const handleClose = (id: string) => { | |
const newClosedBanners = [...closedBanners, id]; | |
setClosedBanners(newClosedBanners); | |
localStorage.setItem("closedBanners", JSON.stringify(newClosedBanners)); | |
}; | |
// Map through the and create an array of objects with the banner id, if its contextual, and the pages it should be shown or hidden on | |
const bannerInfo = banners.flatMap((banner) => { | |
const pageIds = banner.data.pages.flatMap((page) => { | |
const linkedPage = page.page as any as Content.AllDocumentTypes; | |
const hasPages = | |
isFilled.group(banner.data.pages) && | |
isFilled.contentRelationship(page.page); | |
return hasPages ? linkedPage.id : []; | |
}); | |
return { | |
id: banner.id, | |
include: banner.data.contextual, | |
pages: pageIds | |
}; | |
}); | |
// Filter the banners based on the pages they should be shown on and if they have been closed | |
const filteredBanners = bannerInfo.filter( | |
(banner) => | |
!closedBanners.includes(banner.id) && | |
(banner.include | |
? banner.pages.includes(page!) | |
: !banner.pages.includes(page!)) | |
); | |
return ( | |
page && | |
!noBanner && | |
banners && ( | |
<div className="px-3 flex flex-col" id="siteBanner"> | |
<AnimatePresence initial={false}> | |
{filteredBanners.map((banner) => { | |
const bannerContent = banners.find((b) => b.id === banner.id); | |
return ( | |
<motion.div | |
initial={{ opacity: 0, height: 0 }} | |
animate={{ opacity: 1, height: "auto" }} | |
exit={{ opacity: 0, height: 0 }} | |
transition={{ duration: 0.25 }} | |
key={bannerContent!.id} | |
className="group" | |
> | |
<div | |
className={clsx( | |
"flex items-center justify-center p-3 pr-12 border rounded-md gap-2 mt-1.5 group-first:mt-3 relative", | |
{ | |
"bg-gray-F7 border-gray-EE": theme === "light", | |
"bg-gray-1F border-gray-30": theme === "dark" | |
} | |
)} | |
> | |
<div> | |
{isFilled.image(bannerContent!.data.avatar) && ( | |
<PrismicNextImage | |
field={bannerContent!.data.avatar} | |
className="rounded-full w-8 h-8 inline-block mr-2" | |
/> | |
)} | |
{isFilled.keyText(bannerContent!.data.tag) && ( | |
<span | |
className={clsx( | |
"text-xs mr-2 -translate-y-0.5 inline-block font-headings py-0.5 px-2.5 rounded-full text-white whitespace-nowrap", | |
{ | |
"bg-primary-purple": | |
bannerContent!.data.tag_color === "purple", | |
"bg-primary-blue": | |
bannerContent!.data.tag_color === "blue", | |
"bg-primary-green": | |
bannerContent!.data.tag_color === "green", | |
"bg-primary-orange": | |
bannerContent!.data.tag_color === "orange", | |
"bg-primary-pink": | |
bannerContent!.data.tag_color === "pink" | |
} | |
)} | |
> | |
{bannerContent!.data.tag} | |
</span> | |
)} | |
<span | |
className={clsx({ | |
"text-gray-50": theme === "light", | |
"text-gray-A4": theme === "dark" | |
})} | |
> | |
{asText(bannerContent!.data.message)} | |
</span> | |
{isFilled.link(bannerContent!.data.link) && ( | |
<PrismicLink | |
field={bannerContent!.data.link} | |
className="ml-2 underline underline-offset-8 focus:ring-4 hover:underline-offset-4" | |
onClick={() => { | |
segmentCtaEvent("CTA Clicked", { | |
ctaType: "tertiary link", | |
ctaPosition: "banner", | |
ctaText: isFilled.keyText( | |
bannerContent!.data.link_label | |
) | |
? bannerContent!.data.link_label | |
: "", | |
ctaUrl: isFilled.link(bannerContent!.data.link) | |
? bannerContent!.data.link.url | |
: "" | |
}); | |
}} | |
> | |
{bannerContent!.data.link_label} | |
</PrismicLink> | |
)} | |
</div> | |
<button | |
onClick={() => handleClose(bannerContent!.id)} | |
className="w-12 h-12 p-3 absolute right-0 top-1/2 -translate-y-1/2 cursor-pointer" | |
> | |
<CloseIcon className="w-6 h-6 pointer-events-none" /> | |
<span className="sr-only">Close</span> | |
</button> | |
</div> | |
</motion.div> | |
); | |
})} | |
</AnimatePresence> | |
</div> | |
) | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment