'use client';/** * @title Chip Tabs * @description Chip 형태의 탭으로, 카테고리나 필터를 골라 콘텐츠를 전환할 때 써요. */import * as React from 'react';import { Tabs as TabsPrimitive } from 'radix-ui';import { cva } from 'class-variance-authority';import { cn } from '@/lib/utils';// SEED Rootage spec: chip-tablist.yaml + chip.yaml(variant=solid/outlineStrong) / qvism recipe: chip-tabs.tstype ChipTabsVariant = 'neutralSolid' | 'neutralOutline';type ChipTabsSize = 'medium' | 'large';type ChipTabsContentLayout = 'fill' | 'hug';interface ChipTabsContextValue { variant: ChipTabsVariant; size: ChipTabsSize; stickyList: boolean;}const ChipTabsContext = React.createContext<ChipTabsContextValue>({ variant: 'neutralSolid', size: 'medium', stickyList: false,});const chipTabsListVariants = cva( [ 'relative isolate flex flex-nowrap items-stretch overflow-x-auto px-x4', '[scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden', ], { variants: { size: { medium: 'gap-x2', large: 'gap-x2', }, stickyList: { true: 'sticky top-0 z-1', false: '', }, }, defaultVariants: { size: 'medium', stickyList: false }, },);const chipTabsTriggerVariants = cva( [ 'box-border flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap border-none font-[inherit]', 'rounded-full font-medium', 'transition-[background-color,scale,outline-color] duration-(--rb-duration-color-transition) ease-easing', 'outline-2 outline-offset-2 outline-transparent focus-visible:outline-stroke-focus-ring', // SEED scaleScope: self 'enabled:active:scale-[0.95] motion-reduce:enabled:active:scale-100', 'disabled:cursor-not-allowed disabled:opacity-50', ], { variants: { variant: { neutralSolid: [ 'bg-bg-neutral-weak-alpha text-fg-neutral', 'enabled:active:bg-bg-neutral-weak-alpha-pressed', 'data-[state=active]:bg-bg-neutral-solid data-[state=active]:text-fg-on-neutral-solid', 'enabled:data-[state=active]:active:bg-bg-neutral-solid-pressed', ], neutralOutline: [ 'border border-solid border-stroke-neutral-muted bg-bg-transparent text-fg-neutral', 'enabled:active:bg-bg-transparent-pressed', 'data-[state=active]:border-transparent data-[state=active]:bg-bg-neutral-solid data-[state=active]:text-fg-on-neutral-solid', 'enabled:data-[state=active]:active:bg-bg-neutral-solid-pressed', ], }, size: { // paddingX = chip root paddingX + label paddingX medium: 'min-h-[36px] min-w-x12 px-x3_5 text-t4', large: 'min-h-[40px] min-w-x13 px-[calc(var(--rb-dimension-x2_5)+var(--rb-dimension-x1_5))] text-t4', }, }, defaultVariants: { variant: 'neutralSolid', size: 'medium' }, },);export interface ChipTabsRootProps extends React.ComponentProps<typeof TabsPrimitive.Root> { variant?: ChipTabsVariant; size?: ChipTabsSize; /** 콘텐츠 영역이 남은 높이를 채울지(fill) 콘텐츠 높이에 맞출지(hug) 정해요. */ contentLayout?: ChipTabsContentLayout; /** 스크롤해도 탭 목록이 화면 위에 붙어 있어요. */ stickyList?: boolean;}function ChipTabsRoot({ className, variant = 'neutralSolid', size = 'medium', contentLayout = 'hug', stickyList = false, ...props}: ChipTabsRootProps) { const context = React.useMemo(() => ({ variant, size, stickyList }), [variant, size, stickyList]); return ( <ChipTabsContext.Provider value={context}> <TabsPrimitive.Root data-slot="chip-tabs" data-variant={variant} data-size={size} className={cn('relative', contentLayout === 'fill' ? 'flex h-full flex-col' : 'block', className)} {...props} /> </ChipTabsContext.Provider> );}export interface ChipTabsListProps extends React.ComponentProps<typeof TabsPrimitive.List> {}function ChipTabsList({ className, ...props }: ChipTabsListProps) { const { size, stickyList } = React.useContext(ChipTabsContext); return ( <TabsPrimitive.List data-slot="chip-tabs-list" className={cn(chipTabsListVariants({ size, stickyList }), className)} {...props} /> );}export interface ChipTabsTriggerProps extends React.ComponentProps<typeof TabsPrimitive.Trigger> { /** 라벨 오른쪽 위에 새 콘텐츠를 알리는 Notification Badge(점)를 보여줘요. */ notification?: boolean;}function ChipTabsTrigger({ className, children, notification = false, ...props }: ChipTabsTriggerProps) { const { variant, size } = React.useContext(ChipTabsContext); return ( <TabsPrimitive.Trigger data-slot="chip-tabs-trigger" className={cn(chipTabsTriggerVariants({ variant, size }), className)} {...props} > {notification ? ( <span className="relative"> {children} <span aria-hidden data-slot="chip-tabs-notification" className="absolute top-0 -right-[8px] size-[6px] rounded-full bg-bg-brand-solid" /> </span> ) : ( children )} </TabsPrimitive.Trigger> );}export interface ChipTabsContentProps extends React.ComponentProps<typeof TabsPrimitive.Content> {}function ChipTabsContent({ className, ...props }: ChipTabsContentProps) { return ( <TabsPrimitive.Content data-slot="chip-tabs-content" className={cn( 'min-w-0 overflow-x-hidden overflow-y-auto outline-none', 'focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-stroke-focus-ring', 'data-[state=inactive]:hidden', className, )} {...props} /> );}export { ChipTabsRoot, ChipTabsList, ChipTabsTrigger, ChipTabsContent, chipTabsListVariants, chipTabsTriggerVariants };