"use client"
import * as React from "react"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
type StackAtBreakpoint = "sm" | "md" | "lg" | "xl" | "2xl"
interface RadioTabsProps extends React.ComponentProps<typeof RadioGroupPrimitive.Root> {
stackAtBreakpoint?: StackAtBreakpoint
containerClassName?: string
}
function RadioTabs({ className, stackAtBreakpoint, containerClassName, ...props }: RadioTabsProps) {
// Detect if any children contain descriptions to adjust container height
const hasDescriptions = React.Children.toArray(props.children).some(child => {
if (React.isValidElement(child) && child.props && typeof child.props === "object" && "children" in child.props) {
const itemChildren = React.Children.toArray(child.props.children as React.ReactNode)
return itemChildren.some(itemChild => {
return React.isValidElement(itemChild) && itemChild.type === RadioTabsItemDescription
})
}
return false
})
const containerStyles = cn(
"flex gap-x-[2px] rounded-[6px] p-[2px]",
!hasDescriptions && "h-[38px]",
"bg-white dark:bg-zinc-900",
"border",
stackAtBreakpoint && getClassNamesForBreakpoint(stackAtBreakpoint),
containerClassName,
)
return (
<RadioGroupPrimitive.Root data-slot="radio-tabs" className={cn("w-full", className)} {...props}>
<div className={containerStyles}>{props.children}</div>
</RadioGroupPrimitive.Root>
)
}
const radioTabsItemVariants = cva(
[
"group flex-1 overflow-hidden cursor-pointer",
"px-[7px] py-[4px]",
"select-none outline-none rounded-[4px]",
"transition-colors",
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
"disabled:cursor-not-allowed disabled:opacity-50",
],
{
variants: {
variant: {
default: [
"data-[state=checked]:bg-foreground dark:data-[state=checked]:bg-foreground",
"data-[state=checked]:border data-[state=checked]:border-foreground",
"hover:bg-muted/50",
],
outline: [
"border border-muted-foreground/20",
"data-[state=checked]:border-foreground data-[state=checked]:bg-foreground/5",
"hover:border-muted-foreground/30",
],
outline_highlight: [
"data-[state=checked]:bg-blue-500/5 dark:data-[state=checked]:bg-blue-500/10",
"data-[state=checked]:border data-[state=checked]:border-blue-500",
"hover:bg-muted/50",
],
primary: [
"data-[state=checked]:bg-foreground",
"data-[state=checked]:border data-[state=checked]:border-primary",
"hover:bg-muted/50",
],
highlight: [
"data-[state=checked]:bg-blue-500 dark:data-[state=checked]:bg-blue-500",
"data-[state=checked]:!text-white/100",
// "data-[state=checked]:border data-[state=checked]:border-blue-500",
"border-none",
"hover:bg-muted/50",
],
secondary: [
"data-[state=checked]:bg-muted",
"data-[state=checked]:border data-[state=checked]:border-muted-foreground/20",
"hover:bg-muted/50",
],
},
},
defaultVariants: {
variant: "default",
},
},
)
interface RadioTabsItemProps
extends React.ComponentProps<typeof RadioGroupPrimitive.Item>,
VariantProps<typeof radioTabsItemVariants> {
children: React.ReactNode
}
function RadioTabsItem({ className, variant = "default", children, ...props }: RadioTabsItemProps) {
// Check if children contain only text/simple content or structured components
const hasStructuredChildren = React.Children.toArray(children).some(child => {
return React.isValidElement(child) && (child.type === RadioTabsItemLabel || child.type === RadioTabsItemDescription)
})
return (
<RadioGroupPrimitive.Item
data-slot="radio-tabs-item"
className={cn(
radioTabsItemVariants({ variant }),
hasStructuredChildren
? "flex flex-col justify-center items-center text-center"
: "flex justify-center items-center",
hasStructuredChildren && "px-10",
className,
)}
{...props}
>
{hasStructuredChildren ? (
children
) : (
<span
className={cn(
"font-medium text-sm",
"overflow-hidden whitespace-nowrap text-ellipsis",
// "text-muted-foreground",
variant === "default" && "group-data-[state=checked]:text-background",
variant === "outline" && "group-data-[state=checked]:text-foreground",
variant === "primary" && "group-data-[state=checked]:text-primary",
variant === "secondary" && "group-data-[state=checked]:text-foreground",
)}
>
{children}
</span>
)}
</RadioGroupPrimitive.Item>
)
}
interface RadioTabsItemLabelProps extends React.HTMLAttributes<HTMLElement> {
children: React.ReactNode
}
function RadioTabsItemLabel({ className, children, ...props }: RadioTabsItemLabelProps) {
return (
<span
data-slot="radio-tabs-item-label"
className={cn(
"font-semibold text-base",
"overflow-hidden whitespace-nowrap text-ellipsis",
"w-full",
"text-muted-foreground",
"transition-colors duration-300",
className,
)}
{...props}
>
{children}
</span>
)
}
interface RadioTabsItemDescriptionProps extends React.HTMLAttributes<HTMLElement> {
children: React.ReactNode
}
function RadioTabsItemDescription({ className, children, ...props }: RadioTabsItemDescriptionProps) {
return (
<span
data-slot="radio-tabs-item-description"
className={cn(
"text-sm",
"text-muted-foreground opacity-50",
"group-data-[state=checked]:opacity-100",
"transition-colors duration-300",
className,
)}
{...props}
>
{children}
</span>
)
}
export { RadioTabs, RadioTabsItem, RadioTabsItemLabel, RadioTabsItemDescription, radioTabsItemVariants }
const getClassNamesForBreakpoint = (breakpoint: StackAtBreakpoint): string => {
const breakpointClasses: Record<StackAtBreakpoint, string> = {
sm: "flex-col gap-y-[2px] h-auto sm:flex-row sm:gap-x-[2px]",
md: "flex-col gap-y-[2px] h-auto md:flex-row md:gap-x-[2px]",
lg: "flex-col gap-y-[2px] h-auto lg:flex-row lg:gap-x-[2px]",
xl: "flex-col gap-y-[2px] h-auto xl:flex-row xl:gap-x-[2px]",
"2xl": "flex-col gap-y-[2px] h-auto 2xl:flex-row 2xl:gap-x-[2px]",
}
return breakpointClasses[breakpoint]
}