import {
Children,
createContext,
useContext,
useEffect,
useRef,
useState,
} from 'react'
import { Tab } from '@headlessui/react'
import clsx from 'clsx'
import { create } from 'zustand'
import { Tag } from '@/components/Tag'
const languageNames = {
js: 'JavaScript',
ts: 'TypeScript',
javascript: 'JavaScript',
typescript: 'TypeScript',
php: 'PHP',
python: 'Python',
ruby: 'Ruby',
go: 'Go',
}
function getPanelTitle({ title, language }) {
return title ?? languageNames[language] ?? 'Code'
}
function ClipboardIcon(props) {
return (
)
}
function CopyButton({ code }) {
let [copyCount, setCopyCount] = useState(0)
let copied = copyCount > 0
useEffect(() => {
if (copyCount > 0) {
let timeout = setTimeout(() => setCopyCount(0), 1000)
return () => {
clearTimeout(timeout)
}
}
}, [copyCount])
return (
)
}
function CodePanelHeader({ tag, label }) {
if (!tag && !label) {
return null
}
return (
{tag && (
{tag}
)}
{tag && label && (
)}
{label && (
{label}
)}
)
}
function CodePanel({ tag, label, code, children }) {
let child = Children.only(children)
return (
)
}
function CodeGroupHeader({ title, children, selectedIndex }) {
let hasTabs = Children.count(children) > 1
if (!title && !hasTabs) {
return null
}
return (
{title && (
{title}
)}
{hasTabs && (
{Children.map(children, (child, childIndex) => (
{getPanelTitle(child.props)}
))}
)}
)
}
function CodeGroupPanels({ children, ...props }) {
let hasTabs = Children.count(children) > 1
if (hasTabs) {
return (
{Children.map(children, (child) => (
{child}
))}
)
}
return {children}
}
function usePreventLayoutShift() {
let positionRef = useRef()
let rafRef = useRef()
useEffect(() => {
return () => {
window.cancelAnimationFrame(rafRef.current)
}
}, [])
return {
positionRef,
preventLayoutShift(callback) {
let initialTop = positionRef.current.getBoundingClientRect().top
callback()
rafRef.current = window.requestAnimationFrame(() => {
let newTop = positionRef.current.getBoundingClientRect().top
window.scrollBy(0, newTop - initialTop)
})
},
}
}
const usePreferredLanguageStore = create((set) => ({
preferredLanguages: [],
addPreferredLanguage: (language) =>
set((state) => ({
preferredLanguages: [
...state.preferredLanguages.filter(
(preferredLanguage) => preferredLanguage !== language
),
language,
],
})),
}))
function useTabGroupProps(availableLanguages) {
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',
ref: positionRef,
selectedIndex,
onChange: (newSelectedIndex) => {
preventLayoutShift(() =>
addPreferredLanguage(availableLanguages[newSelectedIndex])
)
},
}
}
const CodeGroupContext = createContext(false)
export function CodeGroup({ children, title, ...props }) {
let languages = Children.map(children, (child) => getPanelTitle(child.props))
let tabGroupProps = useTabGroupProps(languages)
let hasTabs = Children.count(children) > 1
let Container = hasTabs ? Tab.Group : 'div'
let containerProps = hasTabs ? tabGroupProps : {}
let headerProps = hasTabs
? { selectedIndex: tabGroupProps.selectedIndex }
: {}
return (
{children}
{children}
)
}
export function Code({ children, ...props }) {
let isGrouped = useContext(CodeGroupContext)
if (isGrouped) {
return
}
return {children}
}
export function Pre({ children, ...props }) {
let isGrouped = useContext(CodeGroupContext)
if (isGrouped) {
return children
}
return {children}
}