'use client'
import {
Children,
createContext,
useContext,
useEffect,
useRef,
useState,
} from 'react'
import { Tab } from '@headlessui/react'
import { Tag } from './tag'
import classNames from '@/utils/classnames'
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.ReactElement
[key: string]: any
}
function getPanelTitle({ className }: { className: string }) {
const language = className.split('-')[1]
return languageNames[language] ?? 'Code'
}
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 ICodePanelProps = {
children: React.ReactElement
tag?: string
code?: string
label?: string
targetCode?: string
}
function CodePanel({ tag, label, code, children, targetCode }: ICodePanelProps) {
const child = Children.only(children)
return (
{/*
{children}
*/}
{/*
*/}
{/*
*/}
{targetCode || children}
)
}
function CodeGroupHeader({ title, children, selectedIndex }: IChildrenProps) {
const hasTabs = Children.count(children) > 1
if (!title && !hasTabs)
return null
return (
{title && (
{title}
)}
{hasTabs && (
{Children.map(children, (child, childIndex) => (
{getPanelTitle(child.props.children.props)}
))}
)}
)
}
type ICodeGroupPanelsProps = {
children: React.ReactElement
[key: string]: any
}
function CodeGroupPanels({ children, targetCode, ...props }: ICodeGroupPanelsProps) {
const hasTabs = Children.count(children) > 1
if (hasTabs) {
return (
{Children.map(children, child => (
{child}
))}
)
}
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)
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)
export function CodeGroup({ children, title, inputs, targetCode, ...props }: IChildrenProps) {
const languages = Children.map(children, child =>
getPanelTitle(child.props.children.props),
)
const tabGroupProps = useTabGroupProps(languages)
const hasTabs = Children.count(children) > 1
const Container = hasTabs ? Tab.Group : 'div'
const containerProps = hasTabs ? tabGroupProps : {}
const headerProps = hasTabs
? { selectedIndex: tabGroupProps.selectedIndex }
: {}
return (
{children}
{children}
)
}
type IChildProps = {
children: string
[key: string]: any
}
export function Code({ children, ...props }: IChildProps) {
const isGrouped = useContext(CodeGroupContext)
if (isGrouped)
return
return {children}
}
export function Pre({ children, ...props }: IChildrenProps) {
const isGrouped = useContext(CodeGroupContext)
if (isGrouped)
return children
return {children}
}