"use client" import React, { Children, useCallback, useEffect, useMemo, useRef } from "react" import { Badge, BaseTabType, CodeBlockProps, CodeBlockStyle, useColorMode, useTabs, } from "../.." import clsx from "clsx" import { CodeBlockActions, CodeBlockActionsProps } from "../CodeBlock/Actions" import { CodeBlockHeaderWrapper } from "../CodeBlock/Header/Wrapper" type CodeTab = BaseTabType & { codeProps: CodeBlockProps codeBlock: React.ReactNode children?: React.ReactNode } type CodeTabProps = { children: React.ReactNode className?: string group?: string blockStyle?: CodeBlockStyle } export const CodeTabs = ({ children, className, group = "client", blockStyle = "loud", }: CodeTabProps) => { const { colorMode } = useColorMode() const isCodeBlock = ( node: React.ReactNode ): node is | React.ReactElement> | React.ReactPortal => { if (!React.isValidElement(node)) { return false } if (node.type === "pre") { return true } const typedProps = node.props as Record return "source" in typedProps } const getCodeBlockProps = ( codeBlock: React.ReactElement< unknown, string | React.JSXElementConstructor > ): CodeBlockProps | undefined => { if (typeof codeBlock.props !== "object" || !codeBlock.props) { return undefined } if ("source" in codeBlock.props) { return codeBlock.props as CodeBlockProps } if ( "children" in codeBlock.props && typeof codeBlock.props.children === "object" && codeBlock.props.children ) { return getCodeBlockProps( codeBlock.props.children as React.ReactElement< unknown, string | React.JSXElementConstructor > ) } return undefined } const tabs: CodeTab[] = useMemo(() => { const tempTabs: CodeTab[] = [] Children.forEach(children, (child) => { if (!React.isValidElement(child)) { return } const typedChildProps = child.props as CodeTab if ( !React.isValidElement(child) || !typedChildProps.label || !typedChildProps.value || !React.isValidElement(typedChildProps.children) ) { return } const codeBlock: React.ReactNode = isCodeBlock(typedChildProps.children) ? typedChildProps.children : undefined if (!codeBlock) { return } let codeBlockProps = codeBlock.props as CodeBlockProps const showBadge = !codeBlockProps.title const originalBadgeLabel = codeBlockProps.badgeLabel const commonProps = { badgeLabel: showBadge ? undefined : originalBadgeLabel, hasTabs: true, className: clsx("!my-0", codeBlockProps.className), } if ( typeof codeBlock.type !== "string" && (("name" in codeBlock.type && codeBlock.type.name === "CodeBlock") || "source" in codeBlockProps) ) { codeBlockProps = { ...codeBlockProps, ...commonProps, } } const modifiedProps: CodeBlockProps = { ...(getCodeBlockProps(codeBlock) || { source: "", }), ...commonProps, } tempTabs.push({ label: typedChildProps.label, value: typedChildProps.value, codeProps: { ...modifiedProps, badgeLabel: !showBadge ? undefined : originalBadgeLabel, }, codeBlock: { ...codeBlock, props: { ...codeBlockProps, children: { ...(typeof codeBlockProps.children === "object" ? codeBlockProps.children : {}), props: modifiedProps, }, }, }, }) }) return tempTabs }, [children]) const { selectedTab, changeSelectedTab } = useTabs({ tabs, group, }) const tabRefs: (HTMLButtonElement | null)[] = useMemo(() => [], []) const codeTabSelectorRef = useRef(null) const codeTabsWrapperRef = useRef(null) const bgColor = useMemo( () => clsx( blockStyle === "loud" && "bg-medusa-contrast-bg-base", blockStyle === "subtle" && [ colorMode === "light" && "bg-medusa-bg-component", colorMode === "dark" && "bg-medusa-code-bg-header", ] ), [blockStyle, colorMode] ) const boxShadow = useMemo( () => clsx( blockStyle === "loud" && "shadow-elevation-code-block dark:shadow-elevation-code-block-dark", blockStyle === "subtle" && "shadow-none" ), [blockStyle] ) const changeTabSelectorCoordinates = useCallback( (selectedTabElm: HTMLElement) => { if (!codeTabSelectorRef?.current || !codeTabsWrapperRef?.current) { return } const selectedTabsCoordinates = selectedTabElm.getBoundingClientRect() const tabsWrapperCoordinates = codeTabsWrapperRef.current.getBoundingClientRect() codeTabSelectorRef.current.style.left = `${ selectedTabsCoordinates.left - tabsWrapperCoordinates.left }px` codeTabSelectorRef.current.style.width = `${selectedTabsCoordinates.width}px` if (blockStyle !== "loud") { codeTabSelectorRef.current.style.height = `${selectedTabsCoordinates.height}px` } }, [blockStyle] ) useEffect(() => { if (codeTabSelectorRef?.current && tabRefs.length) { const selectedTabElm = tabRefs.find( (tab) => tab?.getAttribute("aria-selected") === "true" ) if (selectedTabElm) { changeTabSelectorCoordinates( selectedTabElm.parentElement || selectedTabElm ) } } }, [codeTabSelectorRef, tabRefs, changeTabSelectorCoordinates, selectedTab]) const actionsProps: CodeBlockActionsProps | undefined = useMemo(() => { if (!selectedTab) { return } return { source: selectedTab?.codeProps.source, blockStyle, noReport: selectedTab?.codeProps.noReport, noCopy: selectedTab?.codeProps.noCopy, inInnerCode: true, showGradientBg: false, inHeader: true, isCollapsed: false, } }, [selectedTab]) return (
{selectedTab?.codeProps.badgeLabel && ( {selectedTab.codeProps.badgeLabel} )}
    {Children.map(children, (child, index) => { if (!React.isValidElement(child)) { return <> } return ( tabRefs.push(tabButton) } blockStyle={blockStyle} isSelected={ !selectedTab ? index === 0 : selectedTab.value === (child.props as CodeTab).value } /> ) })}
{actionsProps && }
{selectedTab?.codeBlock}
) }