Files
medusa-store/www/packages/docs-ui/src/components/CodeTabs/index.tsx
Shahed Nasser c38f6d07c2 docs: update design + colors (#7593)
Update design and colors in docs to match those in Figma
2024-06-04 07:41:24 +00:00

206 lines
5.6 KiB
TypeScript

"use client"
import React, { Children, useCallback, useEffect, useMemo, useRef } from "react"
import {
Badge,
BaseTabType,
CodeBlockProps,
CodeBlockStyle,
useColorMode,
useTabs,
} from "../.."
import clsx from "clsx"
type CodeTab = BaseTabType & {
codeProps: CodeBlockProps
codeBlock: React.ReactNode
}
type CodeTabProps = {
children: React.ReactNode
className?: string
group?: string
title?: string
blockStyle?: CodeBlockStyle
}
export const CodeTabs = ({
children,
className,
group = "client",
blockStyle = "loud",
}: CodeTabProps) => {
const { colorMode } = useColorMode()
const tabs: CodeTab[] = useMemo(() => {
const tempTabs: CodeTab[] = []
Children.forEach(children, (child) => {
if (
!React.isValidElement(child) ||
!child.props.label ||
!child.props.value ||
!React.isValidElement(child.props.children)
) {
return
}
// extract child code block
const codeBlock =
child.props.children.type === "pre" &&
React.isValidElement(child.props.children.props.children)
? child.props.children.props.children
: child.props.children
tempTabs.push({
label: child.props.label,
value: child.props.value,
codeProps: codeBlock.props,
codeBlock: {
...codeBlock,
props: {
...codeBlock.props,
badgeLabel: undefined,
hasTabs: true,
className: clsx("!my-0", codeBlock.props.className),
},
},
})
})
return tempTabs
}, [children])
const { selectedTab, changeSelectedTab } = useTabs<CodeTab>({
tabs,
group,
})
const tabRefs: (HTMLButtonElement | null)[] = useMemo(() => [], [])
const codeTabSelectorRef = useRef<HTMLSpanElement | null>(null)
const codeTabsWrapperRef = useRef<HTMLDivElement | null>(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])
return (
<div
className={clsx(
"my-docs_1 w-full max-w-full",
"rounded-docs_lg",
bgColor,
boxShadow,
className
)}
>
<div
className={clsx(
"flex gap-docs_0.75 relative",
"pt-[10px] px-docs_1 pb-px",
blockStyle === "loud" &&
selectedTab?.codeProps.title &&
"border border-solid border-b border-medusa-contrast-border-bot"
)}
ref={codeTabsWrapperRef}
>
<span
className={clsx(
"xs:absolute xs:transition-all xs:duration-200 xs:ease-ease xs:bottom-0",
blockStyle === "loud" && "bg-medusa-contrast-fg-primary h-px",
blockStyle === "subtle" && [
colorMode === "light" &&
"xs:border-medusa-border-base xs:bg-medusa-bg-base",
colorMode === "dark" &&
"xs:border-medusa-code-border xs:bg-medusa-code-bg-base",
]
)}
ref={codeTabSelectorRef}
></span>
<ul
className={clsx(
"!list-none flex gap-docs_0.75 items-center",
"p-0 mb-0"
)}
>
{Children.map(children, (child, index) => {
if (!React.isValidElement(child)) {
return <></>
}
return (
<child.type
{...child.props}
changeSelectedTab={changeSelectedTab}
pushRef={(tabButton: HTMLButtonElement | null) =>
tabRefs.push(tabButton)
}
blockStyle={blockStyle}
isSelected={
!selectedTab
? index === 0
: selectedTab.value === child.props.value
}
/>
)
})}
</ul>
{selectedTab?.codeProps.badgeLabel && (
<Badge variant={selectedTab?.codeProps.badgeColor || "code"}>
{selectedTab.codeProps.badgeLabel}
</Badge>
)}
</div>
{selectedTab?.codeBlock}
</div>
)
}