/** * @title Switch * @description 특정 설정 및 상태를 즉시 켜거나 끌 수 있도록 하는 컴포넌트예요. */'use client';import * as React from 'react';import { Switch as SwitchPrimitive } from 'radix-ui';import { cva, type VariantProps } from 'class-variance-authority';import { cn } from '@/lib/utils';// SEED Rootage spec: switchmark.yaml// Track(root) 색은 d1 + 20ms 지연으로, Thumb 크기·위치는 d3 로 전환돼요.const switchmarkVariants = cva( [ 'group/switchmark relative box-border block shrink-0 cursor-pointer rounded-full bg-palette-gray-600', 'transition-[background-color,opacity,scale,outline-color] ease-easing', 'duration-[var(--rb-duration-d1),var(--rb-duration-d1),var(--rb-duration-pressed-scale),var(--rb-duration-d3)] delay-[20ms,0ms,0ms,0ms]', 'outline-2 outline-offset-2 outline-transparent focus-visible:outline-stroke-focus-ring', 'enabled:active:scale-[0.95] group-active/switch:enabled:scale-[0.95]', 'disabled:cursor-not-allowed disabled:opacity-38', ], { variants: { tone: { brand: 'data-[state=checked]:bg-bg-brand-solid', neutral: 'data-[state=checked]:bg-bg-neutral-solid disabled:data-[state=checked]:bg-palette-gray-600', }, size: { '32': 'min-h-[32px] min-w-[52px] px-[3px] py-[3px]', '24': 'min-h-[24px] min-w-[38px] px-[2px] py-[2px]', '16': 'min-h-[16px] min-w-[26px] px-[2px] py-[2px]', }, }, defaultVariants: { tone: 'brand', size: '32', }, },);const switchThumbVariants = cva( [ 'pointer-events-none block rounded-full scale-80 data-[state=checked]:scale-100', 'transition-[scale,translate,background-color] duration-[var(--rb-duration-d3),var(--rb-duration-d3),var(--rb-duration-d1)] ease-easing', 'delay-[0ms,0ms,20ms]', ], { variants: { tone: { brand: 'bg-palette-static-white data-[state=checked]:bg-fg-on-brand-solid', neutral: 'bg-fg-on-neutral-solid group-disabled/switchmark:bg-palette-static-black-alpha-700', }, size: { // 선택 시 이동 거리: Track 너비 - Track 높이 '32': 'size-[26px] data-[state=checked]:translate-x-[20px]', '24': 'size-[20px] data-[state=checked]:translate-x-[14px]', '16': 'size-[12px] data-[state=checked]:translate-x-[10px]', }, }, defaultVariants: { tone: 'brand', size: '32', }, },);// SEED Rootage spec: switch.yamlconst switchVariants = cva( [ 'group/switch relative isolate box-border inline-flex cursor-pointer items-start justify-between align-top', 'has-[button:disabled]:cursor-not-allowed', ], { variants: { size: { '32': 'min-h-x8 gap-x2_5', '24': 'min-h-x6 gap-x2', // 16 은 최소 터치 영역을 위해 24 높이를 가져요. '16': 'min-h-x6 gap-x1_5', }, }, defaultVariants: { size: '32' }, },);const switchLabelVariants = cva( [ 'font-medium text-fg-neutral', 'transition-opacity duration-(--rb-duration-d1) ease-easing group-has-[button:disabled]/switch:opacity-58', ], { variants: { size: { // 수직 위치 보정: (root 높이 - 행간) / 2 '32': 'mt-[calc(var(--rb-dimension-x8)/2_-_var(--rb-line-height-t5)/2)] text-t5', '24': 'mt-[calc(var(--rb-dimension-x6)/2_-_var(--rb-line-height-t4)/2)] text-t4', '16': 'mt-[calc(var(--rb-dimension-x6)/2_-_var(--rb-line-height-t3)/2)] text-t3', }, }, defaultVariants: { size: '32' }, },);// 수직 위치 보정: (root 높이 - Switchmark 높이) / 2const switchmarkOffset = { '32': 'my-0', '24': 'my-0', '16': 'my-[4px]',} as const;type SwitchmarkVariantProps = VariantProps<typeof switchmarkVariants>;export interface SwitchmarkProps extends React.ComponentProps<typeof SwitchPrimitive.Root>, SwitchmarkVariantProps {}/** 라벨 없이 쓰는 스위치 표시(Track + Thumb)예요. List 처럼 다른 요소와 조합할 때 사용해요. 요소 크기 이상의 터치 영역은 없어요. */function Switchmark({ className, tone, size, ...props }: SwitchmarkProps) { return ( <SwitchPrimitive.Root data-slot="switchmark" className={cn(switchmarkVariants({ tone, size }), className)} {...props} > <SwitchPrimitive.Thumb data-slot="switchmark-thumb" className={switchThumbVariants({ tone, size })} /> </SwitchPrimitive.Root> );}export interface SwitchProps extends SwitchmarkProps { /** 스위치 옆에 보여줄 라벨이에요. 라벨을 눌러도 상태가 바뀌어요. */ label?: React.ReactNode; /** 바깥 label 요소에 줄 className 이에요. */ rootClassName?: string; rootRef?: React.Ref<HTMLLabelElement>;}function Switch({ label, tone, size, className, rootClassName, rootRef, ...props }: SwitchProps) { const resolvedSize = size ?? '32'; if (label == null) { return <Switchmark tone={tone} size={resolvedSize} className={className} {...props} />; } return ( <label ref={rootRef} data-slot="switch" className={cn(switchVariants({ size: resolvedSize }), rootClassName)}> <Switchmark tone={tone} size={resolvedSize} className={cn(switchmarkOffset[resolvedSize], className)} {...props} /> <span data-slot="switch-label" className={switchLabelVariants({ size: resolvedSize })}> {label} </span> </label> );}export { Switch, Switchmark, switchVariants, switchmarkVariants };