'use client';/** * @title Toggle Button * @description 사용자가 특정 상태를 켜거나 끌 수 있게 해주는 버튼 형태의 컴포넌트예요. 필터링이나 뷰 전환처럼 즉각적인 상태 변경에 사용해요. */import * as React from 'react';import { Toggle as TogglePrimitive } from 'radix-ui';import { cva, type VariantProps } from 'class-variance-authority';import { cn } from '@/lib/utils';// SEED Rootage spec: toggle-button.yaml// 선택 상태(pressed)는 Radix Toggle 이 붙이는 data-state="on" 으로 표현해요.const toggleButtonVariants = cva( [ 'relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center whitespace-nowrap align-middle font-bold antialiased', 'transition-[background-color,color,scale,outline-color] duration-(--rb-duration-color-transition) ease-easing', 'outline-2 outline-offset-2 outline-transparent focus-visible:outline-stroke-focus-ring', 'enabled:active:scale-[0.97] data-[loading]:active:scale-100', 'disabled:cursor-not-allowed disabled:bg-bg-disabled disabled:text-fg-disabled', '[&_svg]:pointer-events-none [&_svg]:shrink-0', ], { variants: { variant: { brandSolid: [ 'bg-bg-brand-solid text-fg-on-brand-solid enabled:active:bg-bg-brand-solid-pressed data-[loading]:bg-bg-brand-solid-pressed', '[--track-color:var(--rb-color-palette-static-white-alpha-300)] [--range-color:var(--rb-color-fg-on-brand-solid)]', 'data-[state=on]:bg-bg-neutral-weak data-[state=on]:text-fg-neutral', 'data-[state=on]:enabled:active:bg-bg-neutral-weak-pressed data-[state=on]:data-[loading]:bg-bg-neutral-weak-pressed', 'data-[state=on]:[--track-color:var(--rb-color-palette-gray-500)] data-[state=on]:[--range-color:var(--rb-color-fg-neutral)]', 'data-[state=on]:disabled:bg-bg-disabled data-[state=on]:disabled:text-fg-disabled', ], neutralWeak: [ 'bg-bg-neutral-weak text-fg-neutral enabled:active:bg-bg-neutral-weak-pressed data-[loading]:bg-bg-neutral-weak-pressed', '[--track-color:var(--rb-color-palette-gray-500)] [--range-color:var(--rb-color-fg-neutral)]', 'data-[state=on]:enabled:active:bg-bg-neutral-weak-pressed', 'data-[state=on]:disabled:bg-bg-disabled data-[state=on]:disabled:text-fg-disabled', ], }, size: { xsmall: 'h-x8 gap-x1 rounded-full px-x3_5 py-x1_5 text-t3 [&_svg]:size-x3_5', small: 'h-x9 gap-x1 rounded-full px-x4 py-x2 text-t4 [&_svg]:size-x3_5', }, }, defaultVariants: { variant: 'brandSolid', size: 'small', }, },);function ToggleButtonSpinner() { // progressCircle: size 14px, thickness 2px return ( <svg className="size-[14px] animate-spin" viewBox="0 0 16 16" fill="none" aria-hidden> <circle cx="8" cy="8" r="7" className="stroke-(--track-color)" strokeWidth="2" /> <path d="M8 1a7 7 0 0 1 7 7" className="stroke-(--range-color)" strokeWidth="2" strokeLinecap="round" /> </svg> );}export interface ToggleButtonProps extends React.ComponentProps<typeof TogglePrimitive.Root>, VariantProps<typeof toggleButtonVariants> { /** 로딩 중에는 라벨을 숨기고 스피너를 보여주며 클릭을 막아요. */ loading?: boolean;}function ToggleButton({ className, variant, size, loading = false, asChild = false, children, ...props}: ToggleButtonProps) { return ( <TogglePrimitive.Root data-slot="toggle-button" data-loading={loading || undefined} aria-busy={loading || undefined} asChild={asChild} className={cn(toggleButtonVariants({ variant, size }), loading && 'pointer-events-none', className)} {...props} > {asChild ? ( children ) : ( <> <span className={cn('inline-flex items-center gap-[inherit]', loading && 'invisible')}>{children}</span> {loading && ( <span className="absolute inset-0 flex items-center justify-center"> <ToggleButtonSpinner /> </span> )} </> )} </TogglePrimitive.Root> );}export { ToggleButton, toggleButtonVariants };