@gusun/design-system 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/Tabs-C_N6MDdv.js +1534 -0
- package/dist-lib/{Editor-wECQz1CV.js.map → Tabs-C_N6MDdv.js.map} +1 -1
- package/dist-lib/editor.d.ts +1 -0
- package/dist-lib/editor.js +716 -2
- package/dist-lib/editor.js.map +1 -0
- package/dist-lib/index.d.ts +0 -1
- package/dist-lib/index.js +1313 -1561
- package/dist-lib/index.js.map +1 -1
- package/package.json +1 -1
- package/dist-lib/Editor-wECQz1CV.js +0 -1994
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Editor-wECQz1CV.js","names":[],"sources":["../src/components/Slot.tsx","../src/components/Tooltip.tsx","../src/components/TruncatingText.tsx","../src/components/useHoverTooltip.tsx","../src/components/Button.tsx","../src/components/Input.tsx","../src/components/ScrollArea.tsx","../src/components/chipStyles.ts","../src/components/InlineFieldTrigger.tsx","../src/components/SearchBar.tsx","../src/components/Checkbox.tsx","../src/components/PopoverMenu.tsx","../src/components/ListEmpty.tsx","../src/components/ListGroup.tsx","../src/components/Tag.tsx","../src/components/Radio.tsx","../src/components/Switch.tsx","../src/components/List.tsx","../src/components/usePopoverPosition.ts","../src/components/useOutsideDismiss.ts","../src/components/popoverLayers.ts","../src/components/Select.tsx","../src/components/ButtonGroup.tsx","../src/components/Popover.tsx","../src/components/EditorToolbar.tsx","../src/components/SegmentControl.tsx","../src/components/MergeFieldNode.ts","../src/components/Editor.tsx"],"sourcesContent":["// Slot — asChild 합성 유틸(2026-07-20, Radix Slot 패턴 참고).\n// 단일 자식 엘리먼트에 props를 '머지'해 래퍼 없이 그 엘리먼트 자체로 렌더한다.\n// - className : 이어붙이기(자식 것 + 우리 것)\n// - style : 병합(우리 것이 우선)\n// - on* 핸들러: 합성(자식 핸들러 먼저, 우리 것 나중 — 둘 다 실행)\n// - ref : 병합(자식 ref + 우리 ref 둘 다 채움)\n// - content : 지정 시 자식의 내부 내용을 대체(미지정이면 자식 원래 내용 유지)\n// 용도: Button asChild 등 — \"버튼처럼 보이는 <a>\"를 유효하지 않은 중첩 없이 렌더.\nimport { cloneElement, isValidElement } from 'react';\nimport type { ReactElement, ReactNode, Ref } from 'react';\n\ntype AnyProps = Record<string, unknown>;\n\nfunction composeHandlers(childHandler: unknown, slotHandler: unknown) {\n return (event: unknown) => {\n (childHandler as ((e: unknown) => void) | undefined)?.(event);\n (slotHandler as ((e: unknown) => void) | undefined)?.(event);\n };\n}\n\nfunction mergeRefs(...refs: (Ref<unknown> | undefined)[]) {\n return (node: unknown) => {\n for (const ref of refs) {\n if (!ref) continue;\n if (typeof ref === 'function') ref(node);\n else (ref as { current: unknown }).current = node;\n }\n };\n}\n\nfunction mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {\n const merged: AnyProps = { ...childProps };\n for (const key in slotProps) {\n const slotValue = slotProps[key];\n const childValue = childProps[key];\n if (/^on[A-Z]/.test(key)) {\n // 이벤트 핸들러 — 양쪽 다 있으면 합성, 아니면 있는 쪽\n merged[key] = childValue ? composeHandlers(childValue, slotValue) : slotValue;\n } else if (key === 'className') {\n merged[key] = [childValue, slotValue].filter(Boolean).join(' ');\n } else if (key === 'style') {\n merged[key] = { ...(childValue as object), ...(slotValue as object) };\n } else {\n // 그 외는 우리 값 우선(undefined면 자식 값 유지)\n merged[key] = slotValue !== undefined ? slotValue : childValue;\n }\n }\n return merged;\n}\n\nexport interface SlotProps {\n /** 합성 대상 — 반드시 유효한 단일 엘리먼트 하나 */\n children: ReactNode;\n /** 자식의 내부 내용을 대체할 새 콘텐츠(미지정 시 자식 원래 내용 유지) */\n content?: ReactNode;\n /** 병합할 ref(우리 쪽) */\n ref?: Ref<unknown>;\n /** 그 외 머지할 props(className·style·on*·기타 속성) */\n [prop: string]: unknown;\n}\n\nexport function Slot({ children, content, ref, ...slotProps }: SlotProps) {\n if (!isValidElement(children)) {\n // 오용(자식이 엘리먼트가 아니거나 여러 개) — 조용히 무시하지 않고 개발 중 알림\n console.warn('Slot(asChild): 유효한 단일 엘리먼트 자식 하나가 필요합니다.');\n return null;\n }\n const child = children as ReactElement<AnyProps & { children?: ReactNode }>;\n const childRef = (child as { ref?: Ref<unknown> }).ref;\n const merged = mergeProps(slotProps, child.props);\n if (content !== undefined) merged.children = content;\n if (ref || childRef) merged.ref = mergeRefs(ref, childRef);\n return cloneElement(child, merged);\n}\n","// Tooltip — 오버레이 말풍선\n// Figma 02_textfield의 툴팁(error / normal 타입)을 컴포넌트화.\n// 위치 지정은 호출부에서 처리하고, 이 컴포넌트는 \"말풍선 박스 + 꼬리(beak)\"만 그린다.\n// - error: 빨강 배경 + beak (필수 입력 등)\n// - normal: 검정 배경, beak 없이 사용 (말줄임 전체 텍스트 등)\nimport type { ReactNode } from 'react';\nimport { textFieldColors } from '../tokens';\n\n// variant별 색상 — 시멘틱 토큰값을 인라인으로 적용\n// (Tailwind 클래스 'tf-normal-tooltip-*'의 -bg/-text 모호성 회피)\nconst VARIANT_COLOR = {\n error: { bg: textFieldColors['error-tooltip-bg'], fg: textFieldColors['error-tooltip-text'] },\n normal: { bg: textFieldColors['normal-tooltip-bg'], fg: textFieldColors['normal-tooltip-text'] },\n};\n\n// 꼬리(beak) 위치 — 'top' = 말풍선이 대상 아래, 꼬리가 위를 가리킴\nconst BEAK_STYLE = {\n top: '-top-spacing-2 left-spacing-5',\n bottom: '-bottom-spacing-2 left-spacing-5',\n};\n\ninterface TooltipProps {\n children?: ReactNode;\n variant?: keyof typeof VARIANT_COLOR; // 'error' | 'normal'\n beak?: keyof typeof BEAK_STYLE | 'none'; // 'top' | 'bottom' | 'none'\n className?: string;\n}\n\nexport function Tooltip({\n children,\n variant = 'error', // 'error' | 'normal'\n beak = 'top', // 'top' | 'bottom' | 'none'\n className = '',\n}: TooltipProps) {\n const c = VARIANT_COLOR[variant] ?? VARIANT_COLOR.error;\n\n return (\n <div\n role=\"tooltip\"\n style={{ backgroundColor: c.bg, color: c.fg }}\n className={`relative inline-flex items-center rounded-round-4 px-spacing-5 py-spacing-2 drop-shadow-[0px_2px_2px_rgba(13,13,13,0.12)] ${className}`}\n >\n {beak !== 'none' && (\n <span\n aria-hidden=\"true\"\n style={{ backgroundColor: c.bg }}\n className={`absolute h-spacing-5 w-spacing-5 rotate-45 rounded-[1px] ${BEAK_STYLE[beak] ?? BEAK_STYLE.top}`}\n />\n )}\n <p className=\"relative whitespace-nowrap text-12\">{children}</p>\n </div>\n );\n}\n","// TruncatingText — 말줄임(truncate) 텍스트 + hover 툴팁\n// 영역이 좁아 텍스트가 잘릴 수 있는 곳에 사용한다. 실제로 잘렸을 때만(scrollWidth>clientWidth)\n// hover 시 normal Tooltip으로 전체 텍스트를 보여준다.\n// 툴팁은 portal(document.body) + fixed로 overflow를 벗어나며, viewport 경계에서\n// 위→아래·오른쪽→왼쪽으로 자동 반전한다.\nimport { useRef, useState, useLayoutEffect } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Tooltip } from './Tooltip';\n\ninterface TruncatingTextProps extends ComponentPropsWithoutRef<'p'> {\n as?: ElementType; // 'p' | 'span' | 'div' 등\n}\n\nexport function TruncatingText({\n children,\n as: Component = 'p', // 'p' | 'span' | 'div' 등\n className = '',\n ...props\n}: TruncatingTextProps) {\n const ref = useRef<HTMLElement | null>(null);\n const tipRef = useRef<HTMLDivElement | null>(null);\n const [tipRect, setTipRect] = useState<DOMRect | null>(null);\n const [tipPos, setTipPos] = useState<{ top: number; left: number } | null>(null);\n\n const onEnter = () => {\n const el = ref.current;\n if (el && el.scrollWidth > el.clientWidth) setTipRect(el.getBoundingClientRect());\n };\n const onLeave = () => {\n setTipRect(null);\n setTipPos(null);\n };\n\n // 툴팁 크기를 재서 viewport를 벗어나면 위→아래, 오른쪽→왼쪽으로 반전\n useLayoutEffect(() => {\n if (!tipRect || !tipRef.current) return;\n const tw = tipRef.current.offsetWidth;\n const th = tipRef.current.offsetHeight;\n const gap = 6;\n const margin = 4;\n let top = tipRect.top - gap - th;\n if (top < margin) top = tipRect.bottom + gap;\n let left = tipRect.left;\n if (left + tw > window.innerWidth - margin) left = window.innerWidth - tw - margin;\n if (left < margin) left = margin;\n setTipPos({ top, left });\n }, [tipRect]);\n\n return (\n <>\n <Component\n ref={ref}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n className={`truncate ${className}`}\n {...props}\n >\n {children}\n </Component>\n {/* 툴팁은 형제 + portal로 분리해 텍스트 요소 truncate 레이아웃에 영향 주지 않음 */}\n {tipRect &&\n createPortal(\n <div\n ref={tipRef}\n className=\"pointer-events-none fixed z-[1000]\"\n style={{\n left: tipPos ? tipPos.left : tipRect.left,\n top: tipPos ? tipPos.top : tipRect.top,\n visibility: tipPos ? 'visible' : 'hidden',\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {children}\n </Tooltip>\n </div>,\n document.body,\n )}\n </>\n );\n}\n","// useHoverTooltip — 라벨이 안 보이는 컨트롤(아이콘 전용 버튼 등)에 hover 시 명칭을 알려주는 툴팁.\n// TruncatingText와 같은 패턴: normal Tooltip을 portal(document.body) + fixed로 띄워 부모 overflow를\n// 벗어나고, viewport 경계에서 아래→위·좌우로 자동 반전한다. 대상 요소는 반환한 onMouseEnter/Leave로 감지.\n// - label: 표시할 문구(없으면 비활성 — 툴팁 안 뜸)\n// - delay: 아이콘이 늘어선 툴바에서 커서 이동 시 번쩍임 방지용 표시 지연(ms, 기본 300)\n// 사용: const t = useHoverTooltip(label); <button onMouseEnter={t.onMouseEnter} .../>{t.tooltip}\nimport { useRef, useState, useLayoutEffect, useEffect, useCallback } from 'react';\nimport type { ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Tooltip } from './Tooltip';\n\nexport function useHoverTooltip(label: ReactNode, { delay = 300 }: { delay?: number } = {}) {\n const [rect, setRect] = useState<DOMRect | null>(null);\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n const tipRef = useRef<HTMLDivElement | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const enabled = !!label;\n\n // 표시 지연 타이머 — 언마운트/leave 시 정리(잔여 setState 방지)\n useEffect(() => () => clearTimeout(timerRef.current), []);\n\n const onMouseEnter = useCallback(\n // 실제 MouseEvent뿐 아니라 {currentTarget}만 담은 합성 호출도 허용(FormulaAddZone의 툴팁 재표시 등)\n (e: { currentTarget: HTMLElement }) => {\n if (!enabled) return;\n const el = e.currentTarget;\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setRect(el.getBoundingClientRect()), delay);\n },\n [enabled, delay],\n );\n\n const onMouseLeave = useCallback(() => {\n clearTimeout(timerRef.current);\n setRect(null);\n setPos(null);\n }, []);\n\n // 툴팁 크기를 재서 아래 공간이 없으면 위로, 좌우 경계는 안으로 밀어 넣는다.\n useLayoutEffect(() => {\n if (!rect || !tipRef.current) return;\n const tw = tipRef.current.offsetWidth;\n const th = tipRef.current.offsetHeight;\n const gap = 6;\n const margin = 4;\n let top = rect.bottom + gap;\n if (top + th > window.innerHeight - margin) top = rect.top - gap - th;\n let left = rect.left + rect.width / 2 - tw / 2; // 대상 중앙 정렬\n if (left + tw > window.innerWidth - margin) left = window.innerWidth - tw - margin;\n if (left < margin) left = margin;\n setPos({ top, left });\n }, [rect]);\n\n const tooltip = rect\n ? createPortal(\n <div\n ref={tipRef}\n className=\"pointer-events-none fixed z-[1000]\"\n style={{\n left: pos ? pos.left : rect.left,\n top: pos ? pos.top : rect.bottom,\n visibility: pos ? 'visible' : 'hidden', // 위치 계산 전 첫 프레임 숨김(점프 방지)\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {label}\n </Tooltip>\n </div>,\n document.body,\n )\n : null;\n\n return { onMouseEnter, onMouseLeave, tooltip };\n}\n","import { useLayoutEffect, useRef, isValidElement } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType, MouseEvent as ReactMouseEvent, ReactElement, ReactNode, Ref } from 'react';\nimport { LoaderCircle } from 'lucide-react';\nimport { Slot } from './Slot';\nimport { TruncatingText } from './TruncatingText';\nimport { useHoverTooltip } from './useHoverTooltip';\n\ninterface ButtonProps extends ComponentPropsWithoutRef<'button'> {\n size?: '32' | '24' | '18'; // '18' = 아이콘 전용 소형 — 버튼 18×18·아이콘 14×14\n variant?: 'fill' | 'line' | 'ghost' | 'underline'; // 'underline' = 밑줄 텍스트 버튼\n color?: 'black' | 'red' | 'blue' | 'green' | 'violet' | 'pink' | 'orange'; // underline 전용 색 — 'black'(기본)\n weight?: 'normal' | 'semibold'; // underline 전용 두께 — 'normal'(기본)\n leftIcon?: ElementType | null; // lucide 외 커스텀 아이콘 컴포넌트도 허용\n rightIcon?: ElementType | null;\n icon?: ElementType | null;\n loading?: boolean;\n truncate?: boolean; // 라벨이 부모 폭을 넘으면 말줄임(테이블 셀 등 좁은 영역용)\n width?: 'hug' | 'fill'; // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭) — underline 변형엔 미적용\n showTooltip?: boolean; // 아이콘 전용 버튼 hover 명칭 툴팁 on/off (기본 켬)\n tooltip?: ReactNode; // 툴팁 문구 커스텀. 미지정 시 aria-label 사용\n asChild?: boolean; // true면 <button> 대신 자식 엘리먼트(단일)를 루트로 렌더하고\n // 버튼 스타일·동작을 그 엘리먼트에 머지(예: 링크를 버튼처럼 — Slot).\n // 이때 children은 유효한 단일 엘리먼트여야 하며, 그 내용이 라벨이 된다.\n}\n\nexport function Button({\n children,\n size = '32', // '32' | '24' | '18'(아이콘 전용 소형 — 버튼 18×18·아이콘 14×14)\n variant = 'fill', // 'fill' | 'line' | 'ghost' | 'underline'(밑줄 텍스트 버튼)\n color = 'black', // underline 전용 색 — 'black'(기본) | 'red' | 'blue' | 'green' | 'violet' | 'pink' | 'orange'\n weight = 'normal', // underline 전용 두께 — 'normal'(기본) | 'semibold'\n leftIcon: LeftIcon = null,\n rightIcon: RightIcon = null,\n icon: Icon = null,\n disabled = false,\n loading = false,\n truncate = false, // 라벨이 부모 폭을 넘으면 말줄임(테이블 셀 등 좁은 영역용)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭) — underline 변형엔 미적용\n showTooltip = true, // 아이콘 전용 버튼 hover 명칭 툴팁 on/off (기본 켬)\n tooltip, // 툴팁 문구 커스텀. 미지정 시 aria-label 사용\n asChild = false, // <button> 대신 자식 엘리먼트를 루트로(Slot 합성)\n onClick,\n onMouseEnter,\n onMouseLeave,\n className = '',\n ...props\n}: ButtonProps) {\n const inactive = disabled || loading;\n const iconOnly = !!Icon;\n // 아이콘만 있는 버튼은 명칭이 안 보이므로 hover 툴팁으로 알려준다(showTooltip으로 on/off,\n // 문구=tooltip prop ?? aria-label).\n const tipLabel = iconOnly && showTooltip ? (tooltip ?? props['aria-label']) : null;\n const hoverTip = useHoverTooltip(tipLabel);\n const iconSize = size === '18' || size === '24' ? 14 : 16;\n // fill: 부모 폭을 100% 채운다(밑줄 텍스트 버튼은 박스가 없어 제외).\n const isFill = width === 'fill' && variant !== 'underline';\n const ref = useRef<HTMLElement | null>(null);\n // asChild 유효성 — 자식이 단일 엘리먼트일 때만 Slot 합성(아니면 일반 <button> 폴백)\n const slotChild = asChild && isValidElement(children) ? (children as ReactElement<{ children?: ReactNode }>) : null;\n // 라벨 소스 — Slot이면 자식 엘리먼트의 내용, 아니면 children prop\n const label = slotChild ? slotChild.props.children : children;\n\n useLayoutEffect(() => {\n const el = ref.current;\n // truncate(셀 폭 추종)·fill(부모 폭 100%)이면 콘텐츠 기준 너비 고정을 하지 않는다.\n if (!el || truncate || isFill) {\n if (el && isFill) el.style.width = '';\n return;\n }\n el.style.width = '';\n el.style.width = el.offsetWidth + 'px';\n }, [children, size, variant, LeftIcon, RightIcon, Icon, disabled, loading, truncate, isFill]);\n\n // 텍스트 두께 — underline 변형만 semibold 옵션 지원(2026-07-15), 그 외는 400 고정\n const weightClass =\n variant === 'underline' && weight === 'semibold' ? 'font-semibold' : 'font-normal';\n // 포커스 디자인 = 호버 디자인(2026-07-16 지시) — 기본 아웃라인을 끄고, 변형별로\n // focus-visible(키보드 포커스)에 hover와 같은 효과를 준다(마우스 클릭에는 표시 없음).\n const base =\n `inline-flex items-center justify-center relative font-pretendard ${weightClass} ` +\n 'whitespace-nowrap rounded-round-4 transition-colors select-none focus:outline-none';\n // truncate: 버튼이 부모 폭 안에서 줄어들고(min-w-0/max-w-full) 라벨이 말줄임되게 한다.\n const truncStyle = truncate ? 'min-w-0 max-w-full' : '';\n // fill: 부모 전체 폭(inline-flex라도 width:100% 적용됨).\n const widthStyle = isFill ? 'w-full' : '';\n\n let sizeStyle;\n if (variant === 'underline') {\n // 밑줄 텍스트 버튼 — 배경/패딩 없이 텍스트(+아이콘)만. 높이만 맞춘다.\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] text-[12px] leading-5 tracking-[0px]'\n : 'min-h-[32px] text-[14px] leading-6 tracking-[0px]';\n } else if (iconOnly) {\n // 18 = 아이콘 전용 소형(18×18, 아이콘 14 → 상하좌우 2px 패딩). 24/32는 min-h/w로 높이 확보.\n sizeStyle =\n size === '18'\n ? 'min-h-[18px] min-w-[18px] p-spacing-2'\n : size === '24'\n ? 'min-h-[24px] min-w-[24px] p-spacing-2'\n : 'min-h-[32px] min-w-[32px] p-spacing-3';\n } else {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] px-spacing-5 py-spacing-2 text-[12px] leading-5 tracking-[0px]'\n : 'min-h-[32px] min-w-[32px] px-spacing-6 py-spacing-3 text-[14px] leading-6 tracking-[0px]';\n }\n\n let colorStyle;\n if (variant === 'fill') {\n colorStyle = inactive\n ? 'bg-button-fill-disabled-bg text-button-fill-disabled-fg cursor-not-allowed'\n : 'bg-button-fill-default-bg text-button-fill-default-fg cursor-pointer ' +\n 'hover:bg-button-fill-hover-bg focus-visible:bg-button-fill-hover-bg ' +\n 'active:bg-button-fill-default-bg';\n } else if (variant === 'line') {\n colorStyle = inactive\n ? 'bg-button-line-disabled-bg text-button-line-disabled-fg cursor-not-allowed'\n : 'bg-button-line-default-bg text-button-line-default-fg ring-1 ring-inset ring-button-line-default-line cursor-pointer ' +\n 'hover:bg-button-line-hover-bg hover:ring-button-line-hover-line ' +\n 'focus-visible:bg-button-line-hover-bg focus-visible:ring-button-line-hover-line ' +\n 'active:bg-button-line-default-bg active:ring-button-line-default-line';\n } else if (variant === 'underline') {\n // 밑줄 텍스트 버튼 — 배경 없이 텍스트색만, hover 시 밑줄(active=눌렸을 땐 밑줄 제거).\n // color 변형(2026-07-15): black 기본 + 칩 컬러 대응 6색(button-underline-* 토큰, 정적 맵=purge 안전)\n const UNDERLINE_COLOR = {\n black: 'text-button-underline-black-fg',\n red: 'text-button-underline-red-fg',\n blue: 'text-button-underline-blue-fg',\n green: 'text-button-underline-green-fg',\n violet: 'text-button-underline-violet-fg',\n pink: 'text-button-underline-pink-fg',\n orange: 'text-button-underline-orange-fg',\n };\n colorStyle = inactive\n ? 'bg-transparent text-font-icon-2 cursor-not-allowed'\n : `bg-transparent ${UNDERLINE_COLOR[color] ?? UNDERLINE_COLOR.black} cursor-pointer ` +\n 'hover:underline focus-visible:underline active:no-underline';\n } else {\n // ghost — 비활성은 세그먼트 컨트롤과 동일(투명 배경 + font-icon-2 텍스트)\n colorStyle = inactive\n ? 'bg-transparent text-font-icon-2 cursor-not-allowed'\n : 'bg-transparent text-button-ghost-default-fg cursor-pointer ' +\n 'hover:bg-button-ghost-hover-bg focus-visible:bg-button-ghost-hover-bg ' +\n 'active:bg-transparent';\n }\n\n // 내부 콘텐츠(로딩 스피너 + 아이콘·라벨 레이아웃) — button/Slot 두 경로 공통\n const innerContent = (\n <>\n {loading && (\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <LoaderCircle\n size={iconSize}\n strokeWidth={1.8}\n className=\"animate-spin text-font-icon-3\"\n />\n </span>\n )}\n <span className={`inline-flex items-center gap-spacing-3 ${truncate ? 'min-w-0' : ''} ${loading ? 'opacity-0' : ''}`}>\n {iconOnly ? (\n <Icon size={iconSize} strokeWidth={1.8} />\n ) : (\n <>\n {LeftIcon && <LeftIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n {truncate ? (\n <TruncatingText as=\"span\" className=\"min-w-0\">{label}</TruncatingText>\n ) : (\n label\n )}\n {RightIcon && <RightIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n </>\n )}\n </span>\n </>\n );\n\n const rootClassName = `${base} ${sizeStyle} ${colorStyle} ${truncStyle} ${widthStyle} ${className}`;\n const handleMouseEnter = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onMouseEnter?.(e);\n hoverTip.onMouseEnter(e);\n };\n const handleMouseLeave = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onMouseLeave?.(e);\n hoverTip.onMouseLeave();\n };\n\n return (\n <>\n {slotChild ? (\n // asChild — 자식 엘리먼트를 루트로, 버튼 스타일·동작을 머지(래퍼 없음).\n // <a>엔 disabled 속성이 없으므로 aria-disabled로 대체하고 onClick은 비활성 시 차단.\n <Slot\n ref={ref}\n content={innerContent}\n className={rootClassName}\n aria-disabled={inactive || undefined}\n onClick={!inactive ? onClick : undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n {...props}\n >\n {slotChild}\n </Slot>\n ) : (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n className={rootClassName}\n disabled={inactive}\n onClick={!inactive ? onClick : undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n {...props}\n >\n {innerContent}\n </button>\n )}\n {hoverTip.tooltip}\n </>\n );\n}\n","// Input — 텍스트 입력 필드 (Figma 02_textfield / input, solid 타입)\n// 상태(default/hover/focused/filled)는 CSS와 실제 입력값으로 자동 처리.\n// disabled / readOnly / error 는 props로 노출(완전 옵션화).\n//\n// 에러 표현 규칙: 에러는 인풋 테두리를 바꾸지 않고 \"툴팁\"으로만 표시한다.\n// - 툴팁은 인풋 박스 아래에 absolute 오버레이로 띄워, 레이아웃 흐름과\n// 형제 컴포넌트 영역에 전혀 영향을 주지 않는다(공간을 차지하지 않음).\nimport type { ChangeEvent, ComponentPropsWithoutRef } from 'react';\nimport { Tooltip } from './Tooltip';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰). 색은 시멘틱 토큰.\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// 사이즈 — '32'(기본, 32px·text-14) | '22'(작게, 22px·text-12·leading-18로 핏하게, 좁은 셀/인라인용).\nconst SIZE_STYLE = {\n '32': { box: 'min-h-[32px] px-spacing-6 py-spacing-3', text: 'text-14' },\n '22': { box: 'min-h-[22px] px-spacing-5 py-spacing-2', text: 'text-12 leading-18' },\n};\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <input> 속성은 inputProps로 전달한다.\ninterface InputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string | number;\n defaultValue?: string | number;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n size?: keyof typeof SIZE_STYLE; // '32'(32px·14px) | '22'(22px·12px, 라인하이트 핏)\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n inputProps?: ComponentPropsWithoutRef<'input'>;\n}\n\nexport function Input({\n value,\n defaultValue,\n onChange,\n placeholder = '텍스트를 입력하세요',\n size = '32', // '32'(32px·14px) | '22'(22px·12px, 라인하이트 핏)\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 200, // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n className = '',\n inputProps = {},\n ...props\n}: InputProps) {\n const interactive = !disabled && !readOnly;\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const sizeStyle = SIZE_STYLE[size] ?? SIZE_STYLE['32'];\n\n const textColor = disabled\n ? 'text-text-field-disabled-text'\n : readOnly\n ? 'text-text-field-readonly-text'\n : 'text-text-field-filled-text';\n // disabled면 플레이스홀더도 비활성 색(#c9c9c9) — TextArea와 동일 패턴(Figma disabled 스펙)\n const placeholderColor = disabled\n ? 'placeholder:text-text-field-disabled-text'\n : 'placeholder:text-text-field-default-text';\n\n return (\n <div\n style={{ width: widthStyle }}\n className={`relative flex items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg transition-shadow ${sizeStyle.box} ${\n interactive ? RING : 'cursor-not-allowed'\n } ${className}`}\n {...props}\n >\n <input\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={error || undefined}\n className={`min-w-0 flex-1 bg-transparent outline-none ${sizeStyle.text} ${placeholderColor} disabled:cursor-not-allowed read-only:cursor-default ${textColor}`}\n {...inputProps}\n />\n\n {/* 에러 툴팁 — absolute 오버레이라 인풋 아래 공간을 차지하지 않는다 */}\n {error && errorMessage && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errorMessage}\n </Tooltip>\n </div>\n )}\n </div>\n );\n}\n","// ScrollArea — 커스텀 오버레이 스크롤바를 가진 스크롤 영역 (공용)\n// 스크롤이 생기는 모든 곳에서 사용한다. 네이티브 스크롤바를 숨기고(.hide-native-scroll),\n// 콘텐츠 위에 absolute thumb를 직접 그려 오버레이(콘텐츠 폭/높이를 줄이지 않음)로 표시한다.\n// - thumb 색: 기본 scroll-bar(gray-900-50), hover·드래그 중 scroll-bar-hover(gray-900-75)\n// - thumb 양끝 여백 TRACK_PAD로 끝에 닿지 않게, 드래그/스크롤로 위치 동기화\n// - horizontal=true 이면 가로 스크롤도 동일한 오버레이 thumb로 처리(세로와 함께 가능)\n//\n// 사용: <ScrollArea maxHeight={240}> ... </ScrollArea> (세로 오버레이)\n// <ScrollArea maxHeight={240} horizontal> ... </ScrollArea> (세로+가로 오버레이)\nimport { useRef, useState, useCallback, useEffect, useLayoutEffect } from 'react';\nimport type { ComponentPropsWithoutRef, MouseEvent as ReactMouseEvent, UIEvent } from 'react';\nimport { listColors } from '../tokens';\n\nconst THUMB_MIN = 24; // thumb 최소 길이(px)\nconst TRACK_PAD = 6; // thumb 양끝 여백(px)\nconst CROSS_INSET = 16; // 가로·세로 스크롤이 동시에 있을 때 교차 영역만큼 트랙을 줄이는 여백(px)\n\n// 세로 트랙 메트릭 — sticky 헤더(topInset)와 가로 스크롤바 자리(bottomInset)를 제외한 트랙 높이.\nfunction verticalTrack(el: HTMLElement, horizontal: boolean) {\n const stickyTop = el.querySelector<HTMLElement>('[data-scroll-sticky-top]');\n const topInset = stickyTop ? stickyTop.offsetHeight : 0;\n const bottomInset = horizontal && el.scrollWidth > el.clientWidth + 1 ? CROSS_INSET : 0;\n return { topInset, trackH: el.clientHeight - topInset - bottomInset - TRACK_PAD * 2 };\n}\n\n// thumb 색 변형 — default(밝은 배경)·light(어두운 배경 위 흰색)\nconst THUMB_COLORS = {\n default: { base: listColors['scroll-bar'], active: listColors['scroll-bar-hover'] },\n light: { base: listColors['scroll-bar-light'], active: listColors['scroll-bar-light-hover'] },\n};\n\n// 오버레이 thumb — 세로/가로 공용(축만 다름).\n// 시각 두께 6px 유지 + 클릭(히트) 영역은 10px — 투명 ::before를 사방 2px 확장해 hover/드래그 시작이\n// 쉬워진다(pseudo-element 영역도 원본 요소의 히트 대상, 2026-07-06 지시 · 8→10 확대).\nconst THUMB_HIT = \"before:absolute before:-inset-spacing-2 before:content-['']\";\n\ninterface ThumbState {\n pos: number;\n size: number;\n visible: boolean;\n}\n\ninterface ThumbProps {\n orientation: 'vertical' | 'horizontal';\n thumb: ThumbState;\n active: boolean;\n colors: { base: string; active: string };\n onDown: (e: ReactMouseEvent<HTMLDivElement>) => void;\n onEnter: () => void;\n onLeave: () => void;\n}\n\nfunction Thumb({ orientation, thumb, active, colors, onDown, onEnter, onLeave }: ThumbProps) {\n const vertical = orientation === 'vertical';\n const placement = vertical ? 'right-spacing-3 w-spacing-4' : 'bottom-spacing-3 h-spacing-4'; // 두께 6px(2026-07-06 8→6)\n const extent = vertical\n ? { top: `${thumb.pos}px`, height: `${thumb.size}px` }\n : { left: `${thumb.pos}px`, width: `${thumb.size}px` };\n return (\n <div\n onMouseDown={onDown}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n className={`absolute cursor-pointer rounded-round-00 ${THUMB_HIT} ${placement}`}\n style={{\n ...extent,\n opacity: thumb.visible ? 1 : 0,\n pointerEvents: thumb.visible ? 'auto' : 'none',\n backgroundColor: active ? colors.active : colors.base,\n }}\n />\n );\n}\n\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<'div'> {\n maxHeight?: number | string;\n horizontal?: boolean;\n /** 'default'(밝은 배경) | 'light'(어두운 배경 위 흰색 thumb) */\n variant?: keyof typeof THUMB_COLORS;\n /** (선택) 내부 스크롤 요소를 넘겨받는 콜백 — 부모가 scrollTop 등을 직접 제어할 때 */\n onViewport?: (el: HTMLDivElement | null) => void;\n /** (선택) 스크롤 이벤트 패스스루(내부 thumb 갱신과 함께 호출) */\n onScroll?: (e: UIEvent<HTMLDivElement>) => void;\n /**\n * (선택) 세로 thumb를 자신의 viewport 대신 이 요소의 세로 스크롤에 연동.\n * 가로 스크롤 콘텐츠 '안'에 세로 스크롤 영역이 중첩될 때(표 바디 등) 세로 thumb를\n * 가로 스크롤을 안 받는 이 컴포넌트(고정 폭)에서 그려 항상 보이게 한다(2026-07-06).\n */\n vScrollEl?: HTMLElement | null;\n contentClassName?: string;\n}\n\nexport function ScrollArea({\n children,\n maxHeight,\n horizontal = false,\n variant = 'default',\n onViewport,\n onScroll,\n vScrollEl = null,\n className = '',\n contentClassName = '',\n ...props\n}: ScrollAreaProps) {\n const thumbColors = THUMB_COLORS[variant] ?? THUMB_COLORS.default;\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<HTMLDivElement | null>(null); // vScrollEl 연동 시 thumb 세로 위치 기준(루트 상단 대비 오프셋 측정)\n // 내부 scrollRef를 채우면서 외부로 스크롤 요소를 전달하는 콜백 ref\n const setViewport = useCallback(\n (el: HTMLDivElement | null) => {\n scrollRef.current = el;\n onViewport?.(el);\n },\n [onViewport],\n );\n const vDragRef = useRef(false);\n const hDragRef = useRef(false);\n const [vThumb, setVThumb] = useState<ThumbState>({ pos: 0, size: 0, visible: false });\n const [hThumb, setHThumb] = useState<ThumbState>({ pos: 0, size: 0, visible: false });\n const [vActive, setVActive] = useState(false); // hover 또는 드래그 중\n const [hActive, setHActive] = useState(false);\n\n const maxHeightStyle = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight;\n\n // 세로 트랙 정보 — vScrollEl 연동 시: 대상 요소 기준 메트릭 + 루트 상단 대비 오프셋(헤더 높이 등).\n // sizeInset은 sticky-top(자기 viewport 내부 요소) 케이스에서만 스크롤 계산에 반영된다.\n const vTrack = useCallback(() => {\n const el = scrollRef.current;\n if (vScrollEl) {\n const rootTop = rootRef.current?.getBoundingClientRect().top ?? 0;\n const topInset = vScrollEl.getBoundingClientRect().top - rootTop;\n const bottomInset =\n horizontal && el && el.scrollWidth > el.clientWidth + 1 ? CROSS_INSET : 0;\n return {\n vEl: vScrollEl,\n topInset,\n sizeInset: 0,\n trackH: vScrollEl.clientHeight - bottomInset - TRACK_PAD * 2,\n };\n }\n const { topInset, trackH } = verticalTrack(el as HTMLElement, horizontal);\n return { vEl: el as HTMLElement, topInset, sizeInset: topInset, trackH };\n }, [vScrollEl, horizontal]);\n\n const measureThumbs = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n const { scrollWidth, clientWidth, scrollLeft } = el;\n\n // 세로 — vScrollEl이 있으면 그 요소의 스크롤 메트릭 기준\n const { vEl, topInset, sizeInset, trackH } = vTrack();\n const { scrollHeight, clientHeight, scrollTop } = vEl;\n if (scrollHeight <= clientHeight + 1) {\n setVThumb((t) => (t.visible ? { ...t, visible: false } : t));\n } else {\n const size = Math.max(((clientHeight - sizeInset) / (scrollHeight - sizeInset)) * trackH, THUMB_MIN);\n const pos = TRACK_PAD + topInset + (scrollTop / (scrollHeight - clientHeight)) * (trackH - size);\n setVThumb({ pos, size, visible: true });\n }\n\n // 가로\n if (!horizontal || scrollWidth <= clientWidth + 1) {\n setHThumb((t) => (t.visible ? { ...t, visible: false } : t));\n } else {\n const trackW = clientWidth - TRACK_PAD * 2;\n const size = Math.max((clientWidth / scrollWidth) * trackW, THUMB_MIN);\n const pos = TRACK_PAD + (scrollLeft / (scrollWidth - clientWidth)) * (trackW - size);\n setHThumb({ pos, size, visible: true });\n }\n }, [horizontal, vTrack]);\n\n // 측정 rAF 코얼레싱(2026-07-07 감사) — scroll·RO·vScrollEl 리스너가 같은 프레임에 겹쳐도\n // DOM 측정+setState는 프레임당 1회만 수행한다(스크롤 중 measure 반복으로 인한 jank 방지).\n const rafRef = useRef(0);\n const updateThumbs = useCallback(() => {\n if (rafRef.current) return;\n rafRef.current = requestAnimationFrame(() => {\n rafRef.current = 0;\n measureThumbs();\n });\n }, [measureThumbs]);\n useEffect(\n () => () => {\n // 취소 후 반드시 0으로 리셋 — 안 하면 StrictMode 재마운트 후 rafRef가 죽은 id로 남아\n // updateThumbs의 '이미 예약됨' 가드에 걸려 측정이 영영 스킵된다(썸 안 보임).\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n }\n },\n [],\n );\n\n useLayoutEffect(() => {\n updateThumbs(); // rAF 예약이라 effect 내 동기 setState 아님\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(updateThumbs);\n ro.observe(el);\n return () => ro.disconnect();\n }, [updateThumbs, children]);\n\n // vScrollEl 연동 — 외부 세로 스크롤 요소의 scroll/resize에 thumb를 동기화한다.\n // (초기 1회 동기화는 위 useLayoutEffect가 담당 — vScrollEl 변경 시 updateThumbs 정체성이\n // 바뀌어 위 effect가 재실행되므로 여기서 직접 호출하지 않는다)\n useLayoutEffect(() => {\n if (!vScrollEl) return;\n vScrollEl.addEventListener('scroll', updateThumbs);\n const ro = new ResizeObserver(updateThumbs);\n ro.observe(vScrollEl);\n return () => {\n vScrollEl.removeEventListener('scroll', updateThumbs);\n ro.disconnect();\n };\n }, [vScrollEl, updateThumbs]);\n\n // 드래그 도중 언마운트(ESC로 팝오버/모달 닫힘 등) 시 window 리스너·body userSelect가\n // 잔존하지 않게 진행 중인 드래그의 정리 함수를 보관했다가 unmount에서 실행한다(2026-07-07 감사).\n const dragCleanupRef = useRef<(() => void) | null>(null);\n useEffect(() => () => dragCleanupRef.current?.(), []);\n\n // thumb 드래그 — 세로/가로 공용. 클릭 지점 대비 이동량을 스크롤 비율로 환산해 동기화한다.\n const startThumbDrag = (e: ReactMouseEvent<HTMLDivElement>, axis: 'vertical' | 'horizontal') => {\n e.preventDefault();\n e.stopPropagation();\n const vertical = axis === 'vertical';\n const dragRef = vertical ? vDragRef : hDragRef;\n const setActive = vertical ? setVActive : setHActive;\n // 세로 드래그는 vScrollEl 연동 대상이 있으면 그 요소를 스크롤한다\n const el = (vertical ? vTrack().vEl : scrollRef.current) as HTMLElement;\n dragRef.current = true;\n setActive(true);\n\n const start = vertical ? e.clientY : e.clientX;\n const startScroll = vertical ? el.scrollTop : el.scrollLeft;\n const maxScroll = vertical ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;\n const trackLen = vertical ? vTrack().trackH : el.clientWidth - TRACK_PAD * 2;\n const thumbSize = vertical ? vThumb.size : hThumb.size;\n // 트랙이 thumb 최소 길이(24px)보다 짧으면 분모가 0/음수 → NaN 스크롤 점프 방지(이동 무시)\n const denom = trackLen - thumbSize;\n const ratio = denom > 0 ? maxScroll / denom : 0;\n\n const onMove = (ev: MouseEvent) => {\n const delta = (vertical ? ev.clientY : ev.clientX) - start;\n if (vertical) el.scrollTop = startScroll + delta * ratio;\n else el.scrollLeft = startScroll + delta * ratio;\n };\n const onUp = () => {\n dragRef.current = false;\n setActive(false);\n document.body.style.userSelect = '';\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n dragCleanupRef.current = null;\n };\n document.body.style.userSelect = 'none';\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n dragCleanupRef.current = onUp;\n };\n\n // 기본은 relative(thumb의 기준). 단 className에 position 유틸(absolute 등)을 주면 그걸 따른다\n // — 부모 박스를 explicit height로 채워(예: absolute inset-0) 내부 h-full 스크롤이 확실히 동작하게.\n const hasPosition = /(^|\\s)(absolute|fixed|relative|sticky)(\\s|$)/.test(className);\n return (\n <div ref={rootRef} className={`${hasPosition ? '' : 'relative'} ${className}`} {...props}>\n <div\n ref={setViewport}\n onScroll={(e) => {\n updateThumbs();\n onScroll?.(e);\n }}\n className={`hide-native-scroll ${horizontal ? 'overflow-auto' : 'overflow-y-auto'} ${contentClassName}`}\n style={{ maxHeight: maxHeightStyle }}\n >\n {children}\n </div>\n\n {/* 세로 오버레이 thumb — 항상 렌더, opacity로 표시 제어 */}\n <Thumb\n orientation=\"vertical\"\n thumb={vThumb}\n active={vActive}\n colors={thumbColors}\n onDown={(e) => startThumbDrag(e, 'vertical')}\n onEnter={() => setVActive(true)}\n onLeave={() => {\n if (!vDragRef.current) setVActive(false);\n }}\n />\n\n {/* 가로 오버레이 thumb */}\n {horizontal && (\n <Thumb\n orientation=\"horizontal\"\n thumb={hThumb}\n active={hActive}\n colors={thumbColors}\n onDown={(e) => startThumbDrag(e, 'horizontal')}\n onEnter={() => setHActive(true)}\n onLeave={() => {\n if (!hDragRef.current) setHActive(false);\n }}\n />\n )}\n </div>\n );\n}\n","// 칩 비주얼 공용 클래스 맵 — Chip(제거형 칩)과 SelectChip(칩형 Select 트리거)이 공유한다.\n// 컴포넌트 파일에서 상수를 export하면 react-refresh 규칙에 걸려 별도 파일로 분리(modalContext와 동일 관례).\n// color별 전체 클래스(Tailwind purge를 위해 동적 문자열이 아닌 완전한 클래스명으로 나열).\n// text 색만 지정하면 내부 아이콘(lucide, stroke=currentColor)이 상속받는다. icon 토큰=text 토큰 값.\n// 포커스 디자인 = 호버 디자인(2026-07-16 지시): 링 대신 hover 색 전환을\n// focus-visible(키보드 Tab)과 data-[chain-focus](체인 programmatic focus)에 동일 적용.\nexport const CHIP_COLOR_CLASS = {\n gray: 'text-chip-gray-default-text border-chip-gray-default-line bg-chip-gray-default-bg hover:text-chip-gray-hover-text hover:border-chip-gray-hover-line hover:bg-chip-gray-hover-bg focus-visible:text-chip-gray-hover-text focus-visible:border-chip-gray-hover-line focus-visible:bg-chip-gray-hover-bg data-[chain-focus]:text-chip-gray-hover-text data-[chain-focus]:border-chip-gray-hover-line data-[chain-focus]:bg-chip-gray-hover-bg',\n red: 'text-chip-red-default-text border-chip-red-default-line bg-chip-red-default-bg hover:text-chip-red-hover-text hover:border-chip-red-hover-line hover:bg-chip-red-hover-bg focus-visible:text-chip-red-hover-text focus-visible:border-chip-red-hover-line focus-visible:bg-chip-red-hover-bg data-[chain-focus]:text-chip-red-hover-text data-[chain-focus]:border-chip-red-hover-line data-[chain-focus]:bg-chip-red-hover-bg',\n blue: 'text-chip-blue-default-text border-chip-blue-default-line bg-chip-blue-default-bg hover:text-chip-blue-hover-text hover:border-chip-blue-hover-line hover:bg-chip-blue-hover-bg focus-visible:text-chip-blue-hover-text focus-visible:border-chip-blue-hover-line focus-visible:bg-chip-blue-hover-bg data-[chain-focus]:text-chip-blue-hover-text data-[chain-focus]:border-chip-blue-hover-line data-[chain-focus]:bg-chip-blue-hover-bg',\n black: 'text-chip-black-default-text border-chip-black-default-line bg-chip-black-default-bg hover:text-chip-black-hover-text hover:border-chip-black-hover-line hover:bg-chip-black-hover-bg focus-visible:text-chip-black-hover-text focus-visible:border-chip-black-hover-line focus-visible:bg-chip-black-hover-bg data-[chain-focus]:text-chip-black-hover-text data-[chain-focus]:border-chip-black-hover-line data-[chain-focus]:bg-chip-black-hover-bg',\n green: 'text-chip-green-default-text border-chip-green-default-line bg-chip-green-default-bg hover:text-chip-green-hover-text hover:border-chip-green-hover-line hover:bg-chip-green-hover-bg focus-visible:text-chip-green-hover-text focus-visible:border-chip-green-hover-line focus-visible:bg-chip-green-hover-bg data-[chain-focus]:text-chip-green-hover-text data-[chain-focus]:border-chip-green-hover-line data-[chain-focus]:bg-chip-green-hover-bg',\n violet: 'text-chip-violet-default-text border-chip-violet-default-line bg-chip-violet-default-bg hover:text-chip-violet-hover-text hover:border-chip-violet-hover-line hover:bg-chip-violet-hover-bg focus-visible:text-chip-violet-hover-text focus-visible:border-chip-violet-hover-line focus-visible:bg-chip-violet-hover-bg data-[chain-focus]:text-chip-violet-hover-text data-[chain-focus]:border-chip-violet-hover-line data-[chain-focus]:bg-chip-violet-hover-bg',\n pink: 'text-chip-pink-default-text border-chip-pink-default-line bg-chip-pink-default-bg hover:text-chip-pink-hover-text hover:border-chip-pink-hover-line hover:bg-chip-pink-hover-bg focus-visible:text-chip-pink-hover-text focus-visible:border-chip-pink-hover-line focus-visible:bg-chip-pink-hover-bg data-[chain-focus]:text-chip-pink-hover-text data-[chain-focus]:border-chip-pink-hover-line data-[chain-focus]:bg-chip-pink-hover-bg',\n orange: 'text-chip-orange-default-text border-chip-orange-default-line bg-chip-orange-default-bg hover:text-chip-orange-hover-text hover:border-chip-orange-hover-line hover:bg-chip-orange-hover-bg focus-visible:text-chip-orange-hover-text focus-visible:border-chip-orange-hover-line focus-visible:bg-chip-orange-hover-bg data-[chain-focus]:text-chip-orange-hover-text data-[chain-focus]:border-chip-orange-hover-line data-[chain-focus]:bg-chip-orange-hover-bg',\n};\n\n// 칩 컬러 키 타입 — Chip·SelectChip color prop이 공유\nexport type ChipColor = keyof typeof CHIP_COLOR_CLASS;\n","// InlineFieldTrigger — 박스 없는 인라인 텍스트형 트리거(리딩 아이콘? + 값 텍스트 + chevron).\n// Select(variant=\"text\")와 DatePicker 연.월 선택(DualSelectField)이 공유하는 \"트리거 비주얼\"이다.\n// 클릭·키보드·포커스·위치계산 같은 상호작용은 사용처가 담당한다:\n// - Select: 이 컴포넌트에 role/tabIndex/onClick/onKeyDown/ref를 직접 spread\n// - DatePicker: Popover가 트리거를 감싸 클릭/anchor를 처리(여기엔 open만 전달)\n// 따라서 이 컴포넌트는 순수 표현(색·크기·말줄임·hover 밑줄·열림 회색·chevron 회전)만 책임진다.\nimport { forwardRef } from 'react';\nimport type { ComponentPropsWithoutRef, ComponentType, CSSProperties } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { TruncatingText } from './TruncatingText';\n\n// 리딩 아이콘 타입 — lucide 아이콘 컴포넌트(size/strokeWidth/className을 받는다)\ntype IconComponent = ComponentType<{ size?: number | string; strokeWidth?: number | string; className?: string }>;\n\n// ...props는 루트 <span>으로 spread된다(role/tabIndex/onClick/onKeyDown 등 사용처가 부여).\ninterface InlineFieldTriggerProps extends ComponentPropsWithoutRef<'span'> {\n icon?: IconComponent | null; // 좌측 리딩 아이콘(lucide 컴포넌트) — 없으면 미표시\n size?: '24' | '20'; // '24'(14px) | '20'(12px)\n open?: boolean; // 펼침 상태 — 텍스트 회색 + chevron 180° 회전\n disabled?: boolean;\n readOnly?: boolean;\n interactive?: boolean; // 상호작용 가능 여부(미지정 시 !disabled && !readOnly)\n maxWidth?: CSSProperties['maxWidth']; // 텍스트 최대 폭(CSS 길이) — 넘으면 말줄임\n fill?: boolean; // 부모 전체 폭 채움 — 텍스트 왼쪽·chevron 오른쪽 끝(justify-between). 기본 hug\n}\n\nexport const InlineFieldTrigger = forwardRef<HTMLSpanElement, InlineFieldTriggerProps>(function InlineFieldTrigger(\n {\n icon: Icon = null, // 좌측 리딩 아이콘(lucide 컴포넌트) — 없으면 미표시\n children, // 표시 텍스트(선택값/플레이스홀더)\n size = '24', // '24'(14px) | '20'(12px)\n open = false, // 펼침 상태 — 텍스트 회색 + chevron 180° 회전\n disabled = false,\n readOnly = false,\n interactive, // 상호작용 가능 여부(미지정 시 !disabled && !readOnly)\n maxWidth, // 텍스트 최대 폭(CSS 길이) — 넘으면 말줄임\n fill = false, // 부모 전체 폭 채움 — 텍스트 왼쪽·chevron 오른쪽 끝(justify-between). 기본 hug\n className = '',\n ...props\n },\n ref,\n) {\n const canInteract = interactive ?? (!disabled && !readOnly);\n\n // 텍스트 색: disabled=흐림 / 열림=회색 / 그 외(readOnly·placeholder·filled)=진함\n const textColor = disabled ? 'text-font-icon-2' : open ? 'text-font-icon-3' : 'text-font-icon-5';\n // 아이콘 색(리딩 아이콘·chevron 공통): disabled·readOnly=흐림 / 그 외=진함\n const iconColor = disabled || readOnly ? 'text-font-icon-2' : 'text-font-icon-5';\n\n // 글자/아이콘 크기 — size 토큰(24=14px / 20=12px), 아이콘은 16/14\n const sizeTextClass = size === '20' ? 'text-12' : 'text-14';\n const iconSize = size === '20' ? 14 : 16;\n\n return (\n <span\n ref={ref}\n className={`group min-w-0 select-none items-center gap-spacing-3 focus:outline-none ${\n fill ? 'flex w-full justify-between' : 'inline-flex'\n } ${canInteract ? 'cursor-pointer' : disabled ? 'cursor-not-allowed' : 'cursor-default'} ${className}`}\n {...props}\n >\n {Icon && <Icon size={iconSize} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />}\n <TruncatingText\n style={maxWidth ? { maxWidth } : undefined}\n className={`min-w-0 font-normal ${fill ? 'flex-1' : ''} ${sizeTextClass} ${textColor} ${\n canInteract ? 'group-hover:underline group-focus-visible:underline' : '' /* 포커스=호버 */\n }`}\n >\n {children}\n </TruncatingText>\n <ChevronDown\n size={iconSize}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${iconColor} ${\n open ? 'rotate-180' : ''\n }`}\n />\n </span>\n );\n});\n","// SearchBar — 검색 입력 (Figma 02_textfield / search bar, solid 타입)\n// 상태(default/hover/focused/filled)는 CSS와 실제 입력값으로 자동 처리한다.\n// - hover/focus 테두리는 ring으로 구현해 두께 변화에 따른 레이아웃 시프트를 막는다.\n// - filled(값 입력)/placeholder 텍스트 색은 native input과 placeholder 의사클래스로 자동.\nimport type { ChangeEvent, ComponentPropsWithoutRef, KeyboardEvent } from 'react';\nimport { Search } from 'lucide-react';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰). 색은 시멘틱 토큰.\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <input> 속성은 inputProps로 전달한다.\ninterface SearchBarProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'onSubmit'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n onSubmit?: (value: string) => void; // Enter 제출 — 현재 입력값 전달\n placeholder?: string;\n disabled?: boolean;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n inputProps?: ComponentPropsWithoutRef<'input'>;\n}\n\nexport function SearchBar({\n value,\n defaultValue,\n onChange,\n onSubmit,\n placeholder = '검색어를 입력하세요',\n disabled = false,\n width = 200, // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n className = '',\n inputProps = {},\n ...props\n}: SearchBarProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n return (\n <div\n style={{ width: widthStyle }}\n className={`group relative flex min-h-[32px] items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n disabled ? 'cursor-not-allowed' : RING\n } ${className}`}\n {...props}\n >\n <Search\n size={16}\n strokeWidth={1.8}\n className={`shrink-0 ${\n disabled\n ? 'text-text-field-disabled-icon'\n : 'text-text-field-default-text group-focus-within:text-text-field-filled-text'\n }`}\n />\n <input\n type=\"search\"\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n className=\"min-w-0 flex-1 bg-transparent text-14 text-text-field-filled-text outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed disabled:text-text-field-disabled-text disabled:placeholder:text-text-field-disabled-text [&::-webkit-search-cancel-button]:appearance-none\"\n {...inputProps}\n onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {\n // Enter 제출 + 소비자 onKeyDown 합성 — 스프레드가 덮어써 onSubmit이 무력화되지 않게\n if (e.key === 'Enter' && onSubmit) onSubmit(e.currentTarget.value);\n inputProps?.onKeyDown?.(e);\n }}\n />\n </div>\n );\n}\n","// Checkbox — 체크박스 (Figma check box)\n// 상태: unselected/selected × default/hover/disabled (6가지)\n// - unselected: 흰 박스 + 회색 테두리 / selected: blue 채움 + 흰 체크\n// - hover: 바깥 ring(checkbox-*-hover-outline) / disabled: 연하게\n// 체크 아이콘은 Figma check_small SVG(node 7257:2464)를 인라인으로 사용(fill=currentColor).\n// 색은 checkbox-* 시멘틱 토큰(base 경유)만 사용. controlled/uncontrolled 모두 지원.\n// - Checkbox : 단일 체크박스 1개\n// - CheckboxGroup : 여러 항목의 다중 선택(선택값 배열)을 관리하는 컨테이너\nimport { useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { OptionItem } from './formulaFunctions';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — Radio/SegmentControl과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px (기본 — 체크박스 항목 간격)\n};\n\n// Figma check_small SVG — fill을 currentColor로 바꿔 텍스트 색 토큰을 따르게 함\nfunction CheckMark({ className }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 8.34188 6.51667\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={className}\n >\n <path d=\"M2.84188 4.11667L6.64188 0.316667C6.85299 0.105556 7.08355 0 7.33355 0C7.58355 0 7.8141 0.105556 8.02521 0.316667C8.23633 0.527778 8.34188 0.761111 8.34188 1.01667C8.34188 1.27222 8.23633 1.50556 8.02521 1.71667L3.54188 6.21667C3.34188 6.41667 3.10577 6.51667 2.83355 6.51667C2.56132 6.51667 2.32521 6.41667 2.12521 6.21667L0.308547 4.4C0.0974359 4.18889 -0.00534188 3.95556 0.000213675 3.7C0.00576923 3.44444 0.114103 3.21111 0.325214 3C0.536325 2.78889 0.769658 2.68333 1.02521 2.68333C1.28077 2.68333 1.5141 2.78889 1.72521 3L2.84188 4.11667Z\" />\n </svg>\n );\n}\n\n// ...props는 sr-only <input type=\"checkbox\">로 전달된다.\ninterface CheckboxProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Checkbox({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n className = '',\n ...props\n}: CheckboxProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isChecked = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 박스 바깥에 간격(offset)을 두고 둥글게 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 = hover와 동일 디자인(2026-07-16 지시) — sr-only input이 포커스를 가지므로\n // peer-focus-visible로 박스에 hover와 같은 링 표시(마우스 클릭에는 없음, Radio와 동일 패턴)\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let boxClass;\n if (disabled) {\n boxClass = isChecked ? 'bg-checkbox-selected-disabled-bg' : 'bg-checkbox-disabled-bg';\n } else if (isChecked) {\n boxClass = `bg-checkbox-selected-bg ${HOVER_RING} group-hover:ring-checkbox-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-checkbox-selected-hover-outline`;\n } else {\n boxClass = `bg-checkbox-unselected-bg ${HOVER_RING} group-hover:ring-checkbox-hover-outline ${FOCUS_RING} peer-focus-visible:ring-checkbox-hover-outline`;\n }\n\n const checkColor = disabled ? 'text-checkbox-disabled-check' : 'text-checkbox-check';\n const textColor = disabled ? 'text-checkbox-disabled-text' : 'text-checkbox-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"checkbox\"\n className=\"peer sr-only\"\n checked={isChecked}\n onChange={handleChange}\n disabled={disabled}\n {...props}\n />\n <span\n className={`flex size-[16px] shrink-0 items-center justify-center rounded-round-3 transition-shadow ${boxClass}`}\n >\n {/* 항상 렌더하고 visibility로만 토글 — 체크/해제 시 레이아웃이 흔들리지 않게 */}\n <CheckMark className={`w-[8px] ${isChecked ? checkColor : 'invisible'}`} />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface CheckboxGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: OptionItem[]; // [{ value, label, disabled }]\n value?: string[]; // controlled 선택값 배열\n defaultValue?: string[]; // uncontrolled 초기 선택값 배열\n onChange?: (nextValues: string[], meta: { value: string; checked: boolean }) => void; // (nextValues, { value, checked }) => void\n direction?: 'vertical' | 'horizontal';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '7'(16px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function CheckboxGroup({\n items = [], // [{ value, label, disabled }]\n value, // controlled 선택값 배열\n defaultValue = [], // uncontrolled 초기 선택값 배열\n onChange, // (nextValues, { value, checked }) => void\n direction = 'vertical', // 'vertical' | 'horizontal'\n gap = '7', // 간격 토큰 키 — 기본 '7'(16px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: CheckboxGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n\n const handleToggle = (val: string, checked: boolean) => {\n const next = checked\n ? [...selected, val]\n : selected.filter((v) => v !== val);\n if (!isControlled) setInternal(next);\n onChange?.(next, { value: val, checked });\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['7'];\n const dirStyle = direction === 'horizontal' ? 'flex-row items-center' : 'flex-col items-start';\n\n return (\n <div\n role=\"group\"\n className={`inline-flex ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <Checkbox\n key={item.value}\n label={item.label}\n checked={selected.includes(item.value)}\n disabled={disabled || item.disabled}\n onChange={(e) => handleToggle(item.value, e.target.checked)}\n />\n ))}\n </div>\n );\n}\n","// PopoverMenu — 옵션 목록을 감싸는 팝오버 (Figma option list / popover menu)\n// 내부에 ListGroup을 담는다(빈 상태는 ListGroup이 내부에서 ListEmpty로 처리). topArea로 상단 영역을 선택한다\n// ('none'|'search'=검색바(SearchBar)|'input'=일반 텍스트 입력(Input)) — 셋 중 하나만 노출.\n// footer 옵션을 켜면 하단에 푸터 영역이 추가된다 — 좌측은 footerStart 슬롯(텍스트·체크박스·언더라인 버튼 등\n// 임의 노드), 우측은 취소/확인 버튼 그룹. Modal 푸터와 동일한 prop 네이밍·구조를 따른다.\n// 추후 다양한 옵션이 추가될 예정이라 확장 가능한 구조로 둔다.\n// 컨테이너 색은 list-popover-* 시멘틱 토큰(bg gray100 / outline gray200).\nimport { RotateCcw } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from 'react';\nimport { SearchBar } from './SearchBar';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { Checkbox } from './Checkbox';\n\nexport interface PopoverMenuProps extends ComponentPropsWithoutRef<'div'> {\n // ── 상단 영역 ─────────────────────────────────────────\n /** 'none'=없음 | 'search'=검색바(SearchBar) | 'input'=일반 텍스트 입력(Input). 셋 중 하나만 선택. */\n topArea?: 'none' | 'search' | 'input';\n // topArea='search'일 때\n searchValue?: string;\n onSearchChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n searchPlaceholder?: string;\n /** 검색 input에 전달(autoFocus·onKeyDown 등) */\n searchInputProps?: ComponentPropsWithoutRef<'input'>;\n // topArea='input'일 때 (검색 아이콘 없는 일반 텍스트 입력)\n inputValue?: string;\n onInputChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n inputPlaceholder?: string;\n /** Input의 inputProps로 전달(autoFocus 등) */\n inputProps?: ComponentPropsWithoutRef<'input'>;\n // ── 푸터 ──────────────────────────────────────────────\n /** 하단 푸터 영역 표시 여부 */\n footer?: boolean;\n // 푸터 좌측 — 자주 쓰는 3종은 전용 props, 그 외엔 footerStart 범용 슬롯.\n // 우선순위: footerStart > footerCheckbox > footerReset > footerText\n /** 푸터 좌측 범용 슬롯 — 문자열(자동 텍스트 스타일) 또는 임의 노드 */\n footerStart?: ReactNode;\n /** 좌측 텍스트(선택 개수·가이드 문구 등) */\n footerText?: ReactNode;\n /** 좌측 전체 선택 체크박스 */\n footerCheckbox?: boolean;\n footerChecked?: boolean;\n onFooterCheckChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n footerCheckLabel?: ReactNode;\n /** 좌측 초기화 언더라인 버튼 */\n footerReset?: boolean;\n onFooterReset?: () => void;\n footerResetLabel?: ReactNode;\n footerResetIcon?: LucideIcon;\n // 푸터 우측 — 취소/확인 버튼\n cancelText?: ReactNode;\n onCancel?: () => void;\n confirmText?: ReactNode;\n onConfirm?: () => void;\n /** 확인 버튼 variant ('fill' | 'line' | 'ghost') */\n confirmVariant?: 'fill' | 'line' | 'ghost';\n confirmDisabled?: boolean;\n confirmLoading?: boolean;\n showCancel?: boolean;\n showConfirm?: boolean;\n /** 푸터 버튼 그룹을 fill(균등 분할 전체폭)로 — 좌측 슬롯 없이 버튼만 꽉 채움 */\n footerButtonsFill?: boolean;\n // ──────────────────────────────────────────────────────\n width?: number | string;\n}\n\nexport function PopoverMenu({\n children,\n topArea = 'none',\n searchValue,\n onSearchChange,\n searchPlaceholder = '검색어를 입력하세요',\n searchInputProps = {},\n inputValue,\n onInputChange,\n inputPlaceholder = '텍스트를 입력하세요',\n inputProps = {},\n footer = false,\n footerStart = null,\n footerText = null,\n footerCheckbox = false,\n footerChecked = false,\n onFooterCheckChange,\n footerCheckLabel = '전체 선택',\n footerReset = false,\n onFooterReset,\n footerResetLabel = '초기화',\n footerResetIcon: FooterResetIcon = RotateCcw,\n cancelText = '취소',\n onCancel,\n confirmText = '확인',\n onConfirm,\n confirmVariant = 'fill',\n confirmDisabled = false,\n confirmLoading = false,\n showCancel = true,\n showConfirm = true,\n footerButtonsFill = false,\n width = 304,\n className = '',\n ...props\n}: PopoverMenuProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n // 푸터 좌측 노드 결정 — footerStart(범용 슬롯)가 최우선, 없으면 전용 props로 조립.\n let footerStartNode: ReactNode = null;\n if (footerStart != null) {\n footerStartNode =\n typeof footerStart === 'string' ? (\n <p className=\"text-14 text-font-icon-5\">{footerStart}</p>\n ) : (\n footerStart\n );\n } else if (footerCheckbox) {\n footerStartNode = (\n <Checkbox label={footerCheckLabel} checked={footerChecked} onChange={onFooterCheckChange} />\n );\n } else if (footerReset) {\n footerStartNode = (\n <Button variant=\"underline\" size=\"32\" leftIcon={FooterResetIcon} onClick={onFooterReset}>\n {footerResetLabel}\n </Button>\n );\n } else if (footerText != null) {\n footerStartNode = <p className=\"text-14 text-font-icon-5\">{footerText}</p>;\n }\n\n // 푸터 확인 버튼 Enter 실행(2026-07-14 지시) — 푸터가 있고 확인이 활성일 때 Enter=확인 클릭.\n // 내부 위젯(Select 옵션 확정 등)이 이미 처리한 Enter는 preventDefault돼 있어 건너뛴다(중복 실행 방지).\n const onConfirmEnter = (e: KeyboardEvent<HTMLDivElement>) => {\n if (!footer || !showConfirm) return;\n if (e.key !== 'Enter' || e.defaultPrevented) return;\n if (confirmDisabled || confirmLoading) return;\n e.preventDefault();\n onConfirm?.();\n };\n\n return (\n <div\n style={{ width: widthStyle }}\n onKeyDown={onConfirmEnter}\n // tabIndex -1: 비포커스 요소(라디오 행 여백 등) 클릭 시에도 패널이 포커스를 받아 Enter가 잡히게.\n // 시각 변화 없음 — 이 outline은 항상 지정돼 있어 브라우저 기본 포커스 링을 대체한다.\n tabIndex={footer && showConfirm ? -1 : undefined}\n className={`flex flex-col gap-spacing-1 overflow-hidden rounded-round-4 outline outline-1 outline-list-popover-outline bg-list-popover-bg shadow-[0px_2px_5px_0px_rgba(13,13,13,0.12)] ${className}`}\n {...props}\n >\n {/* search area — 흰 배경. popover(gray100)와 gap-spacing-1(1px) 틈이 구분선이 된다. */}\n {topArea === 'search' && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <SearchBar\n value={searchValue}\n onChange={onSearchChange}\n placeholder={searchPlaceholder}\n width=\"100%\"\n inputProps={searchInputProps}\n />\n </div>\n )}\n {/* input area — 검색 아이콘 없는 일반 텍스트 입력. search area와 동일한 패딩·구분선 규칙. */}\n {topArea === 'input' && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <Input\n value={inputValue}\n onChange={onInputChange}\n placeholder={inputPlaceholder}\n width=\"100%\"\n inputProps={inputProps}\n />\n </div>\n )}\n {children}\n {/* footer area — 흰 배경. 위쪽 gap-spacing-1(1px) 틈이 목록과의 구분선이 된다.\n 기본: 좌측 footerStart 슬롯 + 우측 취소/확인 버튼 그룹(justify-between).\n footerButtonsFill: 좌측 슬롯 없이 버튼 그룹을 fill(균등 분할 전체폭)로. */}\n {footer && (\n <div\n className={`flex w-full items-center bg-list-group-bg py-spacing-5 ${\n footerButtonsFill ? 'px-spacing-5' : 'justify-between pl-spacing-6 pr-spacing-5'\n }`}\n >\n {!footerButtonsFill && <div className=\"flex min-w-0 items-center\">{footerStartNode}</div>}\n <ButtonGroup\n gap=\"5\"\n width={footerButtonsFill ? 'fill' : 'hug'}\n className={footerButtonsFill ? '' : 'shrink-0'}\n >\n {showCancel && (\n <Button variant=\"line\" size=\"32\" onClick={onCancel}>\n {cancelText}\n </Button>\n )}\n {showConfirm && (\n <Button\n variant={confirmVariant}\n size=\"32\"\n disabled={confirmDisabled}\n loading={confirmLoading}\n onClick={onConfirm}\n >\n {confirmText}\n </Button>\n )}\n </ButtonGroup>\n </div>\n )}\n </div>\n );\n}\n\n// ── rich menu 조립 컴포넌트 (Figma popover menu type=rich menu, node 8416:38124) ──────────\n// 선택 항목이 아주 많을 때 쓰는 다중 컬럼 메뉴. PopoverMenu(넓은 width) 안에서\n// Columns(가로) > Column(세로) > Section(카테고리 타이틀+행들) 순으로 조립하고,\n// 행은 기존 List(rightButton 등)를 그대로 쓴다. 컬럼·섹션 사이 1px 틈(popover bg)이 구분선.\n//\n// <PopoverMenu width={804}>\n// <PopoverMenuColumns>\n// <PopoverMenuColumn>\n// <PopoverMenuSection title=\"인적사항\">\n// <List title=\"현주소\" rightButton rightButtonIcon={Plus} rightButtonAriaLabel=\"추가\" ... />\n// </PopoverMenuSection>\n// </PopoverMenuColumn>\n// …\n// </PopoverMenuColumns>\n// </PopoverMenu>\n\n// 가로 컬럼 컨테이너 — 컬럼들을 균등 폭으로 나란히, 1px 틈이 세로 구분선\nexport function PopoverMenuColumns({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n return (\n <div className={`flex w-full items-stretch gap-spacing-1 ${className}`} {...props}>\n {children}\n </div>\n );\n}\n\n// 세로 컬럼 — 섹션들을 위에서 아래로, 1px 틈이 가로 구분선. 마지막 섹션이 남는 높이를 채운다\nexport function PopoverMenuColumn({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n return (\n <div className={`flex min-w-0 flex-1 flex-col gap-spacing-1 ${className}`} {...props}>\n {children}\n </div>\n );\n}\n\n// 카테고리 섹션 — 회색 타이틀 + 행(List)들. grow=true면 컬럼의 남는 높이를 채운다(마지막 섹션용)\ninterface PopoverMenuSectionProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n title?: ReactNode;\n grow?: boolean;\n}\n\nexport function PopoverMenuSection({ title, children, grow = false, className = '', ...props }: PopoverMenuSectionProps) {\n return (\n <div className={`w-full bg-list-group-bg py-spacing-4 ${grow ? 'min-h-0 flex-1' : ''} ${className}`} {...props}>\n {title != null && (\n <p className=\"px-spacing-6 py-spacing-2 text-12 text-font-icon-3\">{title}</p>\n )}\n {children}\n </div>\n );\n}\n","// ListEmpty — 목록이 비었을 때 표시 (Figma option list / list empty)\n// ListGroup이 항목 0개(또는 empty)일 때 그룹 안에서 이 컴포넌트를 렌더한다(빈 상태 메시지).\n// 단독으로도 쓸 수 있다. 색은 list-empty-text 시멘틱 토큰.\n\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\ninterface ListEmptyProps extends ComponentPropsWithoutRef<'div'> {\n message?: ReactNode;\n}\n\nexport function ListEmpty({ message = '검색 결과가 없습니다.', className = '', ...props }: ListEmptyProps) {\n return (\n <div\n className={`flex w-full items-center justify-center bg-list-empty-bg px-spacing-6 py-spacing-9 text-14 text-list-empty-text ${className}`}\n {...props}\n >\n {message}\n </div>\n );\n}\n","// ListGroup — List 묶음 (Figma option list / list group)\n// 상하 패딩(py-spacing-4) 안에서 List들을 세로로 쌓고, 개수가 maxVisible(기본 6)을\n// 넘으면 내부 스크롤한다. 스크롤바는 공용 ScrollArea(커스텀 오버레이 스크롤바)를 사용.\n// 항목이 없으면(자식 0개 또는 empty=true) 같은 컨테이너 안(스크롤 영역 내부)에\n// ListEmpty를 렌더해, 빈 상태도 그룹의 패딩·배경을 그대로 따른다.\nimport { Children } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { ScrollArea } from './ScrollArea';\nimport { ListEmpty } from './ListEmpty';\n\nconst ITEM_HEIGHT = 32; // List 한 행의 표시 높이(px) — min-h-32\nconst PADDING_Y = 6; // 상하 패딩(px) — spacing-4\n\n// 행 간격(gap) 토큰 키 → 클래스 (Tailwind purge 안전한 정적 매핑). 미지정=0(붙여 쌓음).\nconst GAP_STYLE = {\n '2': 'gap-spacing-2', // 2px\n '3': 'gap-spacing-3', // 4px\n};\nconst GAP_PX = { '2': 2, '3': 4 };\n\ninterface ListGroupProps extends ComponentPropsWithoutRef<'div'> {\n maxVisible?: number;\n gap?: keyof typeof GAP_STYLE; // 행 간격 토큰 키 — '2'=2px | '3'=4px. 미지정 시 붙여 쌓음(0px)\n empty?: boolean;\n emptyMessage?: ReactNode;\n}\n\nexport function ListGroup({ children, maxVisible = 6, gap, empty = false, emptyMessage, className = '', ...props }: ListGroupProps) {\n // gap 지정 시 행 간격 반영(라디오 리스트 등) — '2'=2px | '3'=4px. 미지정 시 기존처럼 붙여 쌓음(0px).\n const gapPx = gap ? GAP_PX[gap] : 0;\n const maxHeight = maxVisible * ITEM_HEIGHT + gapPx * (maxVisible - 1) + PADDING_Y * 2;\n const isEmpty = empty || Children.count(children) === 0;\n\n return (\n <div className={`w-full bg-list-group-bg ${className}`} {...props}>\n {/* 상하 패딩을 스크롤 영역 안(contentClassName)에 두어, 스크롤 시 패딩까지 함께 움직인다 */}\n <ScrollArea maxHeight={maxHeight} contentClassName={`py-spacing-4 ${gap ? `flex flex-col ${GAP_STYLE[gap]}` : ''}`}>\n {isEmpty ? <ListEmpty message={emptyMessage} /> : children}\n </ScrollArea>\n </div>\n );\n}\n","// Tag — 작은 칩 라벨 (Figma option list / tag)\n// color 속성으로 색을 바꾼다: 'blue' | 'red' | 'gray' | 'black'. lookup 객체로 분기. (Figma Tag의 color variant와 동일)\n// black = gray500 솔리드 배경 + 흰 텍스트(Figma 변형명 'color4', Chip black과 동일 값)\n// width 속성으로 너비를 바꾼다: 'hug'(콘텐츠 맞춤, 기본) | 'fill'(부모 폭 채움).\n// 색은 tag-* 시멘틱 토큰(base 경유)만 사용.\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst COLOR_STYLE = {\n blue: 'bg-tag-blue-bg text-tag-blue-text',\n red: 'bg-tag-red-bg text-tag-red-text',\n gray: 'bg-tag-gray-bg text-tag-gray-text',\n black: 'bg-tag-black-bg text-tag-black-text',\n};\n\ninterface TagProps extends ComponentPropsWithoutRef<'span'> {\n color?: keyof typeof COLOR_STYLE;\n width?: 'hug' | 'fill';\n}\n\nexport function Tag({\n children = '태그',\n color = 'blue',\n width = 'hug', // 'hug' | 'fill'\n className = '',\n ...props\n}: TagProps) {\n const colorStyle = COLOR_STYLE[color] ?? COLOR_STYLE.blue;\n const sizing = width === 'fill' ? 'flex w-full' : 'inline-flex shrink-0';\n\n return (\n <span\n className={`${sizing} items-center justify-center rounded-round-4 px-spacing-4 py-spacing-1 text-12 ${colorStyle} ${className}`}\n {...props}\n >\n {children}\n </span>\n );\n}\n\n// NewTag — 원형 'N' 뱃지 (Figma new tag, node 8187:40848). 신규 항목 표시용.\n// 18×18 고정 원형(rounded-round-00) + semibold 12 텍스트. 색은 new-tag-* 시멘틱 토큰만 사용. (22→20→18)\nconst NEW_TAG_BG = {\n blue: 'bg-new-tag-blue-bg',\n red: 'bg-new-tag-red-bg',\n black: 'bg-new-tag-black-bg',\n};\n\ninterface NewTagProps extends ComponentPropsWithoutRef<'span'> {\n color?: keyof typeof NEW_TAG_BG; // 'blue' | 'red' | 'black'\n}\n\nexport function NewTag({\n color = 'blue', // 'blue' | 'red' | 'black'\n children = 'N', // 뱃지 글자(기본 'N')\n className = '',\n ...props\n}: NewTagProps) {\n return (\n <span\n className={`inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-round-00 text-12 font-semibold text-new-tag-title ${\n NEW_TAG_BG[color] ?? NEW_TAG_BG.blue\n } ${className}`}\n {...props}\n >\n {children}\n </span>\n );\n}\n","// Radio — 라디오 버튼 (Figma radio, node 7368:1243)\n// 상태: unselected/selected × default/hover/disabled (6가지)\n// - unselected: 회색 원 / selected: blue 원 + 흰 점(point)\n// - hover: 바깥 ring(radio-*-hover-outline) / disabled: 연하게(selected disabled = 연한 blue + blue 점)\n// 색은 radio-* 시멘틱 토큰(base 경유)만 사용. 체크박스와 구조는 같으나 원형 + 가운데 점.\n// - Radio : 단일 라디오 버튼 1개(controlled/uncontrolled 모두 지원)\n// - RadioGroup : name 공유로 단일 선택을 관리하는 컨테이너\nimport { useId, useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { OptionItem } from './formulaFunctions';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — SegmentControl/ButtonGroup과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px (기본 — 라디오 항목 간격)\n};\n\n// ...props는 sr-only <input type=\"radio\">로 전달된다.\ninterface RadioProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Radio({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n name,\n value,\n className = '',\n ...props\n}: RadioProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isChecked = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 원 바깥에 간격(offset)을 두고 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 링(2026-07-15) — sr-only input이 포커스를 가지므로 peer로 원에 표시(hover 링과 동일 색)\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let boxClass;\n if (disabled) {\n boxClass = isChecked ? 'bg-radio-selected-disabled-bg' : 'bg-radio-disabled-bg';\n } else if (isChecked) {\n boxClass = `bg-radio-selected-bg ${HOVER_RING} group-hover:ring-radio-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-radio-selected-hover-outline`;\n } else {\n boxClass = `bg-radio-unselected-bg ${HOVER_RING} group-hover:ring-radio-hover-outline ${FOCUS_RING} peer-focus-visible:ring-radio-hover-outline`;\n }\n\n const pointColor = disabled ? 'bg-radio-disabled-point' : 'bg-radio-point';\n const textColor = disabled ? 'text-radio-disabled-text' : 'text-radio-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"radio\"\n className=\"peer sr-only\"\n checked={isChecked}\n onChange={handleChange}\n disabled={disabled}\n name={name}\n value={value}\n {...props}\n />\n <span\n className={`flex h-spacing-7 w-spacing-7 shrink-0 items-center justify-center rounded-round-00 transition-shadow ${boxClass}`}\n >\n {/* 가운데 점은 항상 렌더하고 visibility로만 토글 — 선택/해제 시 레이아웃이 흔들리지 않게 */}\n <span className={`h-spacing-4 w-spacing-4 rounded-round-00 ${pointColor} ${isChecked ? '' : 'invisible'}`} />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface RadioGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: OptionItem[]; // [{ value, label, disabled }]\n value?: string; // controlled 선택값\n defaultValue?: string; // uncontrolled 초기 선택값\n onChange?: (value: string) => void; // (value) => void\n name?: string; // 라디오 그룹 name (미지정 시 자동 생성)\n direction?: 'vertical' | 'horizontal';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '7'(16px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function RadioGroup({\n items = [], // [{ value, label, disabled }]\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n name, // 라디오 그룹 name (미지정 시 자동 생성)\n direction = 'vertical', // 'vertical' | 'horizontal'\n gap = '7', // 간격 토큰 키 — 기본 '7'(16px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: RadioGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n\n // name 미지정 시 그룹 인스턴스마다 고유 name 생성 — useId(SSR/hydration 안전, Math.random은 서버·클라 불일치)\n const autoName = useId();\n const groupName = name ?? `radio-group-${autoName}`;\n\n const handleSelect = (val: string) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['7'];\n const dirStyle = direction === 'horizontal' ? 'flex-row items-center' : 'flex-col items-start';\n\n return (\n <div\n role=\"radiogroup\"\n className={`inline-flex ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <Radio\n key={item.value}\n name={name ?? groupName}\n value={item.value}\n label={item.label}\n checked={selected === item.value}\n disabled={disabled || item.disabled}\n onChange={() => handleSelect(item.value)}\n />\n ))}\n </div>\n );\n}\n","// Switch — 스위치 토글 (Figma switch, node 7370:115)\n// 상태: off(unselected)/on(selected) × default/hover/disabled (6가지)\n// - off: 회색 track + 흰 thumb(좌측) / on: blue track + 흰 thumb(우측)\n// - hover: 바깥 ring(switch-*-hover-outline)\n// - disabled(그림자 없음): off=연한 회색 track + 진회색 thumb / on=연한 blue track + blue thumb\n// 색은 switch-* 시멘틱 토큰(base 경유)만 사용. controlled/uncontrolled 모두 지원.\n// track 28×16, thumb 12×10, 이동 거리 8px(spacing-5)을 translate로 애니메이션.\nimport { useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\n\n// ...props는 sr-only <input type=\"checkbox\" role=\"switch\">로 전달된다.\ninterface SwitchProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Switch({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n className = '',\n ...props\n}: SwitchProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isOn = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 track 바깥에 간격(offset)을 두고 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 = hover와 동일 디자인(2026-07-16 지시) — peer-focus-visible로 track에 hover 링\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let trackClass;\n if (disabled) {\n trackClass = isOn ? 'bg-switch-selected-disabled-bg' : 'bg-switch-disabled-bg';\n } else if (isOn) {\n trackClass = `bg-switch-selected-bg ${HOVER_RING} group-hover:ring-switch-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-switch-selected-hover-outline`;\n } else {\n trackClass = `bg-switch-unselected-bg ${HOVER_RING} group-hover:ring-switch-hover-outline ${FOCUS_RING} peer-focus-visible:ring-switch-hover-outline`;\n }\n\n // thumb 색 — disabled는 그림자 없음(on=blue, off=진회색), 그 외 흰 thumb + 그림자\n let thumbColor;\n if (disabled) {\n thumbColor = isOn ? 'bg-switch-disabled-thumb' : 'bg-switch-disabled-thumb-off';\n } else {\n thumbColor = 'bg-switch-thumb shadow';\n }\n const textColor = disabled ? 'text-switch-disabled-text' : 'text-switch-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"checkbox\"\n role=\"switch\"\n className=\"peer sr-only\"\n checked={isOn}\n onChange={handleChange}\n disabled={disabled}\n {...props}\n />\n <span\n className={`relative h-[16px] w-[28px] shrink-0 rounded-round-5 transition-[background-color,box-shadow] ${trackClass}`}\n >\n <span\n className={`absolute left-spacing-3 top-1/2 h-[10px] w-[12px] -translate-y-1/2 rounded-round-4 transition-transform ${thumbColor} ${\n isOn ? 'translate-x-spacing-5' : 'translate-x-0'\n }`}\n />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n","// List — 옵션 목록의 한 행 (Figma option list / list, node 7206:9327)\n// 상태: default / hover / pressed / selected / disabled\n// - hover/pressed는 CSS(:hover/:active), selected·disabled는 props\n// - selected 시 chevron·아이콘이 파란색(list-selected-icon)\n// 요소(필요에 따라 on/off):\n// left : tag · checkbox · icon(lucide) · title\n// right : switch · rightButton(고스트 ⋯) · endIcon(chevron)\n// 색은 list-* 시멘틱 토큰만 사용. checkbox/switch는 공용 컴포넌트 재사용.\n// checkbox가 있으면 행 전체가 체크박스 클릭 영역이 된다(행 어디를 눌러도 토글). switch는 행 클릭과 분리(stopPropagation).\nimport { ChevronRight, MoreHorizontal } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from 'react';\nimport { Tag } from './Tag';\nimport { Checkbox } from './Checkbox';\nimport { Radio } from './Radio';\nimport { Switch } from './Switch';\nimport { TruncatingText } from './TruncatingText';\n\n// 체크박스/라디오/스위치 변경 이벤트 — 행 클릭 합성 이벤트({ target:{ checked } })와 실제 change 이벤트 겸용\ntype CheckChangeEvent = { target: { checked: boolean } };\n\ninterface ListProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n title?: ReactNode;\n tag?: boolean; // 태그 표시 여부\n tagText?: ReactNode;\n icon?: LucideIcon | null; // 왼쪽 lucide 아이콘 (예: User)\n checkbox?: boolean; // 왼쪽 체크박스 표시(다중 선택)\n radio?: boolean; // 왼쪽 라디오 표시(단일 선택) — checkbox와 배타. checked/onCheckChange 공용\n checked?: boolean; // 체크박스/라디오 선택 상태\n onCheckChange?: (e: CheckChangeEvent) => void; // (e) => void — 체크박스/라디오 변경(e.target.checked)\n showSwitch?: boolean; // 오른쪽 스위치 표시\n switchChecked?: boolean; // 스위치 상태\n onSwitchChange?: (e: CheckChangeEvent) => void; // (e) => void — 스위치 변경\n rightButton?: boolean; // 고스트 아이콘 버튼 (기본 ⋯)\n rightButtonIcon?: LucideIcon; // 우측 버튼 아이콘(lucide) — 예: Trash2(삭제)\n rightButtonAriaLabel?: string; // 우측 버튼 aria-label\n endIcon?: boolean; // chevron-right\n rightSlot?: ReactNode; // 우측 임의 콘텐츠(입력칸+단위 등) — 행 클릭과 분리(자체 stopPropagation은 사용처가)\n selected?: boolean;\n highlighted?: boolean; // 키보드 내비게이션 강조 (hover 색)\n disabled?: boolean;\n onButtonClick?: (e: MouseEvent<HTMLButtonElement>) => void;\n}\n\nexport function List({\n title = 'list',\n tag = false, // 태그 표시 여부\n tagText = '태그',\n icon: Icon = null, // 왼쪽 lucide 아이콘 (예: User)\n checkbox = false, // 왼쪽 체크박스 표시(다중 선택)\n radio = false, // 왼쪽 라디오 표시(단일 선택) — checkbox와 배타. checked/onCheckChange 공용\n checked = false, // 체크박스/라디오 선택 상태\n onCheckChange, // (e) => void — 체크박스/라디오 변경(e.target.checked)\n showSwitch = false, // 오른쪽 스위치 표시\n switchChecked = false, // 스위치 상태\n onSwitchChange, // (e) => void — 스위치 변경\n rightButton = false, // 고스트 아이콘 버튼 (기본 ⋯)\n rightButtonIcon: RightButtonIcon = MoreHorizontal, // 우측 버튼 아이콘(lucide) — 예: Trash2(삭제)\n rightButtonAriaLabel = '더보기', // 우측 버튼 aria-label\n endIcon = false, // chevron-right\n rightSlot = null, // 우측 임의 콘텐츠(입력칸+단위 등) — 행 클릭과 분리(자체 stopPropagation은 사용처가)\n selected = false,\n highlighted = false, // 키보드 내비게이션 강조 (hover 색)\n disabled = false,\n onClick,\n onButtonClick,\n className = '',\n ...props\n}: ListProps) {\n const interactive = !disabled;\n // 라디오 행은 hover/pressed 배경 없음(선택 표시는 라디오가 담당) + 내부 상하 패딩 제거(2026-07-09 지시)\n const noHover = radio;\n\n const rowBg = disabled\n ? 'bg-list-default-bg cursor-not-allowed'\n : highlighted && !noHover\n ? 'bg-list-hover-bg cursor-pointer' // 키보드 강조 우선\n : selected || noHover\n ? 'bg-list-default-bg cursor-pointer' // 선택·라디오: hover/pressed 효과 없음\n : 'bg-list-default-bg cursor-pointer hover:bg-list-hover-bg active:bg-list-pressed-bg';\n\n const titleColor = disabled\n ? 'text-list-disabled-text'\n : selected\n ? 'text-list-selected-text'\n : 'text-list-default-text';\n const ellipsisColor = disabled ? 'text-list-disabled-icon' : 'text-list-default-icon';\n // 아이콘(왼쪽 lucide · 오른쪽 chevron) 공통 색: disabled→회색, selected→파랑, 그 외 기본\n const iconColor = disabled\n ? 'text-list-disabled-icon'\n : selected\n ? 'text-list-selected-icon'\n : 'text-list-default-icon';\n\n // 체크박스·스위치는 행 onClick과 분리한다(직접 클릭 시 각자 onChange만 발화 → 행 핸들러로 전파 차단)\n const stop = (e: MouseEvent<HTMLSpanElement>) => e.stopPropagation();\n\n // 체크박스/라디오가 있으면 행 전체가 클릭 영역이 된다 — 행 어디를 눌러도 체크박스는 토글, 라디오는 선택.\n // (컨트롤을 직접 누른 경우는 stop으로 전파가 막혀 여기로 오지 않으므로 이중 처리가 없다.)\n const handleRowClick = (e: MouseEvent<HTMLDivElement>) => {\n if (!interactive) return;\n if (checkbox) onCheckChange?.({ target: { checked: !checked } });\n else if (radio && !checked) onCheckChange?.({ target: { checked: true } }); // 라디오는 선택만(해제 없음)\n onClick?.(e);\n };\n\n return (\n <div\n role=\"option\"\n aria-selected={selected || undefined}\n aria-disabled={disabled || undefined}\n onClick={interactive ? handleRowClick : undefined}\n className={`flex min-h-[32px] w-full items-center justify-between px-spacing-6 ${\n noHover ? '' : 'py-spacing-3'\n } transition-colors ${rowBg} ${className}`}\n {...props}\n >\n {/* left: tag · checkbox · icon · title (말줄임 시 hover 툴팁 — TruncatingText) */}\n <div className=\"flex min-w-0 flex-1 items-center gap-spacing-4\">\n {tag && <Tag>{tagText}</Tag>}\n {checkbox && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Checkbox checked={checked} onChange={onCheckChange} disabled={disabled} />\n </span>\n )}\n {radio && !checkbox && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Radio checked={checked} onChange={onCheckChange} disabled={disabled} />\n </span>\n )}\n {Icon && <Icon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />}\n <TruncatingText className={`min-w-0 flex-1 text-14 ${titleColor}`}>\n {title}\n </TruncatingText>\n </div>\n\n {/* right: 임의 슬롯 · switch · 고스트 ⋯ 버튼 · chevron */}\n {(rightSlot || showSwitch || rightButton || endIcon) && (\n <div className=\"flex shrink-0 items-center gap-spacing-2\">\n {rightSlot}\n {showSwitch && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Switch checked={switchChecked} onChange={onSwitchChange} disabled={disabled} />\n </span>\n )}\n {rightButton && (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n onButtonClick?.(e);\n }}\n className=\"flex min-h-[24px] min-w-[24px] items-center justify-center rounded-round-4 p-spacing-2 opacity-80 transition-colors hover:bg-button-ghost-hover-bg focus:outline-none focus-visible:bg-button-ghost-hover-bg disabled:cursor-not-allowed\"\n aria-label={rightButtonAriaLabel}\n >\n <RightButtonIcon size={14} strokeWidth={1.8} className={ellipsisColor} />\n </button>\n )}\n {endIcon && <ChevronRight size={16} strokeWidth={1.8} className={iconColor} />}\n </div>\n )}\n </div>\n );\n}\n","// usePopoverPosition — 트리거(anchorRef) 기준으로 떠 있는 패널(menuRef)의 fixed 위치를 계산.\n// Select·Popover가 공유하는 드롭다운 위치 로직(공간에 따른 위/아래·좌/우 자동 반전, portal 전제).\n// - placement: 'auto' | '{top|bottom|auto}-{left|right|auto}' (축별 auto 지원)\n// - menuWidth: 숫자(px)/CSS 길이. 미지정 시 트리거 너비\n// - deps: 패널 높이/너비를 바꾸는 값(검색 결과 개수 등)을 넘기면 재계산\n// open=false면 null을 반환한다(패널 미표시).\nimport { useState, useLayoutEffect } from 'react';\nimport type { CSSProperties, RefObject } from 'react';\n\ninterface UsePopoverPositionOptions {\n open: boolean;\n anchorRef: RefObject<HTMLElement | null>;\n menuRef: RefObject<HTMLElement | null>;\n placement?: string; // 'auto' | '{top|bottom|auto}-{left|right|auto}'\n menuWidth?: number | string;\n deps?: unknown[];\n}\nimport { spacing } from '../tokens';\n\n// 트리거 ↔ 패널 간격 — spacing-3(4px) 토큰\nconst MENU_GAP = parseInt(spacing['spacing-3'], 10);\n\nexport function usePopoverPosition({ open, anchorRef, menuRef, placement = 'auto', menuWidth, deps = [] }: UsePopoverPositionOptions): CSSProperties | null {\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n\n useLayoutEffect(() => {\n if (!open) {\n // 닫힐 때 위치 스타일 리셋 — 의도된 effect 내 setState\n setMenuStyle(null); // eslint-disable-line react-hooks/set-state-in-effect\n return;\n }\n const measure = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const tr = anchor.getBoundingClientRect();\n const menu = menuRef.current;\n const menuH = menu?.offsetHeight ?? 0;\n const gap = MENU_GAP;\n // 패널 너비(숫자) — 위치 계산용. 숫자/미지정은 즉시 알 수 있고, 문자열만 측정 fallback.\n const w =\n menuWidth == null\n ? tr.width\n : typeof menuWidth === 'number'\n ? menuWidth\n : (menu?.offsetWidth ?? tr.width);\n const widthCss = menuWidth != null && typeof menuWidth !== 'number' ? menuWidth : `${w}px`;\n\n let vertical: string;\n let horizontal: string;\n if (placement === 'auto') {\n vertical = 'auto';\n horizontal = 'auto';\n } else {\n [vertical, horizontal] = placement.split('-');\n }\n // 축별 auto — 'auto-right'(상하 자동·우측 고정), 'bottom-auto'(하단 고정·좌우 자동) 등 부분 자동 지원\n if (vertical === 'auto') {\n const spaceBelow = window.innerHeight - tr.bottom;\n const spaceAbove = tr.top;\n vertical = spaceBelow < menuH + gap && spaceAbove > spaceBelow ? 'top' : 'bottom';\n }\n if (horizontal === 'auto') {\n horizontal = tr.left + w > window.innerWidth ? 'right' : 'left';\n }\n\n const top = vertical === 'top' ? tr.top - gap - menuH : tr.bottom + gap;\n const left = horizontal === 'right' ? tr.right - w : tr.left;\n // 값이 같으면 이전 객체를 유지 — 참조 변경으로 인한 팝오버 서브트리 불필요 리렌더 방지\n setMenuStyle((prev) =>\n prev && prev.top === top && prev.left === left && prev.width === widthCss\n ? prev\n : { position: 'fixed' as const, top, left, width: widthCss },\n );\n };\n measure(); // 열림 직후 1회는 동기 측정 — 첫 프레임에 올바른 위치로 표시\n // scroll(capture, 문서 내 모든 스크롤에 발화)·resize는 rAF로 코얼레싱 —\n // 프레임당 1회만 측정/재계산해 스크롤 중 측정+리렌더 반복을 줄인다(2026-07-07 감사)\n let raf = 0;\n const schedule = () => {\n if (raf) return;\n raf = requestAnimationFrame(() => {\n raf = 0;\n measure();\n });\n };\n window.addEventListener('resize', schedule);\n window.addEventListener('scroll', schedule, true);\n return () => {\n if (raf) cancelAnimationFrame(raf);\n window.removeEventListener('resize', schedule);\n window.removeEventListener('scroll', schedule, true);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, placement, menuWidth, ...deps]);\n\n return menuStyle;\n}\n","// useOutsideDismiss — 열린 팝오버/드롭다운의 \"바깥 클릭 닫기\" 공용 훅 (Popover·Select 공유)\n// capture 단계 mousedown에서 바깥 클릭을 선점해:\n// ① 메뉴만 닫고(onDismiss)\n// ② 그 mousedown과 이어지는 click을 삼켜 아래 요소(버튼 등)가 함께 클릭되지 않게 한다(click-through 방지).\n// 첫 클릭 = 닫기 전용, 두 번째 클릭부터 평소처럼 동작한다. (Esc capture 처리와 동일한 우선순위 원칙)\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\n\ninterface UseOutsideDismissOptions {\n open: boolean;\n refs: RefObject<HTMLElement | null>[]; // 트리거·패널 등 '안쪽'으로 볼 요소들\n onDismiss: (e: MouseEvent) => void;\n guard?: (e: MouseEvent) => boolean; // false면 이번 클릭은 무시(닫지도 삼키지도 않음)\n}\n\nexport function useOutsideDismiss({ open, refs, onDismiss, guard }: UseOutsideDismissOptions) {\n useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n const inside = refs.some((r) => r.current && r.current.contains(e.target as Node));\n if (inside) return;\n if (guard && !guard(e)) return; // 예: 중첩 팝오버 — 맨 위가 아니면 닫지 않음(삼키지도 않음)\n // 바깥 클릭 — 닫기만 하고 클릭은 삼킨다.\n // stopImmediatePropagation: 한 mousedown에 dismiss는 '하나만' — 닫힘이 동기 flush되면\n // 스택이 줄어 다음 리스너(부모 팝오버)가 자기를 맨 위로 오인해 연쇄로 닫히는 것을 차단.\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n const swallow = (ce: Event) => {\n ce.stopPropagation();\n ce.preventDefault();\n };\n document.addEventListener('click', swallow, { capture: true, once: true });\n // 드래그아웃 등으로 click이 발생하지 않는 경우 잔여 리스너 정리\n setTimeout(() => document.removeEventListener('click', swallow, true), 300);\n // preventDefault로 포커스 이동까지 막히므로, 트리거/패널 안에 남은 포커스는 직접 blur해\n // 원래 일어났을 blur(입력 확정·focused 해제)를 복원한다 — 안 하면 인풋이 활성 상태로 남아\n // 재클릭 시 focus 이벤트가 없어 팝오버가 다시 열리지 않는다(DateField 등 onFocus 오픈형).\n const active = document.activeElement;\n if (\n active instanceof HTMLElement &&\n refs.some((r) => r.current && r.current.contains(active))\n )\n active.blur();\n onDismiss(e);\n };\n document.addEventListener('mousedown', onDown, true);\n return () => document.removeEventListener('mousedown', onDown, true);\n });\n}\n","// popoverLayers — 열려 있는 팝오버/드롭다운 패널(portal) 레이어 스택 (Popover·Select 공유)\n// 패널은 각자 portal이라 DOM 포함 관계로는 중첩을 알 수 없다 → 연 순서 스택으로 \"맨 위\" 판정.\n// - 외부 클릭: 맨 위 레이어 하나만 닫는다(아래 레이어는 자기 차례가 올 때까지 유지)\n// - Esc: 맨 위 레이어만 닫는다(중첩 팝오버·팝오버 안 셀렉트가 한 번에 다 닫히지 않게)\n// 원래 Popover.jsx 내부 배열이었으나, 팝오버 패널 안에 Select(드롭다운도 portal)를 넣는\n// 복수 조건 설정(2026-07-16)에서 Select도 같은 스택에 서야 해 공용 모듈로 추출했다.\n// 레이어 식별자 — 각 패널의 menuRef 객체(동일성 비교만 하므로 내용 타입은 무관)\ntype LayerRef = object;\n\nconst layers: LayerRef[] = [];\n\nexport function pushPopoverLayer(ref: LayerRef) {\n layers.push(ref);\n}\n\nexport function removePopoverLayer(ref: LayerRef) {\n const i = layers.indexOf(ref);\n if (i >= 0) layers.splice(i, 1);\n}\n\nexport function isTopPopoverLayer(ref: LayerRef) {\n return layers[layers.length - 1] === ref;\n}\n\n// 열린 레이어가 하나라도 있는지 — 필요 시 전역 판단용\nexport function hasPopoverLayers() {\n return layers.length > 0;\n}\n","// Select — 드롭다운 선택 (Figma 02_textfield / select)\n// 트리거는 Input과 동일한 필드 컨테이너 + chevron, 펼친 목록은 커스텀 PopoverMenu(옵션 목록).\n// native <select> 대신 직접 구현해 디자인 시스템 목록(List/ListGroup)을 그대로 사용한다.\n//\n// 기능:\n// - 키보드: ↑/↓ 이동, Enter/Space 열기·선택, Esc 닫기, Tab 닫기\n// - 외부 클릭 시 닫힘\n// - 선택 항목은 List selected(파란색), 키보드 강조는 List highlighted(hover 색)\n// - multiple: 체크박스 다중 선택 — value는 배열, 행 클릭=토글(메뉴 유지),\n// 트리거에는 선택 라벨을 ', '로 이어 표시하고 넘치면 말줄임(기존 TruncatingText 재사용)\n// 색은 tf-* 시멘틱 토큰(트리거)과 list-* 토큰(목록)을 사용.\nimport { useState, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from 'react';\nimport type {\n ComponentPropsWithoutRef,\n KeyboardEvent as ReactKeyboardEvent,\n ReactNode,\n RefObject,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { ChevronDown } from 'lucide-react';\nimport { Tooltip } from './Tooltip';\nimport { TruncatingText } from './TruncatingText';\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport type { ChipColor } from './chipStyles';\nimport { InlineFieldTrigger } from './InlineFieldTrigger';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { ListEmpty } from './ListEmpty';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useOutsideDismiss } from './useOutsideDismiss';\nimport { pushPopoverLayer, removePopoverLayer, isTopPopoverLayer } from './popoverLayers';\nimport type { OptionItem } from './formulaFunctions';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰), 색은 hover-line(gray-900 알파) 공통.\n// 포커스 링은 focus-visible(키보드 포커스)에만 — 마우스 클릭/프레스 후 링이 남지 않게(2026-07-16 지시)\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-visible:ring-2';\n\n// 드롭다운 배치 — usePopoverPosition 규약('auto' | '{top|bottom|auto}-{left|right|auto}')\nexport type SelectPlacement = 'auto' | `${'top' | 'bottom' | 'auto'}-${'left' | 'right' | 'auto'}`;\n\n// 내부 라벨(label) 구분자 아이콘 — 세로 점 2개(2×7). lucide에 없는 커스텀 셰이프라\n// Figma 원본(Ellipse, 8219:76216) path를 인라인 SVG로 가져왔다(2026-07-07 개정 — 콜론 텍스트 대체).\n// 색은 currentColor — 사용처에서 시멘틱 토큰 클래스(text-font-icon-3 = #878787, Figma 값)로 지정.\nfunction LabelSeparatorIcon({ className = '' }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 2 7\"\n width={2}\n height={7}\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={`shrink-0 ${className}`}\n >\n <path d=\"M2 1C2 1.55228 1.55228 2 1 2C0.447715 2 0 1.55228 0 1C0 0.447715 0.447715 0 1 0C1.55228 0 2 0.447715 2 1Z\" />\n <path d=\"M2 6C2 6.55228 1.55228 7 1 7C0.447715 7 0 6.55228 0 6C0 5.44772 0.447715 5 1 5C1.55228 5 2 5.44772 2 6Z\" />\n </svg>\n );\n}\n\n// 공통 props — ...props는 루트 컨테이너(div)로 전달된다.\ninterface SelectBaseProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {\n options?: OptionItem[]; // [{ value, label, disabled? }] — disabled 옵션은 목록에 비활성 행으로 표시(선택 불가)\n confirm?: boolean; // multiple 전용 — 선택을 draft로 들고 푸터(전체 선택+취소/확인)에서\n // 확인을 눌러야 onChange 반영(취소·외부 클릭은 폐기). Figma 8535:9239\n variant?: 'box' | 'text' | 'chip'; // 'box'(필드형, 기본) | 'text'(인라인 텍스트형 — 필터·문단 사이용)\n // | 'chip'(칩형 트리거 — Figma select chip 8219:81717, SelectChip으로 export)\n size?: '24' | '20'; // text variant 전용: '24'(14px) | '20'(12px) — box는 항상 14px\n weight?: 'normal' | 'semibold'; // chip variant 전용 텍스트 두께 — 'normal'(기본) | 'semibold'\n defaultOpen?: boolean; // 마운트 시 드롭다운을 연 상태로 시작(1회성 — 이후는 일반 동작)\n tabOpens?: boolean; // 트리거 포커스 상태에서 Tab=메뉴 열기(수식 키보드 체인용 — 일반 폼은 기본 꺼짐)\n color?: ChipColor; // chip variant 전용: 'gray' | 'red' | 'blue' | 'black' — Chip과 동일한\n // chip-* 시멘틱 토큰(CHIP_COLOR_CLASS) 재사용. pressed=default(Figma 동일)\n label?: ReactNode; // 내부 라벨(box 전용, Figma type=\"solid label\") — 값 선택 시 트리거에\n // \"라벨 ⋮ 값\"으로 표시(구분자=점 2개 아이콘, 라벨 고정·값만 말줄임).\n // placeholder 상태는 미표시. 외부 Label을 못 쓰는 좁은 배치의 대안(2026-07-07 Figma 개정).\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 트리거 너비: 숫자(px) | CSS 길이 | 'hug'(콘텐츠 맞춤). 미지정 시 200px\n maxWidth?: number | string; // 트리거 최대 너비(숫자 px/CSS 길이). hug일 때 제한용 — 넘으면 말줄임\n menuWidth?: number | string; // 드롭다운 너비: 숫자(px)/CSS 길이. 미지정 시 트리거와 동일\n placement?: SelectPlacement; // 'auto' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n searchable?: boolean; // 드롭다운 상단 검색바로 옵션 필터\n searchPlaceholder?: string;\n emptyMessage?: string;\n noResultMessage?: string;\n}\n\n// 값 계약 — onChange는 { target: { value } } 합성 페이로드(단일=string, multiple=string[])\nexport interface SelectSingleProps extends SelectBaseProps {\n multiple?: false;\n value?: string | null; // null=미선택(placeholder 표시)\n defaultValue?: string;\n onChange?: (e: { target: { value: string } }) => void;\n}\nexport interface SelectMultipleProps extends SelectBaseProps {\n multiple: true; // 체크박스 다중 선택 — value/defaultValue/onChange 값은 배열([value])\n value?: string[];\n defaultValue?: string[];\n onChange?: (e: { target: { value: string[] } }) => void;\n}\nexport type SelectProps = SelectSingleProps | SelectMultipleProps;\n\nexport function Select({\n value,\n defaultValue = '',\n onChange: onChangeProp,\n options = [], // [{ value, label, disabled? }] — disabled 옵션은 목록에 비활성 행으로 표시(선택 불가)\n multiple = false, // 체크박스 다중 선택 — value/defaultValue/onChange 값은 배열([value])\n confirm = false, // multiple 전용 — 선택을 draft로 들고 푸터(전체 선택+취소/확인)에서\n // 확인을 눌러야 onChange 반영(취소·외부 클릭은 폐기). Figma 8535:9239\n variant = 'box', // 'box'(필드형, 기본) | 'text'(인라인 텍스트형 — 필터·문단 사이용)\n // | 'chip'(칩형 트리거 — Figma select chip 8219:81717, SelectChip으로 export)\n size = '24', // text variant 전용: '24'(14px) | '20'(12px) — box는 항상 14px\n weight = 'normal', // chip variant 전용 텍스트 두께 — 'normal'(기본) | 'semibold'\n defaultOpen = false, // 마운트 시 드롭다운을 연 상태로 시작(1회성 — 이후는 일반 동작)\n tabOpens = false, // 트리거 포커스 상태에서 Tab=메뉴 열기(수식 키보드 체인용 — 일반 폼은 기본 꺼짐)\n color = 'gray', // chip variant 전용: 'gray' | 'red' | 'blue' | 'black' — Chip과 동일한\n // chip-* 시멘틱 토큰(CHIP_COLOR_CLASS) 재사용. pressed=default(Figma 동일)\n label = null, // 내부 라벨(box 전용, Figma type=\"solid label\") — 값 선택 시 트리거에\n // \"라벨 ⋮ 값\"으로 표시(구분자=점 2개 아이콘, 라벨 고정·값만 말줄임).\n // placeholder 상태는 미표시. 외부 Label을 못 쓰는 좁은 배치의 대안(2026-07-07 Figma 개정).\n placeholder = '선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 200, // 트리거 너비: 숫자(px) | CSS 길이 | 'hug'(콘텐츠 맞춤). 미지정 시 200px\n maxWidth, // 트리거 최대 너비(숫자 px/CSS 길이). hug일 때 제한용 — 넘으면 말줄임\n menuWidth, // 드롭다운 너비: 숫자(px)/CSS 길이. 미지정 시 트리거와 동일\n placement = 'auto', // 'auto' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n searchable = false, // 드롭다운 상단 검색바로 옵션 필터\n searchPlaceholder = '검색어를 입력하세요',\n emptyMessage = '옵션이 없습니다.',\n noResultMessage = '검색 결과가 없습니다.',\n className = '',\n ...props\n}: SelectProps) {\n // 단일/다중 onChange 유니언을 구현부 공용 시그니처로 좁힌다(외부 계약은 SelectProps가 보장 — 런타임 동일 참조)\n const onChange = onChangeProp as ((e: { target: { value: string | string[] } }) => void) | undefined;\n const isControlled = value !== undefined;\n // multiple인데 defaultValue가 배열이 아니면(기본 '') 빈 배열로 시작\n const [internal, setInternal] = useState<string | string[]>(() =>\n multiple && !Array.isArray(defaultValue) ? [] : defaultValue,\n );\n const current = value !== undefined ? value : internal;\n // multiple 전용 — 현재 선택 배열(비배열 방어)\n const selectedValues = useMemo(\n () => (multiple && Array.isArray(current) ? current : []),\n [multiple, current],\n );\n const [open, setOpen] = useState(defaultOpen); // defaultOpen: 마운트 시 메뉴 열림(키보드 체인 시작점 등)\n const isConfirm = multiple && confirm;\n const [draft, setDraft] = useState<string[]>([]); // confirm 모드 — 열려 있는 동안의 임시 선택(확인 시에만 반영)\n const searchInputRef = useRef<HTMLInputElement | null>(null);\n const skipTabOpenRef = useRef(false); // 옵션 순회 종료 직후의 Tab은 다시 열지 않고 통과(체인 진행)\n useEffect(() => {\n if (!open || !searchable) return undefined;\n // 패널이 위치 계산 전 hidden이라 autoFocus가 실패한다 — 한 틱 뒤 검색 인풋 포커스\n const t = setTimeout(() => searchInputRef.current?.focus(), 0);\n return () => clearTimeout(t);\n }, [open, searchable]);\n const [highlight, setHighlight] = useState(-1);\n const [query, setQuery] = useState('');\n // 트리거 텍스트 최대 너비(px) — 크롬 flex/grid truncate 보강용으로 명시 지정\n const [textMaxW, setTextMaxW] = useState<number | undefined>(undefined);\n\n // 트리거는 variant에 따라 div 또는 span(InlineFieldTrigger)이므로 HTMLElement로 두고 JSX에서 좁힌다\n const rootRef = useRef<HTMLElement | null>(null);\n const triggerRef = useRef<HTMLElement | null>(null);\n const menuRef = useRef<HTMLElement | null>(null);\n\n // text variant: 박스/보더 없는 인라인 텍스트형. 항상 hug(콘텐츠 맞춤) + maxWidth만 받는다.\n // disabled/readOnly/error는 box와 동일하게 지원(테이블 바디·폼용). 드롭다운·키보드·검색 동작도 동일.\n const isText = variant === 'text';\n // text variant fill — 부모 전체 폭(라벨 왼쪽·chevron 오른쪽 끝). width='fill'일 때만.\n const isTextFill = variant === 'text' && width === 'fill';\n // chip variant: 칩 비주얼 트리거(콘텐츠 hug) — 드롭다운·키보드·검색 동작은 box와 동일.\n const isChip = variant === 'chip';\n // text/chip variant는 트리거가 좁으므로(hug) 드롭다운 기본 너비를 120px로 둔다(menuWidth 미지정 시).\n const effectiveMenuWidth = menuWidth ?? (isText || isChip ? 120 : undefined);\n const selectedOption = multiple ? undefined : options.find((o) => o.value === current);\n // 트리거 표시 텍스트 — multiple은 선택 라벨을 ', '로 연결(옵션 순서 기준), 넘치면 TruncatingText가 말줄임\n const selectedLabels = multiple\n ? options.filter((o) => selectedValues.includes(o.value)).map((o) => o.label)\n : [];\n const isPlaceholder = multiple ? selectedLabels.length === 0 : !selectedOption;\n const displayLabel = multiple\n ? (isPlaceholder ? placeholder : selectedLabels.join(', '))\n : (selectedOption ? selectedOption.label : placeholder);\n const interactive = !disabled && !readOnly;\n // box variant 트리거 너비(text variant는 콘텐츠 hug라 style을 주지 않는다)\n const widthStyle =\n width === 'hug' ? 'fit-content' : typeof width === 'number' ? `${width}px` : width;\n const maxWidthStyle =\n maxWidth != null ? (typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth) : undefined;\n\n // 검색 필터링된 옵션 (searchable + 검색어 있을 때만)\n const filtered =\n searchable && query.trim()\n ? options.filter((o) =>\n String(o.label).toLowerCase().includes(query.trim().toLowerCase()),\n )\n : options;\n\n const selectValue = useCallback(\n (val: string) => {\n if (!isControlled) setInternal(val);\n onChange?.({ target: { value: val } });\n setOpen(false);\n triggerRef.current?.focus();\n },\n [isControlled, onChange],\n );\n\n // multiple: 값 토글 — 메뉴는 닫지 않는다(연속 체크). 배열 순서는 옵션 순서를 따르지 않고 토글 순서.\n // confirm 모드는 draft만 토글하고, 확인 버튼에서 한 번에 반영한다.\n const toggleValue = useCallback(\n (val: string) => {\n if (isConfirm) {\n setDraft((d) => (d.includes(val) ? d.filter((v) => v !== val) : [...d, val]));\n return;\n }\n const next = selectedValues.includes(val)\n ? selectedValues.filter((v) => v !== val)\n : [...selectedValues, val];\n if (!isControlled) setInternal(next);\n onChange?.({ target: { value: next } });\n },\n [isConfirm, isControlled, onChange, selectedValues],\n );\n const commitDraft = useCallback(\n (next: string[]) => {\n if (!isControlled) setInternal(next);\n onChange?.({ target: { value: next } });\n setOpen(false);\n triggerRef.current?.focus();\n },\n [isControlled, onChange],\n );\n // 체크 표시 기준 — confirm 모드는 draft, 아니면 확정값\n const checkedValues = isConfirm ? draft : selectedValues;\n\n // 행 확정 동작 — 단일=선택 후 닫기 / multiple=토글 후 유지. disabled 옵션은 확정 불가(키보드 포함)\n const commitOption = (opt: OptionItem) => {\n if (opt?.disabled) return;\n return multiple ? toggleValue(opt.value) : selectValue(opt.value);\n };\n\n // 트리거 폭을 재서 텍스트 max-width를 계산(콘텐츠폭 − chevron − gap) — 크롬 truncate 보강.\n // hug(fit-content)는 트리거가 콘텐츠를 따라가므로 max-width를 주면 무한 축소 순환이 생긴다 → 제외.\n useLayoutEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger || isText || isChip || width === 'hug') {\n setTextMaxW(undefined);\n return;\n }\n const update = () => {\n const cs = getComputedStyle(trigger);\n const pl = parseFloat(cs.paddingLeft) || 0;\n const pr = parseFloat(cs.paddingRight) || 0;\n const gapPx = parseFloat(cs.columnGap) || 0;\n const chevronW = 16;\n const contentW = trigger.clientWidth - pl - pr;\n setTextMaxW(Math.max(0, contentW - chevronW - gapPx));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(trigger);\n return () => ro.disconnect();\n }, [width, isText, isChip]);\n\n // 열린 동안 드롭다운을 팝오버 레이어 스택에 등록 — 팝오버 패널 안에서 열렸을 때\n // 바깥 팝오버가 이 메뉴 안 클릭을 '외부'로 오인해 닫히지 않게 한다(2026-07-16, 복수 조건 설정).\n useEffect(() => {\n if (!open) return undefined;\n pushPopoverLayer(menuRef);\n return () => removePopoverLayer(menuRef);\n }, [open]);\n\n // 외부 클릭 닫기 (트리거·드롭다운 둘 다 바깥일 때 — 드롭다운은 portal).\n // 공용 훅 useOutsideDismiss — 바깥 클릭은 \"닫기 전용\"으로 삼켜 아래 요소가 함께 클릭되지 않는다.\n // guard: 맨 위 레이어일 때만 닫는다(위에 다른 패널이 떠 있으면 그쪽 차례).\n useOutsideDismiss({\n open,\n refs: [rootRef, menuRef],\n guard: () => isTopPopoverLayer(menuRef),\n onDismiss: () => setOpen(false),\n });\n\n // Esc 닫기 — capture 단계에서 먼저 받아 전파를 끊는다(포커스가 목록에 있어도 동작,\n // 모달 안에선 드롭다운만 닫히고 모달은 유지되도록 Modal의 Esc 리스너보다 우선).\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!isTopPopoverLayer(menuRef)) return; // 맨 위 레이어만 — 중첩 시 위 패널부터\n e.stopPropagation();\n setOpen(false);\n triggerRef.current?.focus();\n };\n document.addEventListener('keydown', onKey, true);\n return () => document.removeEventListener('keydown', onKey, true);\n }, [open]);\n\n // 열릴 때: 검색어 초기화 + 현재 선택 항목을 강조 시작점으로\n useEffect(() => {\n if (!open) return;\n // 열릴 때 1회 리셋 — 의도된 effect 내 setState\n setQuery(''); // eslint-disable-line react-hooks/set-state-in-effect\n if (isConfirm) setDraft(selectedValues); // confirm 모드 — 확정값으로 draft 시드\n const idx = options.findIndex((o) =>\n multiple ? selectedValues.includes(o.value) : o.value === current,\n );\n setHighlight(idx >= 0 ? idx : 0);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // 강조 항목이 스크롤 영역 밖이면 보이도록 스크롤 (드롭다운은 portal이라 menuRef 기준)\n useEffect(() => {\n if (!open || highlight < 0) return;\n const el = menuRef.current?.querySelector(`[data-option-index=\"${highlight}\"]`);\n el?.scrollIntoView({ block: 'nearest' });\n }, [highlight, open]);\n\n // 드롭다운 fixed 위치 계산 — Popover와 공유하는 공용 훅 (검색으로 목록 높이가 바뀌면 재계산)\n const menuStyle = usePopoverPosition({\n open,\n anchorRef: triggerRef,\n menuRef,\n placement,\n menuWidth: effectiveMenuWidth,\n deps: [filtered.length, query],\n });\n\n // 목록 키보드 네비게이션 — 트리거/검색바 공용, filtered 기준\n const handleListNav = (e: ReactKeyboardEvent<HTMLElement>) => {\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n setOpen(false);\n triggerRef.current?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n setHighlight((h) => Math.min(h + 1, filtered.length - 1));\n break;\n case 'ArrowUp':\n e.preventDefault();\n setHighlight((h) => Math.max(h - 1, 0));\n break;\n case 'Enter': {\n e.preventDefault();\n const opt = filtered[highlight];\n if (opt) commitOption(opt);\n break;\n }\n case 'Tab': {\n // 열린 메뉴에서 Tab=옵션 순회(2026-07-15) — 하이라이트가 끝에 닿으면 닫고 다음 요소로 자연 이동\n if (!e.shiftKey && highlight < filtered.length - 1) {\n e.preventDefault();\n setHighlight((h) => Math.min(h + 1, filtered.length - 1));\n } else if (e.shiftKey && highlight > 0) {\n e.preventDefault();\n setHighlight((h) => Math.max(h - 1, 0));\n } else {\n setOpen(false);\n if (searchable) {\n // 검색 인풋이 포커스를 쥔 채 닫히면 포커스가 유실된다 — 트리거로 복귀,\n // 다음 Tab은 skip 플래그로 재오픈 없이 다음 요소로 진행\n e.preventDefault();\n skipTabOpenRef.current = true;\n triggerRef.current?.focus();\n }\n }\n break;\n }\n default:\n break;\n }\n };\n\n const onTriggerKeyDown = (e: ReactKeyboardEvent<HTMLElement>) => {\n if (!interactive) return;\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n setOpen(true);\n } else if (tabOpens && e.key === 'Tab' && !e.shiftKey) {\n if (skipTabOpenRef.current) {\n skipTabOpenRef.current = false; // 순회를 마친 직후 — 열지 않고 다음 요소로 자연 이동\n return;\n }\n // 키보드 체인(수식): 포커스된 트리거에서 Tab → 메뉴 열기(이후 Tab은 옵션 순회)\n e.preventDefault();\n setOpen(true);\n }\n return;\n }\n // 열린 상태: 검색 모드면 검색바가 키를 처리, 아니면 트리거에서 네비게이션\n if (searchable) return;\n if (e.key === ' ') {\n e.preventDefault();\n const opt = filtered[highlight];\n if (opt) commitOption(opt);\n } else {\n handleListNav(e);\n }\n };\n\n // box variant 색 — tf-* 시멘틱 토큰. (text variant의 색·크기는 InlineFieldTrigger가 담당)\n const textColor = disabled\n ? 'text-text-field-disabled-text'\n : readOnly\n ? 'text-text-field-readonly-text'\n : isPlaceholder\n ? 'text-text-field-default-text'\n : 'text-text-field-filled-text';\n\n const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : 'text-text-field-default-text group-focus-within:text-text-field-filled-text';\n\n // box variant 화살표 크기는 항상 16\n const chevronSize = 16;\n\n return (\n <div\n ref={rootRef as RefObject<HTMLDivElement | null>}\n className={`relative ${\n isTextFill ? 'flex w-full' : isText || isChip ? 'inline-flex max-w-full align-middle' : ''\n } ${className}`}\n style={isText || isChip ? undefined : { width: widthStyle, maxWidth: maxWidthStyle }}\n {...props}\n >\n {/* 트리거 — 상호작용(클릭·키보드·포커스·위치 anchor)은 box·text·chip 공통, 비주얼은 분기 */}\n {isText ? (\n // 인라인 텍스트형: 비주얼은 공유 프리미티브 InlineFieldTrigger에 위임(Select·DatePicker 공용)\n <InlineFieldTrigger\n ref={triggerRef as RefObject<HTMLSpanElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n size={size}\n open={open}\n disabled={disabled}\n readOnly={readOnly}\n interactive={interactive}\n maxWidth={maxWidthStyle}\n fill={isTextFill}\n >\n {displayLabel}\n </InlineFieldTrigger>\n ) : isChip ? (\n // 칩형: Chip과 동일 박스 규격(pl-4/pr-3/py-1·gap-3·round-4·text-12)+chip-* 토큰, chevron 12.\n // 상태는 default/hover(CSS)만 — pressed는 Figma에서 default 재사용, disabled 변형은 미제공(동작만 차단).\n <div\n ref={triggerRef as RefObject<HTMLDivElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n style={maxWidthStyle ? { maxWidth: maxWidthStyle } : undefined}\n className={`inline-flex min-w-0 items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 font-pretendard text-12 font-normal transition-colors focus:outline-none ${\n CHIP_COLOR_CLASS[color] ?? CHIP_COLOR_CLASS.gray /* 포커스 표시=hover 색 전환(맵에 포함) */\n } ${interactive ? 'cursor-pointer' : 'cursor-not-allowed'}`}\n >\n <TruncatingText\n className={`min-w-0 text-12 ${weight === 'semibold' ? 'font-semibold' : 'font-normal'}`}\n >\n {displayLabel}\n </TruncatingText>\n <ChevronDown\n size={12}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}\n />\n </div>\n ) : (\n <div\n ref={triggerRef as RefObject<HTMLDivElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n className={`group relative grid min-h-[32px] grid-cols-[minmax(0,1fr)_auto] items-center gap-spacing-4 rounded-round-4 bg-text-field-default-bg py-spacing-3 pl-spacing-6 pr-spacing-6 transition-shadow focus:outline-none ${\n interactive ? `cursor-pointer ${RING}` : 'cursor-not-allowed'\n }`}\n >\n {/* box: grid minmax(0,1fr) + JS max-width 보강.\n 내부 라벨(label)은 값이 있을 때만 \"라벨 ⋮ 값\" — 라벨·점 구분자 고정(shrink-0), 값만 말줄임(Figma solid label). */}\n {label != null && !isPlaceholder ? (\n <div\n style={textMaxW != null ? { maxWidth: `${textMaxW}px` } : undefined}\n className=\"flex min-w-0 items-center gap-spacing-3\"\n >\n <span className={`shrink-0 text-14 font-normal ${textColor}`}>{label}</span>\n <LabelSeparatorIcon className=\"text-font-icon-3\" />\n <TruncatingText className={`min-w-0 flex-1 text-14 font-normal ${textColor}`}>\n {displayLabel}\n </TruncatingText>\n </div>\n ) : (\n <TruncatingText\n style={textMaxW != null ? { maxWidth: `${textMaxW}px` } : undefined}\n className={`text-14 font-normal ${textColor}`}\n >\n {displayLabel}\n </TruncatingText>\n )}\n <ChevronDown\n size={chevronSize}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${iconColor} ${\n open ? 'rotate-180' : ''\n }`}\n />\n </div>\n )}\n\n {/* 드롭다운 — portal + fixed (트리거 컨테이너와 분리해 트리거 레이아웃에 영향 없음) */}\n {open &&\n createPortal(\n <div\n ref={menuRef as RefObject<HTMLDivElement | null>}\n className=\"z-[1000]\"\n style={menuStyle ?? { position: 'fixed', top: 0, left: 0, visibility: 'hidden' }}\n >\n <PopoverMenu\n width=\"100%\"\n topArea={searchable ? 'search' : 'none'}\n searchValue={query}\n onSearchChange={(e) => {\n setQuery(e.target.value);\n setHighlight(0);\n }}\n searchPlaceholder={searchPlaceholder}\n searchInputProps={\n searchable\n ? // ref는 PopoverMenu가 내부 <input>에 전달한다(선언 타입엔 없지만 런타임 계약 — 기존 동작 유지 cast)\n ({ ref: searchInputRef, onKeyDown: handleListNav } as ComponentPropsWithoutRef<'input'>)\n : {}\n }\n {...(isConfirm\n ? {\n // confirm 모드 푸터 — 전체 선택(비활성 옵션 제외) + 취소/확인(확인 시에만 반영)\n footer: true,\n footerCheckbox: true,\n footerChecked:\n options.filter((o) => !o.disabled).length > 0 &&\n options.filter((o) => !o.disabled).every((o) => draft.includes(o.value)),\n onFooterCheckChange: () =>\n setDraft((d) => {\n const all = options.filter((o) => !o.disabled).map((o) => o.value);\n return all.every((v) => d.includes(v)) ? [] : all;\n }),\n onCancel: () => {\n setOpen(false);\n triggerRef.current?.focus();\n },\n onConfirm: () => commitDraft(draft),\n }\n : {})}\n >\n {filtered.length > 0 ? (\n <ListGroup>\n {filtered.map((opt, i) =>\n multiple ? (\n // 체크박스 행 — List가 행 전체를 체크 토글 영역으로 처리(메뉴 유지)\n <List\n key={opt.value}\n title={opt.label}\n checkbox\n disabled={opt.disabled}\n checked={checkedValues.includes(opt.value)}\n onCheckChange={opt.disabled ? undefined : () => toggleValue(opt.value)}\n highlighted={!opt.disabled && i === highlight}\n onMouseEnter={() => !opt.disabled && setHighlight(i)}\n data-option-index={i}\n />\n ) : (\n <List\n key={opt.value}\n title={opt.label}\n disabled={opt.disabled}\n selected={opt.value === current}\n highlighted={!opt.disabled && i === highlight}\n onClick={opt.disabled ? undefined : () => selectValue(opt.value)}\n onMouseEnter={() => !opt.disabled && setHighlight(i)}\n data-option-index={i}\n />\n ),\n )}\n </ListGroup>\n ) : (\n <ListEmpty message={searchable && query.trim() ? noResultMessage : emptyMessage} />\n )}\n </PopoverMenu>\n </div>,\n document.body,\n )}\n\n {/* 에러 툴팁 — 닫혔을 때 필드 아래 오버레이 (box·text 공통) */}\n {error && errorMessage && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errorMessage}\n </Tooltip>\n </div>\n )}\n </div>\n );\n}\n\n// SelectChip — 칩 비주얼 트리거의 Select (Figma `select chip` 8219:81717, color gray/red/blue/black ×\n// state Default/hover/pressed=default). 클릭하면 팝오버 옵션 메뉴가 열리고, options·value/onChange·\n// multiple·searchable·menuWidth 등 모든 옵션·동작은 Select와 완전히 동일하다(variant=\"chip\" 별칭).\nexport function SelectChip(props: SelectProps) {\n return <Select variant=\"chip\" {...props} />;\n}\n","// ButtonGroup — 버튼들을 일정 간격으로 묶는 컨테이너\n// 버튼과 버튼들을 가로/세로로 모아 일관된 간격(기본 8px = spacing-5)으로 배치한다.\n// 간격은 등록된 spacing 토큰만 사용하도록 lookup으로 제한한다(임의값 금지).\n// 규칙: 자식에 Select가 섞이면 Select를 버튼보다 앞(선두)에 배치한다.\nimport { Children, cloneElement, Fragment, isValidElement } from 'react';\nimport type { ComponentProps, ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Button } from './Button';\nimport { Select } from './Select';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑)\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px (기본)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\n// fragment·배열·조건부(`{x && <>…</>}`) children을 재귀적으로 평탄화해\n// 실제 자식 요소(Button/Select 등)만 추려낸다. (toArray는 fragment를 펴지 않으므로 직접 처리)\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = [];\n Children.forEach(children, (child) => {\n if (isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment) {\n out.push(...flattenChildren(child.props.children));\n } else if (child != null && typeof child !== 'boolean') {\n out.push(child);\n }\n });\n return out;\n}\n\nconst isSelect = (c: ReactNode) => isValidElement(c) && c.type === Select;\n\ninterface ButtonGroupProps extends ComponentPropsWithoutRef<'div'> {\n direction?: 'horizontal' | 'vertical';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '5'(8px)\n width?: 'hug' | 'fill'; // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 버튼들 균등 분할)\n}\n\nexport function ButtonGroup({\n children,\n direction = 'horizontal', // 'horizontal' | 'vertical'\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 버튼들 균등 분할)\n className = '',\n ...props\n}: ButtonGroupProps) {\n const dirStyle = direction === 'vertical' ? 'flex-col' : 'flex-row';\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['5'];\n const isFill = width === 'fill';\n\n // Select 자식을 버튼보다 앞으로 — 그 외 순서는 유지(안정 정렬).\n const items = flattenChildren(children);\n const selects = items.filter(isSelect);\n const others = items.filter((c) => !isSelect(c));\n const ordered = selects.length ? [...selects, ...others] : items;\n\n // 최종 자식 배열 — 모든 요소에 안정 key를 직접 부여한다.\n // (map 결과 배열은 key가 없으면 React가 key 경고를 내며, Children.toArray로 감싸도 막히지 않는다.)\n // fill이면 Button 자식을 width=\"fill\"로 복제해 컨테이너 폭을 균등 분할한다(명시된 width는 존중).\n const rendered = ordered.map((c, i) => {\n if (!isValidElement(c)) return c;\n const fillProps =\n isFill && c.type === Button\n ? { width: (c as ReactElement<ComponentProps<typeof Button>>).props.width ?? 'fill' }\n : {};\n return cloneElement(c as ReactElement<Record<string, unknown>>, { key: c.key ?? `bg-${i}`, ...fillProps });\n });\n\n return (\n <div\n className={`${isFill ? 'flex w-full' : 'inline-flex'} ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {rendered}\n </div>\n );\n}\n","// Popover — 임의의 트리거(버튼 등)에 떠 있는 패널(PopoverMenu 등)을 붙이는 범용 컨테이너.\n// Select에 들어 있던 드롭다운 로직(위치 자동계산·외부클릭/Esc 닫기·portal+자동반전)을\n// 트리거에 비종속적으로 추출한 것. 패널 내용은 children으로 자유롭게 구성한다(보통 PopoverMenu).\n//\n// 사용:\n// <Popover menuWidth={200} trigger={<Button rightIcon={ChevronDown}>메뉴</Button>}>\n// {(close) => (\n// <PopoverMenu width=\"100%\">\n// <ListGroup>\n// <List title=\"이름 변경\" onClick={close} />\n// </ListGroup>\n// </PopoverMenu>\n// )}\n// </Popover>\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useOutsideDismiss } from './useOutsideDismiss';\n// 열려 있는 패널 레이어 스택 — 중첩 popover·팝오버 안 Select 외부클릭/Esc 판정에 사용.\n// 패널은 각자 portal이라 DOM이 분리되므로, \"나보다 나중에 열린(위) 패널 안 클릭\"은 바깥으로 보지 않는다\n// (예: 조건 팝오버 안 어학/자격 Select 메뉴 클릭에 조건 팝오버가 닫히지 않게). 공용 모듈로 추출(2026-07-16).\nimport { pushPopoverLayer, removePopoverLayer, isTopPopoverLayer } from './popoverLayers';\n\nexport interface PopoverProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children'> {\n /** 트리거 ReactNode (예: <Button>) — 클릭 시 패널 토글 */\n trigger?: ReactNode;\n /** 패널 내용: ReactNode 또는 (close) => ReactNode */\n children?: ReactNode | ((close: () => void) => ReactNode);\n /** 'auto' | '{top|bottom|auto}-{left|right|auto}' (예: 'bottom-right', 'auto-right'=상하 자동·우측 고정) */\n placement?: string;\n /** 패널 너비: 숫자(px)/CSS 길이. 미지정 시 트리거 너비 */\n menuWidth?: number | string;\n /** controlled (선택) */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n}\n\nexport function Popover({\n trigger,\n children,\n placement = 'auto',\n menuWidth,\n open: openProp,\n onOpenChange,\n disabled = false,\n className = '',\n ...props\n}: PopoverProps) {\n const isControlled = openProp !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const open = isControlled ? (openProp as boolean) : internalOpen;\n\n const anchorRef = useRef<HTMLSpanElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n\n const setOpen = useCallback(\n (v: boolean) => {\n if (!isControlled) setInternalOpen(v);\n onOpenChange?.(v);\n },\n [isControlled, onOpenChange],\n );\n const close = useCallback(() => setOpen(false), [setOpen]);\n\n // 열린 동안 패널을 스택에 등록(연 순서 유지) — open 전환에만 의존해 재정렬 방지.\n useEffect(() => {\n if (!open) return undefined;\n pushPopoverLayer(menuRef);\n return () => removePopoverLayer(menuRef);\n }, [open]);\n\n // 외부 클릭 닫기 (트리거·패널 둘 다 바깥일 때 — 패널은 portal). 공용 훅 useOutsideDismiss:\n // 바깥 클릭은 \"닫기 전용\"으로 삼켜 아래 요소가 함께 클릭되지 않는다(click-through 방지).\n // 중첩 대응(guard): 외부 클릭은 \"맨 위(가장 나중에 열린) 팝오버\" 하나만 닫는다.\n // → 시간/연월 선택 팝오버가 위에 있으면 그것부터 닫히고, 다음 외부 클릭에 DatePicker가 닫힌다.\n useOutsideDismiss({\n open,\n refs: [anchorRef, menuRef],\n guard: () => isTopPopoverLayer(menuRef),\n onDismiss: close,\n });\n\n // Esc 닫기 — capture 단계에서 먼저 받아 전파를 끊는다\n // (모달 안에서 팝오버만 닫히고 모달은 유지되도록 — Modal의 document Esc 리스너보다 우선)\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!isTopPopoverLayer(menuRef)) return; // Esc도 맨 위 레이어만 — 안의 Select가 먼저 닫힌다\n e.stopPropagation();\n close();\n };\n document.addEventListener('keydown', onKey, true);\n return () => document.removeEventListener('keydown', onKey, true);\n }, [open, close]);\n\n // 패널 fixed 위치 계산 — Select와 공유하는 공용 훅\n const menuStyle = usePopoverPosition({ open, anchorRef, menuRef, placement, menuWidth });\n\n return (\n <>\n <span\n ref={anchorRef}\n {...props}\n className={`inline-flex ${className}`}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => {\n // 소비자 onClick(클릭 계측 등)과 합성 — 스프레드로 덮어써 무시되지 않게\n props.onClick?.(e);\n if (!disabled) setOpen(!open);\n }}\n >\n {trigger}\n </span>\n\n {open &&\n createPortal(\n <div\n ref={menuRef}\n className=\"z-[1000]\"\n style={menuStyle ?? { position: 'fixed', top: 0, left: 0, visibility: 'hidden' }}\n >\n {typeof children === 'function' ? children(close) : children}\n </div>,\n document.body,\n )}\n </>\n );\n}\n","// EditorToolbar — Editor(편집 모드)의 서식 툴바.\n// Tiptap editor 인스턴스를 받아 editor.chain() 명령으로 서식을 적용하고,\n// editor.isActive(...)로 현재 커서 위치의 활성 상태를 버튼에 표시한다.\n// toolbar prop(키 배열)으로 노출할 기능을 고를 수 있다(미지정 시 전체).\n// 키: mergefield · block · bold · italic · underline · strike · color · highlight ·\n// bulletList · orderedList · blockquote · align · link · table · image · hr · code · codeBlock\n// mergefield: Editor의 mergeFields prop이 있을 때만 노출(머지 태그 삽입 드롭다운).\n// 색·간격은 editor-*/font-icon-*/spacing-*/round-* 토큰만 사용. 재사용 컴포넌트(Popover/PopoverMenu/\n// ListGroup/List/Button)를 그대로 활용한다. 링크·이미지 입력은 PopoverMenu의 input 타입(topArea=\"input\"\n// + footer footerButtonsFill)으로 대체했다.\nimport { Fragment, useState } from 'react';\nimport type { ElementType, MouseEvent as ReactMouseEvent } from 'react';\nimport type { Editor, ChainedCommands } from '@tiptap/react';\nimport {\n Undo2, Redo2,\n Bold, Italic, Underline, Strikethrough, Type, Baseline, Highlighter,\n AlignLeft, AlignCenter, AlignRight, AlignJustify,\n Link as LinkIcon, Table as TableIcon, Image as ImageIcon, Minus,\n Braces, Plus,\n} from 'lucide-react';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Button } from './Button';\nimport { editorTextPalette, editorHighlightPalette } from '../tokens';\n\n// 머지필드 목록 항목 — 문자열 또는 {label,value}\ntype MergeFieldItem = string | { label: string; value: string };\n\n// 셀렉션 유지를 위해 mousedown 기본동작(포커스 이동)을 막는다 — 마크/색이 선택 영역에 적용되도록.\nconst keepSelection = (e: ReactMouseEvent) => e.preventDefault();\n\n// 아이콘 토글 버튼 — 공용 Button(고스트 아이콘 버튼 size 24).\n// active는 시각 표시 없이 aria-pressed(접근성)에만 반영한다(요청에 따라 색 변화 제거).\nfunction ToolbarButton({\n icon,\n label,\n active = false,\n disabled = false,\n onClick,\n}: {\n icon: ElementType;\n label: string;\n active?: boolean;\n disabled?: boolean;\n onClick?: () => void;\n}) {\n return (\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={icon}\n disabled={disabled}\n onClick={onClick}\n onMouseDown={keepSelection}\n aria-label={label}\n aria-pressed={active}\n />\n );\n}\n\n// 드롭다운 트리거 — 공용 Button(ghost, size 24). Popover의 trigger로 사용.\n// 라벨이 있으면(블록 '본문') 텍스트+아래 화살표, 없으면 아이콘 전용 버튼.\nfunction DropTrigger({ icon, label, ariaLabel }: { icon: ElementType; label?: string; ariaLabel?: string }) {\n if (label) {\n return (\n <Button\n variant=\"ghost\"\n size=\"24\"\n leftIcon={icon}\n onMouseDown={keepSelection}\n >\n {label}\n </Button>\n );\n }\n return (\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={icon}\n onMouseDown={keepSelection}\n title={ariaLabel}\n aria-label={ariaLabel}\n />\n );\n}\n\n// 그룹 구분선 — 행 높이만큼 세로선(토큰 보더색).\nfunction Divider() {\n return <span className=\"mx-spacing-2 self-stretch border-l border-editor-divider\" />;\n}\n\nconst BLOCK_OPTIONS = [\n { key: 'paragraph', label: '본문' },\n { key: 'h1', label: '제목 1' },\n { key: 'h2', label: '제목 2' },\n { key: 'h3', label: '제목 3' },\n];\n\n// 팔레트 색 항목 — value=null이면 해제(unset), swatch는 원 표시색만 별도 지정\ninterface PaletteColor {\n label: string;\n value: string | null;\n swatch?: string;\n}\n\n// 글자색/형광 색상 선택 패널 — 토큰 기반 팔레트 스와치.\nfunction ColorPanel({\n palette,\n current,\n onPick,\n}: {\n palette: PaletteColor[];\n current: string | null;\n onPick: (value: string | null) => void;\n}) {\n return (\n <PopoverMenu width={80}>\n <div className=\"bg-list-group-bg py-spacing-4\">\n {palette.map((c) => {\n const selected = current === c.value || (c.value === null && !current);\n const dot = c.swatch ?? c.value; // 원 표시색(값이 null이어도 swatch가 있으면 그 색을 보여줌)\n return (\n <button\n key={c.label}\n type=\"button\"\n onMouseDown={keepSelection}\n onClick={() => onPick(c.value)}\n className={`flex w-full cursor-pointer items-center gap-spacing-4 px-spacing-6 py-spacing-3 text-14 transition-colors hover:bg-list-hover-bg focus:outline-none focus-visible:bg-list-hover-bg ${\n selected ? 'text-list-selected-text' : 'text-list-default-text'\n }`}\n >\n <span\n className=\"inline-block h-[14px] w-[14px] shrink-0 rounded-round-00\"\n style={dot ? { backgroundColor: dot } : undefined}\n />\n {c.label}\n </button>\n );\n })}\n </div>\n </PopoverMenu>\n );\n}\n\n// URL 입력 팝오버 폼 — PopoverMenu input 타입(상단 입력 + fill 버튼) 공통 폼.\n// 링크/이미지 등에서 재사용. onRemove가 있으면 좌측 '해제' 버튼이 함께 노출된다(없으면 단일 버튼).\nfunction UrlInputForm({\n initialUrl = '',\n placeholder,\n submitLabel,\n onSubmit,\n onRemove,\n removeLabel = '해제',\n}: {\n initialUrl?: string;\n placeholder?: string;\n submitLabel: string;\n onSubmit: (url: string) => void;\n onRemove?: () => void;\n removeLabel?: string;\n}) {\n const [url, setUrl] = useState(initialUrl);\n const submit = () => onSubmit(url.trim());\n return (\n <PopoverMenu\n width={300}\n topArea=\"input\"\n inputValue={url}\n onInputChange={(e) => setUrl(e.target.value)}\n inputPlaceholder={placeholder}\n inputProps={{\n autoFocus: true,\n onKeyDown: (e) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n submit();\n }\n },\n }}\n footer\n footerButtonsFill\n showCancel={!!onRemove}\n cancelText={removeLabel}\n onCancel={onRemove}\n confirmText={submitLabel}\n onConfirm={submit}\n />\n );\n}\n\n// 링크 입력 폼(URL 적용/해제).\nfunction LinkForm({ editor, close }: { editor: Editor; close: () => void }) {\n const apply = (url: string) => {\n const chain = editor.chain().focus().extendMarkRange('link');\n if (url) chain.setLink({ href: url }).run();\n else chain.unsetLink().run();\n close();\n };\n const remove = () => {\n editor.chain().focus().extendMarkRange('link').unsetLink().run();\n close();\n };\n return (\n <UrlInputForm\n initialUrl={editor.getAttributes('link').href ?? ''}\n placeholder=\"https://example.com\"\n submitLabel=\"적용\"\n onSubmit={apply}\n onRemove={remove}\n />\n );\n}\n\n// 이미지 URL 입력 폼(삽입 단일 버튼).\nfunction ImageForm({ editor, close }: { editor: Editor; close: () => void }) {\n const insert = (url: string) => {\n if (url) editor.chain().focus().setImage({ src: url }).run();\n close();\n };\n return (\n <UrlInputForm placeholder=\"이미지 주소(URL)\" submitLabel=\"삽입\" onSubmit={insert} />\n );\n}\n\n// 머지필드 삽입 — search list 타입 PopoverMenu(width 180)에 머지 태그 목록(+ 아이콘).\n// 클릭 시 커서 위치에 mergeField 노드(삭제 가능한 칩)를 삽입한다. mergeFields = 문자열 | {label,value}[].\nfunction MergeFieldMenu({\n editor,\n mergeFields,\n close,\n}: {\n editor: Editor;\n mergeFields: MergeFieldItem[];\n close: () => void;\n}) {\n const [q, setQ] = useState('');\n const items = mergeFields\n .map((m) => (typeof m === 'string' ? { label: m, value: m } : m))\n .filter((m) => !q || m.label.toLowerCase().includes(q.toLowerCase()));\n const insert = (value: string) => {\n editor.chain().focus().insertContent({ type: 'mergeField', attrs: { value } }).run();\n close();\n };\n return (\n <PopoverMenu\n width={180}\n topArea=\"search\"\n searchValue={q}\n onSearchChange={(e) => setQ(e.target.value)}\n searchPlaceholder=\"필드명 검색\"\n searchInputProps={{ autoFocus: true }}\n >\n <ListGroup empty={items.length === 0} emptyMessage=\"검색 결과 없음\">\n {items.map((m) => (\n <List key={m.value} icon={Plus} title={m.label} onClick={() => insert(m.value)} />\n ))}\n </ListGroup>\n </PopoverMenu>\n );\n}\n\ninterface EditorToolbarProps {\n editor: Editor | null; // Tiptap editor 인스턴스(초기화 전 null)\n toolbar?: string[]; // 노출할 기능 키 배열(미지정 시 전체)\n mergeFields?: MergeFieldItem[]; // 머지필드 목록 — 있을 때만 mergefield 드롭다운 노출\n}\n\nexport function EditorToolbar({ editor, toolbar, mergeFields = [] }: EditorToolbarProps) {\n if (!editor) return null;\n\n const show = (key: string) => !toolbar || toolbar.includes(key);\n const run = (fn: (chain: ChainedCommands) => ChainedCommands) => fn(editor.chain().focus()).run();\n\n const currentBlock = editor.isActive('heading', { level: 1 })\n ? 'h1'\n : editor.isActive('heading', { level: 2 })\n ? 'h2'\n : editor.isActive('heading', { level: 3 })\n ? 'h3'\n : 'paragraph';\n const setBlock = (key: string, close: () => void) => {\n if (key === 'paragraph') run((c) => c.setParagraph());\n else run((c) => c.toggleHeading({ level: Number(key[1]) as 1 | 2 | 3 }));\n close();\n };\n\n const currentColor: string | null = editor.getAttributes('textStyle').color ?? null;\n const currentHighlight: string | null = editor.getAttributes('highlight').color ?? null;\n const setColor = (value: string | null) =>\n value ? run((c) => c.setColor(value)) : run((c) => c.unsetColor());\n const setHighlight = (value: string | null) =>\n value ? run((c) => c.setHighlight({ color: value })) : run((c) => c.unsetHighlight());\n\n const tableActive = editor.isActive('table');\n\n // 그룹별 노출 컨트롤 — show()로 필터한 뒤, 비어있지 않은 그룹만 구분선으로 잇는다.\n const groups = [\n // 실행 취소 / 다시 실행 (맨 앞)\n [\n show('undo') && (\n <ToolbarButton key=\"undo\" icon={Undo2} label=\"실행 취소\" disabled={!editor.can().undo()} onClick={() => run((c) => c.undo())} />\n ),\n show('redo') && (\n <ToolbarButton key=\"redo\" icon={Redo2} label=\"다시 실행\" disabled={!editor.can().redo()} onClick={() => run((c) => c.redo())} />\n ),\n ],\n // 머지필드 — mergeFields가 있을 때만\n [\n show('mergefield') && mergeFields.length > 0 && (\n <Popover\n key=\"mergefield\"\n placement=\"auto-left\"\n menuWidth={180}\n trigger={<DropTrigger icon={Braces} label=\"머지필드\" />}\n >\n {(close) => <MergeFieldMenu editor={editor} mergeFields={mergeFields} close={close} />}\n </Popover>\n ),\n ],\n // 블록 타입\n [\n show('block') && (\n <Popover\n key=\"block\"\n placement=\"auto-left\"\n menuWidth={80}\n trigger={\n <DropTrigger\n icon={Type}\n label={BLOCK_OPTIONS.find((b) => b.key === currentBlock)?.label}\n />\n }\n >\n {(close) => (\n <PopoverMenu width={80}>\n <ListGroup>\n {BLOCK_OPTIONS.map((b) => (\n <List\n key={b.key}\n title={b.label}\n selected={currentBlock === b.key}\n onClick={() => setBlock(b.key, close)}\n />\n ))}\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n ),\n ],\n // 강조\n [\n show('bold') && (\n <ToolbarButton key=\"bold\" icon={Bold} label=\"굵게\" active={editor.isActive('bold')} onClick={() => run((c) => c.toggleBold())} />\n ),\n show('italic') && (\n <ToolbarButton key=\"italic\" icon={Italic} label=\"기울임\" active={editor.isActive('italic')} onClick={() => run((c) => c.toggleItalic())} />\n ),\n show('underline') && (\n <ToolbarButton key=\"underline\" icon={Underline} label=\"밑줄\" active={editor.isActive('underline')} onClick={() => run((c) => c.toggleUnderline())} />\n ),\n show('strike') && (\n <ToolbarButton key=\"strike\" icon={Strikethrough} label=\"취소선\" active={editor.isActive('strike')} onClick={() => run((c) => c.toggleStrike())} />\n ),\n ],\n // 색\n [\n show('color') && (\n <Popover key=\"color\" placement=\"auto-left\" menuWidth={80} trigger={<DropTrigger icon={Baseline} ariaLabel=\"글자 색\" />}>\n {() => <ColorPanel palette={editorTextPalette} current={currentColor} onPick={setColor} />}\n </Popover>\n ),\n show('highlight') && (\n <Popover key=\"highlight\" placement=\"auto-left\" menuWidth={80} trigger={<DropTrigger icon={Highlighter} ariaLabel=\"형광펜\" />}>\n {() => <ColorPanel palette={editorHighlightPalette} current={currentHighlight} onPick={setHighlight} />}\n </Popover>\n ),\n ],\n // 정렬\n [\n show('align') && (\n <ToolbarButton key=\"al\" icon={AlignLeft} label=\"왼쪽 정렬\" active={editor.isActive({ textAlign: 'left' })} onClick={() => run((c) => c.setTextAlign('left'))} />\n ),\n show('align') && (\n <ToolbarButton key=\"ac\" icon={AlignCenter} label=\"가운데 정렬\" active={editor.isActive({ textAlign: 'center' })} onClick={() => run((c) => c.setTextAlign('center'))} />\n ),\n show('align') && (\n <ToolbarButton key=\"ar\" icon={AlignRight} label=\"오른쪽 정렬\" active={editor.isActive({ textAlign: 'right' })} onClick={() => run((c) => c.setTextAlign('right'))} />\n ),\n show('align') && (\n <ToolbarButton key=\"aj\" icon={AlignJustify} label=\"양쪽 정렬\" active={editor.isActive({ textAlign: 'justify' })} onClick={() => run((c) => c.setTextAlign('justify'))} />\n ),\n ],\n // 삽입\n [\n show('link') && (\n <Popover key=\"link\" placement=\"auto-left\" menuWidth={300} trigger={<DropTrigger icon={LinkIcon} ariaLabel=\"링크\" />}>\n {(close) => <LinkForm editor={editor} close={close} />}\n </Popover>\n ),\n show('table') && (\n <Popover key=\"table\" placement=\"auto-left\" menuWidth={140} trigger={<DropTrigger icon={TableIcon} ariaLabel=\"표\" />}>\n {(close) => (\n <PopoverMenu width={140}>\n <ListGroup>\n <List title=\"표 삽입 (3×3)\" onClick={() => { run((c) => c.insertTable({ rows: 3, cols: 3, withHeaderRow: true })); close(); }} />\n <List title=\"위에 행 추가\" disabled={!tableActive} onClick={() => { run((c) => c.addRowBefore()); close(); }} />\n <List title=\"아래에 행 추가\" disabled={!tableActive} onClick={() => { run((c) => c.addRowAfter()); close(); }} />\n <List title=\"행 삭제\" disabled={!tableActive} onClick={() => { run((c) => c.deleteRow()); close(); }} />\n <List title=\"왼쪽에 열 추가\" disabled={!tableActive} onClick={() => { run((c) => c.addColumnBefore()); close(); }} />\n <List title=\"오른쪽에 열 추가\" disabled={!tableActive} onClick={() => { run((c) => c.addColumnAfter()); close(); }} />\n <List title=\"열 삭제\" disabled={!tableActive} onClick={() => { run((c) => c.deleteColumn()); close(); }} />\n <List title=\"표 삭제\" disabled={!tableActive} onClick={() => { run((c) => c.deleteTable()); close(); }} />\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n ),\n show('image') && (\n <Popover key=\"image\" placement=\"auto-left\" menuWidth={300} trigger={<DropTrigger icon={ImageIcon} ariaLabel=\"이미지\" />}>\n {(close) => <ImageForm editor={editor} close={close} />}\n </Popover>\n ),\n show('hr') && (\n <ToolbarButton key=\"hr\" icon={Minus} label=\"구분선\" onClick={() => run((c) => c.setHorizontalRule())} />\n ),\n ],\n ]\n .map((g) => g.filter(Boolean))\n .filter((g) => g.length);\n\n return (\n <div className=\"flex min-w-0 flex-1 flex-wrap items-center gap-spacing-2\">\n {groups.map((g, i) => (\n <Fragment key={i}>\n {i > 0 && <Divider />}\n {g}\n </Fragment>\n ))}\n </div>\n );\n}\n","// SegmentControl — 세그먼트 컨트롤\n// 여러 선택지 중 하나를 고르는 ghost 기반 토글 버튼 묶음.\n// - SegmentControlButton : 세그먼트 컨트롤 버튼 1개(텍스트 / 아이콘 / 좌·우 아이콘+텍스트)\n// - SegmentControlGroup : 버튼들을 묶어 단일 선택을 관리하는 컨테이너\n//\n// Figma file: h9jZFkEHfcHUGok1TZjjlP (node 7365:1436)\n// 컬러는 기존 ghost 시멘틱 토큰 재사용(하드코딩 금지):\n// 기본(미선택) 텍스트 → font-icon-3(#878787)\n// 선택/hover 배경 → btn-ghost-hover-bg(#0d0d0d12)\n// 선택/hover 텍스트 → font-icon-5(#0d0d0d)\n// 비활성 텍스트 → font-icon-2(#c9c9c9)\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — ButtonGroup과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px (기본 — Figma 세그먼트 그룹 간격)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\ninterface SegmentControlButtonProps extends ComponentPropsWithoutRef<'button'> {\n size?: '32' | '24';\n selected?: boolean;\n leftIcon?: LucideIcon | null;\n rightIcon?: LucideIcon | null;\n icon?: LucideIcon | null; // 아이콘 전용(텍스트 없음)\n}\n\nexport function SegmentControlButton({\n children,\n size = '32', // '32' | '24'\n selected = false,\n disabled = false,\n leftIcon: LeftIcon = null,\n rightIcon: RightIcon = null,\n icon: Icon = null, // 아이콘 전용(텍스트 없음)\n onClick,\n className = '',\n ...props\n}: SegmentControlButtonProps) {\n const iconOnly = !!Icon;\n const iconSize = size === '24' ? 14 : 16;\n\n const base =\n 'inline-flex items-center justify-center relative font-pretendard font-normal ' +\n 'whitespace-nowrap rounded-round-4 transition-colors select-none focus:outline-none';\n\n // 사이즈·레이아웃 — Button과 동일한 패딩 규약(토큰만 사용)\n let sizeStyle;\n if (iconOnly) {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] p-spacing-2'\n : 'min-h-[32px] min-w-[32px] px-spacing-5 py-spacing-3';\n } else {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] px-spacing-5 py-spacing-2 text-12'\n : 'min-h-[32px] min-w-[32px] px-spacing-6 py-spacing-3 text-14';\n }\n\n // 컬러 — ghost 시멘틱 토큰 재사용\n let colorStyle;\n if (disabled) {\n colorStyle = 'bg-transparent text-font-icon-2 cursor-not-allowed';\n } else if (selected) {\n colorStyle = 'bg-button-ghost-hover-bg text-font-icon-5 cursor-pointer';\n } else {\n // 미선택 — 회색 텍스트, hover 시 ghost 배경 + 진한 텍스트(active=눌렀을 땐 배경 제거)\n colorStyle =\n 'bg-transparent text-font-icon-3 cursor-pointer ' +\n 'hover:bg-button-ghost-hover-bg hover:text-font-icon-5 active:bg-transparent ' +\n 'focus-visible:bg-button-ghost-hover-bg focus-visible:text-font-icon-5'; // 포커스=호버(2026-07-16)\n }\n\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n className={`${base} ${sizeStyle} ${colorStyle} ${className}`}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n {...props}\n >\n <span className=\"inline-flex items-center gap-spacing-3\">\n {iconOnly ? (\n <Icon size={iconSize} strokeWidth={1.8} />\n ) : (\n <>\n {LeftIcon && <LeftIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n {children}\n {RightIcon && <RightIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n </>\n )}\n </span>\n </button>\n );\n}\n\n// 그룹 항목 — [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\ninterface SegmentControlItem {\n value: string | number;\n label?: ReactNode;\n leftIcon?: LucideIcon;\n rightIcon?: LucideIcon;\n icon?: LucideIcon;\n disabled?: boolean;\n ariaLabel?: string;\n}\n\ninterface SegmentControlGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: SegmentControlItem[]; // [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\n value?: string | number; // controlled 선택값\n defaultValue?: string | number; // uncontrolled 초기 선택값\n onChange?: (value: string | number) => void; // (value) => void\n size?: '32' | '24'; // '32' | '24' — 모든 버튼에 적용\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '5'(8px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function SegmentControlGroup({\n items = [], // [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n size = '32', // '32' | '24' — 모든 버튼에 적용\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: SegmentControlGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = isControlled ? value : internal;\n\n const handleSelect = (val: string | number) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['5'];\n\n return (\n <div\n role=\"tablist\"\n className={`inline-flex items-center ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <SegmentControlButton\n key={item.value}\n size={size}\n selected={selected === item.value}\n disabled={disabled || item.disabled}\n leftIcon={item.leftIcon}\n rightIcon={item.rightIcon}\n icon={item.icon}\n aria-label={item.ariaLabel}\n onClick={() => handleSelect(item.value)}\n >\n {item.label}\n </SegmentControlButton>\n ))}\n </div>\n );\n}\n","// MergeField — Tiptap 인라인 atom 노드. 머지필드({지원자명} 등)를 편집기 안에서\n// 삭제 가능한 '칩'으로 렌더한다.\n//\n// NodeView는 plain DOM으로 구성한다(React 인라인-atom NodeView는 빈 렌더가 잦아 회피).\n// 시각은 chip-* 시멘틱 토큰(DS Chip과 동일 클래스)으로 맞춘다 — 규칙 1(토큰 경유) 유지.\n//\n// 데이터 단위(HTML)에는 <span data-merge-field=\"{값}\">{값}</span> 로 직렬화된다(renderHTML):\n// - 재오픈(setContent) 시 parseHTML이 이 span을 다시 mergeField 노드(칩)로 복원\n// - 안에 원문 {값} 텍스트가 남아 백엔드 머지 치환 로직도 그대로 동작\n// - preview 모드(raw span 렌더)는 index.css의 [data-merge-field] 규칙으로 칩 모양을 받는다\n// ※ NodeView의 DOM에는 data-merge-field를 두지 않는다(edit 모드에서 CSS 칩 스타일 이중 적용 방지 —\n// 직렬화용 data 속성은 renderHTML이 담당).\n// atom 이라 편집기 안에서 한 덩어리로 선택·백스페이스 삭제되고, 칩의 X 버튼으로도 삭제된다.\nimport { Node, mergeAttributes } from '@tiptap/core';\n\n// 머지필드 칩은 blue color 고정.\nconst CHIP_CLASS =\n 'inline-flex items-center gap-spacing-3 rounded-round-4 border border-chip-blue-default-line ' +\n 'bg-chip-blue-default-bg pl-spacing-4 pr-spacing-3 py-spacing-1 align-middle font-pretendard ' +\n 'text-12 text-chip-blue-default-text transition-colors hover:border-chip-blue-hover-line hover:bg-chip-blue-hover-bg';\n\n// lucide X (size 12) — Chip의 삭제 아이콘과 동일 모양.\nconst X_SVG =\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" ' +\n 'stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">' +\n '<path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>';\n\nexport const MergeFieldNode = Node.create({\n name: 'mergeField',\n group: 'inline',\n inline: true,\n atom: true,\n selectable: true,\n\n addAttributes() {\n return {\n value: {\n default: '',\n parseHTML: (el) => el.getAttribute('data-merge-field') || el.textContent || '',\n renderHTML: (attrs) => ({ 'data-merge-field': attrs.value }),\n },\n };\n },\n\n parseHTML() {\n return [{ tag: 'span[data-merge-field]' }];\n },\n\n // 직렬화(getHTML): <span data-merge-field=\"{값}\">{값}</span> (원문 텍스트 포함)\n renderHTML({ HTMLAttributes, node }) {\n return ['span', mergeAttributes(HTMLAttributes), node.attrs.value];\n },\n\n addNodeView() {\n return ({ node, editor, getPos }) => {\n const dom = document.createElement('span');\n dom.className = CHIP_CLASS;\n dom.contentEditable = 'false';\n\n const label = document.createElement('span');\n label.className = 'whitespace-nowrap';\n label.textContent = node.attrs.value;\n dom.appendChild(label);\n\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'flex shrink-0 items-center justify-center'; // 색은 부모(text-chip-gray-default-text) 상속\n btn.setAttribute('aria-label', '머지필드 삭제');\n btn.innerHTML = X_SVG;\n // mousedown 기본동작(포커스 이동/셀렉션 변경) 차단 후 click으로 노드 삭제.\n btn.addEventListener('mousedown', (e) => e.preventDefault());\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n e.stopPropagation();\n if (typeof getPos !== 'function') return;\n const pos = getPos();\n if (pos == null) return;\n editor.chain().focus().deleteRange({ from: pos, to: pos + node.nodeSize }).run();\n });\n dom.appendChild(btn);\n\n return { dom };\n };\n },\n});\n","// Editor — Tiptap(ProseMirror) 기반 리치 텍스트 에디터 (완전 옵션형)\n// 이메일 템플릿·공고 상세·약관·문자/메일 본문 작성과, 개발자·운영자의 HTML 구조 확인을 함께 대응한다.\n// 엔진(Tiptap)은 이 파일 내부에만 격리하고, 외부에는 value/onChange·mode·toolbar 등 엔진 독립 props만 노출한다.\n//\n// - value / defaultValue / onChange(html): 데이터 단위는 HTML 문자열 하나. controlled(value) 또는 uncontrolled(defaultValue).\n// - mode / onModeChange: 'edit'(WYSIWYG) | 'source'(HTML 코드) | 'preview'(렌더 결과). controlled/내부 상태.\n// - toolbar: 노출할 서식 기능 키 배열(미지정 시 전체). EditorToolbar의 TOOLBAR_ITEMS 키 참고.\n// - readOnly: 편집 비활성(툴바 숨김, 본문 read-only). 소스/미리보기 모드 전환은 유지(HTML 확인용).\n// - allowSourceEdit: 소스 모드에서 HTML 직접 편집 허용(false면 읽기 전용 보기).\n// - placeholder / minHeight / maxHeight: 빈 본문 안내 · 본문 높이(maxHeight 시 ScrollArea 세로 스크롤).\n// - width: 'fill'(부모 전체 폭, 기본) | 숫자(px) | CSS 길이 문자열.\n// 색·간격·보더는 editor-*/table-*/spacing-*/round-* 토큰만 사용(하드코딩 금지).\nimport { useEffect, useLayoutEffect, useReducer, useRef, useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { useEditor, EditorContent } from '@tiptap/react';\nimport { StarterKit } from '@tiptap/starter-kit';\nimport { TextStyle } from '@tiptap/extension-text-style';\nimport { Color } from '@tiptap/extension-color';\nimport { Highlight } from '@tiptap/extension-highlight';\nimport { TextAlign } from '@tiptap/extension-text-align';\nimport { Image } from '@tiptap/extension-image';\nimport { Table, TableRow, TableHeader, TableCell } from '@tiptap/extension-table';\nimport { ScrollArea } from './ScrollArea';\nimport { EditorToolbar } from './EditorToolbar';\nimport { SegmentControlGroup } from './SegmentControl';\nimport { MergeFieldNode } from './MergeFieldNode';\n\n// 모드 값 — 'edit'(WYSIWYG) | 'source'(HTML 코드) | 'preview'(렌더 결과)\ntype EditorMode = 'edit' | 'source' | 'preview';\n\nconst MODE_TABS: { value: EditorMode; label: string }[] = [\n { value: 'edit', label: '편집' },\n { value: 'source', label: 'HTML' },\n { value: 'preview', label: '미리보기' },\n];\n\n// Tiptap 확장 구성 — StarterKit에 link/underline가 이미 포함되어 있어 별도 등록하지 않는다(중복 방지).\n// Color는 TextStyle에 의존하므로 TextStyle을 먼저 둔다.\nfunction buildExtensions() {\n return [\n StarterKit.configure({\n link: { openOnClick: false, HTMLAttributes: { rel: 'noopener noreferrer nofollow' } },\n }),\n TextStyle,\n Color,\n Highlight.configure({ multicolor: true }),\n TextAlign.configure({ types: ['heading', 'paragraph'] }),\n Image,\n Table.configure({ resizable: false }),\n TableRow,\n TableHeader,\n TableCell,\n MergeFieldNode, // 머지필드를 삭제 가능한 인라인 칩으로 렌더(<span data-merge-field>로 직렬화)\n ];\n}\n\n// 모드 탭(편집/HTML/미리보기) — 공용 세그먼트 컨트롤(size 24).\n// 읽기 전용이면 편집 탭을 빼고 HTML·미리보기만 노출한다.\n// showSource=false면 HTML(소스) 탭을 숨긴다(예: SMS 평문 안내문).\nfunction ModeTabs({\n mode,\n onChange,\n readOnly,\n showSource,\n}: {\n mode: EditorMode;\n onChange: (mode: EditorMode) => void;\n readOnly: boolean;\n showSource: boolean;\n}) {\n let items = readOnly ? MODE_TABS.filter((t) => t.value !== 'edit') : MODE_TABS;\n if (!showSource) items = items.filter((t) => t.value !== 'source');\n return (\n <SegmentControlGroup\n size=\"24\"\n gap=\"4\"\n value={mode}\n onChange={(v) => onChange?.(v as EditorMode)} /* items가 EditorMode 값만 담으므로 안전한 좁힘 */\n items={items}\n />\n );\n}\n\ninterface EditorProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'placeholder'> {\n value?: string; // controlled HTML 값\n defaultValue?: string; // uncontrolled 초기 HTML\n onChange?: (html: string) => void;\n mode?: EditorMode; // controlled 모드\n onModeChange?: (mode: EditorMode) => void;\n toolbar?: string[]; // 노출할 서식 기능 키 배열(미지정 시 전체) — EditorToolbar TOOLBAR_ITEMS 키\n mergeFields?: (string | { label: string; value: string })[]; // 머지필드 목록: 문자열 또는 {label,value}[] — 툴바 '머지필드' 드롭다운(search list)\n readOnly?: boolean;\n showSource?: boolean; // HTML(소스) 모드 탭 노출 여부(false면 편집/미리보기만)\n placeholder?: ReactNode;\n minHeight?: number | string;\n maxHeight?: number | string;\n width?: number | string; // 'fill'(부모 전체 폭) | 숫자(px) | CSS 길이 문자열('480px','60%' 등)\n allowSourceEdit?: boolean; // 소스 모드에서 HTML 직접 편집 허용(false면 읽기 전용 보기)\n}\n\nexport function Editor({\n value,\n defaultValue = '',\n onChange,\n mode: modeProp,\n onModeChange,\n toolbar,\n mergeFields = [],\n readOnly = false,\n showSource = true,\n placeholder = '내용을 입력하세요.',\n minHeight = 240,\n maxHeight,\n width = 'fill',\n allowSourceEdit = true,\n className = '',\n ...props\n}: EditorProps) {\n const valueControlled = value !== undefined;\n const modeControlled = modeProp !== undefined;\n // 읽기 전용이면 편집 모드가 없으므로 미리보기를 기본으로 한다.\n const [internalMode, setInternalMode] = useState<EditorMode>(() => (readOnly ? 'preview' : 'edit'));\n const mode = modeControlled ? modeProp : internalMode;\n // controlled value 교체(setContent) 후 placeholder(isEmpty) 조건을 다시 평가하기 위한 강제 재렌더 트리거.\n const [, bumpRender] = useReducer((x) => x + 1, 0);\n\n // 소스 모드에서 편집 중인 HTML 초안(편집 모드와 분리해 커서 튐 방지, 모드 이탈 시 에디터에 반영)\n const [sourceDraft, setSourceDraft] = useState('');\n const sourceRef = useRef<HTMLTextAreaElement | null>(null);\n\n // onChange 최신값을 ref로 유지(editor 'update' 핸들러의 stale 클로저 방지)\n const onChangeRef = useRef(onChange);\n useEffect(() => {\n onChangeRef.current = onChange;\n }, [onChange]);\n\n const editor = useEditor({\n editable: !readOnly,\n content: valueControlled ? value : defaultValue,\n extensions: buildExtensions(),\n // 첫 렌더에서 즉시 뷰를 만들지 않는다(effect에서 생성). React StrictMode가 dev에서\n // 마운트를 2번 돌릴 때 뷰가 파괴/재생성되며 throw하는 것을 막는다(Tiptap React 권장값).\n immediatelyRender: false,\n // Tiptap v3는 기본이 false(트랜잭션이 React 재렌더를 안 일으킴) — 이 컴포넌트는 툴바\n // isActive/undo 상태·placeholder(isEmpty)를 렌더에서 직접 읽으므로 v2 방식으로 되돌린다.\n // 끄면 uncontrolled에서 placeholder가 입력 텍스트에 겹치고, 커서만 옮기면 툴바가 이전\n // 상태로 굳는다(2026-07-07 감사).\n shouldRerenderOnTransaction: true,\n });\n\n // 본문 변경 → onChange(html) 방출\n useEffect(() => {\n if (!editor) return;\n const handler = () => onChangeRef.current?.(editor.getHTML());\n editor.on('update', handler);\n return () => {\n editor.off('update', handler); // off는 체이닝용 Editor를 반환 — cleanup은 void여야 해 블록으로\n };\n }, [editor]);\n\n // readOnly 토글 반영\n useEffect(() => {\n editor?.setEditable(!readOnly);\n }, [editor, readOnly]);\n\n // 읽기 전용으로 바뀌면 편집 모드(탭이 사라짐)에 머물지 않고 미리보기로 이동(uncontrolled).\n useEffect(() => {\n if (readOnly && !modeControlled && internalMode === 'edit') {\n // readOnly 전환 시 편집 모드 → 미리보기로 1회 이동 — 의도된 effect 내 setState\n setInternalMode('preview'); // eslint-disable-line react-hooks/set-state-in-effect\n }\n }, [readOnly, modeControlled, internalMode]);\n\n // HTML 탭이 숨겨졌는데(showSource=false) 소스 모드에 머물면 편집으로 복귀(탭 전환 등으로 갇힘 방지).\n useEffect(() => {\n if (!showSource && !modeControlled && internalMode === 'source') {\n setInternalMode('edit'); // eslint-disable-line react-hooks/set-state-in-effect\n }\n }, [showSource, modeControlled, internalMode]);\n\n // controlled value 외부 변경 동기화(편집 결과로 같은 값이 돌아오면 no-op)\n // useLayoutEffect + bumpRender: 콘텐츠 교체 직후(페인트 전) 재렌더해 placeholder(isEmpty)를 다시 평가한다.\n // (안 하면 채널 전환 등 value가 바뀔 때 stale한 isEmpty로 그려진 placeholder가 새 본문과 겹쳐 보인다.)\n useLayoutEffect(() => {\n if (!editor || !valueControlled) return;\n if (value !== editor.getHTML()) {\n editor.commands.setContent(value, { emitUpdate: false });\n // 소스 모드 열람 중 외부에서 value가 바뀌면(템플릿 불러오기 등) 초안도 동기화 —\n // 안 하면 모드 이탈 시 stale 초안이 외부 변경분을 덮어쓴다(2026-07-07 감사)\n if (mode === 'source') {\n setSourceDraft((d) => (d === value ? d : value)); // eslint-disable-line react-hooks/set-state-in-effect\n }\n bumpRender();\n }\n }, [editor, value, valueControlled, mode]);\n\n const changeMode = (next: EditorMode) => {\n if (next === mode) return;\n if (editor) {\n // 소스 모드를 떠날 때: 초안 HTML을 에디터에 반영하고 onChange 방출\n if (mode === 'source') {\n editor.commands.setContent(sourceDraft, { emitUpdate: false });\n onChangeRef.current?.(editor.getHTML());\n }\n // 소스 모드로 들어갈 때: 현재 에디터 HTML로 초안 시드\n if (next === 'source') {\n setSourceDraft(editor.getHTML());\n }\n }\n if (!modeControlled) setInternalMode(next);\n onModeChange?.(next);\n };\n\n const handleSourceChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n const v = e.target.value;\n setSourceDraft(v);\n onChangeRef.current?.(v);\n };\n\n // 소스 textarea를 내용 높이에 맞춰 늘린다 — 자체(네이티브) 스크롤 대신 ScrollArea 오버레이 스크롤로 위임(규칙 6).\n useLayoutEffect(() => {\n if (mode !== 'source') return;\n const el = sourceRef.current;\n if (!el) return;\n el.style.height = 'auto';\n el.style.height = `${el.scrollHeight}px`;\n }, [mode, sourceDraft]);\n\n const sourceEditable = allowSourceEdit && !readOnly;\n // 전체 문서 직렬화(getHTML)는 미리보기 모드에서만 — edit 모드 타이핑마다 직렬화하지 않는다(2026-07-07 감사)\n const previewHtml = mode === 'preview' && editor ? editor.getHTML() : '';\n const bodyMinHeight = typeof minHeight === 'number' ? `${minHeight}px` : minHeight;\n const widthStyle = width === 'fill' ? '100%' : typeof width === 'number' ? `${width}px` : width;\n\n // 본문 영역 — maxHeight가 있으면 ScrollArea로 감싼다(세로 오버레이 스크롤바, 규칙 6).\n const wrapScroll = (node: ReactNode) =>\n maxHeight != null ? <ScrollArea maxHeight={maxHeight}>{node}</ScrollArea> : node;\n\n let body: ReactNode;\n if (mode === 'source') {\n body = (\n <textarea\n ref={sourceRef}\n value={sourceDraft}\n onChange={handleSourceChange}\n readOnly={!sourceEditable}\n spellCheck={false}\n className=\"block w-full resize-none overflow-hidden bg-editor-source-bg px-spacing-7 py-spacing-6 font-mono text-13 text-editor-source-text outline-none\"\n style={{ minHeight: bodyMinHeight }}\n />\n );\n // 소스 영역은 어두운 배경이라 흰색(light) 스크롤바 변형을 쓴다.\n body = maxHeight != null ? <ScrollArea maxHeight={maxHeight} variant=\"light\">{body}</ScrollArea> : body;\n } else if (mode === 'preview') {\n body = wrapScroll(\n <div\n className=\"tiptap-prose px-spacing-7 py-spacing-6\"\n style={{ minHeight: bodyMinHeight }}\n dangerouslySetInnerHTML={{ __html: previewHtml }}\n />,\n );\n } else {\n // edit\n body = wrapScroll(\n <div\n className=\"relative tiptap-prose cursor-text px-spacing-7 py-spacing-6\"\n style={{ minHeight: bodyMinHeight }}\n onMouseDown={(e) => {\n // 콘텐츠(.tiptap) 밖(패딩·첫 줄 아래 빈 영역)을 클릭하면 에디터 끝에 포커스한다 —\n // 첫 줄까지 가지 않아도 박스 전체가 클릭 영역이 되도록.\n if (editor && !(e.target as HTMLElement).closest('.tiptap')) {\n e.preventDefault();\n editor.commands.focus('end');\n }\n }}\n >\n {editor?.isEmpty && (\n <p className=\"pointer-events-none absolute left-spacing-7 top-spacing-6 text-14 leading-30 text-editor-placeholder\">\n {placeholder}\n </p>\n )}\n <EditorContent editor={editor} />\n </div>,\n );\n }\n\n return (\n <div className={`overflow-hidden rounded-round-4 border border-editor-outline bg-white ${className}`} style={{ width: widthStyle }} {...props}>\n {/* 헤더: (편집 모드·편집 가능 시) 서식 툴바 + 모드 탭 */}\n <div className=\"border-b border-editor-divider bg-editor-toolbar-bg\">\n <div className=\"flex items-center justify-between gap-spacing-5 px-spacing-5 py-spacing-3\">\n {mode === 'edit' && !readOnly ? (\n <EditorToolbar editor={editor} toolbar={toolbar} mergeFields={mergeFields} />\n ) : (\n <span className=\"text-12 text-font-icon-5\">\n {mode === 'source' ? 'HTML 소스' : mode === 'preview' ? '미리보기' : ''}\n </span>\n )}\n <div className=\"shrink-0\">\n <ModeTabs mode={mode} onChange={changeMode} readOnly={readOnly} showSource={showSource} />\n </div>\n </div>\n </div>\n\n {body}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tabs-C_N6MDdv.js","names":[],"sources":["../src/components/Slot.tsx","../src/components/Tooltip.tsx","../src/components/TruncatingText.tsx","../src/components/useHoverTooltip.tsx","../src/components/Button.tsx","../src/components/Input.tsx","../src/components/ScrollArea.tsx","../src/components/chipStyles.ts","../src/components/InlineFieldTrigger.tsx","../src/components/SearchBar.tsx","../src/components/Checkbox.tsx","../src/components/PopoverMenu.tsx","../src/components/ListEmpty.tsx","../src/components/ListGroup.tsx","../src/components/Tag.tsx","../src/components/Radio.tsx","../src/components/Switch.tsx","../src/components/List.tsx","../src/components/usePopoverPosition.ts","../src/components/useOutsideDismiss.ts","../src/components/popoverLayers.ts","../src/components/Select.tsx","../src/components/ButtonGroup.tsx","../src/components/Popover.tsx","../src/components/FileUploadMenu.tsx","../src/components/FileUploadButton.tsx","../src/components/SegmentControl.tsx","../src/components/Tabs.tsx"],"sourcesContent":["// Slot — asChild 합성 유틸(2026-07-20, Radix Slot 패턴 참고).\n// 단일 자식 엘리먼트에 props를 '머지'해 래퍼 없이 그 엘리먼트 자체로 렌더한다.\n// - className : 이어붙이기(자식 것 + 우리 것)\n// - style : 병합(우리 것이 우선)\n// - on* 핸들러: 합성(자식 핸들러 먼저, 우리 것 나중 — 둘 다 실행)\n// - ref : 병합(자식 ref + 우리 ref 둘 다 채움)\n// - content : 지정 시 자식의 내부 내용을 대체(미지정이면 자식 원래 내용 유지)\n// 용도: Button asChild 등 — \"버튼처럼 보이는 <a>\"를 유효하지 않은 중첩 없이 렌더.\nimport { cloneElement, isValidElement } from 'react';\nimport type { ReactElement, ReactNode, Ref } from 'react';\n\ntype AnyProps = Record<string, unknown>;\n\nfunction composeHandlers(childHandler: unknown, slotHandler: unknown) {\n return (event: unknown) => {\n (childHandler as ((e: unknown) => void) | undefined)?.(event);\n (slotHandler as ((e: unknown) => void) | undefined)?.(event);\n };\n}\n\nfunction mergeRefs(...refs: (Ref<unknown> | undefined)[]) {\n return (node: unknown) => {\n for (const ref of refs) {\n if (!ref) continue;\n if (typeof ref === 'function') ref(node);\n else (ref as { current: unknown }).current = node;\n }\n };\n}\n\nfunction mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {\n const merged: AnyProps = { ...childProps };\n for (const key in slotProps) {\n const slotValue = slotProps[key];\n const childValue = childProps[key];\n if (/^on[A-Z]/.test(key)) {\n // 이벤트 핸들러 — 양쪽 다 있으면 합성, 아니면 있는 쪽\n merged[key] = childValue ? composeHandlers(childValue, slotValue) : slotValue;\n } else if (key === 'className') {\n merged[key] = [childValue, slotValue].filter(Boolean).join(' ');\n } else if (key === 'style') {\n merged[key] = { ...(childValue as object), ...(slotValue as object) };\n } else {\n // 그 외는 우리 값 우선(undefined면 자식 값 유지)\n merged[key] = slotValue !== undefined ? slotValue : childValue;\n }\n }\n return merged;\n}\n\nexport interface SlotProps {\n /** 합성 대상 — 반드시 유효한 단일 엘리먼트 하나 */\n children: ReactNode;\n /** 자식의 내부 내용을 대체할 새 콘텐츠(미지정 시 자식 원래 내용 유지) */\n content?: ReactNode;\n /** 병합할 ref(우리 쪽) */\n ref?: Ref<unknown>;\n /** 그 외 머지할 props(className·style·on*·기타 속성) */\n [prop: string]: unknown;\n}\n\nexport function Slot({ children, content, ref, ...slotProps }: SlotProps) {\n if (!isValidElement(children)) {\n // 오용(자식이 엘리먼트가 아니거나 여러 개) — 조용히 무시하지 않고 개발 중 알림\n console.warn('Slot(asChild): 유효한 단일 엘리먼트 자식 하나가 필요합니다.');\n return null;\n }\n const child = children as ReactElement<AnyProps & { children?: ReactNode }>;\n const childRef = (child as { ref?: Ref<unknown> }).ref;\n const merged = mergeProps(slotProps, child.props);\n if (content !== undefined) merged.children = content;\n if (ref || childRef) merged.ref = mergeRefs(ref, childRef);\n return cloneElement(child, merged);\n}\n","// Tooltip — 오버레이 말풍선\n// Figma 02_textfield의 툴팁(error / normal 타입)을 컴포넌트화.\n// 위치 지정은 호출부에서 처리하고, 이 컴포넌트는 \"말풍선 박스 + 꼬리(beak)\"만 그린다.\n// - error: 빨강 배경 + beak (필수 입력 등)\n// - normal: 검정 배경, beak 없이 사용 (말줄임 전체 텍스트 등)\nimport type { ReactNode } from 'react';\nimport { textFieldColors } from '../tokens';\n\n// variant별 색상 — 시멘틱 토큰값을 인라인으로 적용\n// (Tailwind 클래스 'tf-normal-tooltip-*'의 -bg/-text 모호성 회피)\nconst VARIANT_COLOR = {\n error: { bg: textFieldColors['error-tooltip-bg'], fg: textFieldColors['error-tooltip-text'] },\n normal: { bg: textFieldColors['normal-tooltip-bg'], fg: textFieldColors['normal-tooltip-text'] },\n};\n\n// 꼬리(beak) 위치 — 'top' = 말풍선이 대상 아래, 꼬리가 위를 가리킴\nconst BEAK_STYLE = {\n top: '-top-spacing-2 left-spacing-5',\n bottom: '-bottom-spacing-2 left-spacing-5',\n};\n\ninterface TooltipProps {\n children?: ReactNode;\n variant?: keyof typeof VARIANT_COLOR; // 'error' | 'normal'\n beak?: keyof typeof BEAK_STYLE | 'none'; // 'top' | 'bottom' | 'none'\n className?: string;\n}\n\nexport function Tooltip({\n children,\n variant = 'error', // 'error' | 'normal'\n beak = 'top', // 'top' | 'bottom' | 'none'\n className = '',\n}: TooltipProps) {\n const c = VARIANT_COLOR[variant] ?? VARIANT_COLOR.error;\n\n return (\n <div\n role=\"tooltip\"\n style={{ backgroundColor: c.bg, color: c.fg }}\n className={`relative inline-flex items-center rounded-round-4 px-spacing-5 py-spacing-2 drop-shadow-[0px_2px_2px_rgba(13,13,13,0.12)] ${className}`}\n >\n {beak !== 'none' && (\n <span\n aria-hidden=\"true\"\n style={{ backgroundColor: c.bg }}\n className={`absolute h-spacing-5 w-spacing-5 rotate-45 rounded-[1px] ${BEAK_STYLE[beak] ?? BEAK_STYLE.top}`}\n />\n )}\n <p className=\"relative whitespace-nowrap text-12\">{children}</p>\n </div>\n );\n}\n","// TruncatingText — 말줄임(truncate) 텍스트 + hover 툴팁\n// 영역이 좁아 텍스트가 잘릴 수 있는 곳에 사용한다. 실제로 잘렸을 때만(scrollWidth>clientWidth)\n// hover 시 normal Tooltip으로 전체 텍스트를 보여준다.\n// 툴팁은 portal(document.body) + fixed로 overflow를 벗어나며, viewport 경계에서\n// 위→아래·오른쪽→왼쪽으로 자동 반전한다.\nimport { useRef, useState, useLayoutEffect } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Tooltip } from './Tooltip';\n\ninterface TruncatingTextProps extends ComponentPropsWithoutRef<'p'> {\n as?: ElementType; // 'p' | 'span' | 'div' 등\n}\n\nexport function TruncatingText({\n children,\n as: Component = 'p', // 'p' | 'span' | 'div' 등\n className = '',\n ...props\n}: TruncatingTextProps) {\n const ref = useRef<HTMLElement | null>(null);\n const tipRef = useRef<HTMLDivElement | null>(null);\n const [tipRect, setTipRect] = useState<DOMRect | null>(null);\n const [tipPos, setTipPos] = useState<{ top: number; left: number } | null>(null);\n\n const onEnter = () => {\n const el = ref.current;\n if (el && el.scrollWidth > el.clientWidth) setTipRect(el.getBoundingClientRect());\n };\n const onLeave = () => {\n setTipRect(null);\n setTipPos(null);\n };\n\n // 툴팁 크기를 재서 viewport를 벗어나면 위→아래, 오른쪽→왼쪽으로 반전\n useLayoutEffect(() => {\n if (!tipRect || !tipRef.current) return;\n const tw = tipRef.current.offsetWidth;\n const th = tipRef.current.offsetHeight;\n const gap = 6;\n const margin = 4;\n let top = tipRect.top - gap - th;\n if (top < margin) top = tipRect.bottom + gap;\n let left = tipRect.left;\n if (left + tw > window.innerWidth - margin) left = window.innerWidth - tw - margin;\n if (left < margin) left = margin;\n setTipPos({ top, left });\n }, [tipRect]);\n\n return (\n <>\n <Component\n ref={ref}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n className={`truncate ${className}`}\n {...props}\n >\n {children}\n </Component>\n {/* 툴팁은 형제 + portal로 분리해 텍스트 요소 truncate 레이아웃에 영향 주지 않음 */}\n {tipRect &&\n createPortal(\n <div\n ref={tipRef}\n className=\"pointer-events-none fixed z-[1000]\"\n style={{\n left: tipPos ? tipPos.left : tipRect.left,\n top: tipPos ? tipPos.top : tipRect.top,\n visibility: tipPos ? 'visible' : 'hidden',\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {children}\n </Tooltip>\n </div>,\n document.body,\n )}\n </>\n );\n}\n","// useHoverTooltip — 라벨이 안 보이는 컨트롤(아이콘 전용 버튼 등)에 hover 시 명칭을 알려주는 툴팁.\n// TruncatingText와 같은 패턴: normal Tooltip을 portal(document.body) + fixed로 띄워 부모 overflow를\n// 벗어나고, viewport 경계에서 아래→위·좌우로 자동 반전한다. 대상 요소는 반환한 onMouseEnter/Leave로 감지.\n// - label: 표시할 문구(없으면 비활성 — 툴팁 안 뜸)\n// - delay: 아이콘이 늘어선 툴바에서 커서 이동 시 번쩍임 방지용 표시 지연(ms, 기본 300)\n// 사용: const t = useHoverTooltip(label); <button onMouseEnter={t.onMouseEnter} .../>{t.tooltip}\nimport { useRef, useState, useLayoutEffect, useEffect, useCallback } from 'react';\nimport type { ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Tooltip } from './Tooltip';\n\nexport function useHoverTooltip(label: ReactNode, { delay = 300 }: { delay?: number } = {}) {\n const [rect, setRect] = useState<DOMRect | null>(null);\n const [pos, setPos] = useState<{ top: number; left: number } | null>(null);\n const tipRef = useRef<HTMLDivElement | null>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const enabled = !!label;\n\n // 표시 지연 타이머 — 언마운트/leave 시 정리(잔여 setState 방지)\n useEffect(() => () => clearTimeout(timerRef.current), []);\n\n const onMouseEnter = useCallback(\n // 실제 MouseEvent뿐 아니라 {currentTarget}만 담은 합성 호출도 허용(FormulaAddZone의 툴팁 재표시 등)\n (e: { currentTarget: HTMLElement }) => {\n if (!enabled) return;\n const el = e.currentTarget;\n clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => setRect(el.getBoundingClientRect()), delay);\n },\n [enabled, delay],\n );\n\n const onMouseLeave = useCallback(() => {\n clearTimeout(timerRef.current);\n setRect(null);\n setPos(null);\n }, []);\n\n // 툴팁 크기를 재서 아래 공간이 없으면 위로, 좌우 경계는 안으로 밀어 넣는다.\n useLayoutEffect(() => {\n if (!rect || !tipRef.current) return;\n const tw = tipRef.current.offsetWidth;\n const th = tipRef.current.offsetHeight;\n const gap = 6;\n const margin = 4;\n let top = rect.bottom + gap;\n if (top + th > window.innerHeight - margin) top = rect.top - gap - th;\n let left = rect.left + rect.width / 2 - tw / 2; // 대상 중앙 정렬\n if (left + tw > window.innerWidth - margin) left = window.innerWidth - tw - margin;\n if (left < margin) left = margin;\n setPos({ top, left });\n }, [rect]);\n\n const tooltip = rect\n ? createPortal(\n <div\n ref={tipRef}\n className=\"pointer-events-none fixed z-[1000]\"\n style={{\n left: pos ? pos.left : rect.left,\n top: pos ? pos.top : rect.bottom,\n visibility: pos ? 'visible' : 'hidden', // 위치 계산 전 첫 프레임 숨김(점프 방지)\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {label}\n </Tooltip>\n </div>,\n document.body,\n )\n : null;\n\n return { onMouseEnter, onMouseLeave, tooltip };\n}\n","import { useLayoutEffect, useRef, isValidElement } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType, MouseEvent as ReactMouseEvent, ReactElement, ReactNode, Ref } from 'react';\nimport { LoaderCircle } from 'lucide-react';\nimport { Slot } from './Slot';\nimport { TruncatingText } from './TruncatingText';\nimport { useHoverTooltip } from './useHoverTooltip';\n\ninterface ButtonProps extends ComponentPropsWithoutRef<'button'> {\n size?: '32' | '24' | '18'; // '18' = 아이콘 전용 소형 — 버튼 18×18·아이콘 14×14\n variant?: 'fill' | 'line' | 'ghost' | 'underline'; // 'underline' = 밑줄 텍스트 버튼\n color?: 'black' | 'red' | 'blue' | 'green' | 'violet' | 'pink' | 'orange'; // underline 전용 색 — 'black'(기본)\n weight?: 'normal' | 'semibold'; // underline 전용 두께 — 'normal'(기본)\n leftIcon?: ElementType | null; // lucide 외 커스텀 아이콘 컴포넌트도 허용\n rightIcon?: ElementType | null;\n icon?: ElementType | null;\n loading?: boolean;\n truncate?: boolean; // 라벨이 부모 폭을 넘으면 말줄임(테이블 셀 등 좁은 영역용)\n width?: 'hug' | 'fill'; // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭) — underline 변형엔 미적용\n showTooltip?: boolean; // 아이콘 전용 버튼 hover 명칭 툴팁 on/off (기본 켬)\n tooltip?: ReactNode; // 툴팁 문구 커스텀. 미지정 시 aria-label 사용\n asChild?: boolean; // true면 <button> 대신 자식 엘리먼트(단일)를 루트로 렌더하고\n // 버튼 스타일·동작을 그 엘리먼트에 머지(예: 링크를 버튼처럼 — Slot).\n // 이때 children은 유효한 단일 엘리먼트여야 하며, 그 내용이 라벨이 된다.\n}\n\nexport function Button({\n children,\n size = '32', // '32' | '24' | '18'(아이콘 전용 소형 — 버튼 18×18·아이콘 14×14)\n variant = 'fill', // 'fill' | 'line' | 'ghost' | 'underline'(밑줄 텍스트 버튼)\n color = 'black', // underline 전용 색 — 'black'(기본) | 'red' | 'blue' | 'green' | 'violet' | 'pink' | 'orange'\n weight = 'normal', // underline 전용 두께 — 'normal'(기본) | 'semibold'\n leftIcon: LeftIcon = null,\n rightIcon: RightIcon = null,\n icon: Icon = null,\n disabled = false,\n loading = false,\n truncate = false, // 라벨이 부모 폭을 넘으면 말줄임(테이블 셀 등 좁은 영역용)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭) — underline 변형엔 미적용\n showTooltip = true, // 아이콘 전용 버튼 hover 명칭 툴팁 on/off (기본 켬)\n tooltip, // 툴팁 문구 커스텀. 미지정 시 aria-label 사용\n asChild = false, // <button> 대신 자식 엘리먼트를 루트로(Slot 합성)\n onClick,\n onMouseEnter,\n onMouseLeave,\n className = '',\n ...props\n}: ButtonProps) {\n const inactive = disabled || loading;\n const iconOnly = !!Icon;\n // 아이콘만 있는 버튼은 명칭이 안 보이므로 hover 툴팁으로 알려준다(showTooltip으로 on/off,\n // 문구=tooltip prop ?? aria-label).\n const tipLabel = iconOnly && showTooltip ? (tooltip ?? props['aria-label']) : null;\n const hoverTip = useHoverTooltip(tipLabel);\n const iconSize = size === '18' || size === '24' ? 14 : 16;\n // fill: 부모 폭을 100% 채운다(밑줄 텍스트 버튼은 박스가 없어 제외).\n const isFill = width === 'fill' && variant !== 'underline';\n const ref = useRef<HTMLElement | null>(null);\n // asChild 유효성 — 자식이 단일 엘리먼트일 때만 Slot 합성(아니면 일반 <button> 폴백)\n const slotChild = asChild && isValidElement(children) ? (children as ReactElement<{ children?: ReactNode }>) : null;\n // 라벨 소스 — Slot이면 자식 엘리먼트의 내용, 아니면 children prop\n const label = slotChild ? slotChild.props.children : children;\n\n useLayoutEffect(() => {\n const el = ref.current;\n // truncate(셀 폭 추종)·fill(부모 폭 100%)이면 콘텐츠 기준 너비 고정을 하지 않는다.\n if (!el || truncate || isFill) {\n if (el && isFill) el.style.width = '';\n return;\n }\n el.style.width = '';\n el.style.width = el.offsetWidth + 'px';\n }, [children, size, variant, LeftIcon, RightIcon, Icon, disabled, loading, truncate, isFill]);\n\n // 텍스트 두께 — underline 변형만 semibold 옵션 지원(2026-07-15), 그 외는 400 고정\n const weightClass =\n variant === 'underline' && weight === 'semibold' ? 'font-semibold' : 'font-normal';\n // 포커스 디자인 = 호버 디자인(2026-07-16 지시) — 기본 아웃라인을 끄고, 변형별로\n // focus-visible(키보드 포커스)에 hover와 같은 효과를 준다(마우스 클릭에는 표시 없음).\n const base =\n `inline-flex items-center justify-center relative font-pretendard ${weightClass} ` +\n 'whitespace-nowrap rounded-round-4 transition-colors select-none focus:outline-none';\n // truncate: 버튼이 부모 폭 안에서 줄어들고(min-w-0/max-w-full) 라벨이 말줄임되게 한다.\n const truncStyle = truncate ? 'min-w-0 max-w-full' : '';\n // fill: 부모 전체 폭(inline-flex라도 width:100% 적용됨).\n const widthStyle = isFill ? 'w-full' : '';\n\n let sizeStyle;\n if (variant === 'underline') {\n // 밑줄 텍스트 버튼 — 배경/패딩 없이 텍스트(+아이콘)만. 높이만 맞춘다.\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] text-[12px] leading-5 tracking-[0px]'\n : 'min-h-[32px] text-[14px] leading-6 tracking-[0px]';\n } else if (iconOnly) {\n // 18 = 아이콘 전용 소형(18×18, 아이콘 14 → 상하좌우 2px 패딩). 24/32는 min-h/w로 높이 확보.\n sizeStyle =\n size === '18'\n ? 'min-h-[18px] min-w-[18px] p-spacing-2'\n : size === '24'\n ? 'min-h-[24px] min-w-[24px] p-spacing-2'\n : 'min-h-[32px] min-w-[32px] p-spacing-3';\n } else {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] px-spacing-5 py-spacing-2 text-[12px] leading-5 tracking-[0px]'\n : 'min-h-[32px] min-w-[32px] px-spacing-6 py-spacing-3 text-[14px] leading-6 tracking-[0px]';\n }\n\n let colorStyle;\n if (variant === 'fill') {\n colorStyle = inactive\n ? 'bg-button-fill-disabled-bg text-button-fill-disabled-fg cursor-not-allowed'\n : 'bg-button-fill-default-bg text-button-fill-default-fg cursor-pointer ' +\n 'hover:bg-button-fill-hover-bg focus-visible:bg-button-fill-hover-bg ' +\n 'active:bg-button-fill-default-bg';\n } else if (variant === 'line') {\n colorStyle = inactive\n ? 'bg-button-line-disabled-bg text-button-line-disabled-fg cursor-not-allowed'\n : 'bg-button-line-default-bg text-button-line-default-fg ring-1 ring-inset ring-button-line-default-line cursor-pointer ' +\n 'hover:bg-button-line-hover-bg hover:ring-button-line-hover-line ' +\n 'focus-visible:bg-button-line-hover-bg focus-visible:ring-button-line-hover-line ' +\n 'active:bg-button-line-default-bg active:ring-button-line-default-line';\n } else if (variant === 'underline') {\n // 밑줄 텍스트 버튼 — 배경 없이 텍스트색만, hover 시 밑줄(active=눌렸을 땐 밑줄 제거).\n // color 변형(2026-07-15): black 기본 + 칩 컬러 대응 6색(button-underline-* 토큰, 정적 맵=purge 안전)\n const UNDERLINE_COLOR = {\n black: 'text-button-underline-black-fg',\n red: 'text-button-underline-red-fg',\n blue: 'text-button-underline-blue-fg',\n green: 'text-button-underline-green-fg',\n violet: 'text-button-underline-violet-fg',\n pink: 'text-button-underline-pink-fg',\n orange: 'text-button-underline-orange-fg',\n };\n colorStyle = inactive\n ? 'bg-transparent text-font-icon-2 cursor-not-allowed'\n : `bg-transparent ${UNDERLINE_COLOR[color] ?? UNDERLINE_COLOR.black} cursor-pointer ` +\n 'hover:underline focus-visible:underline active:no-underline';\n } else {\n // ghost — 비활성은 세그먼트 컨트롤과 동일(투명 배경 + font-icon-2 텍스트)\n colorStyle = inactive\n ? 'bg-transparent text-font-icon-2 cursor-not-allowed'\n : 'bg-transparent text-button-ghost-default-fg cursor-pointer ' +\n 'hover:bg-button-ghost-hover-bg focus-visible:bg-button-ghost-hover-bg ' +\n 'active:bg-transparent';\n }\n\n // 내부 콘텐츠(로딩 스피너 + 아이콘·라벨 레이아웃) — button/Slot 두 경로 공통\n const innerContent = (\n <>\n {loading && (\n <span className=\"absolute inset-0 flex items-center justify-center\">\n <LoaderCircle\n size={iconSize}\n strokeWidth={1.8}\n className=\"animate-spin text-font-icon-3\"\n />\n </span>\n )}\n <span className={`inline-flex items-center gap-spacing-3 ${truncate ? 'min-w-0' : ''} ${loading ? 'opacity-0' : ''}`}>\n {iconOnly ? (\n <Icon size={iconSize} strokeWidth={1.8} />\n ) : (\n <>\n {LeftIcon && <LeftIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n {truncate ? (\n <TruncatingText as=\"span\" className=\"min-w-0\">{label}</TruncatingText>\n ) : (\n label\n )}\n {RightIcon && <RightIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n </>\n )}\n </span>\n </>\n );\n\n const rootClassName = `${base} ${sizeStyle} ${colorStyle} ${truncStyle} ${widthStyle} ${className}`;\n const handleMouseEnter = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onMouseEnter?.(e);\n hoverTip.onMouseEnter(e);\n };\n const handleMouseLeave = (e: ReactMouseEvent<HTMLButtonElement>) => {\n onMouseLeave?.(e);\n hoverTip.onMouseLeave();\n };\n\n return (\n <>\n {slotChild ? (\n // asChild — 자식 엘리먼트를 루트로, 버튼 스타일·동작을 머지(래퍼 없음).\n // <a>엔 disabled 속성이 없으므로 aria-disabled로 대체하고 onClick은 비활성 시 차단.\n <Slot\n ref={ref}\n content={innerContent}\n className={rootClassName}\n aria-disabled={inactive || undefined}\n onClick={!inactive ? onClick : undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n {...props}\n >\n {slotChild}\n </Slot>\n ) : (\n <button\n ref={ref as Ref<HTMLButtonElement>}\n className={rootClassName}\n disabled={inactive}\n onClick={!inactive ? onClick : undefined}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n {...props}\n >\n {innerContent}\n </button>\n )}\n {hoverTip.tooltip}\n </>\n );\n}\n","// Input — 텍스트 입력 필드 (Figma 02_textfield / input, solid 타입)\n// 상태(default/hover/focused/filled)는 CSS와 실제 입력값으로 자동 처리.\n// disabled / readOnly / error 는 props로 노출(완전 옵션화).\n//\n// 에러 표현 규칙: 에러는 인풋 테두리를 바꾸지 않고 \"툴팁\"으로만 표시한다.\n// - 툴팁은 인풋 박스 아래에 absolute 오버레이로 띄워, 레이아웃 흐름과\n// 형제 컴포넌트 영역에 전혀 영향을 주지 않는다(공간을 차지하지 않음).\nimport type { ChangeEvent, ComponentPropsWithoutRef } from 'react';\nimport { Tooltip } from './Tooltip';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰). 색은 시멘틱 토큰.\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// 사이즈 — '32'(기본, 32px·text-14) | '22'(작게, 22px·text-12·leading-18로 핏하게, 좁은 셀/인라인용).\nconst SIZE_STYLE = {\n '32': { box: 'min-h-[32px] px-spacing-6 py-spacing-3', text: 'text-14' },\n '22': { box: 'min-h-[22px] px-spacing-5 py-spacing-2', text: 'text-12 leading-18' },\n};\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <input> 속성은 inputProps로 전달한다.\ninterface InputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string | number;\n defaultValue?: string | number;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n placeholder?: string;\n size?: keyof typeof SIZE_STYLE; // '32'(32px·14px) | '22'(22px·12px, 라인하이트 핏)\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n inputProps?: ComponentPropsWithoutRef<'input'>;\n}\n\nexport function Input({\n value,\n defaultValue,\n onChange,\n placeholder = '텍스트를 입력하세요',\n size = '32', // '32'(32px·14px) | '22'(22px·12px, 라인하이트 핏)\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 200, // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n className = '',\n inputProps = {},\n ...props\n}: InputProps) {\n const interactive = !disabled && !readOnly;\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const sizeStyle = SIZE_STYLE[size] ?? SIZE_STYLE['32'];\n\n const textColor = disabled\n ? 'text-text-field-disabled-text'\n : readOnly\n ? 'text-text-field-readonly-text'\n : 'text-text-field-filled-text';\n // disabled면 플레이스홀더도 비활성 색(#c9c9c9) — TextArea와 동일 패턴(Figma disabled 스펙)\n const placeholderColor = disabled\n ? 'placeholder:text-text-field-disabled-text'\n : 'placeholder:text-text-field-default-text';\n\n return (\n <div\n style={{ width: widthStyle }}\n className={`relative flex items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg transition-shadow ${sizeStyle.box} ${\n interactive ? RING : 'cursor-not-allowed'\n } ${className}`}\n {...props}\n >\n <input\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={error || undefined}\n className={`min-w-0 flex-1 bg-transparent outline-none ${sizeStyle.text} ${placeholderColor} disabled:cursor-not-allowed read-only:cursor-default ${textColor}`}\n {...inputProps}\n />\n\n {/* 에러 툴팁 — absolute 오버레이라 인풋 아래 공간을 차지하지 않는다 */}\n {error && errorMessage && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errorMessage}\n </Tooltip>\n </div>\n )}\n </div>\n );\n}\n","// ScrollArea — 커스텀 오버레이 스크롤바를 가진 스크롤 영역 (공용)\n// 스크롤이 생기는 모든 곳에서 사용한다. 네이티브 스크롤바를 숨기고(.hide-native-scroll),\n// 콘텐츠 위에 absolute thumb를 직접 그려 오버레이(콘텐츠 폭/높이를 줄이지 않음)로 표시한다.\n// - thumb 색: 기본 scroll-bar(gray-900-50), hover·드래그 중 scroll-bar-hover(gray-900-75)\n// - thumb 양끝 여백 TRACK_PAD로 끝에 닿지 않게, 드래그/스크롤로 위치 동기화\n// - horizontal=true 이면 가로 스크롤도 동일한 오버레이 thumb로 처리(세로와 함께 가능)\n//\n// 사용: <ScrollArea maxHeight={240}> ... </ScrollArea> (세로 오버레이)\n// <ScrollArea maxHeight={240} horizontal> ... </ScrollArea> (세로+가로 오버레이)\nimport { useRef, useState, useCallback, useEffect, useLayoutEffect } from 'react';\nimport type { ComponentPropsWithoutRef, MouseEvent as ReactMouseEvent, UIEvent } from 'react';\nimport { listColors } from '../tokens';\n\nconst THUMB_MIN = 24; // thumb 최소 길이(px)\nconst TRACK_PAD = 6; // thumb 양끝 여백(px)\nconst CROSS_INSET = 16; // 가로·세로 스크롤이 동시에 있을 때 교차 영역만큼 트랙을 줄이는 여백(px)\n\n// 세로 트랙 메트릭 — sticky 헤더(topInset)와 가로 스크롤바 자리(bottomInset)를 제외한 트랙 높이.\nfunction verticalTrack(el: HTMLElement, horizontal: boolean) {\n const stickyTop = el.querySelector<HTMLElement>('[data-scroll-sticky-top]');\n const topInset = stickyTop ? stickyTop.offsetHeight : 0;\n const bottomInset = horizontal && el.scrollWidth > el.clientWidth + 1 ? CROSS_INSET : 0;\n return { topInset, trackH: el.clientHeight - topInset - bottomInset - TRACK_PAD * 2 };\n}\n\n// thumb 색 변형 — default(밝은 배경)·light(어두운 배경 위 흰색)\nconst THUMB_COLORS = {\n default: { base: listColors['scroll-bar'], active: listColors['scroll-bar-hover'] },\n light: { base: listColors['scroll-bar-light'], active: listColors['scroll-bar-light-hover'] },\n};\n\n// 오버레이 thumb — 세로/가로 공용(축만 다름).\n// 시각 두께 6px 유지 + 클릭(히트) 영역은 10px — 투명 ::before를 사방 2px 확장해 hover/드래그 시작이\n// 쉬워진다(pseudo-element 영역도 원본 요소의 히트 대상, 2026-07-06 지시 · 8→10 확대).\nconst THUMB_HIT = \"before:absolute before:-inset-spacing-2 before:content-['']\";\n\ninterface ThumbState {\n pos: number;\n size: number;\n visible: boolean;\n}\n\ninterface ThumbProps {\n orientation: 'vertical' | 'horizontal';\n thumb: ThumbState;\n active: boolean;\n colors: { base: string; active: string };\n onDown: (e: ReactMouseEvent<HTMLDivElement>) => void;\n onEnter: () => void;\n onLeave: () => void;\n}\n\nfunction Thumb({ orientation, thumb, active, colors, onDown, onEnter, onLeave }: ThumbProps) {\n const vertical = orientation === 'vertical';\n const placement = vertical ? 'right-spacing-3 w-spacing-4' : 'bottom-spacing-3 h-spacing-4'; // 두께 6px(2026-07-06 8→6)\n const extent = vertical\n ? { top: `${thumb.pos}px`, height: `${thumb.size}px` }\n : { left: `${thumb.pos}px`, width: `${thumb.size}px` };\n return (\n <div\n onMouseDown={onDown}\n onMouseEnter={onEnter}\n onMouseLeave={onLeave}\n className={`absolute cursor-pointer rounded-round-00 ${THUMB_HIT} ${placement}`}\n style={{\n ...extent,\n opacity: thumb.visible ? 1 : 0,\n pointerEvents: thumb.visible ? 'auto' : 'none',\n backgroundColor: active ? colors.active : colors.base,\n }}\n />\n );\n}\n\nexport interface ScrollAreaProps extends ComponentPropsWithoutRef<'div'> {\n maxHeight?: number | string;\n horizontal?: boolean;\n /** 'default'(밝은 배경) | 'light'(어두운 배경 위 흰색 thumb) */\n variant?: keyof typeof THUMB_COLORS;\n /** (선택) 내부 스크롤 요소를 넘겨받는 콜백 — 부모가 scrollTop 등을 직접 제어할 때 */\n onViewport?: (el: HTMLDivElement | null) => void;\n /** (선택) 스크롤 이벤트 패스스루(내부 thumb 갱신과 함께 호출) */\n onScroll?: (e: UIEvent<HTMLDivElement>) => void;\n /**\n * (선택) 세로 thumb를 자신의 viewport 대신 이 요소의 세로 스크롤에 연동.\n * 가로 스크롤 콘텐츠 '안'에 세로 스크롤 영역이 중첩될 때(표 바디 등) 세로 thumb를\n * 가로 스크롤을 안 받는 이 컴포넌트(고정 폭)에서 그려 항상 보이게 한다(2026-07-06).\n */\n vScrollEl?: HTMLElement | null;\n contentClassName?: string;\n}\n\nexport function ScrollArea({\n children,\n maxHeight,\n horizontal = false,\n variant = 'default',\n onViewport,\n onScroll,\n vScrollEl = null,\n className = '',\n contentClassName = '',\n ...props\n}: ScrollAreaProps) {\n const thumbColors = THUMB_COLORS[variant] ?? THUMB_COLORS.default;\n const scrollRef = useRef<HTMLDivElement | null>(null);\n const rootRef = useRef<HTMLDivElement | null>(null); // vScrollEl 연동 시 thumb 세로 위치 기준(루트 상단 대비 오프셋 측정)\n // 내부 scrollRef를 채우면서 외부로 스크롤 요소를 전달하는 콜백 ref\n const setViewport = useCallback(\n (el: HTMLDivElement | null) => {\n scrollRef.current = el;\n onViewport?.(el);\n },\n [onViewport],\n );\n const vDragRef = useRef(false);\n const hDragRef = useRef(false);\n const [vThumb, setVThumb] = useState<ThumbState>({ pos: 0, size: 0, visible: false });\n const [hThumb, setHThumb] = useState<ThumbState>({ pos: 0, size: 0, visible: false });\n const [vActive, setVActive] = useState(false); // hover 또는 드래그 중\n const [hActive, setHActive] = useState(false);\n\n const maxHeightStyle = typeof maxHeight === 'number' ? `${maxHeight}px` : maxHeight;\n\n // 세로 트랙 정보 — vScrollEl 연동 시: 대상 요소 기준 메트릭 + 루트 상단 대비 오프셋(헤더 높이 등).\n // sizeInset은 sticky-top(자기 viewport 내부 요소) 케이스에서만 스크롤 계산에 반영된다.\n const vTrack = useCallback(() => {\n const el = scrollRef.current;\n if (vScrollEl) {\n const rootTop = rootRef.current?.getBoundingClientRect().top ?? 0;\n const topInset = vScrollEl.getBoundingClientRect().top - rootTop;\n const bottomInset =\n horizontal && el && el.scrollWidth > el.clientWidth + 1 ? CROSS_INSET : 0;\n return {\n vEl: vScrollEl,\n topInset,\n sizeInset: 0,\n trackH: vScrollEl.clientHeight - bottomInset - TRACK_PAD * 2,\n };\n }\n const { topInset, trackH } = verticalTrack(el as HTMLElement, horizontal);\n return { vEl: el as HTMLElement, topInset, sizeInset: topInset, trackH };\n }, [vScrollEl, horizontal]);\n\n const measureThumbs = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n const { scrollWidth, clientWidth, scrollLeft } = el;\n\n // 세로 — vScrollEl이 있으면 그 요소의 스크롤 메트릭 기준\n const { vEl, topInset, sizeInset, trackH } = vTrack();\n const { scrollHeight, clientHeight, scrollTop } = vEl;\n if (scrollHeight <= clientHeight + 1) {\n setVThumb((t) => (t.visible ? { ...t, visible: false } : t));\n } else {\n const size = Math.max(((clientHeight - sizeInset) / (scrollHeight - sizeInset)) * trackH, THUMB_MIN);\n const pos = TRACK_PAD + topInset + (scrollTop / (scrollHeight - clientHeight)) * (trackH - size);\n setVThumb({ pos, size, visible: true });\n }\n\n // 가로\n if (!horizontal || scrollWidth <= clientWidth + 1) {\n setHThumb((t) => (t.visible ? { ...t, visible: false } : t));\n } else {\n const trackW = clientWidth - TRACK_PAD * 2;\n const size = Math.max((clientWidth / scrollWidth) * trackW, THUMB_MIN);\n const pos = TRACK_PAD + (scrollLeft / (scrollWidth - clientWidth)) * (trackW - size);\n setHThumb({ pos, size, visible: true });\n }\n }, [horizontal, vTrack]);\n\n // 측정 rAF 코얼레싱(2026-07-07 감사) — scroll·RO·vScrollEl 리스너가 같은 프레임에 겹쳐도\n // DOM 측정+setState는 프레임당 1회만 수행한다(스크롤 중 measure 반복으로 인한 jank 방지).\n const rafRef = useRef(0);\n const updateThumbs = useCallback(() => {\n if (rafRef.current) return;\n rafRef.current = requestAnimationFrame(() => {\n rafRef.current = 0;\n measureThumbs();\n });\n }, [measureThumbs]);\n useEffect(\n () => () => {\n // 취소 후 반드시 0으로 리셋 — 안 하면 StrictMode 재마운트 후 rafRef가 죽은 id로 남아\n // updateThumbs의 '이미 예약됨' 가드에 걸려 측정이 영영 스킵된다(썸 안 보임).\n if (rafRef.current) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n }\n },\n [],\n );\n\n useLayoutEffect(() => {\n updateThumbs(); // rAF 예약이라 effect 내 동기 setState 아님\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(updateThumbs);\n ro.observe(el);\n return () => ro.disconnect();\n }, [updateThumbs, children]);\n\n // vScrollEl 연동 — 외부 세로 스크롤 요소의 scroll/resize에 thumb를 동기화한다.\n // (초기 1회 동기화는 위 useLayoutEffect가 담당 — vScrollEl 변경 시 updateThumbs 정체성이\n // 바뀌어 위 effect가 재실행되므로 여기서 직접 호출하지 않는다)\n useLayoutEffect(() => {\n if (!vScrollEl) return;\n vScrollEl.addEventListener('scroll', updateThumbs);\n const ro = new ResizeObserver(updateThumbs);\n ro.observe(vScrollEl);\n return () => {\n vScrollEl.removeEventListener('scroll', updateThumbs);\n ro.disconnect();\n };\n }, [vScrollEl, updateThumbs]);\n\n // 드래그 도중 언마운트(ESC로 팝오버/모달 닫힘 등) 시 window 리스너·body userSelect가\n // 잔존하지 않게 진행 중인 드래그의 정리 함수를 보관했다가 unmount에서 실행한다(2026-07-07 감사).\n const dragCleanupRef = useRef<(() => void) | null>(null);\n useEffect(() => () => dragCleanupRef.current?.(), []);\n\n // thumb 드래그 — 세로/가로 공용. 클릭 지점 대비 이동량을 스크롤 비율로 환산해 동기화한다.\n const startThumbDrag = (e: ReactMouseEvent<HTMLDivElement>, axis: 'vertical' | 'horizontal') => {\n e.preventDefault();\n e.stopPropagation();\n const vertical = axis === 'vertical';\n const dragRef = vertical ? vDragRef : hDragRef;\n const setActive = vertical ? setVActive : setHActive;\n // 세로 드래그는 vScrollEl 연동 대상이 있으면 그 요소를 스크롤한다\n const el = (vertical ? vTrack().vEl : scrollRef.current) as HTMLElement;\n dragRef.current = true;\n setActive(true);\n\n const start = vertical ? e.clientY : e.clientX;\n const startScroll = vertical ? el.scrollTop : el.scrollLeft;\n const maxScroll = vertical ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;\n const trackLen = vertical ? vTrack().trackH : el.clientWidth - TRACK_PAD * 2;\n const thumbSize = vertical ? vThumb.size : hThumb.size;\n // 트랙이 thumb 최소 길이(24px)보다 짧으면 분모가 0/음수 → NaN 스크롤 점프 방지(이동 무시)\n const denom = trackLen - thumbSize;\n const ratio = denom > 0 ? maxScroll / denom : 0;\n\n const onMove = (ev: MouseEvent) => {\n const delta = (vertical ? ev.clientY : ev.clientX) - start;\n if (vertical) el.scrollTop = startScroll + delta * ratio;\n else el.scrollLeft = startScroll + delta * ratio;\n };\n const onUp = () => {\n dragRef.current = false;\n setActive(false);\n document.body.style.userSelect = '';\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n dragCleanupRef.current = null;\n };\n document.body.style.userSelect = 'none';\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n dragCleanupRef.current = onUp;\n };\n\n // 기본은 relative(thumb의 기준). 단 className에 position 유틸(absolute 등)을 주면 그걸 따른다\n // — 부모 박스를 explicit height로 채워(예: absolute inset-0) 내부 h-full 스크롤이 확실히 동작하게.\n const hasPosition = /(^|\\s)(absolute|fixed|relative|sticky)(\\s|$)/.test(className);\n return (\n <div ref={rootRef} className={`${hasPosition ? '' : 'relative'} ${className}`} {...props}>\n <div\n ref={setViewport}\n onScroll={(e) => {\n updateThumbs();\n onScroll?.(e);\n }}\n className={`hide-native-scroll ${horizontal ? 'overflow-auto' : 'overflow-y-auto'} ${contentClassName}`}\n style={{ maxHeight: maxHeightStyle }}\n >\n {children}\n </div>\n\n {/* 세로 오버레이 thumb — 항상 렌더, opacity로 표시 제어 */}\n <Thumb\n orientation=\"vertical\"\n thumb={vThumb}\n active={vActive}\n colors={thumbColors}\n onDown={(e) => startThumbDrag(e, 'vertical')}\n onEnter={() => setVActive(true)}\n onLeave={() => {\n if (!vDragRef.current) setVActive(false);\n }}\n />\n\n {/* 가로 오버레이 thumb */}\n {horizontal && (\n <Thumb\n orientation=\"horizontal\"\n thumb={hThumb}\n active={hActive}\n colors={thumbColors}\n onDown={(e) => startThumbDrag(e, 'horizontal')}\n onEnter={() => setHActive(true)}\n onLeave={() => {\n if (!hDragRef.current) setHActive(false);\n }}\n />\n )}\n </div>\n );\n}\n","// 칩 비주얼 공용 클래스 맵 — Chip(제거형 칩)과 SelectChip(칩형 Select 트리거)이 공유한다.\n// 컴포넌트 파일에서 상수를 export하면 react-refresh 규칙에 걸려 별도 파일로 분리(modalContext와 동일 관례).\n// color별 전체 클래스(Tailwind purge를 위해 동적 문자열이 아닌 완전한 클래스명으로 나열).\n// text 색만 지정하면 내부 아이콘(lucide, stroke=currentColor)이 상속받는다. icon 토큰=text 토큰 값.\n// 포커스 디자인 = 호버 디자인(2026-07-16 지시): 링 대신 hover 색 전환을\n// focus-visible(키보드 Tab)과 data-[chain-focus](체인 programmatic focus)에 동일 적용.\nexport const CHIP_COLOR_CLASS = {\n gray: 'text-chip-gray-default-text border-chip-gray-default-line bg-chip-gray-default-bg hover:text-chip-gray-hover-text hover:border-chip-gray-hover-line hover:bg-chip-gray-hover-bg focus-visible:text-chip-gray-hover-text focus-visible:border-chip-gray-hover-line focus-visible:bg-chip-gray-hover-bg data-[chain-focus]:text-chip-gray-hover-text data-[chain-focus]:border-chip-gray-hover-line data-[chain-focus]:bg-chip-gray-hover-bg',\n red: 'text-chip-red-default-text border-chip-red-default-line bg-chip-red-default-bg hover:text-chip-red-hover-text hover:border-chip-red-hover-line hover:bg-chip-red-hover-bg focus-visible:text-chip-red-hover-text focus-visible:border-chip-red-hover-line focus-visible:bg-chip-red-hover-bg data-[chain-focus]:text-chip-red-hover-text data-[chain-focus]:border-chip-red-hover-line data-[chain-focus]:bg-chip-red-hover-bg',\n blue: 'text-chip-blue-default-text border-chip-blue-default-line bg-chip-blue-default-bg hover:text-chip-blue-hover-text hover:border-chip-blue-hover-line hover:bg-chip-blue-hover-bg focus-visible:text-chip-blue-hover-text focus-visible:border-chip-blue-hover-line focus-visible:bg-chip-blue-hover-bg data-[chain-focus]:text-chip-blue-hover-text data-[chain-focus]:border-chip-blue-hover-line data-[chain-focus]:bg-chip-blue-hover-bg',\n black: 'text-chip-black-default-text border-chip-black-default-line bg-chip-black-default-bg hover:text-chip-black-hover-text hover:border-chip-black-hover-line hover:bg-chip-black-hover-bg focus-visible:text-chip-black-hover-text focus-visible:border-chip-black-hover-line focus-visible:bg-chip-black-hover-bg data-[chain-focus]:text-chip-black-hover-text data-[chain-focus]:border-chip-black-hover-line data-[chain-focus]:bg-chip-black-hover-bg',\n green: 'text-chip-green-default-text border-chip-green-default-line bg-chip-green-default-bg hover:text-chip-green-hover-text hover:border-chip-green-hover-line hover:bg-chip-green-hover-bg focus-visible:text-chip-green-hover-text focus-visible:border-chip-green-hover-line focus-visible:bg-chip-green-hover-bg data-[chain-focus]:text-chip-green-hover-text data-[chain-focus]:border-chip-green-hover-line data-[chain-focus]:bg-chip-green-hover-bg',\n violet: 'text-chip-violet-default-text border-chip-violet-default-line bg-chip-violet-default-bg hover:text-chip-violet-hover-text hover:border-chip-violet-hover-line hover:bg-chip-violet-hover-bg focus-visible:text-chip-violet-hover-text focus-visible:border-chip-violet-hover-line focus-visible:bg-chip-violet-hover-bg data-[chain-focus]:text-chip-violet-hover-text data-[chain-focus]:border-chip-violet-hover-line data-[chain-focus]:bg-chip-violet-hover-bg',\n pink: 'text-chip-pink-default-text border-chip-pink-default-line bg-chip-pink-default-bg hover:text-chip-pink-hover-text hover:border-chip-pink-hover-line hover:bg-chip-pink-hover-bg focus-visible:text-chip-pink-hover-text focus-visible:border-chip-pink-hover-line focus-visible:bg-chip-pink-hover-bg data-[chain-focus]:text-chip-pink-hover-text data-[chain-focus]:border-chip-pink-hover-line data-[chain-focus]:bg-chip-pink-hover-bg',\n orange: 'text-chip-orange-default-text border-chip-orange-default-line bg-chip-orange-default-bg hover:text-chip-orange-hover-text hover:border-chip-orange-hover-line hover:bg-chip-orange-hover-bg focus-visible:text-chip-orange-hover-text focus-visible:border-chip-orange-hover-line focus-visible:bg-chip-orange-hover-bg data-[chain-focus]:text-chip-orange-hover-text data-[chain-focus]:border-chip-orange-hover-line data-[chain-focus]:bg-chip-orange-hover-bg',\n};\n\n// 칩 컬러 키 타입 — Chip·SelectChip color prop이 공유\nexport type ChipColor = keyof typeof CHIP_COLOR_CLASS;\n","// InlineFieldTrigger — 박스 없는 인라인 텍스트형 트리거(리딩 아이콘? + 값 텍스트 + chevron).\n// Select(variant=\"text\")와 DatePicker 연.월 선택(DualSelectField)이 공유하는 \"트리거 비주얼\"이다.\n// 클릭·키보드·포커스·위치계산 같은 상호작용은 사용처가 담당한다:\n// - Select: 이 컴포넌트에 role/tabIndex/onClick/onKeyDown/ref를 직접 spread\n// - DatePicker: Popover가 트리거를 감싸 클릭/anchor를 처리(여기엔 open만 전달)\n// 따라서 이 컴포넌트는 순수 표현(색·크기·말줄임·hover 밑줄·열림 회색·chevron 회전)만 책임진다.\nimport { forwardRef } from 'react';\nimport type { ComponentPropsWithoutRef, ComponentType, CSSProperties } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { TruncatingText } from './TruncatingText';\n\n// 리딩 아이콘 타입 — lucide 아이콘 컴포넌트(size/strokeWidth/className을 받는다)\ntype IconComponent = ComponentType<{ size?: number | string; strokeWidth?: number | string; className?: string }>;\n\n// ...props는 루트 <span>으로 spread된다(role/tabIndex/onClick/onKeyDown 등 사용처가 부여).\ninterface InlineFieldTriggerProps extends ComponentPropsWithoutRef<'span'> {\n icon?: IconComponent | null; // 좌측 리딩 아이콘(lucide 컴포넌트) — 없으면 미표시\n size?: '24' | '20'; // '24'(14px) | '20'(12px)\n open?: boolean; // 펼침 상태 — 텍스트 회색 + chevron 180° 회전\n disabled?: boolean;\n readOnly?: boolean;\n interactive?: boolean; // 상호작용 가능 여부(미지정 시 !disabled && !readOnly)\n maxWidth?: CSSProperties['maxWidth']; // 텍스트 최대 폭(CSS 길이) — 넘으면 말줄임\n fill?: boolean; // 부모 전체 폭 채움 — 텍스트 왼쪽·chevron 오른쪽 끝(justify-between). 기본 hug\n}\n\nexport const InlineFieldTrigger = forwardRef<HTMLSpanElement, InlineFieldTriggerProps>(function InlineFieldTrigger(\n {\n icon: Icon = null, // 좌측 리딩 아이콘(lucide 컴포넌트) — 없으면 미표시\n children, // 표시 텍스트(선택값/플레이스홀더)\n size = '24', // '24'(14px) | '20'(12px)\n open = false, // 펼침 상태 — 텍스트 회색 + chevron 180° 회전\n disabled = false,\n readOnly = false,\n interactive, // 상호작용 가능 여부(미지정 시 !disabled && !readOnly)\n maxWidth, // 텍스트 최대 폭(CSS 길이) — 넘으면 말줄임\n fill = false, // 부모 전체 폭 채움 — 텍스트 왼쪽·chevron 오른쪽 끝(justify-between). 기본 hug\n className = '',\n ...props\n },\n ref,\n) {\n const canInteract = interactive ?? (!disabled && !readOnly);\n\n // 텍스트 색: disabled=흐림 / 열림=회색 / 그 외(readOnly·placeholder·filled)=진함\n const textColor = disabled ? 'text-font-icon-2' : open ? 'text-font-icon-3' : 'text-font-icon-5';\n // 아이콘 색(리딩 아이콘·chevron 공통): disabled·readOnly=흐림 / 그 외=진함\n const iconColor = disabled || readOnly ? 'text-font-icon-2' : 'text-font-icon-5';\n\n // 글자/아이콘 크기 — size 토큰(24=14px / 20=12px), 아이콘은 16/14\n const sizeTextClass = size === '20' ? 'text-12' : 'text-14';\n const iconSize = size === '20' ? 14 : 16;\n\n return (\n <span\n ref={ref}\n className={`group min-w-0 select-none items-center gap-spacing-3 focus:outline-none ${\n fill ? 'flex w-full justify-between' : 'inline-flex'\n } ${canInteract ? 'cursor-pointer' : disabled ? 'cursor-not-allowed' : 'cursor-default'} ${className}`}\n {...props}\n >\n {Icon && <Icon size={iconSize} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />}\n <TruncatingText\n style={maxWidth ? { maxWidth } : undefined}\n className={`min-w-0 font-normal ${fill ? 'flex-1' : ''} ${sizeTextClass} ${textColor} ${\n canInteract ? 'group-hover:underline group-focus-visible:underline' : '' /* 포커스=호버 */\n }`}\n >\n {children}\n </TruncatingText>\n <ChevronDown\n size={iconSize}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${iconColor} ${\n open ? 'rotate-180' : ''\n }`}\n />\n </span>\n );\n});\n","// SearchBar — 검색 입력 (Figma 02_textfield / search bar, solid 타입)\n// 상태(default/hover/focused/filled)는 CSS와 실제 입력값으로 자동 처리한다.\n// - hover/focus 테두리는 ring으로 구현해 두께 변화에 따른 레이아웃 시프트를 막는다.\n// - filled(값 입력)/placeholder 텍스트 색은 native input과 placeholder 의사클래스로 자동.\nimport type { ChangeEvent, ComponentPropsWithoutRef, KeyboardEvent } from 'react';\nimport { Search } from 'lucide-react';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰). 색은 시멘틱 토큰.\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <input> 속성은 inputProps로 전달한다.\ninterface SearchBarProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'onSubmit'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n onSubmit?: (value: string) => void; // Enter 제출 — 현재 입력값 전달\n placeholder?: string;\n disabled?: boolean;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n inputProps?: ComponentPropsWithoutRef<'input'>;\n}\n\nexport function SearchBar({\n value,\n defaultValue,\n onChange,\n onSubmit,\n placeholder = '검색어를 입력하세요',\n disabled = false,\n width = 200, // 너비: 숫자(px) 또는 CSS 길이 문자열('100%' 등). 미지정 시 200px\n className = '',\n inputProps = {},\n ...props\n}: SearchBarProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n return (\n <div\n style={{ width: widthStyle }}\n className={`group relative flex min-h-[32px] items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n disabled ? 'cursor-not-allowed' : RING\n } ${className}`}\n {...props}\n >\n <Search\n size={16}\n strokeWidth={1.8}\n className={`shrink-0 ${\n disabled\n ? 'text-text-field-disabled-icon'\n : 'text-text-field-default-text group-focus-within:text-text-field-filled-text'\n }`}\n />\n <input\n type=\"search\"\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n className=\"min-w-0 flex-1 bg-transparent text-14 text-text-field-filled-text outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed disabled:text-text-field-disabled-text disabled:placeholder:text-text-field-disabled-text [&::-webkit-search-cancel-button]:appearance-none\"\n {...inputProps}\n onKeyDown={(e: KeyboardEvent<HTMLInputElement>) => {\n // Enter 제출 + 소비자 onKeyDown 합성 — 스프레드가 덮어써 onSubmit이 무력화되지 않게\n if (e.key === 'Enter' && onSubmit) onSubmit(e.currentTarget.value);\n inputProps?.onKeyDown?.(e);\n }}\n />\n </div>\n );\n}\n","// Checkbox — 체크박스 (Figma check box)\n// 상태: unselected/selected × default/hover/disabled (6가지)\n// - unselected: 흰 박스 + 회색 테두리 / selected: blue 채움 + 흰 체크\n// - hover: 바깥 ring(checkbox-*-hover-outline) / disabled: 연하게\n// 체크 아이콘은 Figma check_small SVG(node 7257:2464)를 인라인으로 사용(fill=currentColor).\n// 색은 checkbox-* 시멘틱 토큰(base 경유)만 사용. controlled/uncontrolled 모두 지원.\n// - Checkbox : 단일 체크박스 1개\n// - CheckboxGroup : 여러 항목의 다중 선택(선택값 배열)을 관리하는 컨테이너\nimport { useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { OptionItem } from './formulaFunctions';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — Radio/SegmentControl과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px (기본 — 체크박스 항목 간격)\n};\n\n// Figma check_small SVG — fill을 currentColor로 바꿔 텍스트 색 토큰을 따르게 함\nfunction CheckMark({ className }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 8.34188 6.51667\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={className}\n >\n <path d=\"M2.84188 4.11667L6.64188 0.316667C6.85299 0.105556 7.08355 0 7.33355 0C7.58355 0 7.8141 0.105556 8.02521 0.316667C8.23633 0.527778 8.34188 0.761111 8.34188 1.01667C8.34188 1.27222 8.23633 1.50556 8.02521 1.71667L3.54188 6.21667C3.34188 6.41667 3.10577 6.51667 2.83355 6.51667C2.56132 6.51667 2.32521 6.41667 2.12521 6.21667L0.308547 4.4C0.0974359 4.18889 -0.00534188 3.95556 0.000213675 3.7C0.00576923 3.44444 0.114103 3.21111 0.325214 3C0.536325 2.78889 0.769658 2.68333 1.02521 2.68333C1.28077 2.68333 1.5141 2.78889 1.72521 3L2.84188 4.11667Z\" />\n </svg>\n );\n}\n\n// ...props는 sr-only <input type=\"checkbox\">로 전달된다.\ninterface CheckboxProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Checkbox({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n className = '',\n ...props\n}: CheckboxProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isChecked = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 박스 바깥에 간격(offset)을 두고 둥글게 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 = hover와 동일 디자인(2026-07-16 지시) — sr-only input이 포커스를 가지므로\n // peer-focus-visible로 박스에 hover와 같은 링 표시(마우스 클릭에는 없음, Radio와 동일 패턴)\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let boxClass;\n if (disabled) {\n boxClass = isChecked ? 'bg-checkbox-selected-disabled-bg' : 'bg-checkbox-disabled-bg';\n } else if (isChecked) {\n boxClass = `bg-checkbox-selected-bg ${HOVER_RING} group-hover:ring-checkbox-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-checkbox-selected-hover-outline`;\n } else {\n boxClass = `bg-checkbox-unselected-bg ${HOVER_RING} group-hover:ring-checkbox-hover-outline ${FOCUS_RING} peer-focus-visible:ring-checkbox-hover-outline`;\n }\n\n const checkColor = disabled ? 'text-checkbox-disabled-check' : 'text-checkbox-check';\n const textColor = disabled ? 'text-checkbox-disabled-text' : 'text-checkbox-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"checkbox\"\n className=\"peer sr-only\"\n checked={isChecked}\n onChange={handleChange}\n disabled={disabled}\n {...props}\n />\n <span\n className={`flex size-[16px] shrink-0 items-center justify-center rounded-round-3 transition-shadow ${boxClass}`}\n >\n {/* 항상 렌더하고 visibility로만 토글 — 체크/해제 시 레이아웃이 흔들리지 않게 */}\n <CheckMark className={`w-[8px] ${isChecked ? checkColor : 'invisible'}`} />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface CheckboxGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: OptionItem[]; // [{ value, label, disabled }]\n value?: string[]; // controlled 선택값 배열\n defaultValue?: string[]; // uncontrolled 초기 선택값 배열\n onChange?: (nextValues: string[], meta: { value: string; checked: boolean }) => void; // (nextValues, { value, checked }) => void\n direction?: 'vertical' | 'horizontal';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '7'(16px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function CheckboxGroup({\n items = [], // [{ value, label, disabled }]\n value, // controlled 선택값 배열\n defaultValue = [], // uncontrolled 초기 선택값 배열\n onChange, // (nextValues, { value, checked }) => void\n direction = 'vertical', // 'vertical' | 'horizontal'\n gap = '7', // 간격 토큰 키 — 기본 '7'(16px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: CheckboxGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n\n const handleToggle = (val: string, checked: boolean) => {\n const next = checked\n ? [...selected, val]\n : selected.filter((v) => v !== val);\n if (!isControlled) setInternal(next);\n onChange?.(next, { value: val, checked });\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['7'];\n const dirStyle = direction === 'horizontal' ? 'flex-row items-center' : 'flex-col items-start';\n\n return (\n <div\n role=\"group\"\n className={`inline-flex ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <Checkbox\n key={item.value}\n label={item.label}\n checked={selected.includes(item.value)}\n disabled={disabled || item.disabled}\n onChange={(e) => handleToggle(item.value, e.target.checked)}\n />\n ))}\n </div>\n );\n}\n","// PopoverMenu — 옵션 목록을 감싸는 팝오버 (Figma option list / popover menu)\n// 내부에 ListGroup을 담는다(빈 상태는 ListGroup이 내부에서 ListEmpty로 처리). topArea로 상단 영역을 선택한다\n// ('none'|'search'=검색바(SearchBar)|'input'=일반 텍스트 입력(Input)) — 셋 중 하나만 노출.\n// footer 옵션을 켜면 하단에 푸터 영역이 추가된다 — 좌측은 footerStart 슬롯(텍스트·체크박스·언더라인 버튼 등\n// 임의 노드), 우측은 취소/확인 버튼 그룹. Modal 푸터와 동일한 prop 네이밍·구조를 따른다.\n// 추후 다양한 옵션이 추가될 예정이라 확장 가능한 구조로 둔다.\n// 컨테이너 색은 list-popover-* 시멘틱 토큰(bg gray100 / outline gray200).\nimport { RotateCcw } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, KeyboardEvent, ReactNode } from 'react';\nimport { SearchBar } from './SearchBar';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { Checkbox } from './Checkbox';\n\nexport interface PopoverMenuProps extends ComponentPropsWithoutRef<'div'> {\n // ── 상단 영역 ─────────────────────────────────────────\n /** 'none'=없음 | 'search'=검색바(SearchBar) | 'input'=일반 텍스트 입력(Input). 셋 중 하나만 선택. */\n topArea?: 'none' | 'search' | 'input';\n // topArea='search'일 때\n searchValue?: string;\n onSearchChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n searchPlaceholder?: string;\n /** 검색 input에 전달(autoFocus·onKeyDown 등) */\n searchInputProps?: ComponentPropsWithoutRef<'input'>;\n // topArea='input'일 때 (검색 아이콘 없는 일반 텍스트 입력)\n inputValue?: string;\n onInputChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n inputPlaceholder?: string;\n /** Input의 inputProps로 전달(autoFocus 등) */\n inputProps?: ComponentPropsWithoutRef<'input'>;\n // ── 푸터 ──────────────────────────────────────────────\n /** 하단 푸터 영역 표시 여부 */\n footer?: boolean;\n // 푸터 좌측 — 자주 쓰는 3종은 전용 props, 그 외엔 footerStart 범용 슬롯.\n // 우선순위: footerStart > footerCheckbox > footerReset > footerText\n /** 푸터 좌측 범용 슬롯 — 문자열(자동 텍스트 스타일) 또는 임의 노드 */\n footerStart?: ReactNode;\n /** 좌측 텍스트(선택 개수·가이드 문구 등) */\n footerText?: ReactNode;\n /** 좌측 전체 선택 체크박스 */\n footerCheckbox?: boolean;\n footerChecked?: boolean;\n onFooterCheckChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n footerCheckLabel?: ReactNode;\n /** 좌측 초기화 언더라인 버튼 */\n footerReset?: boolean;\n onFooterReset?: () => void;\n footerResetLabel?: ReactNode;\n footerResetIcon?: LucideIcon;\n // 푸터 우측 — 취소/확인 버튼\n cancelText?: ReactNode;\n onCancel?: () => void;\n confirmText?: ReactNode;\n onConfirm?: () => void;\n /** 확인 버튼 variant ('fill' | 'line' | 'ghost') */\n confirmVariant?: 'fill' | 'line' | 'ghost';\n confirmDisabled?: boolean;\n confirmLoading?: boolean;\n showCancel?: boolean;\n showConfirm?: boolean;\n /** 푸터 버튼 그룹을 fill(균등 분할 전체폭)로 — 좌측 슬롯 없이 버튼만 꽉 채움 */\n footerButtonsFill?: boolean;\n // ──────────────────────────────────────────────────────\n width?: number | string;\n}\n\nexport function PopoverMenu({\n children,\n topArea = 'none',\n searchValue,\n onSearchChange,\n searchPlaceholder = '검색어를 입력하세요',\n searchInputProps = {},\n inputValue,\n onInputChange,\n inputPlaceholder = '텍스트를 입력하세요',\n inputProps = {},\n footer = false,\n footerStart = null,\n footerText = null,\n footerCheckbox = false,\n footerChecked = false,\n onFooterCheckChange,\n footerCheckLabel = '전체 선택',\n footerReset = false,\n onFooterReset,\n footerResetLabel = '초기화',\n footerResetIcon: FooterResetIcon = RotateCcw,\n cancelText = '취소',\n onCancel,\n confirmText = '확인',\n onConfirm,\n confirmVariant = 'fill',\n confirmDisabled = false,\n confirmLoading = false,\n showCancel = true,\n showConfirm = true,\n footerButtonsFill = false,\n width = 304,\n className = '',\n ...props\n}: PopoverMenuProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n // 푸터 좌측 노드 결정 — footerStart(범용 슬롯)가 최우선, 없으면 전용 props로 조립.\n let footerStartNode: ReactNode = null;\n if (footerStart != null) {\n footerStartNode =\n typeof footerStart === 'string' ? (\n <p className=\"text-14 text-font-icon-5\">{footerStart}</p>\n ) : (\n footerStart\n );\n } else if (footerCheckbox) {\n footerStartNode = (\n <Checkbox label={footerCheckLabel} checked={footerChecked} onChange={onFooterCheckChange} />\n );\n } else if (footerReset) {\n footerStartNode = (\n <Button variant=\"underline\" size=\"32\" leftIcon={FooterResetIcon} onClick={onFooterReset}>\n {footerResetLabel}\n </Button>\n );\n } else if (footerText != null) {\n footerStartNode = <p className=\"text-14 text-font-icon-5\">{footerText}</p>;\n }\n\n // 푸터 확인 버튼 Enter 실행(2026-07-14 지시) — 푸터가 있고 확인이 활성일 때 Enter=확인 클릭.\n // 내부 위젯(Select 옵션 확정 등)이 이미 처리한 Enter는 preventDefault돼 있어 건너뛴다(중복 실행 방지).\n const onConfirmEnter = (e: KeyboardEvent<HTMLDivElement>) => {\n if (!footer || !showConfirm) return;\n if (e.key !== 'Enter' || e.defaultPrevented) return;\n if (confirmDisabled || confirmLoading) return;\n e.preventDefault();\n onConfirm?.();\n };\n\n return (\n <div\n style={{ width: widthStyle }}\n onKeyDown={onConfirmEnter}\n // tabIndex -1: 비포커스 요소(라디오 행 여백 등) 클릭 시에도 패널이 포커스를 받아 Enter가 잡히게.\n // 시각 변화 없음 — 이 outline은 항상 지정돼 있어 브라우저 기본 포커스 링을 대체한다.\n tabIndex={footer && showConfirm ? -1 : undefined}\n className={`flex flex-col gap-spacing-1 overflow-hidden rounded-round-4 outline outline-1 outline-list-popover-outline bg-list-popover-bg shadow-[0px_2px_5px_0px_rgba(13,13,13,0.12)] ${className}`}\n {...props}\n >\n {/* search area — 흰 배경. popover(gray100)와 gap-spacing-1(1px) 틈이 구분선이 된다. */}\n {topArea === 'search' && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <SearchBar\n value={searchValue}\n onChange={onSearchChange}\n placeholder={searchPlaceholder}\n width=\"100%\"\n inputProps={searchInputProps}\n />\n </div>\n )}\n {/* input area — 검색 아이콘 없는 일반 텍스트 입력. search area와 동일한 패딩·구분선 규칙. */}\n {topArea === 'input' && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <Input\n value={inputValue}\n onChange={onInputChange}\n placeholder={inputPlaceholder}\n width=\"100%\"\n inputProps={inputProps}\n />\n </div>\n )}\n {children}\n {/* footer area — 흰 배경. 위쪽 gap-spacing-1(1px) 틈이 목록과의 구분선이 된다.\n 기본: 좌측 footerStart 슬롯 + 우측 취소/확인 버튼 그룹(justify-between).\n footerButtonsFill: 좌측 슬롯 없이 버튼 그룹을 fill(균등 분할 전체폭)로. */}\n {footer && (\n <div\n className={`flex w-full items-center bg-list-group-bg py-spacing-5 ${\n footerButtonsFill ? 'px-spacing-5' : 'justify-between pl-spacing-6 pr-spacing-5'\n }`}\n >\n {!footerButtonsFill && <div className=\"flex min-w-0 items-center\">{footerStartNode}</div>}\n <ButtonGroup\n gap=\"5\"\n width={footerButtonsFill ? 'fill' : 'hug'}\n className={footerButtonsFill ? '' : 'shrink-0'}\n >\n {showCancel && (\n <Button variant=\"line\" size=\"32\" onClick={onCancel}>\n {cancelText}\n </Button>\n )}\n {showConfirm && (\n <Button\n variant={confirmVariant}\n size=\"32\"\n disabled={confirmDisabled}\n loading={confirmLoading}\n onClick={onConfirm}\n >\n {confirmText}\n </Button>\n )}\n </ButtonGroup>\n </div>\n )}\n </div>\n );\n}\n\n// ── rich menu 조립 컴포넌트 (Figma popover menu type=rich menu, node 8416:38124) ──────────\n// 선택 항목이 아주 많을 때 쓰는 다중 컬럼 메뉴. PopoverMenu(넓은 width) 안에서\n// Columns(가로) > Column(세로) > Section(카테고리 타이틀+행들) 순으로 조립하고,\n// 행은 기존 List(rightButton 등)를 그대로 쓴다. 컬럼·섹션 사이 1px 틈(popover bg)이 구분선.\n//\n// <PopoverMenu width={804}>\n// <PopoverMenuColumns>\n// <PopoverMenuColumn>\n// <PopoverMenuSection title=\"인적사항\">\n// <List title=\"현주소\" rightButton rightButtonIcon={Plus} rightButtonAriaLabel=\"추가\" ... />\n// </PopoverMenuSection>\n// </PopoverMenuColumn>\n// …\n// </PopoverMenuColumns>\n// </PopoverMenu>\n\n// 가로 컬럼 컨테이너 — 컬럼들을 균등 폭으로 나란히, 1px 틈이 세로 구분선\nexport function PopoverMenuColumns({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n return (\n <div className={`flex w-full items-stretch gap-spacing-1 ${className}`} {...props}>\n {children}\n </div>\n );\n}\n\n// 세로 컬럼 — 섹션들을 위에서 아래로, 1px 틈이 가로 구분선. 마지막 섹션이 남는 높이를 채운다\nexport function PopoverMenuColumn({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n return (\n <div className={`flex min-w-0 flex-1 flex-col gap-spacing-1 ${className}`} {...props}>\n {children}\n </div>\n );\n}\n\n// 카테고리 섹션 — 회색 타이틀 + 행(List)들. grow=true면 컬럼의 남는 높이를 채운다(마지막 섹션용)\ninterface PopoverMenuSectionProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n title?: ReactNode;\n grow?: boolean;\n}\n\nexport function PopoverMenuSection({ title, children, grow = false, className = '', ...props }: PopoverMenuSectionProps) {\n return (\n <div className={`w-full bg-list-group-bg py-spacing-4 ${grow ? 'min-h-0 flex-1' : ''} ${className}`} {...props}>\n {title != null && (\n <p className=\"px-spacing-6 py-spacing-2 text-12 text-font-icon-3\">{title}</p>\n )}\n {children}\n </div>\n );\n}\n","// ListEmpty — 목록이 비었을 때 표시 (Figma option list / list empty)\n// ListGroup이 항목 0개(또는 empty)일 때 그룹 안에서 이 컴포넌트를 렌더한다(빈 상태 메시지).\n// 단독으로도 쓸 수 있다. 색은 list-empty-text 시멘틱 토큰.\n\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\ninterface ListEmptyProps extends ComponentPropsWithoutRef<'div'> {\n message?: ReactNode;\n}\n\nexport function ListEmpty({ message = '검색 결과가 없습니다.', className = '', ...props }: ListEmptyProps) {\n return (\n <div\n className={`flex w-full items-center justify-center bg-list-empty-bg px-spacing-6 py-spacing-9 text-14 text-list-empty-text ${className}`}\n {...props}\n >\n {message}\n </div>\n );\n}\n","// ListGroup — List 묶음 (Figma option list / list group)\n// 상하 패딩(py-spacing-4) 안에서 List들을 세로로 쌓고, 개수가 maxVisible(기본 6)을\n// 넘으면 내부 스크롤한다. 스크롤바는 공용 ScrollArea(커스텀 오버레이 스크롤바)를 사용.\n// 항목이 없으면(자식 0개 또는 empty=true) 같은 컨테이너 안(스크롤 영역 내부)에\n// ListEmpty를 렌더해, 빈 상태도 그룹의 패딩·배경을 그대로 따른다.\nimport { Children } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { ScrollArea } from './ScrollArea';\nimport { ListEmpty } from './ListEmpty';\n\nconst ITEM_HEIGHT = 32; // List 한 행의 표시 높이(px) — min-h-32\nconst PADDING_Y = 6; // 상하 패딩(px) — spacing-4\n\n// 행 간격(gap) 토큰 키 → 클래스 (Tailwind purge 안전한 정적 매핑). 미지정=0(붙여 쌓음).\nconst GAP_STYLE = {\n '2': 'gap-spacing-2', // 2px\n '3': 'gap-spacing-3', // 4px\n};\nconst GAP_PX = { '2': 2, '3': 4 };\n\ninterface ListGroupProps extends ComponentPropsWithoutRef<'div'> {\n maxVisible?: number;\n gap?: keyof typeof GAP_STYLE; // 행 간격 토큰 키 — '2'=2px | '3'=4px. 미지정 시 붙여 쌓음(0px)\n empty?: boolean;\n emptyMessage?: ReactNode;\n}\n\nexport function ListGroup({ children, maxVisible = 6, gap, empty = false, emptyMessage, className = '', ...props }: ListGroupProps) {\n // gap 지정 시 행 간격 반영(라디오 리스트 등) — '2'=2px | '3'=4px. 미지정 시 기존처럼 붙여 쌓음(0px).\n const gapPx = gap ? GAP_PX[gap] : 0;\n const maxHeight = maxVisible * ITEM_HEIGHT + gapPx * (maxVisible - 1) + PADDING_Y * 2;\n const isEmpty = empty || Children.count(children) === 0;\n\n return (\n <div className={`w-full bg-list-group-bg ${className}`} {...props}>\n {/* 상하 패딩을 스크롤 영역 안(contentClassName)에 두어, 스크롤 시 패딩까지 함께 움직인다 */}\n <ScrollArea maxHeight={maxHeight} contentClassName={`py-spacing-4 ${gap ? `flex flex-col ${GAP_STYLE[gap]}` : ''}`}>\n {isEmpty ? <ListEmpty message={emptyMessage} /> : children}\n </ScrollArea>\n </div>\n );\n}\n","// Tag — 작은 칩 라벨 (Figma option list / tag)\n// color 속성으로 색을 바꾼다: 'blue' | 'red' | 'gray' | 'black'. lookup 객체로 분기. (Figma Tag의 color variant와 동일)\n// black = gray500 솔리드 배경 + 흰 텍스트(Figma 변형명 'color4', Chip black과 동일 값)\n// width 속성으로 너비를 바꾼다: 'hug'(콘텐츠 맞춤, 기본) | 'fill'(부모 폭 채움).\n// 색은 tag-* 시멘틱 토큰(base 경유)만 사용.\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst COLOR_STYLE = {\n blue: 'bg-tag-blue-bg text-tag-blue-text',\n red: 'bg-tag-red-bg text-tag-red-text',\n gray: 'bg-tag-gray-bg text-tag-gray-text',\n black: 'bg-tag-black-bg text-tag-black-text',\n};\n\ninterface TagProps extends ComponentPropsWithoutRef<'span'> {\n color?: keyof typeof COLOR_STYLE;\n width?: 'hug' | 'fill';\n}\n\nexport function Tag({\n children = '태그',\n color = 'blue',\n width = 'hug', // 'hug' | 'fill'\n className = '',\n ...props\n}: TagProps) {\n const colorStyle = COLOR_STYLE[color] ?? COLOR_STYLE.blue;\n const sizing = width === 'fill' ? 'flex w-full' : 'inline-flex shrink-0';\n\n return (\n <span\n className={`${sizing} items-center justify-center rounded-round-4 px-spacing-4 py-spacing-1 text-12 ${colorStyle} ${className}`}\n {...props}\n >\n {children}\n </span>\n );\n}\n\n// NewTag — 원형 'N' 뱃지 (Figma new tag, node 8187:40848). 신규 항목 표시용.\n// 18×18 고정 원형(rounded-round-00) + semibold 12 텍스트. 색은 new-tag-* 시멘틱 토큰만 사용. (22→20→18)\nconst NEW_TAG_BG = {\n blue: 'bg-new-tag-blue-bg',\n red: 'bg-new-tag-red-bg',\n black: 'bg-new-tag-black-bg',\n};\n\ninterface NewTagProps extends ComponentPropsWithoutRef<'span'> {\n color?: keyof typeof NEW_TAG_BG; // 'blue' | 'red' | 'black'\n}\n\nexport function NewTag({\n color = 'blue', // 'blue' | 'red' | 'black'\n children = 'N', // 뱃지 글자(기본 'N')\n className = '',\n ...props\n}: NewTagProps) {\n return (\n <span\n className={`inline-flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-round-00 text-12 font-semibold text-new-tag-title ${\n NEW_TAG_BG[color] ?? NEW_TAG_BG.blue\n } ${className}`}\n {...props}\n >\n {children}\n </span>\n );\n}\n","// Radio — 라디오 버튼 (Figma radio, node 7368:1243)\n// 상태: unselected/selected × default/hover/disabled (6가지)\n// - unselected: 회색 원 / selected: blue 원 + 흰 점(point)\n// - hover: 바깥 ring(radio-*-hover-outline) / disabled: 연하게(selected disabled = 연한 blue + blue 점)\n// 색은 radio-* 시멘틱 토큰(base 경유)만 사용. 체크박스와 구조는 같으나 원형 + 가운데 점.\n// - Radio : 단일 라디오 버튼 1개(controlled/uncontrolled 모두 지원)\n// - RadioGroup : name 공유로 단일 선택을 관리하는 컨테이너\nimport { useId, useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { OptionItem } from './formulaFunctions';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — SegmentControl/ButtonGroup과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px (기본 — 라디오 항목 간격)\n};\n\n// ...props는 sr-only <input type=\"radio\">로 전달된다.\ninterface RadioProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Radio({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n name,\n value,\n className = '',\n ...props\n}: RadioProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isChecked = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 원 바깥에 간격(offset)을 두고 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 링(2026-07-15) — sr-only input이 포커스를 가지므로 peer로 원에 표시(hover 링과 동일 색)\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let boxClass;\n if (disabled) {\n boxClass = isChecked ? 'bg-radio-selected-disabled-bg' : 'bg-radio-disabled-bg';\n } else if (isChecked) {\n boxClass = `bg-radio-selected-bg ${HOVER_RING} group-hover:ring-radio-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-radio-selected-hover-outline`;\n } else {\n boxClass = `bg-radio-unselected-bg ${HOVER_RING} group-hover:ring-radio-hover-outline ${FOCUS_RING} peer-focus-visible:ring-radio-hover-outline`;\n }\n\n const pointColor = disabled ? 'bg-radio-disabled-point' : 'bg-radio-point';\n const textColor = disabled ? 'text-radio-disabled-text' : 'text-radio-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"radio\"\n className=\"peer sr-only\"\n checked={isChecked}\n onChange={handleChange}\n disabled={disabled}\n name={name}\n value={value}\n {...props}\n />\n <span\n className={`flex h-spacing-7 w-spacing-7 shrink-0 items-center justify-center rounded-round-00 transition-shadow ${boxClass}`}\n >\n {/* 가운데 점은 항상 렌더하고 visibility로만 토글 — 선택/해제 시 레이아웃이 흔들리지 않게 */}\n <span className={`h-spacing-4 w-spacing-4 rounded-round-00 ${pointColor} ${isChecked ? '' : 'invisible'}`} />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface RadioGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: OptionItem[]; // [{ value, label, disabled }]\n value?: string; // controlled 선택값\n defaultValue?: string; // uncontrolled 초기 선택값\n onChange?: (value: string) => void; // (value) => void\n name?: string; // 라디오 그룹 name (미지정 시 자동 생성)\n direction?: 'vertical' | 'horizontal';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '7'(16px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function RadioGroup({\n items = [], // [{ value, label, disabled }]\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n name, // 라디오 그룹 name (미지정 시 자동 생성)\n direction = 'vertical', // 'vertical' | 'horizontal'\n gap = '7', // 간격 토큰 키 — 기본 '7'(16px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: RadioGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = value !== undefined ? value : internal;\n\n // name 미지정 시 그룹 인스턴스마다 고유 name 생성 — useId(SSR/hydration 안전, Math.random은 서버·클라 불일치)\n const autoName = useId();\n const groupName = name ?? `radio-group-${autoName}`;\n\n const handleSelect = (val: string) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['7'];\n const dirStyle = direction === 'horizontal' ? 'flex-row items-center' : 'flex-col items-start';\n\n return (\n <div\n role=\"radiogroup\"\n className={`inline-flex ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <Radio\n key={item.value}\n name={name ?? groupName}\n value={item.value}\n label={item.label}\n checked={selected === item.value}\n disabled={disabled || item.disabled}\n onChange={() => handleSelect(item.value)}\n />\n ))}\n </div>\n );\n}\n","// Switch — 스위치 토글 (Figma switch, node 7370:115)\n// 상태: off(unselected)/on(selected) × default/hover/disabled (6가지)\n// - off: 회색 track + 흰 thumb(좌측) / on: blue track + 흰 thumb(우측)\n// - hover: 바깥 ring(switch-*-hover-outline)\n// - disabled(그림자 없음): off=연한 회색 track + 진회색 thumb / on=연한 blue track + blue thumb\n// 색은 switch-* 시멘틱 토큰(base 경유)만 사용. controlled/uncontrolled 모두 지원.\n// track 28×16, thumb 12×10, 이동 거리 8px(spacing-5)을 translate로 애니메이션.\nimport { useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\n\n// ...props는 sr-only <input type=\"checkbox\" role=\"switch\">로 전달된다.\ninterface SwitchProps extends ComponentPropsWithoutRef<'input'> {\n label?: ReactNode;\n}\n\nexport function Switch({\n checked,\n defaultChecked = false,\n onChange,\n disabled = false,\n label,\n className = '',\n ...props\n}: SwitchProps) {\n const isControlled = checked !== undefined;\n const [internal, setInternal] = useState(defaultChecked);\n const isOn = isControlled ? checked : internal;\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternal(e.target.checked);\n onChange?.(e);\n };\n\n // hover ring은 track 바깥에 간격(offset)을 두고 표시\n const HOVER_RING = 'group-hover:ring-2 group-hover:ring-offset-1';\n // 키보드 포커스 = hover와 동일 디자인(2026-07-16 지시) — peer-focus-visible로 track에 hover 링\n const FOCUS_RING = 'peer-focus-visible:ring-2 peer-focus-visible:ring-offset-1';\n let trackClass;\n if (disabled) {\n trackClass = isOn ? 'bg-switch-selected-disabled-bg' : 'bg-switch-disabled-bg';\n } else if (isOn) {\n trackClass = `bg-switch-selected-bg ${HOVER_RING} group-hover:ring-switch-selected-hover-outline ${FOCUS_RING} peer-focus-visible:ring-switch-selected-hover-outline`;\n } else {\n trackClass = `bg-switch-unselected-bg ${HOVER_RING} group-hover:ring-switch-hover-outline ${FOCUS_RING} peer-focus-visible:ring-switch-hover-outline`;\n }\n\n // thumb 색 — disabled는 그림자 없음(on=blue, off=진회색), 그 외 흰 thumb + 그림자\n let thumbColor;\n if (disabled) {\n thumbColor = isOn ? 'bg-switch-disabled-thumb' : 'bg-switch-disabled-thumb-off';\n } else {\n thumbColor = 'bg-switch-thumb shadow';\n }\n const textColor = disabled ? 'text-switch-disabled-text' : 'text-switch-text';\n\n return (\n <label\n className={`group relative inline-flex items-center gap-spacing-4 ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <input\n type=\"checkbox\"\n role=\"switch\"\n className=\"peer sr-only\"\n checked={isOn}\n onChange={handleChange}\n disabled={disabled}\n {...props}\n />\n <span\n className={`relative h-[16px] w-[28px] shrink-0 rounded-round-5 transition-[background-color,box-shadow] ${trackClass}`}\n >\n <span\n className={`absolute left-spacing-3 top-1/2 h-[10px] w-[12px] -translate-y-1/2 rounded-round-4 transition-transform ${thumbColor} ${\n isOn ? 'translate-x-spacing-5' : 'translate-x-0'\n }`}\n />\n </span>\n {label != null && <span className={`text-14 ${textColor}`}>{label}</span>}\n </label>\n );\n}\n","// List — 옵션 목록의 한 행 (Figma option list / list, node 7206:9327)\n// 상태: default / hover / pressed / selected / disabled\n// - hover/pressed는 CSS(:hover/:active), selected·disabled는 props\n// - selected 시 chevron·아이콘이 파란색(list-selected-icon)\n// 요소(필요에 따라 on/off):\n// left : tag · checkbox · icon(lucide) · title\n// right : switch · rightButton(고스트 ⋯) · endIcon(chevron)\n// 색은 list-* 시멘틱 토큰만 사용. checkbox/switch는 공용 컴포넌트 재사용.\n// checkbox가 있으면 행 전체가 체크박스 클릭 영역이 된다(행 어디를 눌러도 토글). switch는 행 클릭과 분리(stopPropagation).\nimport { ChevronRight, MoreHorizontal } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, MouseEvent, ReactNode } from 'react';\nimport { Tag } from './Tag';\nimport { Checkbox } from './Checkbox';\nimport { Radio } from './Radio';\nimport { Switch } from './Switch';\nimport { TruncatingText } from './TruncatingText';\n\n// 체크박스/라디오/스위치 변경 이벤트 — 행 클릭 합성 이벤트({ target:{ checked } })와 실제 change 이벤트 겸용\ntype CheckChangeEvent = { target: { checked: boolean } };\n\ninterface ListProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n title?: ReactNode;\n tag?: boolean; // 태그 표시 여부\n tagText?: ReactNode;\n icon?: LucideIcon | null; // 왼쪽 lucide 아이콘 (예: User)\n checkbox?: boolean; // 왼쪽 체크박스 표시(다중 선택)\n radio?: boolean; // 왼쪽 라디오 표시(단일 선택) — checkbox와 배타. checked/onCheckChange 공용\n checked?: boolean; // 체크박스/라디오 선택 상태\n onCheckChange?: (e: CheckChangeEvent) => void; // (e) => void — 체크박스/라디오 변경(e.target.checked)\n showSwitch?: boolean; // 오른쪽 스위치 표시\n switchChecked?: boolean; // 스위치 상태\n onSwitchChange?: (e: CheckChangeEvent) => void; // (e) => void — 스위치 변경\n rightButton?: boolean; // 고스트 아이콘 버튼 (기본 ⋯)\n rightButtonIcon?: LucideIcon; // 우측 버튼 아이콘(lucide) — 예: Trash2(삭제)\n rightButtonAriaLabel?: string; // 우측 버튼 aria-label\n endIcon?: boolean; // chevron-right\n rightSlot?: ReactNode; // 우측 임의 콘텐츠(입력칸+단위 등) — 행 클릭과 분리(자체 stopPropagation은 사용처가)\n selected?: boolean;\n highlighted?: boolean; // 키보드 내비게이션 강조 (hover 색)\n disabled?: boolean;\n onButtonClick?: (e: MouseEvent<HTMLButtonElement>) => void;\n}\n\nexport function List({\n title = 'list',\n tag = false, // 태그 표시 여부\n tagText = '태그',\n icon: Icon = null, // 왼쪽 lucide 아이콘 (예: User)\n checkbox = false, // 왼쪽 체크박스 표시(다중 선택)\n radio = false, // 왼쪽 라디오 표시(단일 선택) — checkbox와 배타. checked/onCheckChange 공용\n checked = false, // 체크박스/라디오 선택 상태\n onCheckChange, // (e) => void — 체크박스/라디오 변경(e.target.checked)\n showSwitch = false, // 오른쪽 스위치 표시\n switchChecked = false, // 스위치 상태\n onSwitchChange, // (e) => void — 스위치 변경\n rightButton = false, // 고스트 아이콘 버튼 (기본 ⋯)\n rightButtonIcon: RightButtonIcon = MoreHorizontal, // 우측 버튼 아이콘(lucide) — 예: Trash2(삭제)\n rightButtonAriaLabel = '더보기', // 우측 버튼 aria-label\n endIcon = false, // chevron-right\n rightSlot = null, // 우측 임의 콘텐츠(입력칸+단위 등) — 행 클릭과 분리(자체 stopPropagation은 사용처가)\n selected = false,\n highlighted = false, // 키보드 내비게이션 강조 (hover 색)\n disabled = false,\n onClick,\n onButtonClick,\n className = '',\n ...props\n}: ListProps) {\n const interactive = !disabled;\n // 라디오 행은 hover/pressed 배경 없음(선택 표시는 라디오가 담당) + 내부 상하 패딩 제거(2026-07-09 지시)\n const noHover = radio;\n\n const rowBg = disabled\n ? 'bg-list-default-bg cursor-not-allowed'\n : highlighted && !noHover\n ? 'bg-list-hover-bg cursor-pointer' // 키보드 강조 우선\n : selected || noHover\n ? 'bg-list-default-bg cursor-pointer' // 선택·라디오: hover/pressed 효과 없음\n : 'bg-list-default-bg cursor-pointer hover:bg-list-hover-bg active:bg-list-pressed-bg';\n\n const titleColor = disabled\n ? 'text-list-disabled-text'\n : selected\n ? 'text-list-selected-text'\n : 'text-list-default-text';\n const ellipsisColor = disabled ? 'text-list-disabled-icon' : 'text-list-default-icon';\n // 아이콘(왼쪽 lucide · 오른쪽 chevron) 공통 색: disabled→회색, selected→파랑, 그 외 기본\n const iconColor = disabled\n ? 'text-list-disabled-icon'\n : selected\n ? 'text-list-selected-icon'\n : 'text-list-default-icon';\n\n // 체크박스·스위치는 행 onClick과 분리한다(직접 클릭 시 각자 onChange만 발화 → 행 핸들러로 전파 차단)\n const stop = (e: MouseEvent<HTMLSpanElement>) => e.stopPropagation();\n\n // 체크박스/라디오가 있으면 행 전체가 클릭 영역이 된다 — 행 어디를 눌러도 체크박스는 토글, 라디오는 선택.\n // (컨트롤을 직접 누른 경우는 stop으로 전파가 막혀 여기로 오지 않으므로 이중 처리가 없다.)\n const handleRowClick = (e: MouseEvent<HTMLDivElement>) => {\n if (!interactive) return;\n if (checkbox) onCheckChange?.({ target: { checked: !checked } });\n else if (radio && !checked) onCheckChange?.({ target: { checked: true } }); // 라디오는 선택만(해제 없음)\n onClick?.(e);\n };\n\n return (\n <div\n role=\"option\"\n aria-selected={selected || undefined}\n aria-disabled={disabled || undefined}\n onClick={interactive ? handleRowClick : undefined}\n className={`flex min-h-[32px] w-full items-center justify-between px-spacing-6 ${\n noHover ? '' : 'py-spacing-3'\n } transition-colors ${rowBg} ${className}`}\n {...props}\n >\n {/* left: tag · checkbox · icon · title (말줄임 시 hover 툴팁 — TruncatingText) */}\n <div className=\"flex min-w-0 flex-1 items-center gap-spacing-4\">\n {tag && <Tag>{tagText}</Tag>}\n {checkbox && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Checkbox checked={checked} onChange={onCheckChange} disabled={disabled} />\n </span>\n )}\n {radio && !checkbox && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Radio checked={checked} onChange={onCheckChange} disabled={disabled} />\n </span>\n )}\n {Icon && <Icon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />}\n <TruncatingText className={`min-w-0 flex-1 text-14 ${titleColor}`}>\n {title}\n </TruncatingText>\n </div>\n\n {/* right: 임의 슬롯 · switch · 고스트 ⋯ 버튼 · chevron */}\n {(rightSlot || showSwitch || rightButton || endIcon) && (\n <div className=\"flex shrink-0 items-center gap-spacing-2\">\n {rightSlot}\n {showSwitch && (\n <span className=\"flex shrink-0 items-center\" onClick={stop}>\n <Switch checked={switchChecked} onChange={onSwitchChange} disabled={disabled} />\n </span>\n )}\n {rightButton && (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n onButtonClick?.(e);\n }}\n className=\"flex min-h-[24px] min-w-[24px] items-center justify-center rounded-round-4 p-spacing-2 opacity-80 transition-colors hover:bg-button-ghost-hover-bg focus:outline-none focus-visible:bg-button-ghost-hover-bg disabled:cursor-not-allowed\"\n aria-label={rightButtonAriaLabel}\n >\n <RightButtonIcon size={14} strokeWidth={1.8} className={ellipsisColor} />\n </button>\n )}\n {endIcon && <ChevronRight size={16} strokeWidth={1.8} className={iconColor} />}\n </div>\n )}\n </div>\n );\n}\n","// usePopoverPosition — 트리거(anchorRef) 기준으로 떠 있는 패널(menuRef)의 fixed 위치를 계산.\n// Select·Popover가 공유하는 드롭다운 위치 로직(공간에 따른 위/아래·좌/우 자동 반전, portal 전제).\n// - placement: 'auto' | '{top|bottom|auto}-{left|right|auto}' (축별 auto 지원)\n// - menuWidth: 숫자(px)/CSS 길이. 미지정 시 트리거 너비\n// - deps: 패널 높이/너비를 바꾸는 값(검색 결과 개수 등)을 넘기면 재계산\n// open=false면 null을 반환한다(패널 미표시).\nimport { useState, useLayoutEffect } from 'react';\nimport type { CSSProperties, RefObject } from 'react';\n\ninterface UsePopoverPositionOptions {\n open: boolean;\n anchorRef: RefObject<HTMLElement | null>;\n menuRef: RefObject<HTMLElement | null>;\n placement?: string; // 'auto' | '{top|bottom|auto}-{left|right|auto}'\n menuWidth?: number | string;\n deps?: unknown[];\n}\nimport { spacing } from '../tokens';\n\n// 트리거 ↔ 패널 간격 — spacing-3(4px) 토큰\nconst MENU_GAP = parseInt(spacing['spacing-3'], 10);\n\nexport function usePopoverPosition({ open, anchorRef, menuRef, placement = 'auto', menuWidth, deps = [] }: UsePopoverPositionOptions): CSSProperties | null {\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n\n useLayoutEffect(() => {\n if (!open) {\n // 닫힐 때 위치 스타일 리셋 — 의도된 effect 내 setState\n setMenuStyle(null); // eslint-disable-line react-hooks/set-state-in-effect\n return;\n }\n const measure = () => {\n const anchor = anchorRef.current;\n if (!anchor) return;\n const tr = anchor.getBoundingClientRect();\n const menu = menuRef.current;\n const menuH = menu?.offsetHeight ?? 0;\n const gap = MENU_GAP;\n // 패널 너비(숫자) — 위치 계산용. 숫자/미지정은 즉시 알 수 있고, 문자열만 측정 fallback.\n const w =\n menuWidth == null\n ? tr.width\n : typeof menuWidth === 'number'\n ? menuWidth\n : (menu?.offsetWidth ?? tr.width);\n const widthCss = menuWidth != null && typeof menuWidth !== 'number' ? menuWidth : `${w}px`;\n\n let vertical: string;\n let horizontal: string;\n if (placement === 'auto') {\n vertical = 'auto';\n horizontal = 'auto';\n } else {\n [vertical, horizontal] = placement.split('-');\n }\n // 축별 auto — 'auto-right'(상하 자동·우측 고정), 'bottom-auto'(하단 고정·좌우 자동) 등 부분 자동 지원\n if (vertical === 'auto') {\n const spaceBelow = window.innerHeight - tr.bottom;\n const spaceAbove = tr.top;\n vertical = spaceBelow < menuH + gap && spaceAbove > spaceBelow ? 'top' : 'bottom';\n }\n if (horizontal === 'auto') {\n horizontal = tr.left + w > window.innerWidth ? 'right' : 'left';\n }\n\n const top = vertical === 'top' ? tr.top - gap - menuH : tr.bottom + gap;\n const left = horizontal === 'right' ? tr.right - w : tr.left;\n // 값이 같으면 이전 객체를 유지 — 참조 변경으로 인한 팝오버 서브트리 불필요 리렌더 방지\n setMenuStyle((prev) =>\n prev && prev.top === top && prev.left === left && prev.width === widthCss\n ? prev\n : { position: 'fixed' as const, top, left, width: widthCss },\n );\n };\n measure(); // 열림 직후 1회는 동기 측정 — 첫 프레임에 올바른 위치로 표시\n // scroll(capture, 문서 내 모든 스크롤에 발화)·resize는 rAF로 코얼레싱 —\n // 프레임당 1회만 측정/재계산해 스크롤 중 측정+리렌더 반복을 줄인다(2026-07-07 감사)\n let raf = 0;\n const schedule = () => {\n if (raf) return;\n raf = requestAnimationFrame(() => {\n raf = 0;\n measure();\n });\n };\n window.addEventListener('resize', schedule);\n window.addEventListener('scroll', schedule, true);\n return () => {\n if (raf) cancelAnimationFrame(raf);\n window.removeEventListener('resize', schedule);\n window.removeEventListener('scroll', schedule, true);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, placement, menuWidth, ...deps]);\n\n return menuStyle;\n}\n","// useOutsideDismiss — 열린 팝오버/드롭다운의 \"바깥 클릭 닫기\" 공용 훅 (Popover·Select 공유)\n// capture 단계 mousedown에서 바깥 클릭을 선점해:\n// ① 메뉴만 닫고(onDismiss)\n// ② 그 mousedown과 이어지는 click을 삼켜 아래 요소(버튼 등)가 함께 클릭되지 않게 한다(click-through 방지).\n// 첫 클릭 = 닫기 전용, 두 번째 클릭부터 평소처럼 동작한다. (Esc capture 처리와 동일한 우선순위 원칙)\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\n\ninterface UseOutsideDismissOptions {\n open: boolean;\n refs: RefObject<HTMLElement | null>[]; // 트리거·패널 등 '안쪽'으로 볼 요소들\n onDismiss: (e: MouseEvent) => void;\n guard?: (e: MouseEvent) => boolean; // false면 이번 클릭은 무시(닫지도 삼키지도 않음)\n}\n\nexport function useOutsideDismiss({ open, refs, onDismiss, guard }: UseOutsideDismissOptions) {\n useEffect(() => {\n if (!open) return;\n const onDown = (e: MouseEvent) => {\n const inside = refs.some((r) => r.current && r.current.contains(e.target as Node));\n if (inside) return;\n if (guard && !guard(e)) return; // 예: 중첩 팝오버 — 맨 위가 아니면 닫지 않음(삼키지도 않음)\n // 바깥 클릭 — 닫기만 하고 클릭은 삼킨다.\n // stopImmediatePropagation: 한 mousedown에 dismiss는 '하나만' — 닫힘이 동기 flush되면\n // 스택이 줄어 다음 리스너(부모 팝오버)가 자기를 맨 위로 오인해 연쇄로 닫히는 것을 차단.\n e.preventDefault();\n e.stopPropagation();\n e.stopImmediatePropagation();\n const swallow = (ce: Event) => {\n ce.stopPropagation();\n ce.preventDefault();\n };\n document.addEventListener('click', swallow, { capture: true, once: true });\n // 드래그아웃 등으로 click이 발생하지 않는 경우 잔여 리스너 정리\n setTimeout(() => document.removeEventListener('click', swallow, true), 300);\n // preventDefault로 포커스 이동까지 막히므로, 트리거/패널 안에 남은 포커스는 직접 blur해\n // 원래 일어났을 blur(입력 확정·focused 해제)를 복원한다 — 안 하면 인풋이 활성 상태로 남아\n // 재클릭 시 focus 이벤트가 없어 팝오버가 다시 열리지 않는다(DateField 등 onFocus 오픈형).\n const active = document.activeElement;\n if (\n active instanceof HTMLElement &&\n refs.some((r) => r.current && r.current.contains(active))\n )\n active.blur();\n onDismiss(e);\n };\n document.addEventListener('mousedown', onDown, true);\n return () => document.removeEventListener('mousedown', onDown, true);\n });\n}\n","// popoverLayers — 열려 있는 팝오버/드롭다운 패널(portal) 레이어 스택 (Popover·Select 공유)\n// 패널은 각자 portal이라 DOM 포함 관계로는 중첩을 알 수 없다 → 연 순서 스택으로 \"맨 위\" 판정.\n// - 외부 클릭: 맨 위 레이어 하나만 닫는다(아래 레이어는 자기 차례가 올 때까지 유지)\n// - Esc: 맨 위 레이어만 닫는다(중첩 팝오버·팝오버 안 셀렉트가 한 번에 다 닫히지 않게)\n// 원래 Popover.jsx 내부 배열이었으나, 팝오버 패널 안에 Select(드롭다운도 portal)를 넣는\n// 복수 조건 설정(2026-07-16)에서 Select도 같은 스택에 서야 해 공용 모듈로 추출했다.\n// 레이어 식별자 — 각 패널의 menuRef 객체(동일성 비교만 하므로 내용 타입은 무관)\ntype LayerRef = object;\n\nconst layers: LayerRef[] = [];\n\nexport function pushPopoverLayer(ref: LayerRef) {\n layers.push(ref);\n}\n\nexport function removePopoverLayer(ref: LayerRef) {\n const i = layers.indexOf(ref);\n if (i >= 0) layers.splice(i, 1);\n}\n\nexport function isTopPopoverLayer(ref: LayerRef) {\n return layers[layers.length - 1] === ref;\n}\n\n// 열린 레이어가 하나라도 있는지 — 필요 시 전역 판단용\nexport function hasPopoverLayers() {\n return layers.length > 0;\n}\n","// Select — 드롭다운 선택 (Figma 02_textfield / select)\n// 트리거는 Input과 동일한 필드 컨테이너 + chevron, 펼친 목록은 커스텀 PopoverMenu(옵션 목록).\n// native <select> 대신 직접 구현해 디자인 시스템 목록(List/ListGroup)을 그대로 사용한다.\n//\n// 기능:\n// - 키보드: ↑/↓ 이동, Enter/Space 열기·선택, Esc 닫기, Tab 닫기\n// - 외부 클릭 시 닫힘\n// - 선택 항목은 List selected(파란색), 키보드 강조는 List highlighted(hover 색)\n// - multiple: 체크박스 다중 선택 — value는 배열, 행 클릭=토글(메뉴 유지),\n// 트리거에는 선택 라벨을 ', '로 이어 표시하고 넘치면 말줄임(기존 TruncatingText 재사용)\n// 색은 tf-* 시멘틱 토큰(트리거)과 list-* 토큰(목록)을 사용.\nimport { useState, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from 'react';\nimport type {\n ComponentPropsWithoutRef,\n KeyboardEvent as ReactKeyboardEvent,\n ReactNode,\n RefObject,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { ChevronDown } from 'lucide-react';\nimport { Tooltip } from './Tooltip';\nimport { TruncatingText } from './TruncatingText';\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport type { ChipColor } from './chipStyles';\nimport { InlineFieldTrigger } from './InlineFieldTrigger';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { ListEmpty } from './ListEmpty';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useOutsideDismiss } from './useOutsideDismiss';\nimport { pushPopoverLayer, removePopoverLayer, isTopPopoverLayer } from './popoverLayers';\nimport type { OptionItem } from './formulaFunctions';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 모두 2px(border-2 토큰), 색은 hover-line(gray-900 알파) 공통.\n// 포커스 링은 focus-visible(키보드 포커스)에만 — 마우스 클릭/프레스 후 링이 남지 않게(2026-07-16 지시)\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-visible:ring-2';\n\n// 드롭다운 배치 — usePopoverPosition 규약('auto' | '{top|bottom|auto}-{left|right|auto}')\nexport type SelectPlacement = 'auto' | `${'top' | 'bottom' | 'auto'}-${'left' | 'right' | 'auto'}`;\n\n// 내부 라벨(label) 구분자 아이콘 — 세로 점 2개(2×7). lucide에 없는 커스텀 셰이프라\n// Figma 원본(Ellipse, 8219:76216) path를 인라인 SVG로 가져왔다(2026-07-07 개정 — 콜론 텍스트 대체).\n// 색은 currentColor — 사용처에서 시멘틱 토큰 클래스(text-font-icon-3 = #878787, Figma 값)로 지정.\nfunction LabelSeparatorIcon({ className = '' }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 2 7\"\n width={2}\n height={7}\n fill=\"currentColor\"\n aria-hidden=\"true\"\n className={`shrink-0 ${className}`}\n >\n <path d=\"M2 1C2 1.55228 1.55228 2 1 2C0.447715 2 0 1.55228 0 1C0 0.447715 0.447715 0 1 0C1.55228 0 2 0.447715 2 1Z\" />\n <path d=\"M2 6C2 6.55228 1.55228 7 1 7C0.447715 7 0 6.55228 0 6C0 5.44772 0.447715 5 1 5C1.55228 5 2 5.44772 2 6Z\" />\n </svg>\n );\n}\n\n// 공통 props — ...props는 루트 컨테이너(div)로 전달된다.\ninterface SelectBaseProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {\n options?: OptionItem[]; // [{ value, label, disabled? }] — disabled 옵션은 목록에 비활성 행으로 표시(선택 불가)\n confirm?: boolean; // multiple 전용 — 선택을 draft로 들고 푸터(전체 선택+취소/확인)에서\n // 확인을 눌러야 onChange 반영(취소·외부 클릭은 폐기). Figma 8535:9239\n variant?: 'box' | 'text' | 'chip'; // 'box'(필드형, 기본) | 'text'(인라인 텍스트형 — 필터·문단 사이용)\n // | 'chip'(칩형 트리거 — Figma select chip 8219:81717, SelectChip으로 export)\n size?: '24' | '20'; // text variant 전용: '24'(14px) | '20'(12px) — box는 항상 14px\n weight?: 'normal' | 'semibold'; // chip variant 전용 텍스트 두께 — 'normal'(기본) | 'semibold'\n defaultOpen?: boolean; // 마운트 시 드롭다운을 연 상태로 시작(1회성 — 이후는 일반 동작)\n tabOpens?: boolean; // 트리거 포커스 상태에서 Tab=메뉴 열기(수식 키보드 체인용 — 일반 폼은 기본 꺼짐)\n color?: ChipColor; // chip variant 전용: 'gray' | 'red' | 'blue' | 'black' — Chip과 동일한\n // chip-* 시멘틱 토큰(CHIP_COLOR_CLASS) 재사용. pressed=default(Figma 동일)\n label?: ReactNode; // 내부 라벨(box 전용, Figma type=\"solid label\") — 값 선택 시 트리거에\n // \"라벨 ⋮ 값\"으로 표시(구분자=점 2개 아이콘, 라벨 고정·값만 말줄임).\n // placeholder 상태는 미표시. 외부 Label을 못 쓰는 좁은 배치의 대안(2026-07-07 Figma 개정).\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 트리거 너비: 숫자(px) | CSS 길이 | 'hug'(콘텐츠 맞춤). 미지정 시 200px\n maxWidth?: number | string; // 트리거 최대 너비(숫자 px/CSS 길이). hug일 때 제한용 — 넘으면 말줄임\n menuWidth?: number | string; // 드롭다운 너비: 숫자(px)/CSS 길이. 미지정 시 트리거와 동일\n placement?: SelectPlacement; // 'auto' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n searchable?: boolean; // 드롭다운 상단 검색바로 옵션 필터\n searchPlaceholder?: string;\n emptyMessage?: string;\n noResultMessage?: string;\n}\n\n// 값 계약 — onChange는 { target: { value } } 합성 페이로드(단일=string, multiple=string[])\nexport interface SelectSingleProps extends SelectBaseProps {\n multiple?: false;\n value?: string | null; // null=미선택(placeholder 표시)\n defaultValue?: string;\n onChange?: (e: { target: { value: string } }) => void;\n}\nexport interface SelectMultipleProps extends SelectBaseProps {\n multiple: true; // 체크박스 다중 선택 — value/defaultValue/onChange 값은 배열([value])\n value?: string[];\n defaultValue?: string[];\n onChange?: (e: { target: { value: string[] } }) => void;\n}\nexport type SelectProps = SelectSingleProps | SelectMultipleProps;\n\nexport function Select({\n value,\n defaultValue = '',\n onChange: onChangeProp,\n options = [], // [{ value, label, disabled? }] — disabled 옵션은 목록에 비활성 행으로 표시(선택 불가)\n multiple = false, // 체크박스 다중 선택 — value/defaultValue/onChange 값은 배열([value])\n confirm = false, // multiple 전용 — 선택을 draft로 들고 푸터(전체 선택+취소/확인)에서\n // 확인을 눌러야 onChange 반영(취소·외부 클릭은 폐기). Figma 8535:9239\n variant = 'box', // 'box'(필드형, 기본) | 'text'(인라인 텍스트형 — 필터·문단 사이용)\n // | 'chip'(칩형 트리거 — Figma select chip 8219:81717, SelectChip으로 export)\n size = '24', // text variant 전용: '24'(14px) | '20'(12px) — box는 항상 14px\n weight = 'normal', // chip variant 전용 텍스트 두께 — 'normal'(기본) | 'semibold'\n defaultOpen = false, // 마운트 시 드롭다운을 연 상태로 시작(1회성 — 이후는 일반 동작)\n tabOpens = false, // 트리거 포커스 상태에서 Tab=메뉴 열기(수식 키보드 체인용 — 일반 폼은 기본 꺼짐)\n color = 'gray', // chip variant 전용: 'gray' | 'red' | 'blue' | 'black' — Chip과 동일한\n // chip-* 시멘틱 토큰(CHIP_COLOR_CLASS) 재사용. pressed=default(Figma 동일)\n label = null, // 내부 라벨(box 전용, Figma type=\"solid label\") — 값 선택 시 트리거에\n // \"라벨 ⋮ 값\"으로 표시(구분자=점 2개 아이콘, 라벨 고정·값만 말줄임).\n // placeholder 상태는 미표시. 외부 Label을 못 쓰는 좁은 배치의 대안(2026-07-07 Figma 개정).\n placeholder = '선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 200, // 트리거 너비: 숫자(px) | CSS 길이 | 'hug'(콘텐츠 맞춤). 미지정 시 200px\n maxWidth, // 트리거 최대 너비(숫자 px/CSS 길이). hug일 때 제한용 — 넘으면 말줄임\n menuWidth, // 드롭다운 너비: 숫자(px)/CSS 길이. 미지정 시 트리거와 동일\n placement = 'auto', // 'auto' | 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'\n searchable = false, // 드롭다운 상단 검색바로 옵션 필터\n searchPlaceholder = '검색어를 입력하세요',\n emptyMessage = '옵션이 없습니다.',\n noResultMessage = '검색 결과가 없습니다.',\n className = '',\n ...props\n}: SelectProps) {\n // 단일/다중 onChange 유니언을 구현부 공용 시그니처로 좁힌다(외부 계약은 SelectProps가 보장 — 런타임 동일 참조)\n const onChange = onChangeProp as ((e: { target: { value: string | string[] } }) => void) | undefined;\n const isControlled = value !== undefined;\n // multiple인데 defaultValue가 배열이 아니면(기본 '') 빈 배열로 시작\n const [internal, setInternal] = useState<string | string[]>(() =>\n multiple && !Array.isArray(defaultValue) ? [] : defaultValue,\n );\n const current = value !== undefined ? value : internal;\n // multiple 전용 — 현재 선택 배열(비배열 방어)\n const selectedValues = useMemo(\n () => (multiple && Array.isArray(current) ? current : []),\n [multiple, current],\n );\n const [open, setOpen] = useState(defaultOpen); // defaultOpen: 마운트 시 메뉴 열림(키보드 체인 시작점 등)\n const isConfirm = multiple && confirm;\n const [draft, setDraft] = useState<string[]>([]); // confirm 모드 — 열려 있는 동안의 임시 선택(확인 시에만 반영)\n const searchInputRef = useRef<HTMLInputElement | null>(null);\n const skipTabOpenRef = useRef(false); // 옵션 순회 종료 직후의 Tab은 다시 열지 않고 통과(체인 진행)\n useEffect(() => {\n if (!open || !searchable) return undefined;\n // 패널이 위치 계산 전 hidden이라 autoFocus가 실패한다 — 한 틱 뒤 검색 인풋 포커스\n const t = setTimeout(() => searchInputRef.current?.focus(), 0);\n return () => clearTimeout(t);\n }, [open, searchable]);\n const [highlight, setHighlight] = useState(-1);\n const [query, setQuery] = useState('');\n // 트리거 텍스트 최대 너비(px) — 크롬 flex/grid truncate 보강용으로 명시 지정\n const [textMaxW, setTextMaxW] = useState<number | undefined>(undefined);\n\n // 트리거는 variant에 따라 div 또는 span(InlineFieldTrigger)이므로 HTMLElement로 두고 JSX에서 좁힌다\n const rootRef = useRef<HTMLElement | null>(null);\n const triggerRef = useRef<HTMLElement | null>(null);\n const menuRef = useRef<HTMLElement | null>(null);\n\n // text variant: 박스/보더 없는 인라인 텍스트형. 항상 hug(콘텐츠 맞춤) + maxWidth만 받는다.\n // disabled/readOnly/error는 box와 동일하게 지원(테이블 바디·폼용). 드롭다운·키보드·검색 동작도 동일.\n const isText = variant === 'text';\n // text variant fill — 부모 전체 폭(라벨 왼쪽·chevron 오른쪽 끝). width='fill'일 때만.\n const isTextFill = variant === 'text' && width === 'fill';\n // chip variant: 칩 비주얼 트리거(콘텐츠 hug) — 드롭다운·키보드·검색 동작은 box와 동일.\n const isChip = variant === 'chip';\n // text/chip variant는 트리거가 좁으므로(hug) 드롭다운 기본 너비를 120px로 둔다(menuWidth 미지정 시).\n const effectiveMenuWidth = menuWidth ?? (isText || isChip ? 120 : undefined);\n const selectedOption = multiple ? undefined : options.find((o) => o.value === current);\n // 트리거 표시 텍스트 — multiple은 선택 라벨을 ', '로 연결(옵션 순서 기준), 넘치면 TruncatingText가 말줄임\n const selectedLabels = multiple\n ? options.filter((o) => selectedValues.includes(o.value)).map((o) => o.label)\n : [];\n const isPlaceholder = multiple ? selectedLabels.length === 0 : !selectedOption;\n const displayLabel = multiple\n ? (isPlaceholder ? placeholder : selectedLabels.join(', '))\n : (selectedOption ? selectedOption.label : placeholder);\n const interactive = !disabled && !readOnly;\n // box variant 트리거 너비(text variant는 콘텐츠 hug라 style을 주지 않는다)\n const widthStyle =\n width === 'hug' ? 'fit-content' : typeof width === 'number' ? `${width}px` : width;\n const maxWidthStyle =\n maxWidth != null ? (typeof maxWidth === 'number' ? `${maxWidth}px` : maxWidth) : undefined;\n\n // 검색 필터링된 옵션 (searchable + 검색어 있을 때만)\n const filtered =\n searchable && query.trim()\n ? options.filter((o) =>\n String(o.label).toLowerCase().includes(query.trim().toLowerCase()),\n )\n : options;\n\n const selectValue = useCallback(\n (val: string) => {\n if (!isControlled) setInternal(val);\n onChange?.({ target: { value: val } });\n setOpen(false);\n triggerRef.current?.focus();\n },\n [isControlled, onChange],\n );\n\n // multiple: 값 토글 — 메뉴는 닫지 않는다(연속 체크). 배열 순서는 옵션 순서를 따르지 않고 토글 순서.\n // confirm 모드는 draft만 토글하고, 확인 버튼에서 한 번에 반영한다.\n const toggleValue = useCallback(\n (val: string) => {\n if (isConfirm) {\n setDraft((d) => (d.includes(val) ? d.filter((v) => v !== val) : [...d, val]));\n return;\n }\n const next = selectedValues.includes(val)\n ? selectedValues.filter((v) => v !== val)\n : [...selectedValues, val];\n if (!isControlled) setInternal(next);\n onChange?.({ target: { value: next } });\n },\n [isConfirm, isControlled, onChange, selectedValues],\n );\n const commitDraft = useCallback(\n (next: string[]) => {\n if (!isControlled) setInternal(next);\n onChange?.({ target: { value: next } });\n setOpen(false);\n triggerRef.current?.focus();\n },\n [isControlled, onChange],\n );\n // 체크 표시 기준 — confirm 모드는 draft, 아니면 확정값\n const checkedValues = isConfirm ? draft : selectedValues;\n\n // 행 확정 동작 — 단일=선택 후 닫기 / multiple=토글 후 유지. disabled 옵션은 확정 불가(키보드 포함)\n const commitOption = (opt: OptionItem) => {\n if (opt?.disabled) return;\n return multiple ? toggleValue(opt.value) : selectValue(opt.value);\n };\n\n // 트리거 폭을 재서 텍스트 max-width를 계산(콘텐츠폭 − chevron − gap) — 크롬 truncate 보강.\n // hug(fit-content)는 트리거가 콘텐츠를 따라가므로 max-width를 주면 무한 축소 순환이 생긴다 → 제외.\n useLayoutEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger || isText || isChip || width === 'hug') {\n setTextMaxW(undefined);\n return;\n }\n const update = () => {\n const cs = getComputedStyle(trigger);\n const pl = parseFloat(cs.paddingLeft) || 0;\n const pr = parseFloat(cs.paddingRight) || 0;\n const gapPx = parseFloat(cs.columnGap) || 0;\n const chevronW = 16;\n const contentW = trigger.clientWidth - pl - pr;\n setTextMaxW(Math.max(0, contentW - chevronW - gapPx));\n };\n update();\n const ro = new ResizeObserver(update);\n ro.observe(trigger);\n return () => ro.disconnect();\n }, [width, isText, isChip]);\n\n // 열린 동안 드롭다운을 팝오버 레이어 스택에 등록 — 팝오버 패널 안에서 열렸을 때\n // 바깥 팝오버가 이 메뉴 안 클릭을 '외부'로 오인해 닫히지 않게 한다(2026-07-16, 복수 조건 설정).\n useEffect(() => {\n if (!open) return undefined;\n pushPopoverLayer(menuRef);\n return () => removePopoverLayer(menuRef);\n }, [open]);\n\n // 외부 클릭 닫기 (트리거·드롭다운 둘 다 바깥일 때 — 드롭다운은 portal).\n // 공용 훅 useOutsideDismiss — 바깥 클릭은 \"닫기 전용\"으로 삼켜 아래 요소가 함께 클릭되지 않는다.\n // guard: 맨 위 레이어일 때만 닫는다(위에 다른 패널이 떠 있으면 그쪽 차례).\n useOutsideDismiss({\n open,\n refs: [rootRef, menuRef],\n guard: () => isTopPopoverLayer(menuRef),\n onDismiss: () => setOpen(false),\n });\n\n // Esc 닫기 — capture 단계에서 먼저 받아 전파를 끊는다(포커스가 목록에 있어도 동작,\n // 모달 안에선 드롭다운만 닫히고 모달은 유지되도록 Modal의 Esc 리스너보다 우선).\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!isTopPopoverLayer(menuRef)) return; // 맨 위 레이어만 — 중첩 시 위 패널부터\n e.stopPropagation();\n setOpen(false);\n triggerRef.current?.focus();\n };\n document.addEventListener('keydown', onKey, true);\n return () => document.removeEventListener('keydown', onKey, true);\n }, [open]);\n\n // 열릴 때: 검색어 초기화 + 현재 선택 항목을 강조 시작점으로\n useEffect(() => {\n if (!open) return;\n // 열릴 때 1회 리셋 — 의도된 effect 내 setState\n setQuery(''); // eslint-disable-line react-hooks/set-state-in-effect\n if (isConfirm) setDraft(selectedValues); // confirm 모드 — 확정값으로 draft 시드\n const idx = options.findIndex((o) =>\n multiple ? selectedValues.includes(o.value) : o.value === current,\n );\n setHighlight(idx >= 0 ? idx : 0);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // 강조 항목이 스크롤 영역 밖이면 보이도록 스크롤 (드롭다운은 portal이라 menuRef 기준)\n useEffect(() => {\n if (!open || highlight < 0) return;\n const el = menuRef.current?.querySelector(`[data-option-index=\"${highlight}\"]`);\n el?.scrollIntoView({ block: 'nearest' });\n }, [highlight, open]);\n\n // 드롭다운 fixed 위치 계산 — Popover와 공유하는 공용 훅 (검색으로 목록 높이가 바뀌면 재계산)\n const menuStyle = usePopoverPosition({\n open,\n anchorRef: triggerRef,\n menuRef,\n placement,\n menuWidth: effectiveMenuWidth,\n deps: [filtered.length, query],\n });\n\n // 목록 키보드 네비게이션 — 트리거/검색바 공용, filtered 기준\n const handleListNav = (e: ReactKeyboardEvent<HTMLElement>) => {\n switch (e.key) {\n case 'Escape':\n e.preventDefault();\n setOpen(false);\n triggerRef.current?.focus();\n break;\n case 'ArrowDown':\n e.preventDefault();\n setHighlight((h) => Math.min(h + 1, filtered.length - 1));\n break;\n case 'ArrowUp':\n e.preventDefault();\n setHighlight((h) => Math.max(h - 1, 0));\n break;\n case 'Enter': {\n e.preventDefault();\n const opt = filtered[highlight];\n if (opt) commitOption(opt);\n break;\n }\n case 'Tab': {\n // 열린 메뉴에서 Tab=옵션 순회(2026-07-15) — 하이라이트가 끝에 닿으면 닫고 다음 요소로 자연 이동\n if (!e.shiftKey && highlight < filtered.length - 1) {\n e.preventDefault();\n setHighlight((h) => Math.min(h + 1, filtered.length - 1));\n } else if (e.shiftKey && highlight > 0) {\n e.preventDefault();\n setHighlight((h) => Math.max(h - 1, 0));\n } else {\n setOpen(false);\n if (searchable) {\n // 검색 인풋이 포커스를 쥔 채 닫히면 포커스가 유실된다 — 트리거로 복귀,\n // 다음 Tab은 skip 플래그로 재오픈 없이 다음 요소로 진행\n e.preventDefault();\n skipTabOpenRef.current = true;\n triggerRef.current?.focus();\n }\n }\n break;\n }\n default:\n break;\n }\n };\n\n const onTriggerKeyDown = (e: ReactKeyboardEvent<HTMLElement>) => {\n if (!interactive) return;\n if (!open) {\n if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {\n e.preventDefault();\n setOpen(true);\n } else if (tabOpens && e.key === 'Tab' && !e.shiftKey) {\n if (skipTabOpenRef.current) {\n skipTabOpenRef.current = false; // 순회를 마친 직후 — 열지 않고 다음 요소로 자연 이동\n return;\n }\n // 키보드 체인(수식): 포커스된 트리거에서 Tab → 메뉴 열기(이후 Tab은 옵션 순회)\n e.preventDefault();\n setOpen(true);\n }\n return;\n }\n // 열린 상태: 검색 모드면 검색바가 키를 처리, 아니면 트리거에서 네비게이션\n if (searchable) return;\n if (e.key === ' ') {\n e.preventDefault();\n const opt = filtered[highlight];\n if (opt) commitOption(opt);\n } else {\n handleListNav(e);\n }\n };\n\n // box variant 색 — tf-* 시멘틱 토큰. (text variant의 색·크기는 InlineFieldTrigger가 담당)\n const textColor = disabled\n ? 'text-text-field-disabled-text'\n : readOnly\n ? 'text-text-field-readonly-text'\n : isPlaceholder\n ? 'text-text-field-default-text'\n : 'text-text-field-filled-text';\n\n const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : 'text-text-field-default-text group-focus-within:text-text-field-filled-text';\n\n // box variant 화살표 크기는 항상 16\n const chevronSize = 16;\n\n return (\n <div\n ref={rootRef as RefObject<HTMLDivElement | null>}\n className={`relative ${\n isTextFill ? 'flex w-full' : isText || isChip ? 'inline-flex max-w-full align-middle' : ''\n } ${className}`}\n style={isText || isChip ? undefined : { width: widthStyle, maxWidth: maxWidthStyle }}\n {...props}\n >\n {/* 트리거 — 상호작용(클릭·키보드·포커스·위치 anchor)은 box·text·chip 공통, 비주얼은 분기 */}\n {isText ? (\n // 인라인 텍스트형: 비주얼은 공유 프리미티브 InlineFieldTrigger에 위임(Select·DatePicker 공용)\n <InlineFieldTrigger\n ref={triggerRef as RefObject<HTMLSpanElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n size={size}\n open={open}\n disabled={disabled}\n readOnly={readOnly}\n interactive={interactive}\n maxWidth={maxWidthStyle}\n fill={isTextFill}\n >\n {displayLabel}\n </InlineFieldTrigger>\n ) : isChip ? (\n // 칩형: Chip과 동일 박스 규격(pl-4/pr-3/py-1·gap-3·round-4·text-12)+chip-* 토큰, chevron 12.\n // 상태는 default/hover(CSS)만 — pressed는 Figma에서 default 재사용, disabled 변형은 미제공(동작만 차단).\n <div\n ref={triggerRef as RefObject<HTMLDivElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n style={maxWidthStyle ? { maxWidth: maxWidthStyle } : undefined}\n className={`inline-flex min-w-0 items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 font-pretendard text-12 font-normal transition-colors focus:outline-none ${\n CHIP_COLOR_CLASS[color] ?? CHIP_COLOR_CLASS.gray /* 포커스 표시=hover 색 전환(맵에 포함) */\n } ${interactive ? 'cursor-pointer' : 'cursor-not-allowed'}`}\n >\n <TruncatingText\n className={`min-w-0 text-12 ${weight === 'semibold' ? 'font-semibold' : 'font-normal'}`}\n >\n {displayLabel}\n </TruncatingText>\n <ChevronDown\n size={12}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}\n />\n </div>\n ) : (\n <div\n ref={triggerRef as RefObject<HTMLDivElement | null>}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-disabled={disabled || undefined}\n aria-invalid={error || undefined}\n tabIndex={interactive ? 0 : -1}\n onClick={() => interactive && setOpen((o) => !o)}\n onKeyDown={onTriggerKeyDown}\n className={`group relative grid min-h-[32px] grid-cols-[minmax(0,1fr)_auto] items-center gap-spacing-4 rounded-round-4 bg-text-field-default-bg py-spacing-3 pl-spacing-6 pr-spacing-6 transition-shadow focus:outline-none ${\n interactive ? `cursor-pointer ${RING}` : 'cursor-not-allowed'\n }`}\n >\n {/* box: grid minmax(0,1fr) + JS max-width 보강.\n 내부 라벨(label)은 값이 있을 때만 \"라벨 ⋮ 값\" — 라벨·점 구분자 고정(shrink-0), 값만 말줄임(Figma solid label). */}\n {label != null && !isPlaceholder ? (\n <div\n style={textMaxW != null ? { maxWidth: `${textMaxW}px` } : undefined}\n className=\"flex min-w-0 items-center gap-spacing-3\"\n >\n <span className={`shrink-0 text-14 font-normal ${textColor}`}>{label}</span>\n <LabelSeparatorIcon className=\"text-font-icon-3\" />\n <TruncatingText className={`min-w-0 flex-1 text-14 font-normal ${textColor}`}>\n {displayLabel}\n </TruncatingText>\n </div>\n ) : (\n <TruncatingText\n style={textMaxW != null ? { maxWidth: `${textMaxW}px` } : undefined}\n className={`text-14 font-normal ${textColor}`}\n >\n {displayLabel}\n </TruncatingText>\n )}\n <ChevronDown\n size={chevronSize}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${iconColor} ${\n open ? 'rotate-180' : ''\n }`}\n />\n </div>\n )}\n\n {/* 드롭다운 — portal + fixed (트리거 컨테이너와 분리해 트리거 레이아웃에 영향 없음) */}\n {open &&\n createPortal(\n <div\n ref={menuRef as RefObject<HTMLDivElement | null>}\n className=\"z-[1000]\"\n style={menuStyle ?? { position: 'fixed', top: 0, left: 0, visibility: 'hidden' }}\n >\n <PopoverMenu\n width=\"100%\"\n topArea={searchable ? 'search' : 'none'}\n searchValue={query}\n onSearchChange={(e) => {\n setQuery(e.target.value);\n setHighlight(0);\n }}\n searchPlaceholder={searchPlaceholder}\n searchInputProps={\n searchable\n ? // ref는 PopoverMenu가 내부 <input>에 전달한다(선언 타입엔 없지만 런타임 계약 — 기존 동작 유지 cast)\n ({ ref: searchInputRef, onKeyDown: handleListNav } as ComponentPropsWithoutRef<'input'>)\n : {}\n }\n {...(isConfirm\n ? {\n // confirm 모드 푸터 — 전체 선택(비활성 옵션 제외) + 취소/확인(확인 시에만 반영)\n footer: true,\n footerCheckbox: true,\n footerChecked:\n options.filter((o) => !o.disabled).length > 0 &&\n options.filter((o) => !o.disabled).every((o) => draft.includes(o.value)),\n onFooterCheckChange: () =>\n setDraft((d) => {\n const all = options.filter((o) => !o.disabled).map((o) => o.value);\n return all.every((v) => d.includes(v)) ? [] : all;\n }),\n onCancel: () => {\n setOpen(false);\n triggerRef.current?.focus();\n },\n onConfirm: () => commitDraft(draft),\n }\n : {})}\n >\n {filtered.length > 0 ? (\n <ListGroup>\n {filtered.map((opt, i) =>\n multiple ? (\n // 체크박스 행 — List가 행 전체를 체크 토글 영역으로 처리(메뉴 유지)\n <List\n key={opt.value}\n title={opt.label}\n checkbox\n disabled={opt.disabled}\n checked={checkedValues.includes(opt.value)}\n onCheckChange={opt.disabled ? undefined : () => toggleValue(opt.value)}\n highlighted={!opt.disabled && i === highlight}\n onMouseEnter={() => !opt.disabled && setHighlight(i)}\n data-option-index={i}\n />\n ) : (\n <List\n key={opt.value}\n title={opt.label}\n disabled={opt.disabled}\n selected={opt.value === current}\n highlighted={!opt.disabled && i === highlight}\n onClick={opt.disabled ? undefined : () => selectValue(opt.value)}\n onMouseEnter={() => !opt.disabled && setHighlight(i)}\n data-option-index={i}\n />\n ),\n )}\n </ListGroup>\n ) : (\n <ListEmpty message={searchable && query.trim() ? noResultMessage : emptyMessage} />\n )}\n </PopoverMenu>\n </div>,\n document.body,\n )}\n\n {/* 에러 툴팁 — 닫혔을 때 필드 아래 오버레이 (box·text 공통) */}\n {error && errorMessage && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errorMessage}\n </Tooltip>\n </div>\n )}\n </div>\n );\n}\n\n// SelectChip — 칩 비주얼 트리거의 Select (Figma `select chip` 8219:81717, color gray/red/blue/black ×\n// state Default/hover/pressed=default). 클릭하면 팝오버 옵션 메뉴가 열리고, options·value/onChange·\n// multiple·searchable·menuWidth 등 모든 옵션·동작은 Select와 완전히 동일하다(variant=\"chip\" 별칭).\nexport function SelectChip(props: SelectProps) {\n return <Select variant=\"chip\" {...props} />;\n}\n","// ButtonGroup — 버튼들을 일정 간격으로 묶는 컨테이너\n// 버튼과 버튼들을 가로/세로로 모아 일관된 간격(기본 8px = spacing-5)으로 배치한다.\n// 간격은 등록된 spacing 토큰만 사용하도록 lookup으로 제한한다(임의값 금지).\n// 규칙: 자식에 Select가 섞이면 Select를 버튼보다 앞(선두)에 배치한다.\nimport { Children, cloneElement, Fragment, isValidElement } from 'react';\nimport type { ComponentProps, ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Button } from './Button';\nimport { Select } from './Select';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑)\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px (기본)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\n// fragment·배열·조건부(`{x && <>…</>}`) children을 재귀적으로 평탄화해\n// 실제 자식 요소(Button/Select 등)만 추려낸다. (toArray는 fragment를 펴지 않으므로 직접 처리)\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = [];\n Children.forEach(children, (child) => {\n if (isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment) {\n out.push(...flattenChildren(child.props.children));\n } else if (child != null && typeof child !== 'boolean') {\n out.push(child);\n }\n });\n return out;\n}\n\nconst isSelect = (c: ReactNode) => isValidElement(c) && c.type === Select;\n\ninterface ButtonGroupProps extends ComponentPropsWithoutRef<'div'> {\n direction?: 'horizontal' | 'vertical';\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '5'(8px)\n width?: 'hug' | 'fill'; // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 버튼들 균등 분할)\n}\n\nexport function ButtonGroup({\n children,\n direction = 'horizontal', // 'horizontal' | 'vertical'\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 버튼들 균등 분할)\n className = '',\n ...props\n}: ButtonGroupProps) {\n const dirStyle = direction === 'vertical' ? 'flex-col' : 'flex-row';\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['5'];\n const isFill = width === 'fill';\n\n // Select 자식을 버튼보다 앞으로 — 그 외 순서는 유지(안정 정렬).\n const items = flattenChildren(children);\n const selects = items.filter(isSelect);\n const others = items.filter((c) => !isSelect(c));\n const ordered = selects.length ? [...selects, ...others] : items;\n\n // 최종 자식 배열 — 모든 요소에 안정 key를 직접 부여한다.\n // (map 결과 배열은 key가 없으면 React가 key 경고를 내며, Children.toArray로 감싸도 막히지 않는다.)\n // fill이면 Button 자식을 width=\"fill\"로 복제해 컨테이너 폭을 균등 분할한다(명시된 width는 존중).\n const rendered = ordered.map((c, i) => {\n if (!isValidElement(c)) return c;\n const fillProps =\n isFill && c.type === Button\n ? { width: (c as ReactElement<ComponentProps<typeof Button>>).props.width ?? 'fill' }\n : {};\n return cloneElement(c as ReactElement<Record<string, unknown>>, { key: c.key ?? `bg-${i}`, ...fillProps });\n });\n\n return (\n <div\n className={`${isFill ? 'flex w-full' : 'inline-flex'} ${dirStyle} ${gapStyle} ${className}`}\n {...props}\n >\n {rendered}\n </div>\n );\n}\n","// Popover — 임의의 트리거(버튼 등)에 떠 있는 패널(PopoverMenu 등)을 붙이는 범용 컨테이너.\n// Select에 들어 있던 드롭다운 로직(위치 자동계산·외부클릭/Esc 닫기·portal+자동반전)을\n// 트리거에 비종속적으로 추출한 것. 패널 내용은 children으로 자유롭게 구성한다(보통 PopoverMenu).\n//\n// 사용:\n// <Popover menuWidth={200} trigger={<Button rightIcon={ChevronDown}>메뉴</Button>}>\n// {(close) => (\n// <PopoverMenu width=\"100%\">\n// <ListGroup>\n// <List title=\"이름 변경\" onClick={close} />\n// </ListGroup>\n// </PopoverMenu>\n// )}\n// </Popover>\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useOutsideDismiss } from './useOutsideDismiss';\n// 열려 있는 패널 레이어 스택 — 중첩 popover·팝오버 안 Select 외부클릭/Esc 판정에 사용.\n// 패널은 각자 portal이라 DOM이 분리되므로, \"나보다 나중에 열린(위) 패널 안 클릭\"은 바깥으로 보지 않는다\n// (예: 조건 팝오버 안 어학/자격 Select 메뉴 클릭에 조건 팝오버가 닫히지 않게). 공용 모듈로 추출(2026-07-16).\nimport { pushPopoverLayer, removePopoverLayer, isTopPopoverLayer } from './popoverLayers';\n\nexport interface PopoverProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children'> {\n /** 트리거 ReactNode (예: <Button>) — 클릭 시 패널 토글 */\n trigger?: ReactNode;\n /** 패널 내용: ReactNode 또는 (close) => ReactNode */\n children?: ReactNode | ((close: () => void) => ReactNode);\n /** 'auto' | '{top|bottom|auto}-{left|right|auto}' (예: 'bottom-right', 'auto-right'=상하 자동·우측 고정) */\n placement?: string;\n /** 패널 너비: 숫자(px)/CSS 길이. 미지정 시 트리거 너비 */\n menuWidth?: number | string;\n /** controlled (선택) */\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n}\n\nexport function Popover({\n trigger,\n children,\n placement = 'auto',\n menuWidth,\n open: openProp,\n onOpenChange,\n disabled = false,\n className = '',\n ...props\n}: PopoverProps) {\n const isControlled = openProp !== undefined;\n const [internalOpen, setInternalOpen] = useState(false);\n const open = isControlled ? (openProp as boolean) : internalOpen;\n\n const anchorRef = useRef<HTMLSpanElement | null>(null);\n const menuRef = useRef<HTMLDivElement | null>(null);\n\n const setOpen = useCallback(\n (v: boolean) => {\n if (!isControlled) setInternalOpen(v);\n onOpenChange?.(v);\n },\n [isControlled, onOpenChange],\n );\n const close = useCallback(() => setOpen(false), [setOpen]);\n\n // 열린 동안 패널을 스택에 등록(연 순서 유지) — open 전환에만 의존해 재정렬 방지.\n useEffect(() => {\n if (!open) return undefined;\n pushPopoverLayer(menuRef);\n return () => removePopoverLayer(menuRef);\n }, [open]);\n\n // 외부 클릭 닫기 (트리거·패널 둘 다 바깥일 때 — 패널은 portal). 공용 훅 useOutsideDismiss:\n // 바깥 클릭은 \"닫기 전용\"으로 삼켜 아래 요소가 함께 클릭되지 않는다(click-through 방지).\n // 중첩 대응(guard): 외부 클릭은 \"맨 위(가장 나중에 열린) 팝오버\" 하나만 닫는다.\n // → 시간/연월 선택 팝오버가 위에 있으면 그것부터 닫히고, 다음 외부 클릭에 DatePicker가 닫힌다.\n useOutsideDismiss({\n open,\n refs: [anchorRef, menuRef],\n guard: () => isTopPopoverLayer(menuRef),\n onDismiss: close,\n });\n\n // Esc 닫기 — capture 단계에서 먼저 받아 전파를 끊는다\n // (모달 안에서 팝오버만 닫히고 모달은 유지되도록 — Modal의 document Esc 리스너보다 우선)\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (!isTopPopoverLayer(menuRef)) return; // Esc도 맨 위 레이어만 — 안의 Select가 먼저 닫힌다\n e.stopPropagation();\n close();\n };\n document.addEventListener('keydown', onKey, true);\n return () => document.removeEventListener('keydown', onKey, true);\n }, [open, close]);\n\n // 패널 fixed 위치 계산 — Select와 공유하는 공용 훅\n const menuStyle = usePopoverPosition({ open, anchorRef, menuRef, placement, menuWidth });\n\n return (\n <>\n <span\n ref={anchorRef}\n {...props}\n className={`inline-flex ${className}`}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n onClick={(e) => {\n // 소비자 onClick(클릭 계측 등)과 합성 — 스프레드로 덮어써 무시되지 않게\n props.onClick?.(e);\n if (!disabled) setOpen(!open);\n }}\n >\n {trigger}\n </span>\n\n {open &&\n createPortal(\n <div\n ref={menuRef}\n className=\"z-[1000]\"\n style={menuStyle ?? { position: 'fixed', top: 0, left: 0, visibility: 'hidden' }}\n >\n {typeof children === 'function' ? children(close) : children}\n </div>,\n document.body,\n )}\n </>\n );\n}\n","// FileUploadMenu — 파일 업로드 팝오버 (Figma popover menu / type=upload file list)\n//\n// PopoverMenu 컨테이너를 조립한다(규칙 4): 안내 박스 + 파일 목록(List 행, 우측 삭제) + 전체폭 '파일 찾기'.\n// 프레젠테이셔널(controlled) — 파일 상태는 호출부가 소유하고, 선택/삭제는 콜백으로 받는다.\n// - files: [{ name, size? , id? }] (size: 숫자=MB 표기 / 문자열=그대로)\n// - 최대 개수 도달(max) 시 푸터 버튼이 비활성 + 안내 문구로 바뀐다.\n//\n// ⚠️ 주의: guide(화면 안내 문구)와 accept(실제 파일 선택창 필터)는 **별개 prop**이라\n// 자동 연동되지 않는다. 표시 확장자와 실제 허용 확장자가 어긋나지 않도록 **둘을 직접 일치**시킬 것.\n// (예: guide '… .pdf, .doc, .xlsx …' ↔ accept '.pdf,.doc,.xlsx')\nimport { useRef } from 'react';\nimport type { ChangeEvent } from 'react';\nimport { Trash2 } from 'lucide-react';\nimport { PopoverMenu } from './PopoverMenu';\nimport type { PopoverMenuProps } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\n\n// 업로드 파일 항목 — size: 숫자=MB 표기 / 문자열=그대로\nexport interface UploadFileItem {\n name: string;\n size?: number | string;\n id?: string | number;\n}\n\nexport interface FileUploadMenuProps extends PopoverMenuProps {\n /** [{ name, size?, id? }] */\n files?: UploadFileItem[];\n /** 안내 문구(여러 줄은 \\n) */\n guide?: string;\n maxCount?: number;\n /** <input accept> 실제 필터 (예: '.pdf,.doc,.xlsx'). ⚠️ guide 표시와 직접 일치시킬 것 */\n accept?: string;\n multiple?: boolean;\n /** (FileList) => void — '파일 찾기'로 선택 */\n onAdd?: (files: FileList) => void;\n /** (file, index) => void — 행 삭제 */\n onDelete?: (file: UploadFileItem, index: number) => void;\n findText?: string;\n /** 기본 `최대 ${maxCount}개까지 업로드할 수 있습니다` */\n maxText?: string;\n}\n\nexport function FileUploadMenu({\n files = [],\n guide,\n maxCount = 5,\n accept,\n multiple = true,\n onAdd,\n onDelete,\n findText = '파일 찾기',\n maxText,\n width = 420,\n className = '',\n ...props\n}: FileUploadMenuProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const isMax = files.length >= maxCount;\n\n const openPicker = () => inputRef.current?.click();\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files?.length) onAdd?.(e.target.files);\n e.target.value = ''; // 같은 파일 재선택 허용\n };\n const prefix = (size?: number | string) =>\n typeof size === 'number' ? `${size}MB - ` : size ? `${size} - ` : '';\n\n return (\n <PopoverMenu\n width={width}\n className={className}\n footer\n footerButtonsFill\n showCancel={false}\n confirmText={isMax ? (maxText ?? `최대 ${maxCount}개까지 업로드할 수 있습니다`) : findText}\n confirmDisabled={isMax}\n onConfirm={openPicker}\n {...props}\n >\n {guide != null && (\n <div className=\"bg-list-group-bg px-spacing-6 py-spacing-5\">\n <p className=\"whitespace-pre-line text-14 text-list-default-text\">{guide}</p>\n </div>\n )}\n\n {files.length > 0 && (\n <ListGroup>\n {files.map((f, i) => (\n <List\n key={f.id ?? `${f.name}-${i}`}\n title={`${prefix(f.size)}${f.name}`}\n rightButton\n rightButtonIcon={Trash2}\n rightButtonAriaLabel=\"삭제\"\n onButtonClick={() => onDelete?.(f, i)}\n />\n ))}\n </ListGroup>\n )}\n\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n hidden\n onChange={handleChange}\n />\n </PopoverMenu>\n );\n}\n","// FileUploadButton — 버튼 트리거 + 파일 업로드 팝오버 (Figma butto+ file upload)\n//\n// Button을 트리거로 Popover에 FileUploadMenu를 붙인다(규칙 4 조립).\n// - 모든 Button 스타일 사용 가능: buttonProps로 variant/size/leftIcon/rightIcon/width 등 그대로 통과.\n// - 파일이 있으면 트리거 라벨에 개수 표시: `파일 업로드 (N개 등록됨)`.\n// 파일 상태는 호출부가 소유(컨트롤드) — FileUploadMenu와 동일한 props(files/onAdd/onDelete 등)를 그대로 받는다.\nimport { Upload } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { FileUploadMenu } from './FileUploadMenu';\nimport type { FileUploadMenuProps } from './FileUploadMenu';\n\n// 트리거 Button 패스스루 props — variant/size/leftIcon 등 모든 Button 스타일 props 통과\nexport interface UploadTriggerButtonProps {\n leftIcon?: LucideIcon | null;\n variant?: 'fill' | 'line' | 'ghost' | 'underline';\n [key: string]: unknown;\n}\n\nexport interface FileUploadButtonProps extends Omit<FileUploadMenuProps, 'width'> {\n // ── 트리거 버튼 ──\n triggerText?: string;\n /** 파일 N개 시 라벨 = `${triggerText} (N${countSuffix})` */\n countSuffix?: string;\n showCount?: boolean;\n /** 모든 Button 스타일 props 통과(variant/size/leftIcon/...). 기본 variant=line·leftIcon=Upload */\n buttonProps?: UploadTriggerButtonProps;\n // ── 팝오버 ──\n placement?: string;\n /** 팝오버(=FileUploadMenu) 너비 */\n menuWidth?: number | string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n}\n\nexport function FileUploadButton({\n files = [],\n triggerText = '파일 업로드',\n countSuffix = '개 등록됨',\n showCount = true,\n buttonProps = {},\n placement = 'bottom-left',\n menuWidth = 420,\n open,\n onOpenChange,\n disabled = false,\n className = '',\n // ── 나머지는 FileUploadMenu props(guide/maxCount/accept/onAdd/onDelete/...) ──\n ...menuProps\n}: FileUploadButtonProps) {\n const count = files.length;\n const label = showCount && count > 0 ? `${triggerText} (${count}${countSuffix})` : triggerText;\n const { leftIcon = Upload, variant = 'line', ...restButton } = buttonProps;\n\n return (\n <Popover\n placement={placement}\n menuWidth={menuWidth}\n open={open}\n onOpenChange={onOpenChange}\n disabled={disabled}\n className={className}\n trigger={\n <Button variant={variant} leftIcon={leftIcon} disabled={disabled} {...restButton}>\n {label}\n </Button>\n }\n >\n <FileUploadMenu files={files} width=\"100%\" {...menuProps} />\n </Popover>\n );\n}\n","// SegmentControl — 세그먼트 컨트롤\n// 여러 선택지 중 하나를 고르는 ghost 기반 토글 버튼 묶음.\n// - SegmentControlButton : 세그먼트 컨트롤 버튼 1개(텍스트 / 아이콘 / 좌·우 아이콘+텍스트)\n// - SegmentControlGroup : 버튼들을 묶어 단일 선택을 관리하는 컨테이너\n//\n// Figma file: h9jZFkEHfcHUGok1TZjjlP (node 7365:1436)\n// 컬러는 기존 ghost 시멘틱 토큰 재사용(하드코딩 금지):\n// 기본(미선택) 텍스트 → font-icon-3(#878787)\n// 선택/hover 배경 → btn-ghost-hover-bg(#0d0d0d12)\n// 선택/hover 텍스트 → font-icon-5(#0d0d0d)\n// 비활성 텍스트 → font-icon-2(#c9c9c9)\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\n\n// gap 토큰 키 → gap 클래스 (Tailwind purge 안전하게 정적 매핑) — ButtonGroup과 동일 규약\nconst GAP_STYLE = {\n '3': 'gap-spacing-3', // 4px\n '4': 'gap-spacing-4', // 6px\n '5': 'gap-spacing-5', // 8px (기본 — Figma 세그먼트 그룹 간격)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\ninterface SegmentControlButtonProps extends ComponentPropsWithoutRef<'button'> {\n size?: '32' | '24';\n selected?: boolean;\n leftIcon?: LucideIcon | null;\n rightIcon?: LucideIcon | null;\n icon?: LucideIcon | null; // 아이콘 전용(텍스트 없음)\n}\n\nexport function SegmentControlButton({\n children,\n size = '32', // '32' | '24'\n selected = false,\n disabled = false,\n leftIcon: LeftIcon = null,\n rightIcon: RightIcon = null,\n icon: Icon = null, // 아이콘 전용(텍스트 없음)\n onClick,\n className = '',\n ...props\n}: SegmentControlButtonProps) {\n const iconOnly = !!Icon;\n const iconSize = size === '24' ? 14 : 16;\n\n const base =\n 'inline-flex items-center justify-center relative font-pretendard font-normal ' +\n 'whitespace-nowrap rounded-round-4 transition-colors select-none focus:outline-none';\n\n // 사이즈·레이아웃 — Button과 동일한 패딩 규약(토큰만 사용)\n let sizeStyle;\n if (iconOnly) {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] p-spacing-2'\n : 'min-h-[32px] min-w-[32px] px-spacing-5 py-spacing-3';\n } else {\n sizeStyle =\n size === '24'\n ? 'min-h-[24px] min-w-[24px] px-spacing-5 py-spacing-2 text-12'\n : 'min-h-[32px] min-w-[32px] px-spacing-6 py-spacing-3 text-14';\n }\n\n // 컬러 — ghost 시멘틱 토큰 재사용\n let colorStyle;\n if (disabled) {\n colorStyle = 'bg-transparent text-font-icon-2 cursor-not-allowed';\n } else if (selected) {\n colorStyle = 'bg-button-ghost-hover-bg text-font-icon-5 cursor-pointer';\n } else {\n // 미선택 — 회색 텍스트, hover 시 ghost 배경 + 진한 텍스트(active=눌렀을 땐 배경 제거)\n colorStyle =\n 'bg-transparent text-font-icon-3 cursor-pointer ' +\n 'hover:bg-button-ghost-hover-bg hover:text-font-icon-5 active:bg-transparent ' +\n 'focus-visible:bg-button-ghost-hover-bg focus-visible:text-font-icon-5'; // 포커스=호버(2026-07-16)\n }\n\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n className={`${base} ${sizeStyle} ${colorStyle} ${className}`}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n {...props}\n >\n <span className=\"inline-flex items-center gap-spacing-3\">\n {iconOnly ? (\n <Icon size={iconSize} strokeWidth={1.8} />\n ) : (\n <>\n {LeftIcon && <LeftIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n {children}\n {RightIcon && <RightIcon size={iconSize} strokeWidth={1.8} className=\"shrink-0\" />}\n </>\n )}\n </span>\n </button>\n );\n}\n\n// 그룹 항목 — [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\ninterface SegmentControlItem {\n value: string | number;\n label?: ReactNode;\n leftIcon?: LucideIcon;\n rightIcon?: LucideIcon;\n icon?: LucideIcon;\n disabled?: boolean;\n ariaLabel?: string;\n}\n\ninterface SegmentControlGroupProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: SegmentControlItem[]; // [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\n value?: string | number; // controlled 선택값\n defaultValue?: string | number; // uncontrolled 초기 선택값\n onChange?: (value: string | number) => void; // (value) => void\n size?: '32' | '24'; // '32' | '24' — 모든 버튼에 적용\n gap?: keyof typeof GAP_STYLE; // 간격 토큰 키 — 기본 '5'(8px)\n disabled?: boolean; // 그룹 전체 비활성\n}\n\nexport function SegmentControlGroup({\n items = [], // [{ value, label, leftIcon, rightIcon, icon, disabled, ariaLabel }]\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n size = '32', // '32' | '24' — 모든 버튼에 적용\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n disabled = false, // 그룹 전체 비활성\n className = '',\n ...props\n}: SegmentControlGroupProps) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const selected = isControlled ? value : internal;\n\n const handleSelect = (val: string | number) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n const gapStyle = GAP_STYLE[gap] ?? GAP_STYLE['5'];\n\n return (\n <div\n role=\"tablist\"\n className={`inline-flex items-center ${gapStyle} ${className}`}\n {...props}\n >\n {items.map((item) => (\n <SegmentControlButton\n key={item.value}\n size={size}\n selected={selected === item.value}\n disabled={disabled || item.disabled}\n leftIcon={item.leftIcon}\n rightIcon={item.rightIcon}\n icon={item.icon}\n aria-label={item.ariaLabel}\n onClick={() => handleSelect(item.value)}\n >\n {item.label}\n </SegmentControlButton>\n ))}\n </div>\n );\n}\n","// Tabs — 탭 (Figma tab, node 7371:3233)\n// - TabMenu : 탭 1개 (아이콘 / 텍스트 / 태그). 상태 default·select·disabled(+hover)\n// - Tabs : 탭들을 묶어 단일 선택을 관리하는 그룹. variant hug(내용 폭) / fill(균등 분할)\n// · 선택 탭은 하단 2px underline, 그룹은 하단 1px 구분선(겹쳐서 표시)\n// · rightSlot으로 우측에 임의 요소(예: '설정' Select)를 둘 수 있다(justify-between)\n// 색은 tab-* 시멘틱 토큰만 사용. controlled/uncontrolled 모두 지원.\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Tag } from './Tag';\n\ntype TabTagType = 'blue' | 'red' | 'gray';\nexport type TabValue = string | number;\n\nexport interface TabMenuProps extends ComponentPropsWithoutRef<'button'> {\n icon?: LucideIcon | null;\n tag?: boolean;\n tagText?: ReactNode;\n /** Tag 타입: 'blue' | 'red' | 'gray' */\n tagType?: TabTagType;\n selected?: boolean;\n}\n\nexport function TabMenu({\n children,\n icon: Icon = null,\n tag = false,\n tagText = '태그',\n tagType = 'blue',\n selected = false,\n disabled = false,\n onClick,\n className = '',\n ...props\n}: TabMenuProps) {\n const base =\n 'relative inline-flex h-[32px] items-center justify-center gap-spacing-4 ' +\n 'px-spacing-5 border-b-2 -mb-px font-pretendard text-14 whitespace-nowrap ' +\n 'transition-colors select-none focus:outline-none';\n\n let colorStyle;\n if (disabled) {\n colorStyle = 'text-tab-disabled-text border-transparent cursor-not-allowed';\n } else if (selected) {\n colorStyle = 'text-tab-selected-text border-tab-selected-underline cursor-pointer';\n } else {\n colorStyle =\n 'text-tab-default-text border-transparent cursor-pointer ' +\n 'hover:text-tab-hover-text hover:border-tab-hover-underline ' +\n 'focus-visible:text-tab-hover-text focus-visible:border-tab-hover-underline'; // 포커스=호버(2026-07-16)\n }\n\n return (\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n disabled={disabled}\n onClick={!disabled ? onClick : undefined}\n className={`${base} ${colorStyle} ${className}`}\n {...props}\n >\n {Icon && <Icon size={16} strokeWidth={1.8} className=\"shrink-0\" />}\n {children}\n {tag && <Tag color={tagType}>{tagText}</Tag>}\n </button>\n );\n}\n\nexport interface TabItem {\n value: TabValue;\n label: ReactNode;\n icon?: LucideIcon;\n tag?: boolean;\n tagText?: ReactNode;\n tagType?: TabTagType;\n disabled?: boolean;\n}\n\nexport interface TabsProps<V extends TabValue = TabValue>\n extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n /** [{ value, label, icon?, tag?, tagText?, tagType?, disabled? }] */\n items?: (Omit<TabItem, 'value'> & { value: V })[];\n /** controlled 선택값 */\n value?: V;\n /** uncontrolled 초기 선택값 */\n defaultValue?: V;\n /** (value) => void */\n onChange?: (value: V) => void;\n /** 'hug' | 'fill' */\n variant?: 'hug' | 'fill';\n /** 우측 임의 요소(ReactNode) */\n rightSlot?: ReactNode;\n}\n\nexport function Tabs<V extends TabValue>({\n items = [],\n value,\n defaultValue,\n onChange,\n variant = 'hug',\n rightSlot = null,\n className = '',\n ...props\n}: TabsProps<V>) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState<V | undefined>(defaultValue);\n // 첫 탭 폴백은 렌더 파생값 — mount 시 items가 빈 배열(비동기 로드)이어도 도착 후 첫 탭이 선택된다\n const selected = isControlled ? value : (internal ?? items[0]?.value);\n\n const handleSelect = (val: V) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n const isFill = variant === 'fill';\n\n const tabEls = items.map((it) => (\n <TabMenu\n key={it.value}\n icon={it.icon}\n tag={it.tag}\n tagText={it.tagText}\n tagType={it.tagType}\n selected={selected === it.value}\n disabled={it.disabled}\n onClick={() => handleSelect(it.value)}\n className={isFill ? 'flex-1 min-w-px' : ''}\n >\n {it.label}\n </TabMenu>\n ));\n\n return (\n <div\n role=\"tablist\"\n className={`flex w-full border-b border-tab-group-underline ${\n rightSlot ? 'items-center justify-between' : ''\n } ${className}`}\n {...props}\n >\n <div className={`flex items-center gap-spacing-5 ${isFill ? 'w-full' : ''}`}>\n {tabEls}\n </div>\n {rightSlot && <div className=\"flex shrink-0 items-center\">{rightSlot}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
|