'use client'
import {
Children,
createContext,
useContext,
useEffect,
useRef,
useState,
} from 'react'
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
import { Tag } from './tag'
import classNames from '@/utils/classnames'
import { writeTextToClipboard } from '@/utils/clipboard'
import type { PropsWithChildren, ReactElement, ReactNode } from 'react'
const languageNames = {
js: 'JavaScript',
ts: 'TypeScript',
javascript: 'JavaScript',
typescript: 'TypeScript',
php: 'PHP',
python: 'Python',
ruby: 'Ruby',
go: 'Go',
} as { [key: string]: string }
type IChildrenProps = {
children: React.ReactNode
[key: string]: any
}
function ClipboardIcon(props: any) {
return (
)
}
function CopyButton({ code }: { code: string }) {
const [copyCount, setCopyCount] = useState(0)
const copied = copyCount > 0
useEffect(() => {
if (copyCount > 0) {
const timeout = setTimeout(() => setCopyCount(0), 1000)
return () => {
clearTimeout(timeout)
}
}
}, [copyCount])
return (
)
}
function CodePanelHeader({ tag, label }: { tag?: string; label?: string }) {
if (!tag && !label)
return null
return (
{tag && (
{tag}
)}
{tag && label && (
)}
{label && (
{label}
)}
)
}
type CodeExample = {
title?: string
tag?: string
code: string
}
type ICodePanelProps = {
children?: React.ReactNode
tag?: string
label?: string
code?: string
title?: string
targetCode?: CodeExample
}
function CodePanel({ tag, label, children, targetCode }: ICodePanelProps) {
const child = Children.toArray(children)[0] as ReactElement
return (
{/*
{children}
*/}
{/*
*/}
{/*
*/}
{targetCode?.code ? (
{targetCode?.code}
) : (
child
)}
)
}
type CodeGroupHeaderProps = {
title?: string
tabTitles?: string[]
selectedIndex?: number
}
function CodeGroupHeader({ title, tabTitles, selectedIndex }: CodeGroupHeaderProps) {
const hasTabs = (tabTitles?.length ?? 0) > 1
return (
{title && (
{title}
)}
{hasTabs && (
{tabTitles!.map((tabTitle, tabIndex) => (
{tabTitle}
))}
)}
)
}
type ICodeGroupPanelsProps = PropsWithChildren<{
targetCode?: CodeExample[]
[key: string]: any
}>
function CodeGroupPanels({ children, targetCode, ...props }: ICodeGroupPanelsProps) {
if ((targetCode?.length ?? 0) > 1) {
return (
{targetCode!.map(code => (
))}
)
}
return {children}
}
function usePreventLayoutShift() {
const positionRef = useRef()
const rafRef = useRef()
useEffect(() => {
return () => {
window.cancelAnimationFrame(rafRef.current)
}
}, [])
return {
positionRef,
preventLayoutShift(callback: () => {}) {
const initialTop = positionRef.current.getBoundingClientRect().top
callback()
rafRef.current = window.requestAnimationFrame(() => {
const newTop = positionRef.current.getBoundingClientRect().top
window.scrollBy(0, newTop - initialTop)
})
},
}
}
function useTabGroupProps(availableLanguages: string[]) {
const [preferredLanguages, addPreferredLanguage] = useState([])
const [selectedIndex, setSelectedIndex] = useState(0)
const activeLanguage = [...(availableLanguages || [])].sort(
(a, z) => preferredLanguages.indexOf(z) - preferredLanguages.indexOf(a),
)[0]
const languageIndex = availableLanguages?.indexOf(activeLanguage) || 0
const newSelectedIndex = languageIndex === -1 ? selectedIndex : languageIndex
if (newSelectedIndex !== selectedIndex)
setSelectedIndex(newSelectedIndex)
const { positionRef, preventLayoutShift } = usePreventLayoutShift()
return {
as: 'div',
ref: positionRef,
selectedIndex,
onChange: (newSelectedIndex: number) => {
preventLayoutShift(() =>
(addPreferredLanguage(availableLanguages[newSelectedIndex]) as any),
)
},
}
}
const CodeGroupContext = createContext(false)
type CodeGroupProps = PropsWithChildren<{
/** Code example(s) to display */
targetCode?: string | CodeExample[]
/** Example block title */
title?: string
/** HTTP method tag, e.g. GET, POST */
tag?: string
/** API path */
label?: string
}>
export function CodeGroup({ children, title, targetCode, ...props }: CodeGroupProps) {
const examples = typeof targetCode === 'string' ? [{ code: targetCode }] as CodeExample[] : targetCode
const tabTitles = examples?.map(({ title }) => title || 'Code') || []
const tabGroupProps = useTabGroupProps(tabTitles)
const hasTabs = tabTitles.length > 1
const Container = hasTabs ? TabGroup : 'div'
const containerProps = hasTabs ? tabGroupProps : {}
const headerProps = hasTabs
? { selectedIndex: tabGroupProps.selectedIndex, tabTitles }
: {}
return (
{children}
)
}
type IChildProps = {
children: ReactNode
[key: string]: any
}
export function Code({ children, ...props }: IChildProps) {
return {children}
}
export function Pre({ children, ...props }: IChildrenProps) {
const isGrouped = useContext(CodeGroupContext)
if (isGrouped)
return children
return {children}
}
export function Embed({ value, ...props }: IChildrenProps) {
return {value}
}