"use client"; import { Tag } from "@/components/Tag"; import { Tab } from "@headlessui/react"; import clsx from "clsx"; import { Children, createContext, isValidElement, useContext, useEffect, useRef, useState } from "react"; import { create } from "zustand"; const languageNames: Record = { js: "JavaScript", ts: "TypeScript", javascript: "JavaScript", typescript: "TypeScript", php: "PHP", python: "Python", ruby: "Ruby", go: "Go", }; const getPanelTitle = ({ title, language }: { title?: string; language?: string }) => { if (title) { return title; } if (language && language in languageNames) { return languageNames[language]; } return "Code"; }; const ClipboardIcon = (props: React.ComponentPropsWithoutRef<"svg">) => { return ( ); }; const CopyButton = ({ code }: { code: string }) => { let [copyCount, setCopyCount] = useState(0); let copied = copyCount > 0; useEffect(() => { if (copyCount > 0) { let timeout = setTimeout(() => setCopyCount(0), 1000); return () => { clearTimeout(timeout); }; } }, [copyCount]); return ( ); }; const CodePanelHeader = ({ tag, label }: { tag?: string; label?: string }) => { if (!tag && !label) { return null; } return (
{tag && (
{tag}
)} {tag && label && } {label && {label}}
); }; const CodePanel = ({ children, tag, label, code, }: { children: React.ReactNode; tag?: string; label?: string; code?: string; }) => { let child = Children.only(children); if (isValidElement(child)) { tag = child.props.tag ?? tag; label = child.props.label ?? label; code = child.props.code ?? code; } if (!code) { throw new Error("`CodePanel` requires a `code` prop, or a child with a `code` prop."); } return (
{children}
); }; const CodeGroupHeader = ({ title, children, selectedIndex, }: { title: string; children: React.ReactNode; selectedIndex: number; }) => { let hasTabs = Children.count(children) >= 1; if (!title && !hasTabs) { return null; } return (
{title &&

{title}

} {hasTabs && ( {Children.map(children, (child, childIndex) => ( {getPanelTitle(isValidElement(child) ? child.props : {})} ))} )}
); }; const CodeGroupPanels = ({ children, ...props }: React.ComponentPropsWithoutRef) => { let hasTabs = Children.count(children) >= 1; if (hasTabs) { return ( {Children.map(children, (child) => ( {child} ))} ); } return {children}; }; const usePreventLayoutShift = () => { let positionRef = useRef(null); let rafRef = useRef(); useEffect(() => { return () => { if (typeof rafRef.current !== "undefined") { window.cancelAnimationFrame(rafRef.current); } }; }, []); return { positionRef, preventLayoutShift(callback: () => void) { if (!positionRef.current) { return; } let initialTop = positionRef.current.getBoundingClientRect().top; callback(); rafRef.current = window.requestAnimationFrame(() => { let newTop = positionRef.current?.getBoundingClientRect().top ?? initialTop; window.scrollBy(0, newTop - initialTop); }); }, }; }; const usePreferredLanguageStore = create<{ preferredLanguages: Array; addPreferredLanguage: (language: string) => void; }>()((set) => ({ preferredLanguages: [], addPreferredLanguage: (language) => set((state) => ({ preferredLanguages: [ ...state.preferredLanguages.filter((preferredLanguage) => preferredLanguage !== language), language, ], })), })); const useTabGroupProps = (availableLanguages: Array) => { let { preferredLanguages, addPreferredLanguage } = usePreferredLanguageStore(); let [selectedIndex, setSelectedIndex] = useState(0); let activeLanguage = [...availableLanguages].sort( (a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a) )[0]; let languageIndex = availableLanguages.indexOf(activeLanguage); let newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex; if (newSelectedIndex !== selectedIndex) { setSelectedIndex(newSelectedIndex); } let { positionRef, preventLayoutShift } = usePreventLayoutShift(); return { as: "div" as const, ref: positionRef, selectedIndex, onChange: (newSelectedIndex: number) => { preventLayoutShift(() => addPreferredLanguage(availableLanguages[newSelectedIndex])); }, }; }; const CodeGroupContext = createContext(false); export const CodeGroup = ({ children, title, ...props }: React.ComponentPropsWithoutRef & { title: string }) => { let languages = Children.map(children, (child) => getPanelTitle(isValidElement(child) ? child.props : {})) ?? []; let tabGroupProps = useTabGroupProps(languages); let hasTabs = Children.count(children) >= 1; let containerClassName = "not-prose my-6 overflow-hidden rounded-2xl bg-slate-900 shadow-md dark:ring-1 dark:ring-white/10"; let header = ( {children} ); let panels = {children}; return ( {hasTabs ? ( {header} {panels} ) : (
{header} {panels}
)}
); }; export const Code = ({ children, ...props }: React.ComponentPropsWithoutRef<"code">) => { let isGrouped = useContext(CodeGroupContext); if (isGrouped) { if (typeof children !== "string") { throw new Error("`Code` children must be a string when nested inside a `CodeGroup`."); } return ; } return {children}; }; export const Pre = ({ children, ...props }: React.ComponentPropsWithoutRef) => { let isGrouped = useContext(CodeGroupContext); if (isGrouped) { return children; } return {children}; };