import React, { useRef, useState, useEffect, useMemo, HTMLAttributes, } from "react"; import { useRecoilValue, useRecoilState } from "recoil"; import { useHotkeys } from "react-hotkeys-hook"; import { motion, AnimatePresence, Variants } from "framer-motion"; import { Svg2Png } from "svg2png-converter"; import { saveAs } from "file-saver"; import { Copy, CheckCircle, ArrowFatLinesDown, XCircle, CaretDoubleLeft, CaretDoubleRight, } from "@phosphor-icons/react"; import { IconStyle } from "@phosphor-icons/core"; import ReactGA from "react-ga4"; import Tabs, { Tab } from "@/components/Tabs"; import { useMediaQuery, useTransientState, useSessionStorage } from "@/hooks"; import { SnippetType } from "@/lib"; import { iconWeightAtom, iconSizeAtom, iconColorAtom, selectionEntryAtom, isDarkThemeSelector, } from "@/state"; import { getCodeSnippets, supportsWeight } from "@/utils"; import TagCloud from "./TagCloud"; const variants: Record = { desktop: { initial: { y: 188 }, animate: { y: 0 }, exit: { y: 188 }, }, mobile: { initial: { y: "60vh" }, animate: { y: 0 }, exit: { y: "60vh" }, }, }; const RENDERED_SNIPPETS = [ SnippetType.REACT, SnippetType.HTML, SnippetType.VUE, SnippetType.FLUTTER, SnippetType.ELM, SnippetType.SWIFT, ]; enum CopyType { SVG, SVG_RAW, SVG_DATA, PNG, PNG_DATA, UNICODE, } function cloneWithSize(svg: SVGSVGElement, size: number): SVGSVGElement { const sized = svg.cloneNode(true) as SVGSVGElement; sized.setAttribute("width", `${size}`); sized.setAttribute("height", `${size}`); return sized; } const ActionButton = ( props: { active?: boolean; label: string; download?: boolean; disabled?: boolean; } & HTMLAttributes ) => { const { active, download, label, ...rest } = props; const Icon = download ? ArrowFatLinesDown : Copy; return ( ); }; const Panel = () => { const [entry, setSelectionEntry] = useRecoilState(selectionEntryAtom); const weight = useRecoilValue(iconWeightAtom); const size = useRecoilValue(iconSizeAtom); const color = useRecoilValue(iconColorAtom); const isDark = useRecoilValue(isDarkThemeSelector); const [copied, setCopied] = useTransientState( false, 2000 ); const ref = useRef(null); const [showMoreActions, setShowMoreActions] = useState(false); const [i, setInitialTab] = useSessionStorage("tab", 0); const isMobile = useMediaQuery("(max-width: 719px)"); const [snippets, tabs] = useMemo< [Partial>, Tab[]] >(() => { if (!entry) return [{}, []]; const snippets = getCodeSnippets({ displayName: entry?.pascal_name!, name: entry.name, weight, size, color, }); const tabs = [ { header: "Tags", content: ( ([ ...entry.tags, ...entry.categories, ...entry.name.split("-"), ]) )} /> ), }, ].concat( RENDERED_SNIPPETS.map((type) => { const isWeightSupported = supportsWeight({ type, weight }); return { header: type, content: (
                
                  {isWeightSupported
                    ? snippets[type]
                    : "This weight is not yet supported"}
                
                {isWeightSupported && (
                  
                )}
              
), }; }) ); return [snippets, tabs]; }, [entry, weight, size, color, copied, isDark]); useHotkeys("esc", () => setSelectionEntry(null)); useEffect(() => { if (!entry) return; ReactGA.event({ category: "Grid", action: "Details", label: entry.name, }); }, [entry]); const handleCopySnippet = ( event: React.MouseEvent, type: SnippetType ) => { event.currentTarget.blur(); if (!entry) return; setCopied(type); const data = snippets[type]; data && void navigator.clipboard?.writeText(data); }; const handleCopySVG = ( event: React.MouseEvent ) => { event.currentTarget.blur(); if (!entry) return; if (!ref.current) return; navigator.clipboard?.writeText(cloneWithSize(ref.current, size).outerHTML); setCopied(CopyType.SVG); }; const handleCopyDataSVG = ( event: React.MouseEvent ) => { event.currentTarget.blur(); if (!entry) return; if (!ref.current) return; navigator.clipboard?.writeText( "data:image/svg+xml;base64," + btoa( unescape( encodeURIComponent(cloneWithSize(ref.current, size).outerHTML) ) ) ); setCopied(CopyType.SVG_DATA); }; const handleCopyRawSVG = async () => { if (!entry) return; const { name } = entry; const data = await fetch( `https://raw.githubusercontent.com/phosphor-icons/core/main/raw/${weight}/${name}${ weight === "regular" ? "" : `-${weight}` }.svg` ); const content = await data.text(); navigator.clipboard?.writeText(content); setCopied(CopyType.SVG_RAW); }; const handleCopyUnicode = async () => { if (!entry) return; const content = String.fromCharCode(entry.codepoint); navigator.clipboard?.writeText(content); setCopied(CopyType.UNICODE); }; const handleDownloadSVG = ( event: React.MouseEvent ) => { event.currentTarget.blur(); if (!entry) return; if (!ref.current) return; const blob = new Blob([cloneWithSize(ref.current, size).outerHTML]); saveAs( blob, `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.svg` ); }; const handleDownloadRawSVG = async () => { if (!entry) return; const { name } = entry; saveAs( `https://raw.githubusercontent.com/phosphor-icons/core/main/raw/${weight}/${name}${ weight === "regular" ? "" : `-${weight}` }.svg`, `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.svg` ); }; const handleDownloadPNG = async ( event: React.MouseEvent ) => { event.currentTarget.blur(); if (!entry) return; if (!ref.current) return; Svg2Png.save( cloneWithSize(ref.current, size), `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.png` ); }; const handleCopyPNG = async ( event: React.MouseEvent ) => { event.currentTarget.blur(); if (!entry) return; if (!ref.current) return; Svg2Png.toDataURL(cloneWithSize(ref.current, size)) .then((data) => fetch(data)) .then((res) => res.blob()) .then((blob) => navigator.clipboard.write([ new ClipboardItem({ [blob.type]: blob, }), ]) ) .then(() => { setCopied(CopyType.PNG); }); }; // const handleCopyDataPNG = async ( // event: React.MouseEvent // ) => { // event.currentTarget.blur(); // if (!entry) return; // if (!ref.current) return; // const data = await Svg2Png.toDataURL(cloneWithSize(ref.current, size)); // navigator.clipboard?.writeText(data); // setCopied(CopyType.PNG_DATA); // }; return ( {!!entry && (

{entry.name}

U+{entry.codepoint.toString(16).toUpperCase()} available in v{entry.published_in.toFixed(1)}+

{!showMoreActions ? ( <> ) : ( <> )}
)}
); }; export default Panel;