Spaces:
Build error
Build error
| "use client"; | |
| import copy from "copy-to-clipboard"; | |
| import { animate, cubicBezier } from "motion"; | |
| import { AnimatePresence, motion } from "motion/react"; | |
| import Link from "next/link"; | |
| import { useCallback, useEffect, useRef, useState } from "react"; | |
| import FirecrawlIcon from "@/components/shared/firecrawl-icon/firecrawl-icon"; | |
| import Logo from "@/components/shared/header/_svg/Logo"; | |
| import { useHeaderContext } from "@/components/shared/header/HeaderContext"; | |
| import { cn } from "@/utils/cn"; | |
| import Download from "./_svg/Download"; | |
| import Guidelines from "./_svg/Guidelines"; | |
| import Icon from "./_svg/Icon"; | |
| export default function HeaderBrandKit() { | |
| const [open, setOpen] = useState(false); | |
| const { dropdownContent, clearDropdown } = useHeaderContext(); | |
| useEffect(() => { | |
| document.addEventListener("click", () => { | |
| setOpen(false); | |
| }); | |
| }, [open]); | |
| useEffect(() => { | |
| if (dropdownContent) { | |
| setOpen(false); | |
| } | |
| }, [dropdownContent]); | |
| return ( | |
| <div className="relative"> | |
| <Link | |
| className="flex items-center gap-2 relative brand-kit-menu" | |
| href="/" | |
| onContextMenu={(e) => { | |
| e.preventDefault(); | |
| setOpen(!open); | |
| if (!open) { | |
| clearDropdown(true); | |
| } | |
| }} | |
| > | |
| <FirecrawlIcon className="size-28 -top-2 relative" /> | |
| <Logo /> | |
| </Link> | |
| <AnimatePresence initial={false} mode="popLayout"> | |
| {open && <Menu setOpen={setOpen} />} | |
| </AnimatePresence> | |
| </div> | |
| ); | |
| } | |
| const Menu = ({ setOpen }: { setOpen: (open: boolean) => void }) => { | |
| const backgroundRef = useRef<HTMLDivElement>(null); | |
| const timeoutRef = useRef<number | null>(null); | |
| const onMouseEnter = useCallback((e: React.MouseEvent<HTMLButtonElement>) => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| const t = e.target as HTMLElement; | |
| const target = | |
| t instanceof HTMLButtonElement | |
| ? t | |
| : (t.closest("button") as HTMLButtonElement); | |
| if (backgroundRef.current) { | |
| animate(backgroundRef.current, { scale: 0.98, opacity: 1 }).then(() => { | |
| if (backgroundRef.current) { | |
| animate(backgroundRef.current!, { scale: 1 }); | |
| } | |
| }); | |
| animate( | |
| backgroundRef.current, | |
| { | |
| y: target.offsetTop - 4, | |
| }, | |
| { | |
| ease: cubicBezier(0.1, 0.1, 0.25, 1), | |
| duration: 0.2, | |
| }, | |
| ); | |
| } | |
| }, []); | |
| const onMouseLeave = useCallback(() => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| timeoutRef.current = window.setTimeout(() => { | |
| if (backgroundRef.current) { | |
| animate(backgroundRef.current, { scale: 1, opacity: 0 }); | |
| } | |
| }, 100); | |
| }, []); | |
| return ( | |
| <motion.div | |
| animate={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }} | |
| className="absolute w-220 whitespace-nowrap rounded-16 p-4 bg-white left-0 top-[calc(100%+8px)] z-[2000] border border-border-faint" | |
| exit={{ opacity: 0, y: 8, scale: 0.98, filter: "blur(1px)" }} | |
| initial={{ opacity: 0, y: -6, filter: "blur(1px)" }} | |
| style={{ | |
| boxShadow: | |
| "0px 12px 24px rgba(0, 0, 0, 0.08), 0px 4px 8px rgba(0, 0, 0, 0.04)", | |
| }} | |
| transition={{ | |
| ease: cubicBezier(0.1, 0.1, 0.25, 1), | |
| duration: 0.2, | |
| }} | |
| > | |
| <div | |
| className="absolute top-4 opacity-0 z-[2] pointer-events-none inset-x-4 bg-black-alpha-4 rounded-8 h-32" | |
| ref={backgroundRef} | |
| /> | |
| <Button | |
| onClick={() => { | |
| window.open("/", "_blank"); | |
| setOpen(false); | |
| }} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| > | |
| <svg | |
| className="w-16 h-16" | |
| fill="none" | |
| viewBox="0 0 16 16" | |
| xmlns="http://www.w3.org/2000/svg" | |
| > | |
| <path | |
| d="M12 4.5V12.5C12 13.0523 11.5523 13.5 11 13.5H4C3.44772 13.5 3 13.0523 3 12.5V4.5C3 3.94772 3.44772 3.5 4 3.5H7.5M10.5 2.5H13.5M13.5 2.5V5.5M13.5 2.5L8.5 7.5" | |
| stroke="currentColor" | |
| strokeLinecap="round" | |
| strokeLinejoin="round" | |
| strokeWidth="1.25" | |
| /> | |
| </svg> | |
| Open in new tab | |
| </Button> | |
| <div className="px-8 py-4"> | |
| <div className="h-1 w-full bg-black-alpha-5" /> | |
| </div> | |
| <Button | |
| onClick={() => { | |
| copy(`<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"> | |
| <path | |
| d="M13.7605 6.61389C13.138 6.79867 12.6687 7.21667 12.3251 7.67073C12.2513 7.76819 12.0975 7.69495 12.1268 7.57552C12.7848 4.86978 11.9155 2.6209 9.20582 1.51393C9.06836 1.4576 8.92527 1.58097 8.96132 1.72519C10.1939 6.67417 5.00941 6.25673 5.66459 11.8671C5.67585 11.9634 5.56769 12.0293 5.48882 11.973C5.2432 11.7967 4.96885 11.4288 4.78069 11.1702C4.72548 11.0942 4.60605 11.1156 4.5807 11.2063C4.43085 11.7482 4.35986 12.2586 4.35986 12.7656C4.35986 14.7373 5.37333 16.473 6.90734 17.4791C6.99522 17.5366 7.10789 17.4543 7.07804 17.3535C6.99917 17.0887 6.95466 16.8093 6.95128 16.5203C6.95128 16.3429 6.96255 16.1615 6.99015 15.9925C7.05438 15.5677 7.20197 15.1632 7.44985 14.7948C8.29995 13.5188 10.0041 12.2862 9.73199 10.6125C9.71453 10.5066 9.83959 10.4368 9.91846 10.5094C11.119 11.6063 11.3567 13.0817 11.1595 14.405C11.1426 14.5199 11.2868 14.5813 11.3595 14.4912C11.5432 14.2613 11.7674 14.0596 12.0113 13.9081C12.0722 13.8703 12.1533 13.8991 12.1764 13.9667C12.3121 14.3616 12.5138 14.7323 12.7042 15.1029C12.9318 15.5485 13.0529 16.0573 13.0338 16.5958C13.0242 16.8578 12.9808 17.1113 12.9082 17.3524C12.8772 17.4543 12.9887 17.5394 13.0783 17.4808C14.6134 16.4747 15.6275 14.739 15.6275 12.7662C15.6275 12.0806 15.5075 11.4085 15.2804 10.7787C14.8044 9.45766 13.5966 8.46561 13.9019 6.74403C13.9166 6.66178 13.8405 6.59023 13.7605 6.61389Z" | |
| fill="#262626" | |
| /> | |
| </svg>`); | |
| setOpen(false); | |
| }} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| > | |
| <Icon /> | |
| Copy logo as SVG | |
| </Button> | |
| <Button | |
| onClick={() => { | |
| setOpen(false); | |
| }} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| > | |
| <Download /> | |
| Download brand assets | |
| </Button> | |
| <div className="px-8 py-4"> | |
| <div className="h-1 w-full bg-black-alpha-5" /> | |
| </div> | |
| <Button | |
| onClick={() => { | |
| setOpen(false); | |
| }} | |
| onMouseEnter={onMouseEnter} | |
| onMouseLeave={onMouseLeave} | |
| > | |
| <Guidelines /> | |
| Visit brand guidelines | |
| </Button> | |
| </motion.div> | |
| ); | |
| }; | |
| const Button = (attributes: React.ButtonHTMLAttributes<HTMLButtonElement>) => { | |
| return ( | |
| <button | |
| {...attributes} | |
| className={cn( | |
| "flex gap-8 w-full items-center text-label-small group text-accent-black p-6", | |
| attributes.className, | |
| )} | |
| > | |
| {attributes.children} | |
| </button> | |
| ); | |
| }; | |