@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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/components/Divider.tsx","../src/components/Accordion.tsx","../src/components/CalendarDayButton.tsx","../src/components/Chip.tsx","../src/components/ConditionOrderSlot.tsx","../src/components/TwoDepthList.tsx","../src/components/DatePicker.tsx","../src/utils/datetime.ts","../src/components/DateField.tsx","../src/components/ErrorBoundary.tsx","../src/components/Label.tsx","../src/components/Field.tsx","../src/components/FileUploadMenu.tsx","../src/components/FileUploadButton.tsx","../src/components/FormTemplate.tsx","../src/components/ImageUploadMenu.tsx","../src/components/ImageUploadButton.tsx","../src/components/tableView.ts","../src/components/Table.tsx","../src/components/JobPositionTemplate.tsx","../src/components/useFocusTrap.ts","../src/components/modalContext.ts","../src/components/Modal.tsx","../src/components/Tabs.tsx","../src/components/NoticeWritingTemplate.tsx","../src/components/Pagination.tsx","../src/components/ToolBar.tsx","../src/components/SegmentedTabs.tsx","../src/components/ScreeningIndividualSettingModal.tsx","../src/components/formulaFunctions.ts","../src/components/usePanelKeyboard.ts","../src/components/ScreeningConditionCard.tsx","../src/components/ScreeningFormula.tsx","../src/components/ScreeningBuilderTemplate.tsx","../src/components/SelectGroup.tsx","../src/utils/phone.ts","../src/components/SelectOrInput.tsx","../src/components/SideNavigation.tsx","../src/components/SideNavigationTemplate.tsx","../src/components/TableTemplate.tsx","../src/components/TextArea.tsx","../src/components/TimeField.tsx"],"sourcesContent":["// Divider — 구분선 (Figma Divider: Direction Horizontal/Vertical × color Subtle/Default/Strong)\n// 1px 선을 채운 div로 그린다. 색은 divider 시멘틱 토큰 경유(subtle/default/strong).\n// - horizontal: 가로선(높이 1px, 부모 전체 폭)\n// - vertical: 세로선(너비 1px) — self-stretch로 flex 행 높이만큼 늘어난다.\n// (h-full 방식은 부모 높이가 auto면 0으로 계산돼 안 보였음 — 2026-07-07 개선.\n// flex/grid 밖에서 쓰면 className으로 높이를 직접 지정할 것)\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst COLOR = {\n subtle: 'bg-divider-subtle',\n default: 'bg-divider-default',\n strong: 'bg-divider-strong',\n};\n\ninterface DividerProps extends ComponentPropsWithoutRef<'div'> {\n direction?: 'horizontal' | 'vertical';\n color?: keyof typeof COLOR;\n}\n\nexport function Divider({\n direction = 'horizontal',\n color = 'default',\n className = '',\n ...props\n}: DividerProps) {\n const isVertical = direction === 'vertical';\n const shape = isVertical ? 'w-px self-stretch' : 'h-px w-full';\n return (\n <div\n role=\"separator\"\n aria-orientation={isVertical ? 'vertical' : 'horizontal'}\n className={`shrink-0 ${shape} ${COLOR[color] ?? COLOR.default} ${className}`}\n {...props}\n />\n );\n}\n","// Accordion — 아코디언 (Figma accordion 8416:35290 / accordion list SET 8416:35043)\n// 항목을 세로로 쌓고 위·아래·사이를 Divider(default)로 구분한다.\n// - Accordion : 컨테이너 — children(AccordionItem들)을 Divider로 감싸 배치\n// - AccordionItem : 항목 — [∨/∧ + 리스트 네임] 헤더 + 본문(펼침/접힘)\n// 옵션(완전 옵션화):\n// - nameEditable + onTitleChange : 이름 편집(SquarePen → 헤더가 Input+취소/저장으로 전환 — Figma name edit 변형)\n// - deletable + onDelete : 내부 삭제 버튼(Trash2)\n// - open/defaultOpen/onOpenChange: controlled/uncontrolled 펼침\n// - keepMounted / maxHeight : 볼륨 콘텐츠 대응 — 본문은 첫 펼침 때 마운트 후 유지(lazy),\n// keepMounted=true면 접힌 상태에서도 미리 마운트. maxHeight 지정 시\n// 본문 내부 스크롤(ScrollArea, 규칙 9)\n// 펼침 애니메이션은 grid-rows(0fr→1fr) — 콘텐츠 높이와 무관하게 부드럽고 측정 JS 불필요.\n// 색은 accordion-*(list-name)·divider 시멘틱 토큰만 사용. 항목 추가는 외부(사용처 버튼)에서 배열로 관리.\nimport { Children, Fragment, useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement } from 'react';\nimport { ChevronDown, SquarePen, Trash2 } from 'lucide-react';\nimport { Button } from './Button';\nimport { Input } from './Input';\nimport { Divider } from './Divider';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\n\nexport function Accordion({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n const items = Children.toArray(children) as ReactElement[];\n return (\n <div className={`flex w-full flex-col ${className}`} {...props}>\n <Divider />\n {items.map((child) => (\n <Fragment key={child.key ?? undefined}>\n {child}\n <Divider />\n </Fragment>\n ))}\n </div>\n );\n}\n\nexport interface AccordionItemProps extends ComponentPropsWithoutRef<'div'> {\n title?: string;\n defaultOpen?: boolean;\n /** controlled 펼침(선택) */\n open?: boolean;\n /** (open) => void */\n onOpenChange?: (open: boolean) => void;\n /** 이름 편집 버튼(SquarePen) 노출 */\n nameEditable?: boolean;\n /** (title) => void — 저장 시에만(취소/Esc는 폐기) */\n onTitleChange?: (title: string) => void;\n /** 삭제 버튼(Trash2) 노출 */\n deletable?: boolean;\n /** 삭제 버튼 비활성(예: 마지막 항목은 삭제 불가) */\n deleteDisabled?: boolean;\n /** () => void */\n onDelete?: () => void;\n /** 접힌 상태에서도 본문 마운트(폼 상태 유지 등) */\n keepMounted?: boolean;\n /** 본문 내부 스크롤 상한(px) — 미지정 시 자연 확장(페이지 스크롤) */\n maxHeight?: number | string;\n}\n\nexport function AccordionItem({\n title = '',\n children,\n defaultOpen = false,\n open: openProp,\n onOpenChange,\n nameEditable = false,\n onTitleChange,\n deletable = false,\n deleteDisabled = false,\n onDelete,\n keepMounted = false,\n maxHeight,\n className = '',\n ...props\n}: AccordionItemProps) {\n const controlled = openProp !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlled ? (openProp as boolean) : internalOpen;\n const setOpen = (v: boolean) => {\n if (!controlled) setInternalOpen(v);\n onOpenChange?.(v);\n };\n\n // 볼륨 콘텐츠 대응 — 본문은 첫 펼침 때 마운트하고 이후 유지(닫힘 애니메이션 중 내용 유지 + 초기 비용 지연)\n const [openedOnce, setOpenedOnce] = useState(defaultOpen || keepMounted);\n if (open && !openedOnce) setOpenedOnce(true); // 렌더 파생 래치(controlled 열림도 포착)\n const mounted = keepMounted || openedOnce;\n\n // 이름 편집 — 헤더가 Input + 취소/저장(ghost 텍스트 버튼)으로 전환(Figma name edit 변형)\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const startEdit = () => {\n setDraft(title ?? '');\n setEditing(true);\n };\n const cancelEdit = () => setEditing(false);\n const saveEdit = () => {\n setEditing(false);\n const next = draft.trim();\n if (next && next !== title) onTitleChange?.(next);\n };\n\n return (\n <div className={`w-full ${className}`} {...props}>\n {/* 헤더 */}\n {editing ? (\n <div className=\"flex w-full items-start gap-spacing-5 py-spacing-5\">\n <div className=\"min-w-0 flex-1\">\n <Input\n width=\"100%\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n inputProps={{\n autoFocus: true,\n onKeyDown: (e) => {\n if (e.key === 'Enter') saveEdit();\n if (e.key === 'Escape') cancelEdit();\n },\n }}\n />\n </div>\n <div className=\"flex shrink-0 items-center gap-spacing-3\">\n <Button variant=\"ghost\" size=\"32\" onClick={cancelEdit}>\n 취소\n </Button>\n <Button variant=\"ghost\" size=\"32\" onClick={saveEdit}>\n 저장\n </Button>\n </div>\n </div>\n ) : (\n <div className=\"flex w-full items-start gap-spacing-3 p-spacing-5\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n className=\"flex h-[32px] min-w-0 flex-1 cursor-pointer items-center gap-spacing-5 text-left\"\n >\n {/* Figma: 열림=∨(chevron-down) / 닫힘=∧(180° 회전) */}\n <ChevronDown\n size={16}\n strokeWidth={1.8}\n className={`shrink-0 text-font-icon-5 transition-transform ${open ? '' : 'rotate-180'}`}\n />\n <TruncatingText className=\"min-w-0 text-14 text-accordion-list-name\">{title}</TruncatingText>\n </button>\n {nameEditable && (\n <Button variant=\"ghost\" icon={SquarePen} aria-label=\"이름 편집\" onClick={startEdit} />\n )}\n {deletable && (\n <Button\n variant=\"ghost\"\n icon={Trash2}\n aria-label=\"리스트 삭제\"\n disabled={deleteDisabled}\n onClick={onDelete}\n />\n )}\n </div>\n )}\n\n {/* 본문 — grid 0fr→1fr 전환(콘텐츠 높이 무관 부드러운 펼침) */}\n <div\n className={`grid transition-[grid-template-rows] duration-200 ${\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n }`}\n >\n <div className=\"min-h-0 overflow-hidden\">\n {mounted && (\n <div className=\"pb-spacing-6\">\n {maxHeight ? <ScrollArea maxHeight={maxHeight}>{children}</ScrollArea> : children}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n","// CalendarDayButton — 캘린더(DatePicker)의 날짜 한 칸 (Figma date picker / calendar day button, node 7592:3061)\n// 36×24 셀: 뒤쪽 좌/우 반쪽 배경(범위 연속 효과) + 중앙 24px 원형 위에 날짜 텍스트.\n// - 범위(start~end)를 가로로 끊김 없이 칠하기 위해, 셀을 좌/우 18px 두 반쪽으로 나눠\n// 반쪽 배경(range-bg)을 이어 붙이고, 그 위에 원형 하이라이트를 올린다.\n// 상태(state):\n// default — 이번 달 선택 가능 날짜(검정 텍스트, hover 시 회색 원)\n// muted — 흐린 회색 날짜(DatePicker에선 이전/다음 달 또는 선택 불가(disablePast 등))\n// today — 오늘(진회색 원 + 흰 텍스트)\n// selected — 단일 선택 / 범위 시작=끝(파란 원 + 흰 텍스트)\n// range-start — 범위 시작(파란 원 + 오른쪽 반쪽 range-bg)\n// range-end — 범위 끝(파란 원 + 왼쪽 반쪽 range-bg)\n// in-range — 범위 안(연한 파란 배경 + 파란 텍스트)\n// 색은 cal-* 시멘틱 토큰만 사용.\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\n// 좌/우 반쪽 배경 — 범위 연속 칠을 위한 lookup. (range-bg를 칠할지 day-bg(투명)로 둘지)\nconst HALF_FILL = {\n 'default': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'muted': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'today': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'selected': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'range-start': { left: 'bg-calendar-day-bg', right: 'bg-calendar-range-bg' },\n 'range-end': { left: 'bg-calendar-range-bg', right: 'bg-calendar-day-bg' },\n 'in-range': { left: 'bg-calendar-range-bg', right: 'bg-calendar-range-bg' },\n};\n\n// 중앙 원형 배경 + 텍스트 색 lookup.\nconst CIRCLE_STYLE = {\n 'default': { circle: 'bg-calendar-day-bg', text: 'text-calendar-day-text' },\n 'muted': { circle: 'bg-transparent', text: 'text-calendar-muted-text' },\n 'today': { circle: 'bg-calendar-today-bg', text: 'text-calendar-today-text' },\n 'selected': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'range-start': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'range-end': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'in-range': { circle: 'bg-calendar-range-bg', text: 'text-calendar-range-text' },\n};\n\n// hover 시 원에 회색 배경을 줄 수 있는(=아직 강조되지 않은) 상태\nconst HOVERABLE = new Set(['default', 'muted']);\n\n// ...props는 루트 <button>으로 전달된다.\ninterface CalendarDayButtonProps extends ComponentPropsWithoutRef<'button'> {\n state?: keyof typeof HALF_FILL; // 'default' | 'muted' | 'today' | 'selected' | 'range-start' | 'range-end' | 'in-range'\n edge?: 'left' | 'right'; // 'left'(행 첫 칸) | 'right'(행 마지막 칸) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n}\n\nexport function CalendarDayButton({\n children,\n state = 'default', // 'default' | 'muted' | 'today' | 'selected' | 'range-start' | 'range-end' | 'in-range'\n edge, // 'left'(행 첫 칸) | 'right'(행 마지막 칸) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n disabled = false,\n onClick,\n className = '',\n ...props\n}: CalendarDayButtonProps) {\n const half = HALF_FILL[state] ?? HALF_FILL.default;\n const circle = CIRCLE_STYLE[state] ?? CIRCLE_STYLE.default;\n // 포커스=호버(2026-07-16) — group=버튼 자신이므로 group-focus-visible로 같은 배경\n const hover =\n !disabled && HOVERABLE.has(state)\n ? 'group-hover:bg-calendar-hover-bg group-focus-visible:bg-calendar-hover-bg'\n : '';\n // muted는 이미 회색(비활성)으로 읽히므로, disabled여도 추가 dimming(opacity)을 주지 않는다.\n const dimmed = disabled && state !== 'muted';\n // 행 가장자리 + 해당 반쪽이 범위색일 때만 바깥 모서리를 둥글게(줄바꿈으로 각지게 끊기는 것 방지)\n const leftRound =\n edge === 'left' && half.left.includes('range-bg') ? 'rounded-l-round-00' : '';\n const rightRound =\n edge === 'right' && half.right.includes('range-bg') ? 'rounded-r-round-00' : '';\n\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={disabled ? undefined : onClick}\n aria-disabled={disabled || undefined}\n className={`group relative flex h-[24px] w-[36px] items-center justify-center focus:outline-none ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${dimmed ? 'opacity-40' : ''} ${className}`}\n {...props}\n >\n {/* 뒤쪽 좌/우 반쪽 배경 — 범위 연속 칠 (행 가장자리는 바깥 모서리 둥글게) */}\n <span className={`absolute inset-y-0 left-0 w-1/2 ${half.left} ${leftRound}`} aria-hidden=\"true\" />\n <span className={`absolute inset-y-0 right-0 w-1/2 ${half.right} ${rightRound}`} aria-hidden=\"true\" />\n {/* 중앙 원형 하이라이트 + 날짜 텍스트 */}\n <span\n className={`relative z-10 flex size-[24px] items-center justify-center rounded-round-00 transition-colors ${circle.circle} ${hover}`}\n >\n <span className={`text-12 ${circle.text}`}>{children}</span>\n </span>\n </button>\n );\n}\n","// Chip — 제거형 칩 (Figma chip: state Default/hover/pressed × size 22 × color gray/red/blue/black\n// + 함수 계열 대응 green/violet/pink/orange — 2026-07-15 추가)\n// 텍스트 + X(삭제) 버튼. 색은 chip 시멘틱 토큰 경유(default/hover, pressed=default 재사용).\n// - color: gray(기본) · red · blue · black — 분류/의미 색(variant). recolor 금지, prop으로 표현(규칙 5)\n// - onRemove가 있으면 X 노출(클릭 시 삭제, 칩 onClick과 분리)\n// - onClick 주면 칩 전체가 클릭 가능(선택 등)\n// (disabled 변형은 Figma에 없어 미제공 — 필요 시 추후 추가)\nimport { X } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, MouseEvent } from 'react';\n// 칩 색 클래스 맵 — SelectChip(Select variant=\"chip\")과 공유(별도 파일, react-refresh 규칙 회피)\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport type { ChipColor } from './chipStyles';\n\ninterface ChipProps extends ComponentPropsWithoutRef<'span'> {\n onRemove?: (e: MouseEvent<HTMLButtonElement>) => void; // X 클릭 콜백 — 있으면 X 노출\n color?: ChipColor; // gray | red | blue | black\n removeAriaLabel?: string;\n}\n\nexport function Chip({\n children,\n onRemove, // X 클릭 콜백 — 있으면 X 노출\n onClick, // 칩 전체 클릭(선택 등)\n color = 'gray', // gray | red | blue | black\n removeAriaLabel = '삭제',\n className = '',\n ...props\n}: ChipProps) {\n const clickable = !!onClick;\n const base =\n 'inline-flex items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 ' +\n 'font-pretendard font-normal text-12 transition-colors';\n\n return (\n <span\n onClick={clickable ? onClick : undefined}\n className={`${base} ${CHIP_COLOR_CLASS[color] ?? CHIP_COLOR_CLASS.gray} ${clickable ? 'cursor-pointer' : ''} ${className}`}\n {...props}\n >\n <span className=\"whitespace-nowrap\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n aria-label={removeAriaLabel}\n onClick={(e) => {\n e.stopPropagation();\n onRemove(e);\n }}\n className=\"flex shrink-0 items-center justify-center\"\n >\n <X size={12} strokeWidth={1.8} />\n </button>\n )}\n </span>\n );\n}\n","// ConditionOrderSlot / ConditionSlotCard — 조건 정렬 슬롯\n// (Figma ConditionSlotCard 8219:80364 / ConditionOrderSlot 8219:80583 — condition-slot-* 토큰)\n//\n// 카드(ConditionSlotCard): [⠿ grip + \"조건 N.\"] … [Switch] 헤더 + 세부 Select 스택(children).\n// - 세부 select는 children으로 필요만큼 추가한다(2개 고정 아님).\n// - state: default / hover(CSS) / pressed — pressed는 드래그 앤 드롭 중 유지되는 상태\n// (blue.400 라인·텍스트·아이콘, 2026-07-07 지시).\n// 컨테이너(ConditionOrderSlot): 회색 슬롯(bg gray.50, p 12) 안에 카드들을 배치하고 사이에 커넥터.\n// - direction: 'vertical'(↓ 커넥터) | 'horizontal'(› 커넥터) — 레이아웃 전환\n// - 드래그 앤 드롭(grip에서만 시작)으로 카드 순서 변경\n// - 카드별 Switch로 사용/미사용 — 미사용 카드는 적용 순번에서 빠지고 다음 조건이 번호를\n// 당겨받는다(카드의 물리적 위치는 유지). 제목 번호는 표시 순서상 '사용 중' 카드 기준 1..k\n// 자동 부여, 미사용 카드는 \"조건 -.\"로 표기.\n// - items 배열로 조건 카드를 필요만큼 추가한다.\nimport { Children, Fragment, cloneElement, isValidElement, useRef, useState } from 'react';\nimport type { ComponentPropsWithoutRef, HTMLAttributes, ReactElement, ReactNode } from 'react';\nimport { ChevronDown, ChevronRight, GripVertical } from 'lucide-react';\nimport { Switch } from './Switch';\n\n// 미사용(스위치 off) 카드의 세부 컨트롤에 disabled를 강제 주입한다(2026-07-07 지시).\n// body를 <>…</>(fragment)로 감싸 넘기는 경우가 많아 fragment는 한 겹씩 펴서 내려간다.\nfunction withDisabled(nodes: ReactNode): ReactNode {\n return Children.map(nodes, (c) => {\n if (!isValidElement(c)) return c;\n if (c.type === Fragment)\n return <Fragment>{withDisabled((c.props as { children?: ReactNode }).children)}</Fragment>;\n return cloneElement(c as ReactElement<{ disabled?: boolean }>, { disabled: true });\n });\n}\n\n// 카드 상태별 클래스 — hover는 CSS, pressed(드래그 중)는 상태로 적용. 아이콘·텍스트는 currentColor 상속.\nconst CARD_DEFAULT =\n 'border-condition-slot-card-default-line bg-condition-slot-card-default-bg text-condition-slot-card-default-text ' +\n 'hover:border-condition-slot-card-hover-line hover:bg-condition-slot-card-hover-bg hover:text-condition-slot-card-hover-text';\nconst CARD_PRESSED =\n 'border-condition-slot-card-pressed-line bg-condition-slot-card-pressed-bg text-condition-slot-card-pressed-text';\n\ninterface ConditionSlotCardProps extends ComponentPropsWithoutRef<'div'> {\n title?: string; // 헤더 제목 — 컨테이너가 활성 순번으로 자동 부여\n enabled?: boolean; // 사용/미사용(Switch) — 미사용이면 제목 dim + 세부 영역 반투명\n onEnabledChange?: (checked: boolean) => void;\n dragging?: boolean; // pressed 상태(드래그 앤 드롭 중 유지)\n width?: number | string; // Figma 기본 202px — 숫자(px) | CSS 길이\n dragHandleProps?: HTMLAttributes<HTMLSpanElement> | null; // grip에 붙는 핸들러(컨테이너가 주입 — 드래그 시작 허용 판정)\n children?: ReactNode; // 세부 Select들(세로 스택 gap 4=spacing-3) — 필요만큼 추가\n}\n\nexport function ConditionSlotCard({\n title = '조건 1.',\n enabled = true,\n onEnabledChange,\n dragging = false,\n width = 202,\n dragHandleProps = null,\n children,\n className = '',\n ...props\n}: ConditionSlotCardProps) {\n return (\n <div\n style={{ width: typeof width === 'number' ? `${width}px` : width }}\n className={`flex shrink-0 flex-col gap-spacing-6 rounded-round-4 border p-spacing-6 shadow-[0_2px_2px_0_rgba(0,0,0,0.12)] transition-colors ${\n dragging ? CARD_PRESSED : CARD_DEFAULT\n } ${className}`}\n {...props}\n >\n <div className=\"flex w-full items-center justify-between gap-spacing-4\">\n <div className=\"flex min-w-0 items-center gap-spacing-4\">\n <span\n aria-label=\"순서 변경\"\n className=\"flex shrink-0 cursor-grab items-center active:cursor-grabbing\"\n {...(dragHandleProps || {})}\n >\n <GripVertical size={16} strokeWidth={1.8} />\n </span>\n <span className={`truncate text-14 ${enabled ? '' : 'text-font-icon-3'}`}>{title}</span>\n </div>\n <Switch checked={enabled} onChange={(e) => onEnabledChange?.(e.target.checked)} />\n </div>\n {/* 세부 select 스택(gap 4px=spacing-3, Figma Slot) —\n 미사용(스위치 off)이면 자식 컨트롤에 disabled를 주입해 Select 자체 disabled 상태로 표시(값은 보존) */}\n <div className=\"flex w-full flex-col gap-spacing-3\">\n {enabled ? children : withDisabled(children)}\n </div>\n </div>\n );\n}\n\n// 조건 카드 항목 — [{ id, body }] — body=카드 안 세부 Select들(ReactNode)\ninterface ConditionOrderItem {\n id: string;\n body?: ReactNode;\n}\n\ninterface ConditionOrderSlotProps extends ComponentPropsWithoutRef<'div'> {\n items?: ConditionOrderItem[];\n direction?: 'vertical' | 'horizontal'; // 'vertical'(↓) | 'horizontal'(›)\n order?: string[]; // id[] — 카드 배치 순서(controlled). DnD로 변경\n defaultOrder?: string[]; // 초기 순서(uncontrolled) — 미지정 시 items 순서\n onOrderChange?: (ids: string[]) => void;\n enabledIds?: string[]; // id[] — 사용 중 조건(controlled)\n onEnabledChange?: (nextEnabledIds: string[], info: { id: string; enabled: boolean }) => void;\n titlePrefix?: string; // 카드 제목 접두(\"조건 N.\")\n cardWidth?: number | string;\n}\n\nexport function ConditionOrderSlot({\n items = [],\n direction = 'vertical',\n order,\n defaultOrder,\n onOrderChange,\n enabledIds,\n onEnabledChange,\n titlePrefix = '조건',\n cardWidth = 202,\n className = '',\n ...props\n}: ConditionOrderSlotProps) {\n const vertical = direction === 'vertical';\n const ids = items.map((it) => it.id);\n\n // 순서 — controlled/uncontrolled. 렌더 파생으로 items 변동(카드 추가/삭제)을 보정:\n // 사라진 id는 제거, 새 id는 뒤에 붙는다(비동기/동적 추가 대응).\n const orderControlled = order !== undefined;\n const [internalOrder, setInternalOrder] = useState<string[] | null>(defaultOrder ?? null);\n const rawOrder = orderControlled ? order : (internalOrder ?? ids);\n const effOrder = [\n ...rawOrder.filter((id) => ids.includes(id)),\n ...ids.filter((id) => !rawOrder.includes(id)),\n ];\n\n // 사용/미사용 — 내부적으로는 '미사용 목록'으로 추적해 새로 추가된 카드가 기본 사용 상태가 되게 한다.\n const enControlled = enabledIds !== undefined;\n const [internalDisabled, setInternalDisabled] = useState<string[]>([]);\n const isEnabled = (id: string) =>\n enControlled ? enabledIds.includes(id) : !internalDisabled.includes(id);\n const toggleEnabled = (id: string, next: boolean) => {\n if (!enControlled) {\n setInternalDisabled((prev) => (next ? prev.filter((d) => d !== id) : [...prev, id]));\n }\n const nextEnabled = effOrder.filter((oid) => (oid === id ? next : isEnabled(oid)));\n onEnabledChange?.(nextEnabled, { id, enabled: next });\n };\n\n // 드래그 앤 드롭 — grip에서만 시작(allowDragRef), 드래그 중 카드가 pressed 상태를 유지하고\n // 다른 카드 위로 들어가면 그 자리로 라이브 재배치된다.\n const [dragId, setDragId] = useState<string | null>(null);\n const allowDragRef = useRef(false);\n const moveTo = (srcId: string, dstId: string) => {\n if (srcId === dstId) return;\n const si = effOrder.indexOf(srcId);\n const di = effOrder.indexOf(dstId);\n if (si < 0 || di < 0) return;\n // 이동 방향에 따라 hover 카드의 앞/뒤에 삽입한다. 항상 '앞'에 꽂으면 앞→뒤 이동 시\n // src 제거로 dst가 한 칸 당겨져 제자리에 되꽂혀 순서가 안 바뀐다(2026-07-07 사용자 지적).\n const next = effOrder.filter((x) => x !== srcId);\n const base = next.indexOf(dstId);\n next.splice(si < di ? base + 1 : base, 0, srcId);\n if (!orderControlled) setInternalOrder(next);\n onOrderChange?.(next);\n };\n\n const Connector = vertical ? ChevronDown : ChevronRight;\n\n // 제목 번호 — 표시 순서상 '사용 중' 카드에만 1..k 부여(미사용은 번호에서 빠지고 다음이 당겨받음)\n let seq = 0;\n\n return (\n <div\n className={`inline-flex ${vertical ? 'flex-col' : 'flex-row'} items-center gap-spacing-2 rounded-round-4 bg-condition-slot-slot-bg p-spacing-6 ${className}`}\n {...props}\n // 커넥터·여백 위에서 놓아도 드롭 수락 — 카드 밖 드롭의 스냅백 방지(카드 쪽 onDragOver와 동일)\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n >\n {effOrder.map((id, i) => {\n const item = items.find((it) => it.id === id);\n if (!item) return null;\n const enabled = isEnabled(id);\n const title = enabled ? `${titlePrefix} ${++seq}.` : `${titlePrefix} -.`;\n return (\n <div key={id} className={`flex ${vertical ? 'flex-col' : 'flex-row'} items-center gap-spacing-2`}>\n {i > 0 && (\n <Connector\n size={16}\n strokeWidth={1.8}\n className=\"shrink-0 text-condition-slot-card-default-icon\"\n />\n )}\n <ConditionSlotCard\n title={title}\n enabled={enabled}\n onEnabledChange={(next) => toggleEnabled(id, next)}\n dragging={dragId === id}\n width={cardWidth}\n dragHandleProps={{\n onMouseDown: () => {\n allowDragRef.current = true;\n },\n onMouseUp: () => {\n allowDragRef.current = false;\n },\n }}\n draggable\n onDragStart={(e) => {\n // grip 밖에서 시작한 드래그(텍스트 등)는 차단 — 핸들에서만 이동\n if (!allowDragRef.current) {\n e.preventDefault();\n return;\n }\n setDragId(id);\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(id));\n } catch {\n /* 일부 브라우저(구IE 계열) setData 미지원 무시 */\n }\n }}\n onDragEnd={() => {\n setDragId(null);\n allowDragRef.current = false;\n }}\n onDragOver={(e) => {\n // 드래그 중엔 자기 자신 위 포함 항상 드롭 허용 — 미허용 상태로 놓으면\n // 크롬이 고스트를 원위치로 되돌리는 스냅백 애니메이션을 재생한다(2026-07-08 지적)\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n onDragEnter={() => {\n if (dragId != null && dragId !== id) moveTo(dragId, id);\n }}\n >\n {item.body}\n </ConditionSlotCard>\n </div>\n );\n })}\n </div>\n );\n}\n","// TwoDepthList — 입력 영역 + 좌/우 2개 컬럼 목록 팝오버 (Figma 2depth list, node 7595:6736 / 7595:7798)\n// 상단 input area는 좌/우 \"분리 인풋 + 구분자(., :)\" 구조 — [Input] . [Input] (2026-07-02 디자인 개정).\n// 그 아래 두 개의 ListGroup을 1px 틈(gap-spacing-1)으로 나란히 둔다.\n// - year_month(폭 160): 좌=연도, 우=월, 구분자 '.' / time(폭 140): 좌=시, 우=분, 구분자 ':'\n// - 각 컬럼은 ListGroup(maxVisible 초과 시 내부 스크롤). 선택 행은 List selected(파란 텍스트).\n// 외부 계약은 합성 문자열 그대로 유지: inputValue('2026.7'), onInputApply('2026.7')→정규화 문자열.\n// 컨테이너 색은 list-popover-* / list-group-* 시멘틱 토큰(PopoverMenu와 동일 규칙).\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n ChangeEvent,\n ComponentPropsWithoutRef,\n FocusEvent,\n KeyboardEvent,\n ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Input } from './Input';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Tooltip } from './Tooltip';\n\n// 컬럼 옵션 한 개 — { value, label, disabled? }\ninterface TwoDepthOption {\n value: string | number;\n label: ReactNode;\n disabled?: boolean;\n}\n\n// onInputApply 실패 반환형 — 해당 파트 인풋 아래에 그 문구로 툴팁\ninterface TwoDepthApplyError {\n error: 'left' | 'right';\n message?: string;\n}\n\ninterface ColumnProps {\n options: TwoDepthOption[];\n value?: TwoDepthOption['value'];\n onChange?: (value: TwoDepthOption['value']) => void;\n maxVisible?: number;\n}\n\n// 한 컬럼 — 옵션 목록 + 선택 행 자동 스크롤(열릴 때 선택값이 보이도록).\nfunction Column({ options, value, onChange, maxVisible }: ColumnProps) {\n const ref = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const el = ref.current?.querySelector('[aria-selected=\"true\"]');\n el?.scrollIntoView({ block: 'nearest' });\n }, [value]);\n\n return (\n <div ref={ref} className=\"min-w-0 flex-1\">\n <ListGroup maxVisible={maxVisible}>\n {options.map((opt) => (\n <List\n key={opt.value}\n title={opt.label}\n selected={opt.value === value}\n disabled={opt.disabled}\n onClick={() => onChange?.(opt.value)}\n data-value={opt.value}\n />\n ))}\n </ListGroup>\n </div>\n );\n}\n\n// 합성 문자열 ↔ 좌/우 파트 변환 — 첫 구분자 기준 2조각\nconst splitParts = (text: string, separator: string): [string, string] => {\n const i = String(text).indexOf(separator);\n return i < 0 ? [String(text), ''] : [String(text).slice(0, i), String(text).slice(i + 1)];\n};\n\ninterface TwoDepthListProps extends ComponentPropsWithoutRef<'div'> {\n // 상단 입력 영역 — 현재 선택을 좌/우 분리 인풋으로 보여주며, 기본적으로 직접 입력(타이핑)도 가능하다.\n inputValue?: string;\n separator?: string; // 좌/우 파트 구분자 표시·합성 기준 — year_month '.' / time ':'\n showInput?: boolean; // 상단 입력 영역 표시 여부(false면 좌/우 컬럼만 — 트리거가 따로 입력을 가질 때)\n editable?: boolean; // 상단 input 직접 입력 허용(false면 readOnly로 값만 표시)\n onInputChange?: (e: ChangeEvent<HTMLInputElement>) => void; // (e) => void — 입력 변경(raw 이벤트)\n // (text) => string | null — 합성 텍스트('2026.7')로 좌/우 값을 적용하고 \"정규화된 표시 문자열\"을 반환.\n // 형식이 틀리면 null/false. 파싱은 호출부가 담당(연.월/시:분 등 도메인별).\n // 타이핑 즉시(라이브) 적용되고, 입력 영역을 떠날 때 반환된 정규화 문자열로 표시값을 보정한다.\n // 실패 시 null(마지막 편집 인풋에 errorMessage 툴팁) 또는 { error:'left'|'right', message? }\n // (해당 파트 인풋 아래에 그 문구로 툴팁 — 파트별 문구는 호출부가 지정).\n onInputApply?: (text: string) => string | boolean | TwoDepthApplyError | null | undefined;\n errorMessage?: string; // 파트 지정이 없을 때의 기본 툴팁 메시지\n // 파트 입력 허용 문자(문자 1개씩 test) — 기본은 숫자만(구분자는 UI가 표시하므로 입력 불필요).\n allowedChars?: RegExp | null;\n inputPlaceholder?: string; // 'YYYY.MM'·'HH:MM'처럼 구분자 포함 문자열 — 구분자로 쪼개 좌/우 placeholder로 사용\n inputProps?: ComponentPropsWithoutRef<'input'>;\n // 좌측 컬럼\n leftOptions?: TwoDepthOption[]; // [{ value, label, disabled? }]\n leftValue?: TwoDepthOption['value'];\n onLeftChange?: (value: TwoDepthOption['value']) => void;\n // 우측 컬럼\n rightOptions?: TwoDepthOption[]; // [{ value, label, disabled? }]\n rightValue?: TwoDepthOption['value'];\n onRightChange?: (value: TwoDepthOption['value']) => void;\n maxVisible?: number; // 각 컬럼 표시 행 수(초과 시 내부 스크롤)\n width?: number | string;\n}\n\nexport function TwoDepthList({\n // 상단 입력 영역 — 현재 선택을 좌/우 분리 인풋으로 보여주며, 기본적으로 직접 입력(타이핑)도 가능하다.\n inputValue = '',\n separator = ':', // 좌/우 파트 구분자 표시·합성 기준 — year_month '.' / time ':'\n showInput = true, // 상단 입력 영역 표시 여부(false면 좌/우 컬럼만 — 트리거가 따로 입력을 가질 때)\n editable = true, // 상단 input 직접 입력 허용(false면 readOnly로 값만 표시)\n onInputChange, // (e) => void — 입력 변경(raw 이벤트)\n // (text) => string | null — 합성 텍스트('2026.7')로 좌/우 값을 적용하고 \"정규화된 표시 문자열\"을 반환.\n // 형식이 틀리면 null/false. 파싱은 호출부가 담당(연.월/시:분 등 도메인별).\n // 타이핑 즉시(라이브) 적용되고, 입력 영역을 떠날 때 반환된 정규화 문자열로 표시값을 보정한다.\n // 실패 시 null(마지막 편집 인풋에 errorMessage 툴팁) 또는 { error:'left'|'right', message? }\n // (해당 파트 인풋 아래에 그 문구로 툴팁 — 파트별 문구는 호출부가 지정).\n onInputApply,\n errorMessage = '형식이 올바르지 않습니다.', // 파트 지정이 없을 때의 기본 툴팁 메시지\n // 파트 입력 허용 문자(문자 1개씩 test) — 기본은 숫자만(구분자는 UI가 표시하므로 입력 불필요).\n allowedChars = /\\d/,\n inputPlaceholder = '', // 'YYYY.MM'·'HH:MM'처럼 구분자 포함 문자열 — 구분자로 쪼개 좌/우 placeholder로 사용\n inputProps = {},\n // 좌측 컬럼\n leftOptions = [], // [{ value, label, disabled? }]\n leftValue,\n onLeftChange,\n // 우측 컬럼\n rightOptions = [], // [{ value, label, disabled? }]\n rightValue,\n onRightChange,\n maxVisible = 5, // 각 컬럼 표시 행 수(초과 시 내부 스크롤)\n width = 160,\n className = '',\n ...props\n}: TwoDepthListProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const [lPlaceholder, rPlaceholder] = splitParts(inputPlaceholder, separator);\n\n // 직접 입력용 좌/우 파트 텍스트.\n // - 적용은 타이핑 즉시(라이브) — 값을 친 뒤 팝오버 바깥을 눌러 닫혀도 이미 반영돼 있다.\n // - 포커스가 입력 영역 안에 있는 동안엔 정규화 값으로 덮어쓰지 않는다 → \"2:2 → 2:20\"처럼 마저 입력 가능.\n // 포커스가 영역을 떠나면(두 인풋 모두 blur) 정규화된 표시값으로 보정한다.\n // - 외부에서 값(컬럼 선택 등)으로 inputValue가 바뀌면, 포커스가 없을 때만 텍스트를 동기화한다.\n const [focused, setFocused] = useState(false);\n const [parts, setParts] = useState<[string, string]>(() => splitParts(inputValue, separator));\n const [error, setError] = useState<{ side: 'left' | 'right'; message: string } | null>(null); // { side:'left'|'right', message } | null\n // 에러 툴팁은 포털(fixed)로 띄운다(팝오버 overflow-hidden에 잘리지 않게). 위치 기준은 실패한 파트 인풋 rect.\n const inputAreaRef = useRef<HTMLDivElement | null>(null);\n const leftBoxRef = useRef<HTMLDivElement | null>(null);\n const rightBoxRef = useRef<HTMLDivElement | null>(null);\n const lastSideRef = useRef<'left' | 'right'>('left'); // 파트 미지정 실패(null) 시 툴팁을 붙일 마지막 편집 인풋\n const [errorRect, setErrorRect] = useState<DOMRect | null>(null);\n const [prevInput, setPrevInput] = useState(inputValue);\n if (inputValue !== prevInput) {\n setPrevInput(inputValue);\n if (!focused) {\n setParts(splitParts(inputValue, separator));\n setError(null);\n }\n }\n\n const compose = (l: string, r: string) => `${l}${separator}${r}`;\n\n const handlePartChange = (side: 'left' | 'right') => (e: ChangeEvent<HTMLInputElement>) => {\n // 허용 문자만 남긴다(기본 숫자만 — 구분자는 UI가 항상 표시).\n const v = allowedChars\n ? Array.from(e.target.value).filter((c) => allowedChars.test(c)).join('')\n : e.target.value;\n const next: [string, string] = side === 'left' ? [v, parts[1]] : [parts[0], v];\n lastSideRef.current = side;\n setParts(next);\n onInputChange?.(e);\n if (error) setError(null); // 타이핑하면 에러 해제(검증은 영역 이탈 시)\n if ((next[0] + next[1]).trim() !== '') onInputApply?.(compose(next[0], next[1])); // 유효하면 즉시 적용(무효면 호출부가 무시)\n };\n\n const handleFocus = (side: 'left' | 'right') => (e: FocusEvent<HTMLInputElement>) => {\n lastSideRef.current = side;\n setFocused(true);\n inputProps.onFocus?.(e);\n e.target.select(); // 포커스 시 전체 선택 — 기존 값을 바로 덮어쓰기 쉽게\n };\n\n // 입력 영역 이탈(두 인풋 모두 blur) — 형식 검증 + 정규화된 표시값으로 보정.\n // 좌↔우 인풋 사이 이동(relatedTarget이 영역 안)은 이탈로 보지 않는다.\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n inputProps.onBlur?.(e);\n if (inputAreaRef.current?.contains(e.relatedTarget)) return;\n setFocused(false);\n const t = compose(parts[0].trim(), parts[1].trim());\n if (parts[0].trim() === '' && parts[1].trim() === '') {\n setParts(splitParts(inputValue, separator));\n setError(null);\n return;\n }\n const norm = onInputApply ? onInputApply(t) : t; // 유효=정규화 문자열/true, 무효=null 또는 {error, message?}\n if (norm && !(typeof norm === 'object' && norm.error)) {\n setParts(splitParts(typeof norm === 'string' ? norm : inputValue, separator));\n setError(null);\n } else {\n const side = (typeof norm === 'object' && norm !== null && norm.error) || lastSideRef.current;\n const box = side === 'right' ? rightBoxRef.current : leftBoxRef.current;\n setErrorRect(box?.getBoundingClientRect() ?? inputAreaRef.current?.getBoundingClientRect() ?? null);\n setError({ side, message: (typeof norm === 'object' && norm !== null && norm.message) || errorMessage });\n }\n };\n\n // Enter = 확정(blur 처리로 정규화)\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n inputProps.onKeyDown?.(e);\n if (e.key === 'Enter') {\n e.preventDefault();\n e.currentTarget.blur();\n }\n };\n\n const partInput = (side: 'left' | 'right', value: string, placeholder: string) => (\n <div ref={side === 'left' ? leftBoxRef : rightBoxRef} className=\"min-w-0 flex-1\">\n <Input\n value={editable ? value : splitParts(inputValue, separator)[side === 'left' ? 0 : 1]}\n onChange={editable ? handlePartChange(side) : undefined}\n readOnly={!editable}\n placeholder={placeholder}\n width=\"100%\"\n inputProps={\n editable\n ? { ...inputProps, onFocus: handleFocus(side), onBlur: handleBlur, onKeyDown: handleKeyDown }\n : inputProps\n }\n />\n </div>\n );\n\n return (\n <div\n style={{ width: widthStyle }}\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 {/* input area — 흰 배경, [좌 Input] 구분자 [우 Input] (gap 4px). popover(gray100)와 1px 틈이 구분선.\n 에러 툴팁은 Input 내장(absolute)을 쓰면 팝오버 overflow-hidden에 잘리므로,\n 여기선 끄고 아래에서 포털(fixed)로 직접 띄운다. showInput=false면 영역 자체를 숨긴다. */}\n {showInput && (\n <div ref={inputAreaRef} className=\"flex w-full items-center gap-spacing-3 bg-list-group-bg p-spacing-5\">\n {partInput('left', parts[0], lPlaceholder)}\n {/* 구분자 — 아래 정렬(마침표가 인풋 텍스트 하단선에 붙도록 self-end + 인풋 py와 동일한 pb) */}\n <span className=\"shrink-0 self-end pb-spacing-3 text-14 text-text-field-filled-text\">{separator}</span>\n {partInput('right', parts[1], rPlaceholder)}\n </div>\n )}\n {/* 2 컬럼 목록 — 1px 틈(gap-spacing-1)이 두 컬럼 사이 구분선이 된다. */}\n <div className=\"flex w-full gap-spacing-1\">\n <Column options={leftOptions} value={leftValue} onChange={onLeftChange} maxVisible={maxVisible} />\n <Column options={rightOptions} value={rightValue} onChange={onRightChange} maxVisible={maxVisible} />\n </div>\n\n {/* 에러 툴팁 — 포털(fixed)로 입력 행 아래에 띄워 overflow-hidden에 잘리지 않게 한다. */}\n {showInput &&\n error &&\n errorRect &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{ position: 'fixed', top: errorRect.bottom + 6, left: errorRect.left }}\n >\n <Tooltip variant=\"error\" beak=\"top\">\n {error.message}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n","// DatePicker — 캘린더 날짜 선택 팝오버 (Figma date picker, node 7594:4368)\n// 구성(위→아래, 각 영역은 gap-spacing-1(1px) 틈으로 구분):\n// 1) date area — 상단 네비게이션(캘린더 아이콘 + 연.월 선택 / 이전·오늘·다음 버튼) + 요일 헤더\n// 2) calender area — 날짜 그리드(CalendarDayButton). scrollNavigate면 ScrollArea로 주 단위\n// 무한 세로 스크롤(페이지처럼 연속 이동, 스크롤 따라 헤더 연.월 갱신), 아니면 정적 6주 그리드.\n// 3) time area — (showTime) 시작/마감 시간 선택(시:분 2depth 목록)\n// 연.월 선택과 시간 선택은 TwoDepthList(2depth 목록)를 Popover로 띄워 고른다.\n//\n// 선택 모드:\n// - mode='single' : value=Date|null\n// - mode='range' : value={ start: Date|null, end: Date|null }\n// controlled(value/onChange) · uncontrolled(defaultValue) 모두 지원. 표시 월은 month/defaultMonth.\n// 색은 cal-* / list-* / font-icon-* 시멘틱 토큰만 사용.\nimport { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { Popover } from './Popover';\nimport { InlineFieldTrigger } from './InlineFieldTrigger';\nimport { TwoDepthList } from './TwoDepthList';\nimport { CalendarDayButton } from './CalendarDayButton';\nimport { ScrollArea } from './ScrollArea';\nimport { Tooltip } from './Tooltip';\n\n// ── 타입 ─────────────────────────────────────────────────────────────────\n// 범위 선택값 — mode='range'의 value 형태\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n// 선택값(단일=Date|null, 범위=DateRange)\nexport type DatePickerValue = Date | DateRange | null;\n// 연/월·시/분 2depth 목록 옵션\ninterface DepthOption {\n value: string;\n label: string;\n}\n\n// ── 날짜 헬퍼 (외부 라이브러리 없이 native Date) ─────────────────────────\nconst pad2 = (n: number) => String(n).padStart(2, '0');\nconst startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());\nconst isSameDay = (a: Date | null | undefined, b: Date | null | undefined) =>\n !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\nconst addMonths = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth() + n, 1);\nconst fmtYearMonth = (d: Date) => `${d.getFullYear()}.${pad2(d.getMonth() + 1)}`;\n\n// 직접 입력 파싱 — 연.월. 구분자 있으면 연도 2/4자리·월 1~2자리(예: \"2025.05\" · \"25.3\"),\n// 구분자 없으면 YYMM(4자리, 예 \"2505\") 또는 YYYYMM(6자리, 예 \"202505\"). 2자리 연도는 20xx로 보정.\nconst DEFAULT_WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'];\n\n// 표시 월의 6주 × 7일 그리드(앞뒤 달 채움)\nfunction buildWeeks(viewDate: Date, weekStartsOn: number) {\n const y = viewDate.getFullYear();\n const m = viewDate.getMonth();\n const firstDow = (new Date(y, m, 1).getDay() - weekStartsOn + 7) % 7;\n const gridStart = new Date(y, m, 1 - firstDow);\n return Array.from({ length: 6 }, (_, w) =>\n Array.from({ length: 7 }, (_, d) => new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + w * 7 + d)),\n );\n}\n\n// 날짜 셀 라벨 — 매월 1일은 \"M.1\"로 표기해 달 경계를 구분(Figma \"7.1\"·\"8.1\").\nconst dayLabel = (date: Date) => (date.getDate() === 1 ? `${date.getMonth() + 1}.1` : date.getDate());\n\n// ── 주(week) 헬퍼 — 무한 스크롤 캘린더용 ─────────────────────────────────\nconst monthKey = (d: Date) => d.getFullYear() * 12 + d.getMonth();\nconst addWeeks = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n * 7);\nconst startOfWeek = (d: Date, weekStartsOn: number) => {\n const x = startOfDay(d);\n const diff = (x.getDay() - weekStartsOn + 7) % 7;\n return new Date(x.getFullYear(), x.getMonth(), x.getDate() - diff);\n};\nconst firstWeekOfMonth = (monthDate: Date, weekStartsOn: number) =>\n startOfWeek(new Date(monthDate.getFullYear(), monthDate.getMonth(), 1), weekStartsOn);\n\n// 스크롤 메트릭(셀 24 + 행 간격 12 = 한 주 36px) — idx ↔ scrollTop 환산에 사용\nconst ROW_UNIT = 36;\nconst GRID_PAD = 12; // 날짜 영역 상하 패딩(spacing-6) — 정적 그리드(p-spacing-6)와 동일\nconst VISIBLE_HEIGHT = 6 * 24 + 5 * 12 + 2 * GRID_PAD; // 6주+상하 패딩 = 228px (정적 그리드와 동일 높이)\nconst WIN_BEFORE = 20; // 앵커 달 첫 주 기준 위로 채울 주\nconst WIN_AFTER = 26; // 아래로 채울 주\nconst BATCH = 10; // 끝 근처에서 한 번에 덧붙일 주\nconst MAX_WEEKS = 160; // DOM 상한(반대편을 잘라 유지)\n\n// 앵커 달의 첫 주를 index WIN_BEFORE에 두는 주(week-start) 배열\nfunction buildWindow(monthDate: Date, weekStartsOn: number) {\n const fw = firstWeekOfMonth(monthDate, weekStartsOn);\n return Array.from({ length: WIN_BEFORE + WIN_AFTER }, (_, i) => addWeeks(fw, i - WIN_BEFORE));\n}\n\n// CalendarScroller — 주 단위 무한 세로 스크롤 캘린더(페이지 스크롤처럼 연속 이동).\n// ScrollArea(오버레이 스크롤바)로 감싸고, 끝에 가까워지면 주를 prepend/append해 끝없이 이어 붙인다.\n// 보이는 중앙 주의 달을 헤더로 보고(onVisibleMonthChange), 외부에서 달이 바뀌면(anchorKey) 그 달로 스크롤한다.\ninterface CalendarScrollerProps {\n anchorMonth: Date;\n anchorKey: number;\n weekStartsOn: number;\n onVisibleMonthChange: (month: Date) => void;\n renderDay: (date: Date) => ReactNode;\n}\n\nfunction CalendarScroller({ anchorMonth, anchorKey, weekStartsOn, onVisibleMonthChange, renderDay }: CalendarScrollerProps) {\n const vpRef = useRef<HTMLDivElement | null>(null);\n const setVp = useCallback((el: HTMLDivElement | null) => {\n vpRef.current = el;\n }, []);\n const [weeks, setWeeks] = useState<Date[]>(() => buildWindow(anchorMonth, weekStartsOn));\n const lastKeyRef = useRef(anchorKey); // 마지막으로 헤더에 보고/반영한 달\n const adjustRef = useRef<'anchor' | number>('anchor'); // 다음 레이아웃에서 scrollTop 보정: 'anchor' | px(number) | 0\n const busyRef = useRef(false); // prepend/append 진행 중 중복 방지\n\n // 외부에서 달이 바뀌면(버튼·연월 선택) 그 달 기준으로 윈도우 재구성 + 상단 정렬\n useEffect(() => {\n if (anchorKey === lastKeyRef.current) return; // 스크롤로 인한 변경이면 이미 동기화됨\n lastKeyRef.current = anchorKey;\n adjustRef.current = 'anchor';\n setWeeks(buildWindow(anchorMonth, weekStartsOn));\n }, [anchorKey, anchorMonth, weekStartsOn]);\n\n // weeks가 바뀐 직후 scrollTop 보정(앵커 정렬 / prepend·trim 위치 유지)\n useLayoutEffect(() => {\n const el = vpRef.current;\n if (!el) return;\n if (adjustRef.current === 'anchor') el.scrollTop = WIN_BEFORE * ROW_UNIT; // 콘텐츠 py 패딩 덕에 첫 주 위 12px 여백(정적과 동일)\n else if (adjustRef.current) el.scrollTop += adjustRef.current;\n adjustRef.current = 0;\n busyRef.current = false;\n }, [weeks]);\n\n const handleScroll = () => {\n const el = vpRef.current;\n if (!el) return;\n // 보이는 중앙 주의 목요일이 속한 달을 현재 달로 본다\n const idx = Math.min(Math.max(Math.floor((el.scrollTop - GRID_PAD + el.clientHeight / 2) / ROW_UNIT), 0), weeks.length - 1);\n const wk = weeks[idx];\n const mid = new Date(wk.getFullYear(), wk.getMonth(), wk.getDate() + 3);\n const key = monthKey(mid);\n if (key !== lastKeyRef.current) {\n lastKeyRef.current = key;\n onVisibleMonthChange(new Date(mid.getFullYear(), mid.getMonth(), 1));\n }\n // 끝 근처면 무한 확장(+ 반대편 trim)\n if (busyRef.current) return;\n const NEAR = ROW_UNIT * 4;\n if (el.scrollTop < NEAR) {\n busyRef.current = true;\n setWeeks((prev) => {\n const fw = prev[0];\n let next = [...Array.from({ length: BATCH }, (_, i) => addWeeks(fw, i - BATCH)), ...prev];\n adjustRef.current = BATCH * ROW_UNIT; // 위에 끼운 만큼 아래로 보정\n if (next.length > MAX_WEEKS) next = next.slice(0, MAX_WEEKS); // 아래쪽 잘라냄(스크롤 영향 없음)\n return next;\n });\n } else if (el.scrollTop + el.clientHeight > el.scrollHeight - NEAR) {\n busyRef.current = true;\n setWeeks((prev) => {\n const last = prev[prev.length - 1];\n let next = [...prev, ...Array.from({ length: BATCH }, (_, i) => addWeeks(last, i + 1))];\n if (next.length > MAX_WEEKS) {\n const drop = next.length - MAX_WEEKS;\n next = next.slice(drop);\n adjustRef.current = -drop * ROW_UNIT; // 앞쪽 잘라낸 만큼 위로 보정\n }\n return next;\n });\n }\n };\n\n return (\n <ScrollArea\n maxHeight={VISIBLE_HEIGHT}\n onViewport={setVp}\n onScroll={handleScroll}\n className=\"w-full bg-list-group-bg\"\n contentClassName=\"flex select-none flex-col gap-spacing-6 overscroll-contain p-spacing-6\"\n >\n {weeks.map((wkStart) => (\n <div key={wkStart.getTime()} className=\"flex items-center\">\n {Array.from({ length: 7 }, (_, d) =>\n renderDay(new Date(wkStart.getFullYear(), wkStart.getMonth(), wkStart.getDate() + d)),\n )}\n </div>\n ))}\n </ScrollArea>\n );\n}\n\n// ── 연.월 / 시간 선택을 띄우는 셀렉트형 트리거 (텍스트 + chevron, hover 밑줄) ──────\ninterface DualSelectFieldProps {\n icon?: LucideIcon;\n display?: ReactNode;\n panelWidth?: number;\n placement?: string;\n disabled?: boolean;\n // ── 이하 TwoDepthList 패스스루 (TwoDepthListProps와 시그니처 일치) ──\n separator?: string;\n inputValue?: string;\n inputPlaceholder?: string;\n /** 직접 입력 적용 — 정규화 문자열 반환 또는 { error: 'left'|'right', message } */\n onInputApply?: (text: string) => string | { error: 'left' | 'right'; message: string };\n leftOptions?: DepthOption[];\n leftValue?: string;\n onLeftChange?: (value: string | number) => void;\n rightOptions?: DepthOption[];\n rightValue?: string;\n onRightChange?: (value: string | number) => void;\n maxVisible?: number;\n}\n\nfunction DualSelectField({ icon: Icon, display, panelWidth = 160, placement = 'auto-left', disabled = false, ...panelProps }: DualSelectFieldProps) {\n // 트리거 비주얼은 공유 프리미티브 InlineFieldTrigger 사용(Select 인라인 텍스트형과 동일).\n // 열림 상태(텍스트 회색·chevron 회전)를 반영하려고 Popover를 controlled로 둔다.\n const [open, setOpen] = useState(false);\n return (\n <Popover\n placement={placement}\n menuWidth={panelWidth}\n disabled={disabled}\n open={open}\n onOpenChange={setOpen}\n trigger={\n <InlineFieldTrigger icon={Icon} open={open} disabled={disabled}>\n {display}\n </InlineFieldTrigger>\n }\n >\n <TwoDepthList width={panelWidth} {...panelProps} />\n </Popover>\n );\n}\n\n// 시간 영역 한 칸(시작/마감) — 라벨 + 시:분 셀렉트. disabled면 비활성(해당 날짜 미선택 시).\n// justify: 'center'(범위 2칸, 기본) | 'between'(단일 1칸 — 라벨 왼쪽·셀렉터 오른쪽 정렬)\ninterface TimeFieldProps {\n label?: ReactNode;\n value?: string;\n onChange?: (time: string) => void;\n hourOptions: DepthOption[];\n minuteOptions: DepthOption[];\n placement?: string;\n disabled?: boolean;\n justify?: 'center' | 'between';\n}\n\nfunction TimeField({ label, value, onChange, hourOptions, minuteOptions, placement = 'auto-right', disabled = false, justify = 'center' }: TimeFieldProps) {\n const [hh = '00', mm = '00'] = (value || '').split(':');\n const set = (h: string | number, m: string | number) => onChange?.(`${h}:${m}`);\n return (\n <div\n className={`flex h-[44px] flex-1 items-center gap-spacing-6 bg-list-group-bg px-spacing-6 py-spacing-5 ${\n justify === 'between' ? 'justify-between' : 'justify-center'\n }`}\n >\n <span className={`whitespace-nowrap text-12 ${disabled ? 'text-font-icon-2' : 'text-font-icon-5'}`}>{label}</span>\n <DualSelectField\n disabled={disabled}\n placement={placement}\n panelWidth={140}\n separator=\":\"\n display={`${hh}:${mm}`}\n inputValue={`${hh}:${mm}`}\n inputPlaceholder=\"HH:MM\"\n onInputApply={(t) => {\n const [hs, ms] = t.split(':');\n const hh = Number(hs);\n const mm = Number(ms);\n if (hs.trim() === '' || Number.isNaN(hh) || hh > 23) return { error: 'left', message: '시는 0~23으로 입력하세요' };\n if (ms.trim() === '' || Number.isNaN(mm) || mm > 59) return { error: 'right', message: '분은 0~59로 입력하세요' };\n const v = `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;\n onChange?.(v);\n return v;\n }}\n leftOptions={hourOptions}\n leftValue={hh}\n onLeftChange={(h) => set(h, mm)}\n rightOptions={minuteOptions}\n rightValue={mm}\n onRightChange={(m) => set(hh, m)}\n maxVisible={5}\n />\n </div>\n );\n}\n\nexport interface DatePickerProps\n extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {\n /** 'single' | 'range' */\n mode?: 'single' | 'range';\n value?: DatePickerValue;\n defaultValue?: DatePickerValue;\n onChange?: (value: DatePickerValue) => void;\n // 선택 제한 — 지나간 날짜/미래 날짜/범위 밖 날짜를 선택 불가(비활성)로\n /** 오늘 이전 날짜 선택 불가(오늘은 선택 가능) */\n disablePast?: boolean;\n /** 오늘 이후 날짜 선택 불가(오늘은 선택 가능) */\n disableFuture?: boolean;\n /** 이 날짜 이전은 비활성 (Date) */\n minDate?: Date;\n /** 이 날짜 이후는 비활성 (Date) */\n maxDate?: Date;\n /** (date) => boolean — 커스텀 비활성 판정 */\n disabledDate?: (date: Date) => boolean;\n /** 표시 월(controlled) — Date */\n month?: Date;\n defaultMonth?: Date;\n onMonthChange?: (month: Date) => void;\n /** 하단 시간 영역 표시 — 범위=시작/마감 2칸 · 단일=시간 1칸(startTime 사용, 마감 없음) */\n showTime?: boolean;\n startTime?: string;\n endTime?: string;\n onStartTimeChange?: (time: string) => void;\n onEndTimeChange?: (time: string) => void;\n /** 단일 모드 시간 칸 라벨 */\n timeLabel?: ReactNode;\n /** 0=일요일 시작 */\n weekStartsOn?: number;\n weekdayLabels?: string[];\n todayLabel?: string;\n /** 날짜 영역에서 휠 세로 스크롤로 이전/다음 달 이동 */\n scrollNavigate?: boolean;\n // 범위 모드에서 날짜 hover 시 \"시작일/마감일\" 안내 툴팁(찍는 날짜가 무엇인지 인지용)\n rangeTooltip?: boolean;\n rangeStartLabel?: string;\n rangeEndLabel?: string;\n /** 연도 선택 범위(미지정 시 표시 연도 ±12) */\n minYear?: number;\n maxYear?: number;\n width?: number | string;\n}\n\nexport function DatePicker({\n mode = 'single',\n value,\n defaultValue,\n onChange,\n disablePast = false,\n disableFuture = false,\n minDate,\n maxDate,\n disabledDate,\n month,\n defaultMonth,\n onMonthChange,\n showTime = false,\n startTime = '00:00',\n endTime = '23:59',\n onStartTimeChange,\n onEndTimeChange,\n timeLabel = '시간 입력',\n weekStartsOn = 0,\n weekdayLabels = DEFAULT_WEEKDAYS,\n todayLabel = '오늘',\n scrollNavigate = true,\n rangeTooltip = true,\n rangeStartLabel = '시작일',\n rangeEndLabel = '마감일',\n minYear,\n maxYear,\n width = 276,\n className = '',\n ...props\n}: DatePickerProps) {\n const today = startOfDay(new Date());\n\n // ── 선택값 (controlled/uncontrolled) ─────────────────────────\n const isRange = mode === 'range';\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState<DatePickerValue>(\n defaultValue ?? (isRange ? { start: null, end: null } : null),\n );\n const current = isControlled ? (value as DatePickerValue) : internalValue;\n const range = isRange ? (current as DateRange | null) ?? { start: null, end: null } : null;\n const single = !isRange ? (current as Date | null) ?? null : null;\n\n // 범위 모드 hover 안내 툴팁 — 호버한 셀 위에 \"시작일/마감일\"을 띄운다(포털, 클리핑 방지).\n const [hover, setHover] = useState<{ rect: DOMRect; label: string } | null>(null); // { rect, label } | null\n const showRangeTooltip = isRange && rangeTooltip;\n\n const commit = (next: DatePickerValue) => {\n if (!isControlled) setInternalValue(next);\n onChange?.(next);\n };\n\n // ── 표시 월 (controlled/uncontrolled) ────────────────────────\n const initialMonth =\n defaultMonth ?? (isRange ? range?.start : single) ?? today;\n const [internalMonth, setInternalMonth] = useState(() => startOfDay(initialMonth));\n const viewDate = month ?? internalMonth;\n const setView = (next: Date) => {\n if (month === undefined) setInternalMonth(next);\n onMonthChange?.(next);\n };\n\n // ── 선택 제한(비활성) 판정 ───────────────────────────────────\n const isDayDisabled = (date: Date) => {\n const d = startOfDay(date);\n if (disablePast && d < today) return true;\n if (disableFuture && d > today) return true;\n if (minDate && d < startOfDay(minDate)) return true;\n if (maxDate && d > startOfDay(maxDate)) return true;\n return !!disabledDate && !!disabledDate(d);\n };\n\n // ── 날짜 클릭 ────────────────────────────────────────────────\n // 범위 클릭 규칙 — 완성된 범위는 풀리지 않고 한쪽만 이동한다:\n // 시작일 앞을 찍으면 시작일만 이동(마감 유지), 마감일 뒤를 찍으면 마감일만 이동(시작 유지),\n // 범위 안(시작~마감, 경계 포함)은 첫 클릭=시작일 이동, 두 번째 클릭=마감일 이동(교대).\n // 미완성(시작만) 상태는 기존대로 — 시작 앞을 찍으면 시작 재지정, 뒤를 찍으면 마감 확정.\n const insideNextRef = useRef<'start' | 'end'>('start'); // 범위 안 클릭이 다음에 잡을 쪽(교대 상태)\n const nextRange = (d: Date): DateRange => {\n const { start, end } = range as DateRange;\n if (!start) return { start: d, end: null };\n if (end) {\n if (d < start) return { start: d, end };\n if (d > end) return { start, end: d };\n return insideNextRef.current === 'start' ? { start: d, end } : { start, end: d };\n }\n if (d < start) return { start: d, end: null };\n return { start, end: d };\n };\n\n const handleDayClick = (date: Date) => {\n if (isDayDisabled(date)) return; // 비활성 날짜는 무시\n const d = startOfDay(date);\n if (!isRange) {\n commit(d);\n return;\n }\n const { start, end } = range as DateRange;\n const inside = !!(start && end) && d >= (start as Date) && d <= (end as Date);\n commit(nextRange(d));\n // 교대 상태 갱신 — 범위 안 클릭이면 다음 쪽으로 토글, 그 외 클릭은 처음(시작)으로 리셋\n insideNextRef.current = inside && insideNextRef.current === 'start' ? 'end' : 'start';\n };\n\n // 이 날짜를 클릭하면 시작일을 잡는지 마감일을 잡는지 — nextRange와 동일한 분기.\n // r을 넘기면 그 범위 기준으로 판정(클릭 직후 다음 역할 계산용 — state 반영 전).\n const dayRole = (date: Date, r: DateRange = range as DateRange) => {\n const d = startOfDay(date);\n const { start, end } = r;\n if (!start) return 'start';\n if (end) {\n if (d < start) return 'start';\n if (d > end) return 'end';\n return insideNextRef.current;\n }\n return d < start ? 'start' : 'end';\n };\n\n // ── 날짜 셀 상태 계산 ────────────────────────────────────────\n // 우선순위: 비활성(muted) → 선택/범위 → today → 이전·다음 달(muted) → default.\n // - 비활성(disablePast 등) 날짜는 muted(선택 불가).\n // - 선택 가능한 상태에서는 현재(표시) 달은 default, 이전/다음 달은 muted로 구분한다.\n // → 차이: \"이번 달 지난 날짜\"가 선택 가능하면 default, disablePast면 muted가 된다.\n const dayState = (date: Date) => {\n if (isDayDisabled(date)) return 'muted';\n if (isRange) {\n const { start, end } = range as DateRange;\n const isStart = isSameDay(date, start);\n const isEnd = isSameDay(date, end);\n if (isStart && isEnd) return 'selected';\n if (isStart) return end ? 'range-start' : 'selected';\n if (isEnd) return 'range-end';\n if (start && end && date > start && date < end) return 'in-range';\n } else if (isSameDay(date, single)) {\n return 'selected';\n }\n if (isSameDay(date, today)) return 'today';\n if (monthKey(date) !== monthKey(viewDate)) return 'muted'; // 이전/다음 달\n return 'default';\n };\n\n // ── 연/월 옵션 ───────────────────────────────────────────────\n const viewYear = viewDate.getFullYear();\n const lo = Math.min(minYear ?? viewYear - 12, viewYear);\n const hi = Math.max(maxYear ?? viewYear + 12, viewYear);\n const yearOptions = Array.from({ length: hi - lo + 1 }, (_, i) => ({\n value: String(lo + i),\n label: `${lo + i}년`,\n }));\n const monthOptions = Array.from({ length: 12 }, (_, i) => ({\n value: String(i + 1),\n label: `${i + 1}월`,\n }));\n\n // ── 시/분 옵션 ── 값은 'HH'/'MM', 라벨은 \"00시\"/\"00분\"\n const hourOptions = Array.from({ length: 24 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}시` }));\n const minuteOptions = Array.from({ length: 60 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}분` }));\n\n // 정렬용 요일 라벨(weekStartsOn 반영)\n const weekdays = weekdayLabels.map((_, i) => weekdayLabels[(i + weekStartsOn) % 7]);\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n\n // 날짜 셀 1칸 — 정적 그리드/무한 스크롤이 공유한다.\n const renderDay = (date: Date) => {\n const disabled = isDayDisabled(date);\n // 비활성 날짜는 안내 툴팁도 띄우지 않는다(선택 불가이므로 시작일/마감일 안내가 무의미).\n const tip = showRangeTooltip && !disabled;\n // 행 가장자리 칸(주 시작=왼쪽 끝·주 끝=오른쪽 끝) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n const col = (date.getDay() - weekStartsOn + 7) % 7;\n const edge = col === 0 ? 'left' : col === 6 ? 'right' : undefined;\n return (\n <CalendarDayButton\n key={date.toISOString()}\n state={dayState(date)}\n edge={edge}\n disabled={disabled}\n onClick={(e) => {\n // dayRole은 range(범위)에서만 의미 있고 단일 모드에선 range가 null이라 호출 금지.\n // 클릭 직후의 다음 역할은 커밋될 범위(nextRange) 기준으로 계산한다(state 반영 전).\n const nr = tip && !disabled ? nextRange(startOfDay(date)) : null;\n handleDayClick(date);\n if (nr) {\n setHover({\n rect: e.currentTarget.getBoundingClientRect(),\n label: dayRole(date, nr) === 'start' ? rangeStartLabel : rangeEndLabel,\n });\n }\n }}\n onMouseEnter={\n tip\n ? (e) =>\n setHover({\n rect: e.currentTarget.getBoundingClientRect(),\n label: dayRole(date) === 'start' ? rangeStartLabel : rangeEndLabel,\n })\n : undefined\n }\n onMouseLeave={tip ? () => setHover(null) : undefined}\n >\n {dayLabel(date)}\n </CalendarDayButton>\n );\n };\n\n return (\n <div\n style={{ width: widthStyle }}\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 {/* 1) date area — 네비게이션 + 요일 헤더 */}\n <div className=\"flex w-full flex-col gap-spacing-5 bg-list-group-bg p-spacing-6\">\n {/* navigation */}\n <div className=\"flex h-[32px] items-center justify-between pl-spacing-5\">\n {/* 연.월 선택 (캘린더 아이콘 + selected-text → 연/월 2depth 목록) */}\n <DualSelectField\n icon={CalendarIcon}\n separator=\".\"\n display={fmtYearMonth(viewDate)}\n inputValue={fmtYearMonth(viewDate)}\n inputPlaceholder=\"YYYY.MM\"\n onInputApply={(t) => {\n const [ys, ms] = t.split('.');\n if (!/^(\\d{2}|\\d{4})$/.test(ys.trim()))\n return { error: 'left', message: '연도는 YYYY 형식으로 입력하세요 (예: 2026 · 26)' };\n const mo = Number(ms);\n if (ms.trim() === '' || Number.isNaN(mo) || mo < 1 || mo > 12)\n return { error: 'right', message: '월은 1~12로 입력하세요' };\n const yr = ys.trim().length === 2 ? 2000 + Number(ys) : Number(ys);\n const next = new Date(yr, mo - 1, 1);\n setView(next);\n return fmtYearMonth(next); // 정규화된 'YYYY.MM'\n }}\n leftOptions={yearOptions}\n leftValue={String(viewYear)}\n onLeftChange={(y) => setView(new Date(Number(y), viewDate.getMonth(), 1))}\n rightOptions={monthOptions}\n rightValue={String(viewDate.getMonth() + 1)}\n onRightChange={(m) => setView(new Date(viewYear, Number(m) - 1, 1))}\n maxVisible={5}\n />\n {/* 이전 / 오늘 / 다음 */}\n <ButtonGroup gap=\"3\" className=\"shrink-0\">\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={ChevronLeft}\n aria-label=\"이전 달\"\n onClick={() => setView(addMonths(viewDate, -1))}\n />\n <Button variant=\"ghost\" size=\"24\" onClick={() => setView(startOfDay(new Date()))}>\n {todayLabel}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={ChevronRight}\n aria-label=\"다음 달\"\n onClick={() => setView(addMonths(viewDate, 1))}\n />\n </ButtonGroup>\n </div>\n {/* 요일 헤더 */}\n <div className=\"flex items-center\">\n {weekdays.map((w) => (\n <div key={w} className=\"flex h-[24px] w-[36px] items-center justify-center\">\n <span className=\"text-12 text-font-icon-5\">{w}</span>\n </div>\n ))}\n </div>\n </div>\n\n {/* 2) calender area — scrollNavigate면 주 단위 무한 세로 스크롤(스크롤바·페이지처럼 연속 이동),\n 아니면 표시 월의 정적 6주 그리드. */}\n {scrollNavigate ? (\n <CalendarScroller\n anchorMonth={viewDate}\n anchorKey={monthKey(viewDate)}\n weekStartsOn={weekStartsOn}\n onVisibleMonthChange={setView}\n renderDay={renderDay}\n />\n ) : (\n <div className=\"flex w-full flex-col gap-spacing-6 bg-list-group-bg p-spacing-6\">\n {buildWeeks(viewDate, weekStartsOn).map((week, wi) => (\n <div key={wi} className=\"flex items-center\">\n {week.map((date) => renderDay(date))}\n </div>\n ))}\n </div>\n )}\n\n {/* 3) time area — 시간 선택. 해당 날짜를 고르기 전엔 비활성.\n 범위 모드=시작/마감 2칸, 단일 모드=시간 1칸(마감 시간 없음 — 값은 startTime 그대로 사용,\n DateField 단일 표기 \"YY.MM.DD (HH:MM)\"와 일치). */}\n {showTime &&\n (isRange ? (\n <div className=\"flex w-full gap-spacing-1\">\n <TimeField\n label=\"시작 시간\"\n value={startTime}\n onChange={onStartTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!range?.start}\n />\n <TimeField\n label=\"마감 시간\"\n value={endTime}\n onChange={onEndTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!range?.end}\n />\n </div>\n ) : (\n <div className=\"flex w-full gap-spacing-1\">\n <TimeField\n label={timeLabel}\n justify=\"between\"\n value={startTime}\n onChange={onStartTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!single}\n />\n </div>\n ))}\n\n {/* 범위 hover 안내 툴팁 — 말줄임(TruncatingText)과 동일한 디자인(normal·beak 없음).\n 포털(fixed)로 띄워 컨테이너 overflow-hidden에 잘리지 않게 하고, 셀 위 가운데 정렬한다. */}\n {showRangeTooltip &&\n hover &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: hover.rect.left + hover.rect.width / 2,\n top: hover.rect.top - 6,\n transform: 'translate(-50%, -100%)',\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {hover.label}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n","// 날짜·시간 표기 유틸 (디자인 시스템 공통 포맷)\n// 규칙: 날짜·시간 표기는 항상 아래 형식으로 통일한다. 연·월·일·시·분 모두 2자리 0 패딩(연도는 뒤 2자리).\n// - 날짜+시간 1건 : \"YY.MM.DD (HH:MM)\" 예) 25.05.20 (12:00)\n// - 날짜만 : \"YY.MM.DD\" 예) 25.05.20\n// - 범위(둘 다) : \"YY.MM.DD (HH:MM)~YY.MM.DD (HH:MM)\" (틸드 양옆 공백 없음)\n// - 범위(시작만) : \"YY.MM.DD (HH:MM) ~ 마감일 없음\"\n// - 범위(마감만) : \"시작일 없음 ~ YY.MM.DD (HH:MM)\"\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\n// 'HH:MM' 문자열 또는 Date에서 시:분 문자열 추출\nconst toTimeStr = (time?: string | Date | null) => {\n if (!time) return '';\n if (typeof time === 'string') return time; // 이미 'HH:MM'\n return `${pad2(time.getHours())}:${pad2(time.getMinutes())}`;\n};\n\n// 날짜 → \"YY.MM.DD\" (2자리 연·월·일)\nexport function formatDate(date?: Date | null): string {\n if (!date) return '';\n return `${pad2(date.getFullYear() % 100)}.${pad2(date.getMonth() + 1)}.${pad2(date.getDate())}`;\n}\n\n// 날짜+시간 → \"YY.MM.DD (HH:MM)\". time은 'HH:MM' 문자열 또는 Date. 시간이 없으면 날짜만.\nexport function formatDateTime(date?: Date | null, time?: string | Date | null): string {\n if (!date) return '';\n const t = toTimeStr(time);\n return t ? `${formatDate(date)} (${t})` : formatDate(date);\n}\n\n// 날짜+시간 범위\n// - 둘 다 : \"A~B\" (공백 없는 틸드)\n// - 시작만: \"A ~ 마감일 없음\" / 마감만: \"시작일 없음 ~ B\"\n// - 라벨은 emptyStartLabel/emptyEndLabel로 변경 가능. 둘 다 없으면 ''.\nexport function formatDateTimeRange(\n start?: Date | null,\n startTime?: string | Date | null,\n end?: Date | null,\n endTime?: string | Date | null,\n { emptyStartLabel = '시작일 없음', emptyEndLabel = '마감일 없음' }: { emptyStartLabel?: string; emptyEndLabel?: string } = {},\n): string {\n const s = formatDateTime(start, startTime);\n const e = formatDateTime(end, endTime);\n if (start && end) return `${s}~${e}`;\n if (start) return `${s} ~ ${emptyEndLabel}`;\n if (end) return `${emptyStartLabel} ~ ${e}`;\n return '';\n}\n","// DateField — DatePicker를 여는 트리거 + 직접 입력 필드 (Figma date picker / Input, node 7593:4139)\n// 캘린더 아이콘 + 날짜(범위)·시간 값. 아이콘/필드를 누르면 DatePicker 팝오버가 열리고,\n// 입력칸에 직접 타이핑한 뒤 Enter 또는 포커스 아웃하면 파싱해 반영한다.\n// - 표기/입력 형식은 utils/datetime 규칙: \"YY.MM.DD (HH:MM)\", 범위 \"A~B\" / 시작만 \"A ~ 마감일 없음\".\n// - 범위에서 마감일을 안 적고 확정하면(시작만 입력) end=null → \"마감일 없음\"으로 표시된다.\n// - 필드 외형/상태(default·hover·focused·filled·disabled·readOnly·error 툴팁)는 tf-* 토큰.\nimport { useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Calendar as CalendarIcon } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { DatePicker } from './DatePicker';\nimport { Tooltip } from './Tooltip';\nimport { formatDateTime, formatDateTimeRange } from '../utils/datetime';\n\n// 편집 가능 상태의 테두리(ring) — SearchBar/Input과 동일(hover/focus-within 2px, tf-* 토큰)\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n// 입력 허용 문자 — 숫자·구분자(. - / : ~ 공백)·괄호만.\nconst ALLOWED_CHARS = /[\\d.\\-/:~() ]/;\n\n// 값 모델 — single: Date | null / range: { start, end }(각각 null 허용). DatePickerValue와 동일 구조.\ntype DateRange = { start: Date | null; end: Date | null };\ntype DateFieldValue = Date | null | DateRange;\n\n// 날짜 파트 → Date | null. 구분자 형식과 무구분자(YYYYMMDD/YYMMDD/MMDD) 모두 허용.\n// \"25.05.20\"·\"2025.05.20\" / \"20250520\"(→2025) / \"250520\"(→2025) / \"0520\"(→올해)\nfunction parseDatePart(s: string): Date | null {\n const str = s.trim();\n const sep = str.match(/^(\\d{2}|\\d{4})\\D+(\\d{1,2})\\D+(\\d{1,2})$/); // 구분자 있음\n const d8 = str.match(/^(\\d{4})(\\d{2})(\\d{2})$/); // YYYYMMDD\n const d6 = str.match(/^(\\d{2})(\\d{2})(\\d{2})$/); // YYMMDD\n const d4 = str.match(/^(\\d{2})(\\d{2})$/); // MMDD(올해)\n let year: number;\n let mo: number;\n let day: number;\n if (sep) {\n year = sep[1].length === 2 ? 2000 + Number(sep[1]) : Number(sep[1]);\n mo = Number(sep[2]);\n day = Number(sep[3]);\n } else if (d8) {\n year = Number(d8[1]);\n mo = Number(d8[2]);\n day = Number(d8[3]);\n } else if (d6) {\n year = 2000 + Number(d6[1]);\n mo = Number(d6[2]);\n day = Number(d6[3]);\n } else if (d4) {\n year = new Date().getFullYear();\n mo = Number(d4[1]);\n day = Number(d4[2]);\n } else {\n return null;\n }\n if (mo < 1 || mo > 12 || day < 1 || day > 31) return null;\n const date = new Date(year, mo - 1, day);\n if (date.getMonth() !== mo - 1 || date.getDate() !== day) return null; // 실재 날짜 검증\n return date;\n}\n\n// 시간 파트 → 'HH:MM' | null. 콜론(\"0:10\"·\"00:10\") / 무구분(시 2자리 + 분 1~2자리: \"0010\"→00:10, \"011\"→01:01).\nfunction parseTimePart(t: string): string | null {\n const str = t.trim();\n const colon = str.match(/^(\\d{1,2}):(\\d{1,2})$/);\n const compact = str.match(/^(\\d{2})(\\d{1,2})$/);\n const m = colon || compact;\n if (!m) return null;\n const h = Number(m[1]);\n const mn = Number(m[2]);\n if (h > 23 || mn > 59) return null;\n return `${pad2(h)}:${pad2(mn)}`;\n}\n\n// 날짜 토큰 파싱 — \"<날짜>[ <시간>]\" 또는 \"<날짜> (시간)\".\n// → { date, time } / 'empty'(빈 문자열·\"마감일 없음\"·\"시작일 없음\") / null(형식 오류)\nfunction parseDateToken(raw: string | null | undefined): { date: Date; time: string | null } | 'empty' | null {\n let s = (raw ?? '').trim();\n if (s === '' || s === '마감일 없음' || s === '시작일 없음') return 'empty';\n let timeRaw = null;\n const paren = s.match(/\\(([^)]*)\\)\\s*$/); // 끝의 (시간)\n if (paren) {\n timeRaw = paren[1].trim();\n s = s.slice(0, paren.index).trim();\n } else {\n const sp = s.match(/^(.*\\S)\\s+([\\d:]+)$/); // 공백 뒤 시간(숫자·콜론)\n if (sp) {\n s = sp[1].trim();\n timeRaw = sp[2];\n }\n }\n const date = parseDatePart(s);\n if (!date) return null;\n let time = null;\n if (timeRaw) {\n time = parseTimePart(timeRaw);\n if (!time) return null;\n }\n return { date, time };\n}\n\n// 입력 전체 파싱 → { value, startTime?, endTime? } | null(형식 오류)\nfunction parseDateFieldInput(\n text: string,\n isRange: boolean,\n): { value: DateFieldValue; startTime?: string | null; endTime?: string | null } | null {\n const t = text.trim();\n if (!isRange) {\n if (t === '') return { value: null };\n const tok = parseDateToken(t);\n if (tok === null) return null;\n if (tok === 'empty') return { value: null };\n return { value: tok.date, startTime: tok.time };\n }\n // 범위 — '~'로 시작/마감 분리. '~'가 없으면 시작만 입력한 것으로 본다(마감 없음).\n if (t === '') return { value: { start: null, end: null } };\n const idx = t.indexOf('~');\n const startPart = idx >= 0 ? t.slice(0, idx) : t;\n const endPart = idx >= 0 ? t.slice(idx + 1) : '';\n const s = parseDateToken(startPart);\n const e = parseDateToken(endPart);\n if (s === null || e === null) return null;\n return {\n value: { start: s === 'empty' ? null : s.date, end: e === 'empty' ? null : e.date },\n startTime: s === 'empty' ? null : s.time,\n endTime: e === 'empty' ? null : e.time,\n };\n}\n\n// ...props는 Popover로 전달된다(팝오버 옵션 통과).\ninterface DateFieldProps {\n mode?: 'single' | 'range';\n value?: DateFieldValue;\n defaultValue?: DateFieldValue;\n onChange?: (value: DateFieldValue) => void;\n showTime?: boolean;\n startTime?: string;\n defaultStartTime?: string;\n onStartTimeChange?: (time: string) => void;\n endTime?: string;\n defaultEndTime?: string;\n onEndTimeChange?: (time: string) => void;\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n formatErrorMessage?: string; // 직접 입력 형식 오류 메시지\n // 캘린더 선택 완료 시 자동 닫기 — 기본: 단일은 날짜 클릭 즉시 닫힘, 범위는 배경 클릭으로만 닫힘.\n // (showTime이면 시간도 골라야 하므로 true여도 닫지 않음)\n closeOnSelect?: boolean;\n // 너비: 'hug'(콘텐츠 폭) | 'fill'(부모 100%) | 숫자(px) | CSS 길이.\n // 미지정 시 기본 = showTime이면 260, 아니면 180. 폭이 좁으면 텍스트는 말줄임(…).\n width?: number | string;\n className?: string;\n // DatePicker 패스스루\n disablePast?: boolean;\n disableFuture?: boolean;\n minDate?: Date;\n maxDate?: Date;\n disabledDate?: (date: Date) => boolean;\n weekStartsOn?: number;\n minYear?: number;\n maxYear?: number;\n}\n\nexport function DateField({\n mode = 'single', // 'single' | 'range'\n value,\n defaultValue,\n onChange,\n showTime = false,\n startTime,\n defaultStartTime = '00:00',\n onStartTimeChange,\n endTime,\n defaultEndTime = '23:59',\n onEndTimeChange,\n placeholder = '날짜를 선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n formatErrorMessage = '날짜 형식이 올바르지 않습니다.', // 직접 입력 형식 오류 메시지\n // 캘린더 선택 완료 시 자동 닫기 — 기본: 단일은 날짜 클릭 즉시 닫힘, 범위는 배경 클릭으로만 닫힘.\n // (showTime이면 시간도 골라야 하므로 true여도 닫지 않음)\n closeOnSelect = mode !== 'range',\n // 너비: 'hug'(콘텐츠 폭) | 'fill'(부모 100%) | 숫자(px) | CSS 길이.\n // 미지정 시 기본 = showTime이면 260, 아니면 180. 폭이 좁으면 텍스트는 말줄임(…).\n width,\n className = '',\n // DatePicker 패스스루\n disablePast,\n disableFuture,\n minDate,\n maxDate,\n disabledDate,\n weekStartsOn,\n minYear,\n maxYear,\n ...props\n}: DateFieldProps) {\n const isRange = mode === 'range';\n const interactive = !disabled && !readOnly;\n const [open, setOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [text, setText] = useState(''); // 포커스 중 직접 입력 텍스트\n const [parseError, setParseError] = useState(false);\n // Enter/ESC가 blur()를 동기 발화시키므로, onBlur의 commit을 1회 건너뛰는 플래그.\n // 없으면 ESC(취소)가 편집 중 초안을 커밋하고, Enter는 onChange가 2회 발화한다(2026-07-07 감사).\n const skipBlurCommitRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n // 말줄임 hover 툴팁(규칙 5) — 값이 폭을 넘쳐 잘릴 때 전체 값을 보여준다(TruncatingText와 동일 방식).\n const [truncRect, setTruncRect] = useState<DOMRect | null>(null);\n\n // 값 (controlled/uncontrolled)\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState<DateFieldValue>(\n defaultValue ?? (isRange ? { start: null, end: null } : null),\n );\n const current = value !== undefined ? value : internalValue;\n const applyValue = (v: DateFieldValue) => {\n if (!isControlled) setInternalValue(v);\n onChange?.(v);\n setParseError(false); // 값이 정해지면(캘린더 선택·직접 입력 성공) 형식 에러 해제\n };\n\n // 시간 (controlled/uncontrolled)\n const stControlled = startTime !== undefined;\n const [intSt, setIntSt] = useState(defaultStartTime);\n const curSt = startTime !== undefined ? startTime : intSt;\n const setSt = (v: string) => {\n if (!stControlled) setIntSt(v);\n onStartTimeChange?.(v);\n };\n const etControlled = endTime !== undefined;\n const [intEt, setIntEt] = useState(defaultEndTime);\n const curEt = endTime !== undefined ? endTime : intEt;\n const setEt = (v: string) => {\n if (!etControlled) setIntEt(v);\n onEndTimeChange?.(v);\n };\n\n // 표시 문자열 — 규칙 통일(utils/datetime). 비어 있으면 placeholder.\n const display = isRange\n ? formatDateTimeRange(\n (current as DateRange | null)?.start,\n showTime ? curSt : undefined,\n (current as DateRange | null)?.end,\n showTime ? curEt : undefined,\n )\n : formatDateTime(current as Date | null, showTime ? curSt : undefined);\n const isEmpty = !display;\n\n // 캘린더에서 선택 — 완료 시 자동 닫기(closeOnSelect)\n const handlePickerChange = (v: DateFieldValue) => {\n applyValue(v);\n if (closeOnSelect && !showTime) {\n if (!isRange) setOpen(false);\n else if ((v as DateRange | null)?.start && (v as DateRange | null)?.end) setOpen(false);\n }\n };\n\n // 직접 입력 확정(Enter/blur) — 파싱해 값·시간 반영. 형식 틀리면 에러.\n const commit = (raw: string) => {\n const parsed = parseDateFieldInput(raw, isRange);\n if (!parsed) {\n setParseError(true);\n return;\n }\n setParseError(false);\n applyValue(parsed.value);\n if (showTime) {\n if (parsed.startTime != null) setSt(parsed.startTime);\n if (parsed.endTime != null) setEt(parsed.endTime);\n }\n };\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 const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : readOnly\n ? 'text-text-field-readonly-icon'\n : isEmpty\n ? 'text-text-field-default-text' // 값 없음 → 회색 #878787 (Figma)\n : 'text-font-icon-5'; // 값 있음 → 진한색\n\n const showErr = error || parseError;\n const errMsg = parseError ? formatErrorMessage : errorMessage;\n\n // 너비 — 미지정 시 showTime이면 260, 아니면 180. 'hug'=콘텐츠 폭, 'fill'=부모 100%.\n const effWidth = width ?? (showTime ? 260 : 180);\n const isHug = effWidth === 'hug';\n const isFill = effWidth === 'fill';\n const fixedWidth = isHug || isFill ? undefined : typeof effWidth === 'number' ? `${effWidth}px` : effWidth;\n // hug: input을 콘텐츠 폭만큼. size는 라틴 평균 글자폭 기준이라 한글(전각)은 2배로 세고 +1 여유.\n const shown = focused ? text : display;\n const hugSize = Math.max(\n Array.from(shown || placeholder).reduce((n, c) => {\n // 한글·CJK·전각 글자는 전각폭이라 2칸, 그 외 1칸. 끝 글자 잘림 방지로 초기값 +1.\n const code = c.charCodeAt(0);\n const wide =\n (code >= 0x1100 && code <= 0x11ff) ||\n (code >= 0x3000 && code <= 0x9fff) ||\n (code >= 0xac00 && code <= 0xd7ff) ||\n (code >= 0xff00 && code <= 0xffef);\n return n + (wide ? 2 : 1);\n }, 1),\n 1,\n );\n\n const trigger = (\n <div\n className={`relative ${isFill ? 'w-full' : isHug ? 'w-fit' : ''}`}\n style={fixedWidth ? { width: fixedWidth } : undefined}\n >\n <div\n className={`group flex min-h-[32px] ${isHug ? 'w-fit' : 'w-full'} items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n interactive ? `cursor-text ${RING}` : disabled ? 'cursor-not-allowed' : 'cursor-default'\n }`}\n >\n <CalendarIcon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />\n <input\n ref={inputRef}\n type=\"text\"\n value={focused ? text : display}\n placeholder={placeholder}\n readOnly={!interactive}\n disabled={disabled}\n size={isHug ? hugSize : undefined}\n aria-invalid={showErr || undefined}\n onClick={(e) => e.stopPropagation()} // 입력칸 클릭은 팝오버 토글하지 않음\n onMouseEnter={(e) => {\n // 잘렸을 때만(scrollWidth>clientWidth) 전체 값 툴팁. 편집 중(focused)엔 표시 안 함.\n const el = e.currentTarget;\n if (!focused && display && el.scrollWidth > el.clientWidth + 1) {\n setTruncRect(el.getBoundingClientRect());\n }\n }}\n onMouseLeave={() => setTruncRect(null)}\n onFocus={(e) => {\n if (!interactive) return;\n setFocused(true);\n setParseError(false);\n setTruncRect(null);\n setText(display);\n setOpen(true);\n e.target.select();\n }}\n onChange={(e) => {\n // 숫자·구분자·괄호 외 문자는 입력 단계에서 걸러낸다.\n const v = Array.from(e.target.value).filter((c) => ALLOWED_CHARS.test(c)).join('');\n setText(v);\n if (parseError) setParseError(false);\n }}\n onBlur={() => {\n if (!interactive) return; // readOnly/disabled은 commit 안 함(값 보존)\n setFocused(false);\n // Enter(이미 커밋)/ESC(취소) 직후의 blur는 커밋을 건너뛴다\n if (skipBlurCommitRef.current) {\n skipBlurCommitRef.current = false;\n return;\n }\n commit(text);\n }}\n onKeyDown={(e) => {\n if (!interactive) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n commit(text);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n // 취소: 초안을 버리고 기존 표시값으로 복원(커밋 금지)\n setText(display);\n setParseError(false);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n }\n }}\n className={`bg-transparent text-14 outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed read-only:cursor-pointer ${textColor} ${\n isHug ? 'w-auto' : 'min-w-0 flex-1 text-ellipsis'\n }`}\n />\n </div>\n {/* 에러 툴팁 — 닫혔을 때만(열리면 picker와 겹침). 필드 아래 오버레이. */}\n {showErr && errMsg && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errMsg}\n </Tooltip>\n </div>\n )}\n {/* 말줄임 hover 툴팁 — 잘린 전체 값(규칙 5). 포털(fixed)로 필드 위에 띄운다(위 공간 없으면 아래로). */}\n {truncRect &&\n !focused &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: truncRect.left,\n top: truncRect.top > 40 ? truncRect.top - 6 : truncRect.bottom + 6,\n transform: truncRect.top > 40 ? 'translateY(-100%)' : undefined,\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {display}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n\n return (\n <Popover\n open={open}\n onOpenChange={(o: boolean) => {\n setOpen(o);\n // ESC로 닫힐 때: Popover의 capture 리스너가 키를 선점해 인풋 ESC 핸들러가 못 받으므로\n // 여기서 입력 편집을 해제한다 — 초안은 버리고(취소) blur해서, 필드를 바로 다시\n // 클릭하면 focus 이벤트로 즉시 재활성된다(2026-07-07 사용자 지적).\n // 외부 클릭 닫힘은 useOutsideDismiss가 먼저 blur를 복원해(activeElement가 이미 아님) 여기 안 탄다.\n if (!o && inputRef.current && document.activeElement === inputRef.current) {\n setText(display);\n setParseError(false);\n skipBlurCommitRef.current = true;\n inputRef.current.blur();\n }\n }}\n disabled={!interactive}\n placement=\"auto\"\n menuWidth={276}\n trigger={trigger}\n className={`${isFill ? 'w-full' : ''} ${className}`}\n {...props}\n >\n {() => (\n <DatePicker\n mode={mode}\n value={current}\n onChange={handlePickerChange}\n showTime={showTime}\n startTime={curSt}\n onStartTimeChange={setSt}\n endTime={curEt}\n onEndTimeChange={setEt}\n disablePast={disablePast}\n disableFuture={disableFuture}\n minDate={minDate}\n maxDate={maxDate}\n disabledDate={disabledDate}\n weekStartsOn={weekStartsOn}\n minYear={minYear}\n maxYear={maxYear}\n />\n )}\n </Popover>\n );\n}\n","import { Component } from 'react';\nimport type { ErrorInfo, ReactNode } from 'react';\nimport { Button } from './Button';\n\n// ErrorBoundary — 자식(페이지) 렌더 중 던져진 에러를 잡아, 앱 전체(레이아웃·좌측 메뉴)가\n// 언마운트되는 것을 막는다. 에러가 난 자리에만 안내를 표시하고, 나머지 셸은 그대로 유지된다.\n// React에서 getDerivedStateFromError/componentDidCatch는 클래스 컴포넌트에서만 동작하므로 클래스로 둔다.\ninterface ErrorBoundaryProps {\n children?: ReactNode;\n // resetKey: 이 값이 바뀌면(=다른 페이지로 이동) 에러를 자동 해제해 새 자식을 정상 렌더한다.\n resetKey?: unknown;\n // title: 에러 화면 제목.\n title?: ReactNode;\n // description: 제목 아래 보조 안내 문구.\n description?: ReactNode;\n // fallback: ({ error, reset }) => node — 기본 UI 대신 직접 렌더하고 싶을 때.\n fallback?: (args: { error: Error; reset: () => void }) => ReactNode;\n // onError: (error, info) => void — 에러 발생 시 콜백(로깅 등).\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n prevResetKey: unknown;\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { error: null, prevResetKey: props.resetKey };\n this.reset = this.reset.bind(this);\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n // resetKey가 바뀌면(페이지 전환 등) 에러 상태를 비워 새 자식이 다시 렌더되게 한다.\n static getDerivedStateFromProps(props: ErrorBoundaryProps, state: ErrorBoundaryState) {\n if (props.resetKey !== state.prevResetKey) {\n return { error: null, prevResetKey: props.resetKey };\n }\n return null;\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n // 원본 스택을 콘솔에 남겨 디버깅을 돕는다.\n console.error('[ErrorBoundary]', error, info);\n this.props.onError?.(error, info);\n }\n\n reset() {\n this.setState({ error: null });\n }\n\n render() {\n const { error } = this.state;\n const {\n children,\n title = '페이지를 표시할 수 없습니다.',\n description = '이 페이지를 렌더하는 중 오류가 발생했습니다. 다른 메뉴는 정상적으로 이용할 수 있습니다.',\n fallback,\n } = this.props;\n\n if (!error) return children;\n if (fallback) return fallback({ error, reset: this.reset });\n\n return (\n <div className=\"px-spacing-7 py-spacing-10 text-left\">\n <h2 className=\"text-16 font-semibold text-font-icon-5\">{title}</h2>\n <p className=\"mt-spacing-3 text-14 text-font-icon-3\">{description}</p>\n <pre className=\"mt-spacing-6 overflow-auto whitespace-pre-wrap rounded-round-4 border border-gray-200 bg-gray-50 px-spacing-6 py-spacing-5 font-mono text-12 text-font-icon-4\">\n {String(error?.message || error)}\n </pre>\n <div className=\"mt-spacing-6\">\n <Button variant=\"line\" size=\"32\" onClick={this.reset}>\n 다시 시도\n </Button>\n </div>\n </div>\n );\n }\n}\n","// Label — 폼 라벨 텍스트 (Figma label 컴포넌트: Property 1 = 12~16 사이즈 변형)\n//\n// Figma 원본은 \"사이즈 변형(12/13/14/15/16)만\" 가진 텍스트 컴포넌트다.\n// 코드는 실제 폼 사용을 위해 required(*)·disabled·htmlFor 를 추가로 노출한다\n// (규칙 11: 코드가 Figma보다 옵션이 많아도 무방 — 코드를 Figma에 맞춰 깎지 않는다).\n//\n// 단독으로도 쓰고(<Label htmlFor=…>), Field 내부에서도 재사용한다\n// (Checkbox가 단독 + List 내부에서 재사용되는 것과 동일한 합성 패턴).\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst SIZE_STYLES = {\n '12': 'text-12',\n '13': 'text-13',\n '14': 'text-14',\n '15': 'text-15',\n '16': 'text-16',\n};\n\ninterface LabelProps extends ComponentPropsWithoutRef<'label'> {\n size?: keyof typeof SIZE_STYLES; // '12' | '13' | '14' | '15' | '16' (Figma size variant)\n required?: boolean; // 필수 표시(빨강 점) — Figma state=required\n disabled?: boolean; // 비활성 — Figma state=disabled\n}\n\nexport function Label({\n children,\n htmlFor,\n size = '14', // '12' | '13' | '14' | '15' | '16' (Figma size variant)\n required = false, // 필수 표시(빨강 점) — Figma state=required\n disabled = false, // 비활성 — Figma state=disabled\n className = '',\n ...props\n}: LabelProps) {\n // 색은 label-field 시멘틱 토큰 경유(Figma label-field/* 변수와 1:1)\n const textColor = disabled ? 'text-label-field-disabled-text' : 'text-label-field-default-text';\n\n return (\n <label\n htmlFor={htmlFor}\n className={`inline-flex items-center gap-spacing-3 font-pretendard font-normal ${SIZE_STYLES[size]} ${textColor} ${className}`}\n {...props}\n >\n {children}\n {required && (\n // 필수 표시 — 빨강 6px 점(Figma label-field/required mark)\n <span\n className=\"h-spacing-4 w-spacing-4 shrink-0 rounded-round-00 bg-label-field-required-mark\"\n aria-hidden=\"true\"\n />\n )}\n </label>\n );\n}\n","// Field — 라벨 + 컨트롤(Input/Select/DateField/TimeField/Picker 등)을 묶는 폼 행\n//\n// 컨트롤은 손으로 다시 짜지 않고 기존 DS 컴포넌트를 children으로 그대로 받는다(규칙 4 조립):\n// <Field label=\"이름\" htmlFor=\"name\" required>\n// <Input inputProps={{ id: 'name' }} />\n// </Field>\n//\n// 복합 필드(한 필드에 컨트롤 2개+ — 인풋+셀렉트·셀렉트2개·인풋+데이트픽커 등)는\n// controlsDirection=\"row\"로 컨트롤을 한 줄에 나란히(등분) 배치한다:\n// <Field label=\"기간\" controlsDirection=\"row\"><Select/><DateField/></Field>\n//\n// 역할 분담:\n// - Field 가 소유: 라벨(내부 Label 재사용)·필수표시·보조설명(description)·레이아웃(세로/가로)\n// - 에러 표시는 소유하지 않는다 — 기존 컨트롤의 error/errorMessage 툴팁을 그대로 사용한다\n// (Input/Select/DateField 등이 이미 하단 오버레이 툴팁을 갖고 있어 중복을 피한다).\n\nimport { Children } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Label } from './Label';\n\n// 라벨↔컨트롤 간격 — Tailwind JIT가 인식하도록 \"완성 클래스\"를 리터럴로 둔다(동적 조합 금지).\nconst GAP_CLASS = {\n 'spacing-2': 'gap-spacing-2',\n 'spacing-3': 'gap-spacing-3',\n 'spacing-5': 'gap-spacing-5',\n 'spacing-6': 'gap-spacing-6',\n 'spacing-7': 'gap-spacing-7',\n};\nconst DEFAULT_GAP: Record<'vertical' | 'horizontal', keyof typeof GAP_CLASS> = {\n vertical: 'spacing-3',\n horizontal: 'spacing-6',\n};\n\n// ...props는 루트 컨테이너(div)로 전달된다.\ninterface FieldProps extends ComponentPropsWithoutRef<'div'> {\n label?: ReactNode; // string | ReactNode — 주면 내부 Label 렌더\n htmlFor?: string; // 컨트롤 id와 연결(라벨 클릭 시 포커스). 컨트롤에 같은 id 부여 필요\n required?: boolean;\n disabled?: boolean; // 라벨 비활성 스타일(컨트롤 disabled는 컨트롤에 직접 전달)\n description?: ReactNode; // 보조 설명(helper) — 컨트롤 아래 회색 텍스트\n direction?: 'vertical' | 'horizontal'; // 라벨↔컨트롤 배치\n controlsDirection?: 'column' | 'row'; // 컨트롤 여러 개일 때 배치(복합 필드, row=한 줄 등분)\n labelWidth?: number | string; // horizontal일 때 라벨 영역 너비(number=px | CSS 문자열)\n labelSize?: '12' | '13' | '14' | '15' | '16'; // Label size (12~16). 미지정 시 '14' (2026-07-06 세로도 12→14 통일)\n gap?: keyof typeof GAP_CLASS; // 라벨↔컨트롤 간격 토큰 키 override (예: 'spacing-5')\n}\n\nexport function Field({\n label, // string | ReactNode — 주면 내부 Label 렌더\n htmlFor, // 컨트롤 id와 연결(라벨 클릭 시 포커스). 컨트롤에 같은 id 부여 필요\n required = false,\n disabled = false, // 라벨 비활성 스타일(컨트롤 disabled는 컨트롤에 직접 전달)\n description, // 보조 설명(helper) — 컨트롤 아래 회색 텍스트\n direction = 'vertical', // 'vertical' | 'horizontal' (라벨↔컨트롤 배치)\n controlsDirection = 'column', // 'column' | 'row' — 컨트롤 여러 개일 때 배치(복합 필드, row=한 줄 등분)\n labelWidth, // horizontal일 때 라벨 영역 너비(number=px | CSS 문자열)\n labelSize, // Label size (12~16). 미지정 시 '14' (2026-07-06 세로도 12→14 통일)\n gap, // 라벨↔컨트롤 간격 토큰 키 override (예: 'spacing-5')\n className = '',\n children, // 컨트롤\n ...props\n}: FieldProps) {\n const isHorizontal = direction === 'horizontal';\n const isRowControls = controlsDirection === 'row';\n // 라벨 기본 사이즈는 layout에 따라 다름(가로=14 / 세로=12), 명시 지정 시 그것 우선\n const resolvedLabelSize = labelSize ?? '14';\n const gapClass =\n GAP_CLASS[gap ?? DEFAULT_GAP[isHorizontal ? 'horizontal' : 'vertical']];\n\n const wrapper = isHorizontal\n ? `flex flex-row items-start ${gapClass}`\n : `flex flex-col ${gapClass}`;\n\n const labelWidthStyle =\n isHorizontal && labelWidth != null\n ? { width: typeof labelWidth === 'number' ? `${labelWidth}px` : labelWidth }\n : undefined;\n\n return (\n <div className={`${wrapper} ${className}`} {...props}>\n {label != null && (\n <div\n style={labelWidthStyle}\n className={isHorizontal ? 'shrink-0 pt-spacing-3' : undefined}\n >\n <Label htmlFor={htmlFor} size={resolvedLabelSize} required={required} disabled={disabled}>\n {label}\n </Label>\n </div>\n )}\n\n {/* 컨트롤 영역 — controls 컨테이너(가로 등분/세로 스택, 컨트롤 간 8px) + 그 아래 helper(4px).\n 가로 레이아웃에선 남은 폭을 채운다 */}\n <div className={`flex min-w-0 flex-col gap-spacing-3 ${isHorizontal ? 'flex-1' : ''}`}>\n {isRowControls ? (\n // 복합 필드(row): 컨트롤을 한 줄에 등분(각 min-w-0 flex-1), 컨트롤 간 8px\n <div className=\"flex flex-row items-start gap-spacing-5\">\n {/* toArray — 조건부 자식(false/null)을 걸러 유령 등분 컬럼이 생기지 않게 */}\n {Children.toArray(children).map((c) => (\n <div key={(c as ReactElement).key} className=\"min-w-0 flex-1\">\n {c}\n </div>\n ))}\n </div>\n ) : (\n // column: 컨트롤 세로 스택, 컨트롤 간 8px(row와 동일)\n <div className=\"flex flex-col gap-spacing-5\">{children}</div>\n )}\n {description != null && (\n <p className=\"text-12 text-label-field-helper-text\">{description}</p>\n )}\n </div>\n </div>\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","// FormTemplate — 폼 템플릿 (Figma \"form template\", Property 1=1/2/3/mixed column)\n// 12칸 그리드(Figma \"12 grid\") 위에 Field(라벨+컨트롤)를 span으로 배치하는 조립형 템플릿(규칙 4).\n// - columns: 1/2/3 — 필드 기본 폭(12/columns칸). 혼합 배치는 필드별 span(12칸 기준)으로 지정\n// - columnGap/rowGap: 그리드 가로/세로 간격 — 16/20/24/28/32(px, spacing 토큰 7~11)\n// - labelSize: 모든 필드 라벨 크기 일괄 변경('12'~'16', 미지정 시 Field 기본)\n// 컨트롤은 소비자가 field.control로 그대로 주입(Input/Select/DateField/EmailField 등 — 손으로 안 짬).\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { Field } from './Field';\n\n// 간격(px) → 등록 spacing 토큰 클래스 (16=7 · 20=8 · 24=9 · 28=10 · 32=11)\nconst GAP_X = {\n 16: 'gap-x-spacing-7',\n 20: 'gap-x-spacing-8',\n 24: 'gap-x-spacing-9',\n 28: 'gap-x-spacing-10',\n 32: 'gap-x-spacing-11',\n};\nconst GAP_Y = {\n 16: 'gap-y-spacing-7',\n 20: 'gap-y-spacing-8',\n 24: 'gap-y-spacing-9',\n 28: 'gap-y-spacing-10',\n 32: 'gap-y-spacing-11',\n};\n\n// 폼 필드 한 항목 — { key, label, required?, disabled?, description?, span?(1~12), control: ReactNode }\ninterface FormTemplateField {\n key: string;\n label?: ReactNode;\n required?: boolean;\n disabled?: boolean;\n description?: ReactNode;\n span?: number; // 1~12 (12칸 기준)\n control?: ReactNode;\n}\n\ninterface FormTemplateProps extends ComponentPropsWithoutRef<'div'> {\n fields?: FormTemplateField[]; // [{ key, label, required?, disabled?, description?, span?(1~12), control: ReactNode }]\n columns?: 1 | 2 | 3; // 필드 기본 폭(12/columns칸). mixed는 필드별 span으로\n columnGap?: keyof typeof GAP_X; // 가로 간격(px): 16 | 20 | 24 | 28 | 32\n rowGap?: keyof typeof GAP_Y; // 세로 간격(px): 16 | 20 | 24 | 28 | 32\n labelSize?: '12' | '13' | '14' | '15' | '16'; // 라벨 크기 일괄('12'~'16') — 미지정 시 Field 기본(세로 12)\n}\n\nexport function FormTemplate({\n fields = [],\n columns = 1,\n columnGap = 16,\n rowGap = 16,\n labelSize,\n className = '',\n ...props\n}: FormTemplateProps) {\n const cols = Math.min(Math.max(columns, 1), 3);\n const defaultSpan = 12 / cols; // 1→12 · 2→6 · 3→4\n const gapX = GAP_X[columnGap] ?? GAP_X[16];\n const gapY = GAP_Y[rowGap] ?? GAP_Y[16];\n\n return (\n <div className={`grid grid-cols-12 ${gapX} ${gapY} ${className}`} {...props}>\n {fields.map((f) => {\n const span = Math.min(Math.max(f.span ?? defaultSpan, 1), 12);\n return (\n // span은 12칸 기준 — Tailwind purge를 피해 인라인 grid-column으로 지정\n <div key={f.key} style={{ gridColumn: `span ${span} / span ${span}` }}>\n <Field\n label={f.label}\n required={f.required}\n disabled={f.disabled}\n description={f.description}\n {...(labelSize ? { labelSize } : {})}\n >\n {f.control}\n </Field>\n </div>\n );\n })}\n </div>\n );\n}\n","// ImageUploadMenu — 이미지 업로드 팝오버 (Figma popover menu / type=upload image)\n//\n// PopoverMenu 컨테이너를 조립한다(규칙 4): 안내 박스 + 이미지 미리보기 + 푸터.\n// 프레젠테이셔널(controlled) — image(미리보기 URL)는 호출부가 소유.\n// - image 있음: 미리보기 + 푸터 [파일 삭제(line) · 파일 찾기(fill)]\n// - image 없음(empty): 미리보기·삭제 없이 전체폭 '파일 찾기'만\n//\n// ⚠️ 주의: guide(화면 안내 문구)와 accept(실제 파일 선택창 필터)는 **별개 prop**이라\n// 자동 연동되지 않는다. 표시 확장자와 실제 허용 확장자가 어긋나지 않도록 **둘을 직접 일치**시킬 것.\n// (예: guide '… .jpeg, .png, .webp …' ↔ accept '.jpeg,.png,.webp')\nimport { useRef } from 'react';\nimport type { ChangeEvent } from 'react';\nimport { PopoverMenu } from './PopoverMenu';\nimport type { PopoverMenuProps } from './PopoverMenu';\n\n// onSelect는 DOM select 이벤트 핸들러와 이름이 겹쳐 Omit 후 파일 콜백으로 재선언\nexport interface ImageUploadMenuProps extends Omit<PopoverMenuProps, 'onSelect'> {\n /** 미리보기 이미지 URL (없으면 empty) */\n image?: string;\n /** 안내 문구(여러 줄은 \\n) */\n guide?: string;\n /** <input accept> 실제 필터 (예: '.jpeg,.png,.webp'). ⚠️ guide 표시와 직접 일치시킬 것 */\n accept?: string;\n /** (File) => void — '파일 찾기'로 선택 */\n onSelect?: (file: File) => void;\n /** () => void — '파일 삭제' */\n onRemove?: () => void;\n findText?: string;\n removeText?: string;\n imageAlt?: string;\n}\n\nexport function ImageUploadMenu({\n image,\n guide,\n accept,\n onSelect,\n onRemove,\n findText = '파일 찾기',\n removeText = '파일 삭제',\n imageAlt = '업로드 이미지 미리보기',\n width = 320,\n className = '',\n ...props\n}: ImageUploadMenuProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const openPicker = () => inputRef.current?.click();\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files?.[0]) onSelect?.(e.target.files[0]);\n e.target.value = '';\n };\n\n return (\n <PopoverMenu\n width={width}\n className={className}\n footer\n footerButtonsFill\n showCancel={!!image}\n cancelText={removeText}\n onCancel={onRemove}\n confirmText={findText}\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 {image && (\n <div className=\"bg-list-group-bg p-spacing-5\">\n {/* 높이 360px 초과 시 360에서 잘라 보여준다(clip, 가운데 기준 — 위·아래 균등 크롭) */}\n <div className=\"flex max-h-[360px] w-full items-center overflow-hidden rounded-round-4\">\n <img src={image} alt={imageAlt} className=\"block w-full shrink-0\" />\n </div>\n </div>\n )}\n\n <input ref={inputRef} type=\"file\" accept={accept} hidden onChange={handleChange} />\n </PopoverMenu>\n );\n}\n","// ImageUploadButton — 버튼 트리거 + 이미지 업로드 팝오버 (Figma butto+ image upload)\n//\n// Button을 트리거로 Popover에 ImageUploadMenu를 붙인다(규칙 4 조립).\n// - 모든 Button 스타일 사용 가능: buttonProps로 variant/size/leftIcon 등 그대로 통과.\n// 이미지 상태는 호출부가 소유(컨트롤드) — ImageUploadMenu props(image/onSelect/onRemove 등)를 그대로 받는다.\nimport { Image as ImageIcon } from 'lucide-react';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { ImageUploadMenu } from './ImageUploadMenu';\nimport type { ImageUploadMenuProps } from './ImageUploadMenu';\nimport type { UploadTriggerButtonProps } from './FileUploadButton';\n\nexport interface ImageUploadButtonProps extends Omit<ImageUploadMenuProps, 'width'> {\n // ── 트리거 버튼 ──\n triggerText?: string;\n /** 모든 Button 스타일 props 통과. 기본 variant=line·leftIcon=Image */\n buttonProps?: UploadTriggerButtonProps;\n // ── 팝오버 ──\n placement?: string;\n /** 팝오버(=ImageUploadMenu) 너비 */\n menuWidth?: number | string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n}\n\nexport function ImageUploadButton({\n triggerText = '이미지 등록',\n buttonProps = {},\n placement = 'bottom-left',\n menuWidth = 320,\n open,\n onOpenChange,\n disabled = false,\n className = '',\n // ── 나머지는 ImageUploadMenu props(image/guide/accept/onSelect/onRemove/...) ──\n ...menuProps\n}: ImageUploadButtonProps) {\n const { leftIcon = ImageIcon, 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 {triggerText}\n </Button>\n }\n >\n <ImageUploadMenu width=\"100%\" {...menuProps} />\n </Popover>\n );\n}\n","// tableView — Table 헤더 요소(필터·정렬)의 순수 데이터 헬퍼 + 셀 치수 계산.\nexport type TableRowData = Record<string, unknown> & { id?: string | number };\nexport interface TableSort {\n key: string;\n dir: 'asc' | 'desc';\n}\nimport { spacing } from '../tokens';\n// Table 내부와 TableTemplate(페이지 자르기 전 전체 행)이 같은 규칙을 공유하도록 컴포넌트 파일과 분리해 둔다.\n\n// 정렬 비교 — 둘 다 숫자로 해석되면 수치 비교, 아니면 한국어 로케일 문자열 비교. null은 뒤로.\nexport function compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n const na = Number(a);\n const nb = Number(b);\n if (!Number.isNaN(na) && !Number.isNaN(nb) && a !== '' && b !== '') return na - nb;\n return String(a).localeCompare(String(b), 'ko');\n}\n\n// 헤더 필터(filter 컬럼) 적용 — filters={[colKey]:value}, ''/없음이면 그 컬럼 필터 해제. render 셀도 원본 값(row[key]) 기준.\nexport function applyColumnFilters(\n rows: TableRowData[],\n columns: { key: string; filter?: unknown }[],\n filters: Record<string, unknown> = {},\n) {\n return rows.filter((row) =>\n columns.every((c) => {\n const v = filters[c.key];\n return c.filter == null || v == null || v === '' || String(row[c.key]) === String(v);\n }),\n );\n}\n\n// 헤더 메뉴 정렬(sort={key,dir}) 적용 — 원본 배열은 건드리지 않게 복사 후 정렬. sort 없으면 그대로.\nexport function applySort(rows: TableRowData[], sort?: TableSort | null) {\n if (sort?.key == null) return rows;\n return [...rows].sort((a, b) => {\n const r = compareValues(a[sort.key], b[sort.key]);\n return sort.dir === 'desc' ? -r : r;\n });\n}\n\n// 아이콘 전용 셀 너비 계산 — 고스트 아이콘 버튼만 단독으로 들어가는 컬럼의 width를\n// \"셀 좌우 패딩(spacing-5-5×2) + 버튼 폭 합 + 버튼 사이 간격 합\"으로 정확히 산출한다(임의 폭 금지).\n// 예: iconCellWidth(2) = 10+10 + 24×2 + 8 = 76px (ghost size 24 버튼 2개, gap spacing-5)\nconst CELL_PAD_X = parseInt(spacing['spacing-5-5'], 10); // Table 바디/헤더 셀 좌우 패딩과 동일 토큰(10px)\n\nexport function iconCellWidth(\n count = 1,\n { buttonSize = 24, gap = parseInt(spacing['spacing-5'], 10) }: { buttonSize?: number; gap?: number } = {},\n) {\n return CELL_PAD_X * 2 + buttonSize * count + gap * Math.max(0, count - 1);\n}\n","// Table — 데이터 테이블 (Figma table, node 7257:1925)\n// 컬럼 정의(columns)와 데이터(rows)를 받아 렌더하는 완전 옵션형 테이블.\n// - columns: [{ key, label, width?, align?, render?, renderHeader?, filter?, headerMenu? }] width 없으면 가변(fill). fill 컬럼은 최소 40px 유지\n// renderHeader: () => ReactNode — 헤더 셀(라벨 영역)을 직접 렌더(가장 우선). 없으면 filter → label 순으로 처리\n// filter: { options:[{value,label}], allLabel?, placeholder? } — 헤더 라벨 자리에 인라인 텍스트형 Select(size 20)를\n// 넣고, 선택값으로 그 컬럼(row[key])을 기준으로 행을 거른다. 옵션 맨 앞에 전체 옵션(value '')이 자동 추가되고\n// 그걸 고르면 필터가 해제된다. 전체 옵션 라벨은 allLabel(기본 '{컬럼명} 전체')로 정한다.\n// 필터 상태는 filters/onFilterChange로 controlled, 미지정 시 내부 상태.\n// headerMenu: { sortable?, items?, icon?, width? } — 헤더 우측에 ghost 아이콘 버튼(size 24)을 두고, 클릭 시 Popover 메뉴를 연다.\n// sortable=true면 '오름차순/내림차순 정렬' 항목이 자동 추가되고 그 컬럼(row[key]) 기준으로 Table이 내부 정렬한다(현재 정렬은 항목에 selected 표시).\n// items=[{ key?, label, icon?, onClick(column) }] 로 임의 기능을 더 넣을 수 있다. 정렬 상태는 sort/onSortChange로 controlled, 미지정 시 내부 상태.\n// sortable도 items도 없으면(빈 메뉴) 버튼을 렌더하지 않는다.\n// - selectable: 체크박스 선택 컬럼(전체선택 헤더 포함). selectedIds/onSelectChange로 controlled, 미지정 시 내부 상태\n// - bordered: 외곽선 + 라운드(true) / 외곽선 없는 라운드 헤더(false)\n// - wrap: 본문 셀 텍스트 줄바꿈 여부. false(기본)=말줄임+행 높이 고정(잘리면 hover 툴팁) / true=줄바꿈으로 행이 세로로 늘어남\n// (render로 직접 렌더한 셀은 사용자 책임이라 wrap 영향을 받지 않음)\n// - maxHeight: 지정 시 본문 세로 스크롤 + 헤더 고정(sticky) + 헤더 하단 구분선\n// - minWidth: 테이블 최소 너비. 실제 최소 너비는 max(minWidth, 컬럼 최소폭 합)이며,\n// 컨테이너가 이보다 좁아지면 가로 스크롤이 자동 활성화되고 fill 컬럼은 40px를 유지한다\n// - scrollX: minWidth 없이도 가로 스크롤을 켜는 수동 옵션. 세로·가로 모두 ScrollArea 오버레이 스크롤바\n// - loading / emptyMessage: 로딩 · 빈 상태 처리\n// 색·간격·보더는 table-*/spacing-*/border-* 토큰만 사용(하드코딩 금지).\n// sticky 헤더의 하단 구분선은 border-collapse 환경에서 스크롤 시 사라지는 브라우저 버그를 피하려\n// box-shadow(토큰 색 인라인)로 그린다. — Tooltip/ScrollArea의 토큰값 인라인 적용과 동일한 예외.\nimport { useMemo, useState } from 'react';\nimport type { CSSProperties, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { LoaderCircle, MoreVertical } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Checkbox } from './Checkbox';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\nimport { Select } from './Select';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { applyColumnFilters, applySort } from './tableView';\nimport type { TableRowData, TableSort } from './tableView';\nimport { tableColors } from '../tokens';\n\n// 행 선택 키(rowKey 값 또는 인덱스 폴백)\nexport type TableRowKey = string | number;\n\n// 헤더 필터(filter 컬럼) 옵션 — Select options 계약과 동일(값·라벨 문자열)\nexport interface TableColumnFilterOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\n// 헤더 메뉴(items) 항목 — sortable 자동 항목 외 임의 기능 추가용\nexport interface TableHeaderMenuItem {\n key?: string | number;\n label: ReactNode;\n icon?: LucideIcon;\n onClick?: (column: TableColumn) => void;\n}\n\n// 컬럼 정의 — 상단 주석의 columns 스펙과 1:1\nexport interface TableColumn {\n key: string;\n label?: ReactNode;\n /** 없으면 가변(fill). fill 컬럼은 최소 40px 유지 */\n width?: number;\n align?: 'left' | 'center' | 'right';\n /** 본문 셀 직접 렌더 */\n render?: (row: TableRowData) => ReactNode;\n /** 헤더 셀(라벨 영역)을 직접 렌더(가장 우선). 없으면 filter → label 순으로 처리 */\n renderHeader?: () => ReactNode;\n /** 헤더 라벨 자리 인라인 텍스트형 Select(size 20) 필터 */\n filter?: {\n options: TableColumnFilterOption[];\n allLabel?: string;\n placeholder?: string;\n };\n /** 헤더 우측 ghost 아이콘 버튼(size 24) + Popover 메뉴 */\n headerMenu?: {\n sortable?: boolean;\n items?: TableHeaderMenuItem[];\n icon?: LucideIcon;\n width?: number;\n };\n /** 셀 세로 병합 — (row, rowIndex, rows) => n. n>1=아래로 n행 병합, 0=위 셀 병합 범위에 포함(td 생략), 1/미지정=일반 셀 */\n cellSpan?: (row: TableRowData, rowIndex: number, rows: TableRowData[]) => number;\n}\n\nconst ALIGN_STYLE = {\n left: 'text-left justify-start',\n center: 'text-center justify-center',\n right: 'text-right justify-end',\n};\n\nconst CHECKBOX_COL_WIDTH = 44;\nconst COL_MIN_WIDTH = 40; // 가변(fill) 컬럼이 표가 좁아져도 유지하는 최소 너비(px)\n\n// HeaderMenu — 헤더 우측 ghost 아이콘 버튼(size 24) + Popover 메뉴.\n// sortable면 오름차순/내림차순 정렬 항목을 자동 제공(현재 정렬은 selected로 표시), items로 임의 기능 추가.\nfunction HeaderMenu({\n column,\n sort,\n onSort,\n}: {\n column: TableColumn;\n sort: TableSort | null;\n onSort: (sort: TableSort) => void;\n}) {\n const menu = column.headerMenu!; // headerMenu가 있을 때만 렌더된다(호출부 가드)\n const TriggerIcon = menu.icon ?? MoreVertical;\n const sortDir = sort?.key === column.key ? sort.dir : null;\n const items = menu.items ?? [];\n return (\n <Popover\n placement=\"bottom-right\"\n menuWidth={menu.width ?? 120}\n trigger={<Button variant=\"ghost\" size=\"24\" icon={TriggerIcon} aria-label={`${column.label ?? ''} 컬럼 메뉴`} showTooltip={false} />}\n >\n {(close) => (\n <PopoverMenu width=\"100%\">\n <ListGroup>\n {menu.sortable && [\n <List\n key=\"__asc\"\n title=\"오름차순 정렬\"\n selected={sortDir === 'asc'}\n onClick={() => {\n onSort({ key: column.key, dir: 'asc' });\n close();\n }}\n />,\n <List\n key=\"__desc\"\n title=\"내림차순 정렬\"\n selected={sortDir === 'desc'}\n onClick={() => {\n onSort({ key: column.key, dir: 'desc' });\n close();\n }}\n />,\n ]}\n {items.map((it, i) => (\n <List\n key={it.key ?? i}\n title={it.label}\n onClick={() => {\n it.onClick?.(column);\n close();\n }}\n />\n ))}\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n );\n}\n\nexport interface TableProps extends ComponentPropsWithoutRef<'div'> {\n columns?: TableColumn[];\n rows?: TableRowData[];\n rowKey?: string;\n selectable?: boolean;\n selectedIds?: TableRowKey[];\n onSelectChange?: (selectedIds: TableRowKey[]) => void;\n filters?: Record<string, unknown>;\n onFilterChange?: (filters: Record<string, unknown>) => void;\n sort?: TableSort | null;\n onSortChange?: (sort: TableSort | null) => void;\n bordered?: boolean;\n wrap?: boolean;\n /** number | 'fill' — fill=부모 flex 높이를 상한으로(모달 등), 내용 적으면 자연 높이 */\n maxHeight?: number | string;\n /** 바디 스크롤 영역 최소 높이(px|CSS) — 내용이 적어도 이 높이는 확보 */\n minHeight?: number | string;\n minWidth?: number;\n scrollX?: boolean;\n loading?: boolean;\n loadingMessage?: ReactNode;\n emptyMessage?: ReactNode;\n onRowClick?: (row: TableRowData) => void;\n}\n\nexport function Table({\n columns = [],\n rows = [],\n rowKey = 'id',\n selectable = false,\n selectedIds,\n onSelectChange,\n filters,\n onFilterChange,\n sort,\n onSortChange,\n bordered = false,\n wrap = false,\n maxHeight,\n minHeight,\n minWidth,\n scrollX = false,\n loading = false,\n loadingMessage = '불러오는 중…',\n emptyMessage = '데이터가 없습니다.',\n onRowClick,\n className = '',\n ...props\n}: TableProps) {\n // 헤더 필터 상태 — controlled(filters+onFilterChange) 또는 내부 상태. { [colKey]: value }, ''=전체(해제)\n const filtersControlled = filters !== undefined;\n const [internalFilters, setInternalFilters] = useState<Record<string, unknown>>({});\n const activeFilters = filtersControlled ? (filters as Record<string, unknown>) : internalFilters;\n const setFilter = (key: string, val: unknown) => {\n const next = { ...activeFilters, [key]: val };\n if (!filtersControlled) setInternalFilters(next);\n onFilterChange?.(next);\n };\n // 활성 필터를 적용해 행을 거른다(공유 헬퍼). 메모 — 안 하면 선택 토글 등 무관한 state\n // 변경마다 전체 행 재필터+재정렬이 돌아 수천 행에서 클릭마다 지연된다(2026-07-07 감사).\n const filteredRows = useMemo(\n () => applyColumnFilters(rows, columns, activeFilters),\n [rows, columns, activeFilters],\n );\n\n // 정렬 상태 — controlled(sort+onSortChange) 또는 내부 상태. { key, dir:'asc'|'desc' } | null\n const sortControlled = sort !== undefined;\n const [internalSort, setInternalSort] = useState<TableSort | null>(null);\n const activeSort = sortControlled ? (sort as TableSort | null) : internalSort;\n const setSort = (next: TableSort | null) => {\n if (!sortControlled) setInternalSort(next);\n onSortChange?.(next);\n };\n // 정렬 적용(필터 뒤, 공유 헬퍼) — 헤더 메뉴 항목 기준(row[key]). 메모(위와 동일 이유).\n const displayRows = useMemo(() => applySort(filteredRows, activeSort), [filteredRows, activeSort]);\n\n // 선택 상태 — controlled(selectedIds+onSelectChange) 또는 내부 상태\n const isControlled = selectedIds !== undefined;\n const [internalSel, setInternalSel] = useState<TableRowKey[]>([]);\n const selected = isControlled ? (selectedIds as TableRowKey[]) : internalSel;\n const selectedSet = new Set(selected); // O(1) 조회 — 행마다 includes 하지 않게\n\n const getKey = (row: TableRowData, i: number) => (row[rowKey] ?? i) as TableRowKey;\n // 선택 + 인덱스 key 폴백 + 정렬/필터가 겹치면 체크가 다른 행으로 옮겨간다 — 개발 중에만 경고\n const meta = import.meta as { env?: { DEV?: boolean } };\n if (meta.env?.DEV && selectable && rows.length > 0 && rows[0]?.[rowKey] == null) {\n console.warn(\n `[Table] selectable인데 rows에 rowKey('${rowKey}')가 없어 인덱스로 선택을 추적합니다 — 정렬/필터 시 선택이 다른 행으로 이동합니다. 고유 키 필드를 지정하세요.`,\n );\n }\n const emit = (next: TableRowKey[]) => {\n if (!isControlled) setInternalSel(next);\n onSelectChange?.(next);\n };\n // 전체선택은 현재 보이는(필터·정렬된) 행 기준\n const allKeys = displayRows.map(getKey);\n const allChecked = displayRows.length > 0 && allKeys.every((k) => selectedSet.has(k));\n const toggleAll = () => emit(allChecked ? [] : allKeys);\n const toggleRow = (key: TableRowKey) =>\n emit(selectedSet.has(key) ? selected.filter((k) => k !== key) : [...selected, key]);\n\n const totalCols = columns.length + (selectable ? 1 : 0);\n const vMax = maxHeight;\n const fillV = vMax === 'fill'; // 세로 상한을 부모 flex 높이로\n const hasVScroll = vMax != null;\n // 가로+세로 스크롤 중첩 시(헤더 분리형) 바디 스크롤 요소 — 세로 thumb는 가로 스크롤을 안 받는\n // 바깥 가로 ScrollArea(vScrollEl 연동)가 그린다. 바디 자체 ScrollArea로 그리면 thumb가\n // 콘텐츠 전체 폭 우측(뷰포트 밖)에 붙어 가로 스크롤 위치에 따라 안 보인다(2026-07-06 지시).\n const [bodyScrollEl, setBodyScrollEl] = useState<HTMLDivElement | null>(null);\n\n // 컬럼 최소 너비 보장 — 가변(width 미지정=fill) 컬럼은 COL_MIN_WIDTH(40px), 고정 컬럼은 지정 폭.\n // 이들의 합을 테이블 최소 너비로 삼아, 그보다 좁아지면 fill 컬럼이 40px 아래로 줄지 않고 가로 스크롤이 생긴다.\n const hasFill = columns.some((c) => c.width == null);\n const contentMinWidth =\n (selectable ? CHECKBOX_COL_WIDTH : 0) +\n columns.reduce((sum, c) => sum + (c.width ?? COL_MIN_WIDTH), 0);\n\n // 최소 너비(minWidth)·가변 컬럼·scrollX 중 하나라도 있으면 가로 스크롤(오버레이)을 활성화한다.\n const hasHScroll = scrollX || minWidth != null || hasFill;\n const tableMinWidth = hasHScroll ? Math.max(minWidth ?? 0, contentMinWidth) : minWidth;\n const needsScroll = hasVScroll || hasHScroll;\n\n // 셀 공통 — 마지막 컬럼 제외 우측 구분선. 헤더는 한 단계 진한 header-line 토큰(2026-07-03 분리).\n const cellLine = (isLast: boolean) => (isLast ? '' : 'border-r border-table-cell-line');\n const headLine = (isLast: boolean) => (isLast ? '' : 'border-r border-table-header-line');\n\n // empty/loading 행: noneline은 데이터 행처럼 하단 구분선, bordered는 외곽선이 처리하므로 생략.\n const stateLine = bordered ? '' : 'border-b border-table-cell-line';\n\n // 헤더 행 하단 구분선 — bordered 타입에만 적용한다.\n // noneline(외곽선 없음)은 가이드상 둥근 회색 바만 있고 헤더 언더라인이 없어야 하므로,\n // 세로 스크롤(sticky 헤더)이어도 구분선을 긋지 않는다(회색 바 배경으로 본문과 구분).\n // border-collapse 환경에서 스크롤 시 사라지는 버그를 피하려 box-shadow로 안정적으로 그린다.\n // 색은 외곽선(outline)과 동일하게 맞춘다(2026-07-08 지시 — header-line #d6d6d6 → outline #e3e3e3).\n const headDivider: CSSProperties | undefined = bordered\n ? { boxShadow: `inset 0 -1px 0 ${tableColors['outline']}` }\n : undefined;\n\n // noneline(외곽선 없음) 테이블은 헤더가 위·아래 모두 둥근 회색 바 → 헤더 바깥 셀에 좌/우 라운드.\n // bordered는 래퍼 overflow-clip이 코너를 처리하므로 셀 라운드를 주지 않는다.\n const headCorner = (isFirst: boolean, isLast: boolean) =>\n bordered ? '' : `${isFirst ? 'rounded-l-round-4' : ''} ${isLast ? 'rounded-r-round-4' : ''}`;\n\n // 헤더 셀(<th>) 공통 — 구분선·코너·패딩·하단 구분선(box-shadow). 체크박스/라벨 셀이 함께 사용.\n // 오른쪽 패딩은 spacing-4(6px) — 헤더 우측 메뉴 버튼이 가장자리에 너무 떨어지지 않게.\n // 높이는 32px 고정(상하 패딩 없음 — 본문 h-[45px]처럼 컴포넌트 고정 치수).(상하 패딩 없음 — 본문 h-[45px]처럼 컴포넌트 고정 치수).\n const headCellProps = (isFirst: boolean, isLast: boolean, width?: number) => ({\n className: `${headLine(isLast)} ${headCorner(isFirst, isLast)} pl-spacing-5-5 pr-spacing-4 h-[32px] align-middle`,\n style: { ...(width ? { width } : null), ...headDivider },\n });\n\n const headCell = (c: TableColumn, isFirst: boolean, isLast: boolean) => (\n <th key={c.key} {...headCellProps(isFirst, isLast, c.width)}>\n {/* 좌: 라벨 영역(renderHeader → filter → label) / 우: headerMenu(ghost 아이콘 버튼 + Popover). */}\n <div className=\"flex items-center gap-spacing-3\">\n <div className={`flex min-w-0 flex-1 items-center ${ALIGN_STYLE[c.align as keyof typeof ALIGN_STYLE] ?? ALIGN_STYLE.left}`}>\n {/* 우선순위: renderHeader(직접 렌더) → filter(인라인 텍스트형 Select, size 20) → label(말줄임).\n 컬럼이 좁아도 줄바꿈으로 세로로 늘어나지 않게. 잘리면 hover 툴팁. */}\n {c.renderHeader ? (\n c.renderHeader()\n ) : c.filter ? (\n (() => {\n // 전체(필터 해제) 옵션 라벨 — allLabel이 있으면 그대로, 없으면 '{컬럼명} 전체'(label 없으면 '전체')\n const allLabel = c.filter.allLabel ?? (c.label ? `${c.label} 전체` : '전체');\n return (\n <Select\n variant=\"text\"\n size=\"20\"\n value={(activeFilters[c.key] ?? '') as string}\n onChange={(e) => setFilter(c.key, e.target.value)}\n options={[{ value: '', label: allLabel }, ...c.filter.options]}\n placeholder={c.filter.placeholder ?? allLabel}\n />\n );\n })()\n ) : (\n <TruncatingText as=\"span\" className=\"min-w-0 text-12 font-normal text-font-icon-5\">\n {c.label}\n </TruncatingText>\n )}\n </div>\n {/* headerMenu는 정렬(sortable)이나 항목(items)이 하나라도 있을 때만 버튼을 렌더(빈 메뉴 방지).\n flex 래퍼 — inline-flex(Popover 루트)의 베이스라인 갭(하단 2px)을 없애 상하 여백을 대칭으로. */}\n {c.headerMenu && (c.headerMenu.sortable || (c.headerMenu.items?.length ?? 0) > 0) && (\n <div className=\"flex shrink-0 items-center\">\n <HeaderMenu column={c} sort={activeSort} onSort={setSort} />\n </div>\n )}\n </div>\n </th>\n );\n\n // 세로 스크롤이면 타입과 무관하게 헤더를 스크롤 영역 밖으로 분리한다(2026-07-06, noneline도 통일) —\n // sticky 헤더는 알파 배경(header-bg) 뒤/위로 행이 비쳐 보이므로, 세로 스크롤 영역을 '바디 높이'로 한정.\n const splitHeader = hasVScroll;\n\n const colgroupEl = (\n <colgroup>\n {selectable && <col style={{ width: CHECKBOX_COL_WIDTH }} />}\n {columns.map((c) => (\n <col key={c.key} style={c.width ? { width: c.width } : undefined} />\n ))}\n </colgroup>\n );\n const tableClass = 'w-full table-fixed border-separate';\n const tableStyle: CSSProperties = { borderSpacing: 0, ...(tableMinWidth ? { minWidth: tableMinWidth } : null) };\n\n const theadEl = (\n <thead>\n <tr className=\"bg-table-header-bg\">\n {selectable && (\n // 체크박스 컬럼 헤더만 상하좌우 패딩을 8px(spacing-5)로 통일 — 공통 headCellProps의 좌(spacing-6) 패딩 대신.\n <th\n className={`${cellLine(false)} ${headCorner(true, false)} p-spacing-5 align-middle`}\n style={{ width: CHECKBOX_COL_WIDTH, ...headDivider }}\n >\n <div className=\"flex items-center justify-center\">\n <Checkbox checked={allChecked} onChange={toggleAll} />\n </div>\n </th>\n )}\n {columns.map((c, i) =>\n headCell(c, !selectable && i === 0, i === columns.length - 1),\n )}\n </tr>\n </thead>\n );\n\n const tbodyEl = (\n <tbody>\n {loading ? (\n <tr className=\"bg-table-row-bg\">\n <td colSpan={totalCols} className={`${stateLine} px-spacing-5-5 py-spacing-12`}>\n <div className=\"flex items-center justify-center gap-spacing-4 text-14 text-font-icon-3\">\n <LoaderCircle size={16} strokeWidth={1.8} className=\"animate-spin\" />\n {loadingMessage}\n </div>\n </td>\n </tr>\n ) : displayRows.length === 0 ? (\n <tr className=\"bg-table-row-bg\">\n <td colSpan={totalCols} className={`${stateLine} px-spacing-5-5 py-spacing-12 text-center text-14 text-font-icon-3`}>\n {emptyMessage}\n </td>\n </tr>\n ) : (\n displayRows.map((row, ri) => {\n const key = getKey(row, ri);\n const isLastRow = ri === displayRows.length - 1;\n // 행 구분선은 td에 적용한다(border-separate에선 tr 보더가 렌더되지 않음).\n // 바닥이 이미 닫히는 경우(bordered=외곽선 / noneline+세로스크롤=컨테이너 하단선)엔\n // 마지막 행 구분선을 빼 이중선(2px)이 되지 않게 한다.\n const bottomClosed = bordered || hasVScroll;\n const rowLine = bottomClosed && isLastRow ? '' : 'border-b border-table-cell-line';\n return (\n <tr\n key={key}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`bg-table-row-bg transition-colors hover:bg-table-row-hover-bg ${\n onRowClick ? 'cursor-pointer' : ''\n }`}\n >\n {selectable && (\n <td className={`${cellLine(false)} ${rowLine} h-[45px] px-spacing-5-5 py-spacing-4 align-middle`}>\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={selectedSet.has(key)}\n onChange={() => toggleRow(key)}\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n </td>\n )}\n {columns.map((c, ci) => {\n // 셀 세로 병합 — 컬럼 cellSpan((row, rowIndex, rows) => n): n>1=아래로 n행 병합,\n // 0=위 셀의 병합 범위에 포함(이 행에선 td 생략), 1/미지정=일반 셀.\n const span = c.cellSpan ? c.cellSpan(row, ri, displayRows) : 1;\n if (span === 0) return null;\n // 병합 셀의 행 구분선은 셀이 '끝나는' 행 기준으로 판정(마지막 행에서 끝나면 이중선 방지)\n const endsLast = ri + Math.max(span, 1) - 1 >= displayRows.length - 1;\n const spanRowLine =\n bottomClosed && endsLast ? '' : 'border-b border-table-cell-line';\n return (\n <td\n key={c.key}\n rowSpan={span > 1 ? span : undefined}\n className={`${cellLine(ci === columns.length - 1)} ${spanRowLine} h-[45px] px-spacing-5-5 py-spacing-4 align-middle`}\n >\n <div className={`flex items-center ${ALIGN_STYLE[c.align as keyof typeof ALIGN_STYLE] ?? ALIGN_STYLE.left} min-w-0 text-14 text-font-icon-5`}>\n {/* wrap=false면 말줄임(행이 세로로 안 늘어남), wrap=true면 줄바꿈으로 늘어남. */}\n {c.render ? (\n c.render(row)\n ) : wrap ? (\n <span className=\"break-words\">{row[c.key] as ReactNode}</span>\n ) : (\n <TruncatingText as=\"span\" className=\"min-w-0\">\n {row[c.key] as ReactNode}\n </TruncatingText>\n )}\n </div>\n </td>\n );\n })}\n </tr>\n );\n })\n )}\n </tbody>\n );\n\n const table = (\n <table className={tableClass} style={tableStyle}>\n {colgroupEl}\n {theadEl}\n {tbodyEl}\n </table>\n );\n\n // 외곽: bordered → 외곽선+라운드+클립(코너 처리)\n // noneline → 외곽 없음(헤더 셀이 직접 라운드). 단, 세로 스크롤이 생기면 하단에\n // 구분선을 그어 스크롤 영역의 끝을 표시한다. (마지막 행 구분선은 위 rowLine에서 제거되고,\n // 그 선은 스크롤돼야만 보이므로, 스크롤 위치와 무관하게 항상 보이도록 컨테이너에 고정 underline을 둔다.)\n const shell = bordered\n ? 'overflow-clip rounded-round-4 border border-table-outline'\n : hasVScroll\n ? 'border-b border-table-cell-line'\n : '';\n\n // 스크롤 래핑 — splitHeader(bordered+세로)면 헤더는 고정하고 바디만 maxHeight 세로 스크롤.\n // 컬럼 정렬은 동일 colgroup + table-fixed + 동일 최소폭 래퍼로 보장하고,\n // 가로 스크롤은 헤더·바디가 같이 움직이도록 바깥에서 함께 감싼다.\n let body;\n if (splitHeader) {\n // 빈 상태/로딩은 스크롤 영역 자체를 두지 않는다(빈 목록에 세로 썸이 뜨는 아티팩트 방지)\n const bodyTable = (\n <table className={tableClass} style={tableStyle}>\n {colgroupEl}\n {tbodyEl}\n </table>\n );\n const scrollableBody = !loading && displayRows.length > 0;\n const fillActive = fillV && scrollableBody; // 데이터 없을 땐 최대 높이(fill) 미적용 — 자연 높이(2026-07-06)\n const minHStyle = minHeight\n ? { minHeight: typeof minHeight === 'number' ? `${minHeight}px` : minHeight }\n : undefined;\n // fill: 바디 스크롤 상한 = 부모 flex 높이(뷰포트 max-h-full — SideNavigation 독립 스크롤과 동일 패턴)\n const region = scrollableBody ? (\n hasHScroll ? (\n // 가로 스크롤 중첩 — 바디는 thumb 없는 스크롤 요소만 두고, 세로 thumb는 바깥\n // 가로 ScrollArea가 vScrollEl 연동으로 그린다(가로 스크롤 영향 없이 항상 보임)\n fillActive ? (\n <div\n ref={setBodyScrollEl}\n className=\"hide-native-scroll min-h-0 flex-1 overflow-y-auto\"\n style={minHStyle}\n >\n {bodyTable}\n </div>\n ) : (\n <div\n ref={setBodyScrollEl}\n className=\"hide-native-scroll overflow-y-auto\"\n style={{ ...minHStyle, maxHeight: typeof vMax === 'number' ? `${vMax}px` : vMax }}\n >\n {bodyTable}\n </div>\n )\n ) : fillActive ? (\n <ScrollArea className=\"min-h-0 flex-1\" contentClassName=\"max-h-full\" style={minHStyle}>\n {bodyTable}\n </ScrollArea>\n ) : (\n <ScrollArea maxHeight={vMax} style={minHStyle}>{bodyTable}</ScrollArea>\n )\n ) : (\n <div>{bodyTable}</div> // 빈/로딩 상태 — minHeight도 미적용(무조건 hug)\n );\n const split = (\n <div\n className={fillActive ? 'flex min-h-0 flex-1 flex-col' : undefined}\n style={tableMinWidth ? { minWidth: tableMinWidth } : undefined}\n >\n <table className={`${tableClass} shrink-0`} style={tableStyle}>\n {colgroupEl}\n {theadEl}\n </table>\n {region}\n </div>\n );\n body = hasHScroll ? (\n <ScrollArea\n horizontal\n vScrollEl={bodyScrollEl}\n className={fillActive ? 'min-h-0 flex-1' : ''}\n contentClassName={fillActive ? 'flex h-full min-h-0 flex-col' : ''}\n >\n {split}\n </ScrollArea>\n ) : (\n split\n );\n } else if (needsScroll) {\n body = (\n <ScrollArea maxHeight={vMax} horizontal={hasHScroll}>\n {table}\n </ScrollArea>\n );\n } else {\n body = table;\n }\n\n return (\n <div className={`${shell} ${fillV && !loading && displayRows.length > 0 ? 'flex min-h-0 flex-col' : ''} ${className}`} {...props}>\n {body}\n </div>\n );\n}\n","// JobPositionTemplate — 채용 직무 설정 템플릿 (Figma \"JobPositionTemplate\" 8220:82935)\n// Step 01(좌): ConditionOrderSlot — 조건 카드(기준 Select + 값 Select)들을 드래그 정렬·사용/미사용.\n// Step 02(우): 활성 조건의 선택 값이 read-only Input에 \"값1 / 값2 / …\"로 실시간 표시되고,\n// [추가] 클릭 시 하단 외곽선(bordered) 무한 스크롤 테이블(규칙 18)에 로우가 추가된다(최신이 위).\n// 로우의 SelectChip을 클릭하면 그 자리에서 값을 변경할 수 있다.\n// 조립(규칙 4): ConditionOrderSlot + Select + Input + Button + Table + SelectChip — 손 구현 0.\n// 작성 데이터는 onChange(rows)로 반출한다(템플릿 값 반출 계약 — 2026-07-07 감사 교훈).\nimport { useImperativeHandle, useState } from 'react';\nimport type { ComponentPropsWithoutRef, Ref } from 'react';\nimport { ChevronRight, Copy, Plus, Trash2 } from 'lucide-react';\nimport { ConditionOrderSlot } from './ConditionOrderSlot';\nimport { Select, SelectChip } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Table } from './Table';\nimport type { TableColumn } from './Table';\nimport { iconCellWidth } from './tableView';\nimport { Divider } from './Divider';\n\n// 로우 id 생성 — 모듈 카운터(let seq)는 HMR로 모듈이 재평가되면 0으로 리셋돼 기존 로우 id와\n// 충돌하고, React 중복 key로 새 로우가 화면에 안 나타난다(2026-07-07 복사 미동작 원인).\n// 시간+랜덤 조합으로 defaultRows·리마운트·HMR 어떤 경우에도 충돌하지 않게 한다.\nconst newRowId = () =>\n `row-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n\n// 옵션 항목 — [{ value, label }] (로우 칩에서 disabled 파생 부여)\ninterface JobOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n// 로우의 조건 항목 — { criteria, value }\ninterface JobRowItem {\n criteria: string;\n value: string;\n}\n// 테이블 로우 — { id, items: [{ criteria, value }] }\ninterface JobRow {\n id: string;\n items: JobRowItem[];\n}\n// 카드별 (기준, 값) 선택 — 다중 카드는 값이 배열\ninterface JobSelection {\n criteria: string;\n value: string | string[];\n}\n\n// (선택) 저장 API(React 19 ref-as-prop) —\n// validate(): 미선택 칩에 에러 표시 + 통과 여부(false면 저장 중단)\n// getRows(): 저장 시점 최신 로우 조회('변경 없이 저장'도 안전)\nexport interface JobPositionTemplateHandle {\n validate: () => boolean;\n getRows: () => JobRow[];\n}\n\ninterface JobPositionTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n criteriaOptions?: JobOption[]; // 기준 목록 [{ value, label }] — 카드의 첫 Select(예: 지역/고용형태/경력/직무)\n valueOptions?: Record<string, JobOption[]>; // 기준별 값 목록 { [criteriaValue]: [{ value, label }] } — 카드의 둘째 Select·로우 칩 공용\n conditionCount?: number; // 조건 카드 수\n defaultDisabledIds?: string[]; // 초기 미사용(스위치 off) 조건 id 목록 — 예: ['cond-4'] (id=`cond-1`..`cond-N`)\n // multiLastValue — 마지막 조건(표시 순서상 맨 아래 카드)의 값을 체크박스 다중 선택으로.\n // 추가 시 선택한 값마다 행을 하나씩 생성(각 행=단일 조합, 중복은 건너뜀).\n multiLastValue?: boolean;\n defaultRows?: JobRow[]; // 초기 로우 [{ id, items: [{ criteria, value }] }]\n onChange?: (rows: JobRow[]) => void; // 로우 추가/삭제/칩 변경 시 전체 스냅샷 반출\n step1Title?: string;\n step2Title?: string;\n addLabel?: string;\n inputPlaceholder?: string;\n orderLabel?: string;\n jobLabel?: string;\n manageLabel?: string;\n tableHeight?: number | 'fill'; // 테이블 세로 — 'fill'(내용만큼 계속 확장, 넘치면 모달 바디 스크롤) | 숫자(px 고정 상한+바디 스크롤)\n emptyMessage?: string;\n duplicateMessage?: string; // 중복 추가 시도 시 인풋 에러 툴팁 문구\n emptyValueMessage?: string; // 저장 검증 — 미선택 칩 에러 툴팁 문구\n ref?: Ref<JobPositionTemplateHandle>; // (선택) 저장 API(React 19 ref-as-prop)\n}\n\nexport function JobPositionTemplate({\n criteriaOptions = [],\n valueOptions = {},\n conditionCount = 4,\n defaultDisabledIds = [],\n multiLastValue = false,\n defaultRows = [],\n onChange,\n step1Title = '1. 조건 조합 설정',\n step2Title = '2. 채용 직무 추가',\n addLabel = '추가',\n inputPlaceholder = '조건을 선택하세요',\n orderLabel = '순서',\n jobLabel = '채용 직무',\n manageLabel = '관리',\n tableHeight = 'fill',\n emptyMessage = '추가된 채용 직무가 없습니다.',\n duplicateMessage = '이미 추가된 조건 조합입니다.',\n emptyValueMessage = '값을 선택하세요.',\n ref,\n className = '',\n ...props\n}: JobPositionTemplateProps) {\n // Step 01 상태 — 카드 순서/사용 여부는 ConditionOrderSlot controlled로 추적하고,\n // 카드별 (기준, 값) 선택은 템플릿이 소유한다(미리보기·추가 스냅샷 계산용).\n const cardIds = Array.from({ length: conditionCount }, (_, i) => `cond-${i + 1}`);\n const [order, setOrder] = useState(cardIds);\n const [enabledIds, setEnabledIds] = useState(() =>\n cardIds.filter((id) => !defaultDisabledIds.includes(id)),\n );\n const [selections, setSelections] = useState<Record<string, JobSelection>>({}); // { [cardId]: { criteria, value } }\n const effOrder = [\n ...order.filter((id) => cardIds.includes(id)),\n ...cardIds.filter((id) => !order.includes(id)),\n ];\n\n // 마지막 '사용 중' 조건 — multiLastValue면 이 카드의 값 셀렉트가 다중 선택(배열 값)이 된다.\n // 물리적 맨 아래가 아니라 활성 순번상 마지막(제목 \"조건 N.\"의 최대 N)이어야 한다\n // (조건 3·4를 끄면 조건 2가 마지막 → 조건 2가 체크박스, 2026-07-08 지적).\n const activeOrder = effOrder.filter((id) => enabledIds.includes(id));\n const lastCardId = activeOrder[activeOrder.length - 1];\n\n const setCardCriteria = (id: string, criteria: string) =>\n setSelections((p) => ({ ...p, [id]: { criteria, value: '' } })); // 기준 변경 시 값 리셋\n const setCardValue = (id: string, value: string | string[]) =>\n setSelections((p) => ({ ...p, [id]: { ...(p[id] ?? { criteria: '' }), value } }));\n\n // 값 정규화 — 다중 카드는 배열, 그 외는 문자열로 강제한다. 재정렬로 마지막 카드가 바뀌면\n // 이전 카드에 남은 배열 값은 여기서 ''로 취급돼(단일화) 조합·미리보기가 꼬이지 않는다.\n const isMultiCard = (id: string) => multiLastValue && id === lastCardId;\n const valueOf = (id: string): string | string[] => {\n const raw = selections[id]?.value;\n if (isMultiCard(id)) return Array.isArray(raw) ? raw : raw ? [raw] : [];\n return Array.isArray(raw) ? '' : (raw ?? '');\n };\n\n const labelOf = (criteria: string, value: string) =>\n (valueOptions[criteria] ?? []).find((o) => o.value === value)?.label ?? '';\n\n // 다른 '사용 중' 카드가 선택한 기준은 이 카드에서 비활성(중복 조건 방지, 2026-07-07 지시).\n // 앞 카드만 잠그면 뒤 카드가 먼저 고른 기준을 앞 카드가 다시 고를 수 있어(사용자 재현) 양방향 잠금.\n const usedByOthers = (id: string) => {\n const used = new Set<string>();\n for (const pid of effOrder) {\n if (pid === id || !enabledIds.includes(pid)) continue;\n const c = selections[pid]?.criteria;\n if (c) used.add(c);\n }\n return used;\n };\n\n // 활성 조건 중 (기준+값) 모두 선택된 것 — 표시 순서(effOrder) 기준.\n // 다중 값(배열)은 하나 이상 골랐을 때만 완성으로 본다(빈 배열=미완성).\n const activeComplete = effOrder\n .filter((id) => enabledIds.includes(id))\n .map((id) => ({ criteria: selections[id]?.criteria, value: valueOf(id) }))\n .filter((s): s is JobSelection => !!(s.criteria && (Array.isArray(s.value) ? s.value.length > 0 : s.value)));\n // 미리보기 — 다중 값 세그먼트는 라벨을 ', '로 잇는다(예: \"서울 / 신입 / 프론트엔드, 백엔드\")\n const valueLabel = (s: JobSelection) =>\n Array.isArray(s.value)\n ? s.value.map((v) => labelOf(s.criteria, v)).join(', ')\n : labelOf(s.criteria, s.value);\n const preview = activeComplete.map(valueLabel).join(' / ');\n\n // Step 02 로우 — 최신이 위(순서 내림차순). onChange로 전체 스냅샷 반출.\n const [rows, setRows] = useState<JobRow[]>(defaultRows);\n const applyRows = (next: JobRow[]) => {\n setRows(next);\n onChange?.(next);\n };\n\n // 중복 검증(2026-07-07 지시) — 조건 조합(기준+값)이 이미 테이블에 있으면 추가 불가.\n // 순서는 무관하다(2026-07-08 지시): \"정규직>신입…\"과 \"신입>정규직…\"은 값 집합이 같으므로 같은 조합.\n // → 각 (기준:값) 쌍을 정렬한 뒤 join해 순서 독립 키를 만든다.\n // 에러는 추가 시도 시 표시하고, 조합·로우가 바뀌어 중복이 해소되면 자동으로 사라진다(렌더 파생).\n const comboKey = (items: JobRowItem[]) =>\n items\n .map((it) => `${it.criteria}:${it.value}`)\n .sort()\n .join('|');\n // 현재 조건을 추가 가능한 '단일 조합 행'들로 펼친다 — 마지막 조건이 다중이면 값마다 한 조합.\n // 다중 아님: 조합 1개 / 다중(값 N개): 조합 N개(각 행은 값 하나짜리 단일 조합).\n const expandCombos = (): JobRowItem[][] => {\n const idx = activeComplete.findIndex((s) => Array.isArray(s.value));\n if (idx === -1)\n return [activeComplete.map((s) => ({ criteria: s.criteria, value: s.value as string }))];\n return (activeComplete[idx].value as string[]).map((v) =>\n activeComplete.map((s, i) => ({ criteria: s.criteria, value: i === idx ? v : (s.value as string) })),\n );\n };\n const combos = activeComplete.length > 0 ? expandCombos() : [];\n // 추가할 게 하나도 없으면(전부 이미 존재) 중복 — 인풋 에러로 알린다.\n const isDuplicate =\n combos.length > 0 && combos.every((items) => rows.some((r) => comboKey(r.items) === comboKey(items)));\n const [dupAttempted, setDupAttempted] = useState(false);\n const showDupError = dupAttempted && isDuplicate;\n\n const addRow = () => {\n if (activeComplete.length === 0) return;\n // 이미 존재하는 조합은 건너뛰고 새 조합만 추가(다중 값의 일부만 새것일 수 있음)\n const fresh = combos.filter((items) => !rows.some((r) => comboKey(r.items) === comboKey(items)));\n if (fresh.length === 0) {\n setDupAttempted(true); // 추가하지 않고 인풋에 에러 툴팁 표시\n return;\n }\n setDupAttempted(false);\n applyRows([...fresh.map((items) => ({ id: newRowId(), items })), ...rows]);\n };\n const removeRow = (id: string) => applyRows(rows.filter((r) => r.id !== id));\n // 로우 복사(2026-07-07 지시) — 값을 그대로 복제하되 완전 중복을 피하도록 마지막 칩만 미선택으로.\n // 새 로우는 '추가'와 동일하게 맨 위(최대 순번)로 들어간다. 복사는 항상 새 로우를 만든다 —\n // 미완성 중복은 저장 검증(빈 칩)과 칩 편집 잠금(같은 조합 완성 불가)이 걸러준다.\n const copyRow = (row: JobRow) =>\n applyRows([\n {\n id: newRowId(),\n items: row.items.map((it, i) =>\n i === row.items.length - 1 ? { ...it, value: '' } : { ...it },\n ),\n },\n ...rows,\n ]);\n const setRowValue = (rowId: string, itemIndex: number, value: string) => {\n applyRows(\n rows.map((r) =>\n r.id === rowId\n ? { ...r, items: r.items.map((it, i) => (i === itemIndex ? { ...it, value } : it)) }\n : r,\n ),\n );\n // 값이 채워지면 해당 칩의 저장 검증 에러 해제\n setInvalidKeys((p) => {\n const key = `${rowId}:${itemIndex}`;\n if (!p.has(key)) return p;\n const next = new Set(p);\n next.delete(key);\n return next;\n });\n };\n\n // 저장 검증(2026-07-07 지시) — 미선택 칩(복사 직후 등)에 에러 툴팁을 걸고 저장을 막는다.\n // 소비자가 저장 버튼에서 ref.current.validate()를 호출: 통과 시 true.\n const [invalidKeys, setInvalidKeys] = useState<Set<string>>(() => new Set());\n useImperativeHandle(ref, () => ({\n validate: () => {\n const invalid = new Set<string>();\n rows.forEach((r) =>\n r.items.forEach((it, i) => {\n if (!it.value) invalid.add(`${r.id}:${i}`);\n }),\n );\n setInvalidKeys(invalid);\n return invalid.size === 0;\n },\n // 저장 시점 최신 로우 조회 — onChange 스냅샷을 따로 보관하지 않아도 되고,\n // '변경 없이 바로 저장'(onChange 미발화) 케이스에서도 defaultRows 포함 최신 상태를 준다.\n getRows: () => rows,\n }));\n\n // 순서 번호 = 내림차순(맨 위가 가장 큼) — 렌더 파생\n const displayRows = rows.map((r, i) => ({ ...r, orderNo: rows.length - i }));\n\n // 로우 칩 편집 검증(2026-07-07 지시) — 이 값으로 바꾸면 다른 로우와 같은 조합이 되는 옵션은\n // 드롭다운에서 비활성(사전 차단, 추가 시 인풋 검증과 일관된 옵션 disabled 패턴)\n const wouldDuplicate = (row: JobRow, itemIndex: number, value: string) => {\n const key = comboKey(\n row.items.map((it, i) => (i === itemIndex ? { ...it, value } : it)),\n );\n return rows.some((r) => r.id !== row.id && comboKey(r.items) === key);\n };\n\n const columns = [\n { key: 'orderNo', label: orderLabel, width: 60 },\n {\n key: 'items',\n label: jobLabel,\n render: (row: JobRow) => (\n <div className=\"flex min-w-0 flex-wrap items-center gap-spacing-3\">\n {row.items.map((it: JobRowItem, i: number) => (\n <span key={`${it.criteria}-${i}`} className=\"flex items-center gap-spacing-3\">\n {i > 0 && (\n <ChevronRight size={12} strokeWidth={1.8} className=\"shrink-0 text-font-icon-3\" />\n )}\n <SelectChip\n options={(valueOptions[it.criteria] ?? []).map((o) =>\n o.value !== it.value && wouldDuplicate(row, i, o.value)\n ? { ...o, disabled: true }\n : o,\n )}\n value={it.value}\n searchable // 값 목록이 길어질 수 있어 팝오버 상단 검색 제공(2026-07-07 지시)\n searchPlaceholder=\"검색어 입력\" // 좁은 칩 드롭다운(160)에 맞춘 짧은 문구\n menuWidth={160} // 검색바가 들어가므로 칩 기본(120)보다 넓게\n error={invalidKeys.has(`${row.id}:${i}`)}\n errorMessage={emptyValueMessage}\n onChange={(e) => setRowValue(row.id, i, e.target.value)}\n />\n </span>\n ))}\n </div>\n ),\n },\n {\n key: 'manage',\n label: manageLabel,\n // 고스트 아이콘 버튼 2개(복사·삭제) — width는 임의값 대신 계산(규칙 17): 셀 패딩 + 버튼×2 + 간격\n width: iconCellWidth(2, { buttonSize: 32 }),\n render: (row: JobRow) => (\n <div className=\"flex items-center gap-spacing-5\">\n <Button variant=\"ghost\" size=\"32\" icon={Copy} aria-label=\"복사\" onClick={() => copyRow(row)} />\n <Button variant=\"ghost\" size=\"32\" icon={Trash2} aria-label=\"삭제\" onClick={() => removeRow(row.id)} />\n </div>\n ),\n },\n ];\n\n return (\n <div className={`flex w-full items-stretch gap-spacing-7 ${className}`} {...props}>\n {/* Step 01 — 조건 조합 설정 */}\n <div className=\"flex shrink-0 flex-col gap-spacing-6\">\n <p className=\"text-14 text-font-icon-5\">{step1Title}</p>\n <ConditionOrderSlot\n items={cardIds.map((id) => {\n const sel = selections[id] ?? { criteria: '', value: '' };\n const used = usedByOthers(id);\n return {\n id,\n body: (\n <>\n <Select\n width=\"100%\"\n options={criteriaOptions.map((o) =>\n used.has(o.value) ? { ...o, disabled: true } : o,\n )}\n value={sel.criteria}\n label=\"기준\" // 내부 라벨 — 선택 시 \"기준 ⋮ 지역\"으로 표시(2026-07-07 지시)\n placeholder=\"기준 선택\"\n onChange={(e) => setCardCriteria(id, e.target.value)}\n />\n <Select\n width=\"100%\"\n options={valueOptions[sel.criteria] ?? []}\n label=\"값\" // 내부 라벨 — 선택 시 \"값 ⋮ 서울\"으로 표시(2026-07-07 지시)\n placeholder=\"값 선택\"\n disabled={!sel.criteria}\n // 마지막 조건은 체크박스 다중 선택(값 여러 개) — multiple이 동적 boolean이라\n // 판별 유니언(SelectProps)에 스프레드로 한 번 좁혀 전달(런타임 동일)\n {...({\n multiple: isMultiCard(id),\n value: valueOf(id),\n onChange: (e: { target: { value: string | string[] } }) =>\n setCardValue(id, e.target.value),\n } as unknown as Parameters<typeof Select>[0])}\n />\n </>\n ),\n };\n })}\n order={order}\n onOrderChange={setOrder}\n enabledIds={enabledIds}\n onEnabledChange={(nextEnabled, { id: toggledId, enabled }) => {\n setEnabledIds(nextEnabled);\n // 다시 켠 카드가 양보(2026-07-07 지적) — 미사용 동안 다른 활성 카드가 같은 기준을\n // 선택했을 수 있으므로, 켜는 순간 기준이 이미 사용 중이면 이 카드의 선택을 리셋한다.\n // (이미 활성인 카드는 건드리지 않아 동작이 예측 가능)\n if (enabled) {\n const c = selections[toggledId]?.criteria;\n const taken =\n c &&\n nextEnabled.some(\n (oid) => oid !== toggledId && selections[oid]?.criteria === c,\n );\n if (taken) setSelections((p) => ({ ...p, [toggledId]: { criteria: '', value: '' } }));\n }\n }}\n />\n </div>\n\n <Divider direction=\"vertical\" />\n\n {/* Step 02 — 채용 직무 추가 */}\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col gap-spacing-6\">\n <p className=\"text-14 text-font-icon-5\">{step2Title}</p>\n <div className=\"flex items-center gap-spacing-5\">\n <div className=\"min-w-0 flex-1\">\n <Input\n width=\"100%\"\n readOnly\n value={preview}\n placeholder={inputPlaceholder}\n error={showDupError}\n errorMessage={duplicateMessage}\n />\n </div>\n <Button variant=\"line\" leftIcon={Plus} disabled={activeComplete.length === 0} onClick={addRow}>\n {addLabel}\n </Button>\n </div>\n {/* 규칙 18 — 모달 안 무한 스크롤 테이블: bordered + maxHeight='fill' + min-h-0(shrink 상한) */}\n <Table\n /* render 파라미터를 구체 행 타입(JobRow)으로 쓰므로 Table 계약(TableRowData)으로 한 번 캐스팅 —\n rows에 JobRow[]만 넘겨 런타임 안전 */\n columns={columns as unknown as TableColumn[]}\n rows={displayRows}\n rowKey=\"id\"\n bordered\n maxHeight={tableHeight}\n className={tableHeight === 'fill' ? 'min-h-0' : ''}\n emptyMessage={emptyMessage}\n />\n </div>\n </div>\n );\n}\n","// useFocusTrap — 열린 오버레이(Dialog/Modal 등)의 접근성 포커스 관리(2026-07-20, Radix FocusScope 참고·자체 구현).\n// ① 진입: 열릴 때 컨테이너(또는 첫 포커스 요소)로 포커스를 옮기고, 열기 직전 포커스 위치를 기억한다.\n// ② 트랩: Tab/Shift+Tab이 컨테이너 밖으로 나가지 않게 처음↔끝을 순환시킨다.\n// ③ 복원: 닫힐 때(언마운트·active=false) 열기 직전 포커스로 되돌린다.\n// 중첩 모달은 각자 자기 컨테이너에 트랩을 걸므로 별도 스택 조율이 필요 없다(inner 이벤트는 outer로 안 샌다).\n// 컨테이너 안 위젯(Select 옵션 순회·설정 패널 키보드 등)이 Tab을 이미 처리(preventDefault)하면 존중해 건너뛴다.\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\n\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nexport interface UseFocusTrapOptions {\n /** 진입 포커스 대상 — 'container'(기본, 컨테이너 자체) | 'first'(첫 포커스 가능 요소) */\n initialFocus?: 'container' | 'first';\n}\n\nexport function useFocusTrap(\n active: boolean,\n containerRef: RefObject<HTMLElement | null>,\n { initialFocus = 'container' }: UseFocusTrapOptions = {},\n) {\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n const focusables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => el.offsetParent !== null, // 화면에 보이는 것만\n );\n\n // ① 진입 포커스\n if (initialFocus === 'first') {\n (focusables()[0] ?? container).focus();\n } else {\n container.focus();\n }\n\n // ② Tab 트랩 — 컨테이너 안 위젯이 이미 처리한 Tab(defaultPrevented)은 존중해 건너뛴다\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || e.defaultPrevented) return;\n const els = focusables();\n if (els.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = els[0];\n const last = els[els.length - 1];\n const activeEl = document.activeElement;\n if (activeEl && !container.contains(activeEl)) {\n // 어쩌다 포커스가 밖에 있으면 안으로 회수\n e.preventDefault();\n first.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n } else if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n }\n // 그 외(내부 순방향/역방향 이동)는 브라우저 기본 동작에 맡긴다\n };\n container.addEventListener('keydown', onKeyDown);\n\n return () => {\n container.removeEventListener('keydown', onKeyDown);\n // ③ 포커스 복원 — 열기 직전 요소가 아직 문서에 살아 있을 때만\n if (previouslyFocused && document.contains(previouslyFocused)) {\n previouslyFocused.focus();\n }\n };\n }, [active, containerRef, initialFocus]);\n}\n","// ModalBodyMaxContext — Modal이 계산한 본문 '내용' 가용 높이(px, 본문 패딩 제외).\n// 모달 본문을 꽉 채우려는 자식(예: SideNavigationTemplate height='fill')이 소비한다.\n// 모달 밖에서는 null — 소비자는 부모 100% 등으로 폴백한다.\n// (컴포넌트 파일과 분리 — react-refresh only-export-components 회피, tableView/utils 전례)\nimport { createContext } from 'react';\n\nexport const ModalBodyMaxContext = createContext<number | null>(null);\n","// Modal — 오버레이 다이얼로그 (base + 변형)\n// Figma: h9jZFkEHfcHUGok1TZjjlP — modal (node 7349:1758474)\n//\n// 구조 (모달 설계 가이드):\n// ModalOverlay (딤)\n// └── ModalBox (size로 width 고정, 내부 요소는 width:100%)\n// ├── ModalHeader (타이틀 + 닫기 버튼)\n// ├── ModalBody (ScrollArea — maxHeight 70vh, 내부 스크롤)\n// │ └── children (ModalContent — 외부 주입)\n// └── ModalFooter (좌측 텍스트/컴포넌트 + 버튼 그룹)\n//\n// ModalBox 배경은 modal-inline(gray-100) + gap 1px → 헤더/바디/푸터(white) 사이가\n// 1px 구분선으로 비친다. 외곽선은 modal-outline 1px ring, round-6(10px), 그림자.\n//\n// 변형(아래 export):\n// - FormModal(취소/저장 + form) — 기본 Modal 구조(헤더 + 자유 본문 + 푸터) 그대로.\n// - AlertModal(확인 1개) · ConfirmModal(취소/확인) — 헤더 없이 본문에 고정 슬롯\n// [타이틀 → 설명 → 2뎁스 상세(회색 박스) → 체크박스(Confirm 전용)]만 둔다.\n// 슬롯 종류는 고정(텍스트만 자유 수정), 푸터 버튼은 전체 폭 균등 분할.\n// 체크박스는 삭제 등 위험 액션의 재확인 요소(ConfirmModal에서만 사용).\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n ChangeEvent,\n ComponentPropsWithoutRef,\n FormEvent,\n ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useFocusTrap } from './useFocusTrap';\nimport { X } from 'lucide-react';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { ScrollArea } from './ScrollArea';\nimport { ModalBodyMaxContext } from './modalContext';\nimport { Checkbox } from './Checkbox';\n\n// 중첩 모달 전역 관리(2026-07-07 감사) —\n// openModalStack: ESC는 '맨 위' 모달만 닫는다(없으면 ESC 한 번에 중첩 모달이 전부 닫혀 폼 유실).\n// closeOnEsc=false 모달도 스택에는 올라 아래 모달의 ESC를 막는다(맨 위가 안 닫히는 게 맞는 동작).\n// scrollLock: body 스크롤 잠금 참조 카운트 — 인스턴스별 prev 저장/복원은 A→B 순서로 열고 A를\n// 먼저 닫으면 B가 떠 있는데 잠금이 풀리므로, 첫 잠금의 원래 값을 마지막 해제 때만 복원한다.\nconst openModalStack: object[] = [];\nlet scrollLockCount = 0;\nlet scrollLockPrev = '';\n\n// ModalBox width 규격 — sm:360 md:480 lg:600 xl:720 2xl:840 3xl:960 4xl:1260\n// fill = 브라우저 너비에서 좌우 16px씩 뺀 값(calc(100vw-32px)), 단 최소 너비 1260px\nconst SIZE_WIDTH = {\n sm: 'w-[360px]',\n md: 'w-[480px]',\n lg: 'w-[600px]',\n xl: 'w-[720px]',\n '2xl': 'w-[840px]',\n '3xl': 'w-[960px]',\n '4xl': 'w-[1260px]',\n fill: 'w-[calc(100vw-32px)]',\n};\n\nexport interface ModalProps\n extends Omit<ComponentPropsWithoutRef<'div'>, 'title' | 'onSubmit'> {\n open: boolean;\n onClose: () => void;\n title?: ReactNode;\n /** 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'fill'(좌우 16씩 제외) */\n size?: keyof typeof SIZE_WIDTH;\n /** 'center'(브라우저 중앙) | 'top'(상단 정렬, 여백 16 ~ 화면높이/2−150 가변). 일반 팝업·FormModal 기본=top, Alert/Confirm은 center */\n placement?: 'center' | 'top';\n showHeader?: boolean;\n /** 헤더 우측 닫기(X) 버튼 */\n showClose?: boolean;\n showFooter?: boolean;\n /** 푸터 전체 커스텀 노드(주면 아래 버튼 props 무시) */\n footer?: ReactNode;\n /** 푸터 좌측 영역 — 새로고침/불러오기 버튼, 안내글, 유효성 메시지 등(텍스트/컴포넌트) */\n footerStart?: ReactNode;\n /** 'text' | 'button' — footerStart 내용 유형. 좌측 여백 결정: text=16(spacing-7) / button=12(spacing-6, 버튼 자체 여백 감안) — 2026-07-06 지시 */\n footerStartType?: 'text' | 'button';\n // 푸터 기본 버튼 — footer 미지정 시 사용\n confirmText?: ReactNode;\n cancelText?: ReactNode;\n onConfirm?: () => void;\n /** 미지정 시 onClose */\n onCancel?: () => void;\n /** 주동작 버튼 variant */\n confirmVariant?: 'fill' | 'line' | 'ghost' | 'underline';\n confirmDisabled?: boolean;\n confirmLoading?: boolean;\n /** 취소(보조) 버튼 노출 여부 */\n showCancel?: boolean;\n /** 푸터 버튼을 전체 폭으로 균등 분할(footerStart 없는 단순 액션용 — Alert/Confirm) */\n footerFullWidth?: boolean;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n /** 본문 최대 높이(초과 시 내부 스크롤) */\n bodyMaxHeight?: number | string;\n /** 본문 패딩(토큰 클래스). Alert/Confirm은 p-spacing-8 */\n bodyPadding?: string;\n /** 푸터 패딩 직접 지정(토큰 클래스) — 미지정 시 footerStartType 기반 기본값(좌 16/12·우 12·상하 12). Alert/Confirm은 px-spacing-8 py-spacing-7 */\n footerPadding?: string | null;\n bodyClassName?: string;\n /** 주면 본문+푸터를 <form>으로 감싸고 주동작 버튼 type=submit */\n onSubmit?: (e: FormEvent<HTMLFormElement>) => void;\n}\n\nexport function Modal({\n open,\n onClose,\n title,\n children, // ModalContent — 본문에 주입할 내용\n size = 'md',\n placement = 'top',\n showHeader = true,\n showClose = true,\n showFooter = true,\n footer,\n footerStart = null,\n footerStartType = 'text',\n confirmText = '확인',\n cancelText = '취소',\n onConfirm,\n onCancel,\n confirmVariant = 'fill',\n confirmDisabled = false,\n confirmLoading = false,\n showCancel = true,\n footerFullWidth = false,\n closeOnOverlayClick = true,\n closeOnEsc = true,\n bodyMaxHeight = '70vh',\n bodyPadding = 'p-spacing-7',\n footerPadding = null,\n bodyClassName = '',\n onSubmit,\n className = '',\n ...props\n}: ModalProps) {\n const boxRef = useRef<HTMLDivElement | null>(null);\n const headerRef = useRef<HTMLElement | null>(null);\n const footerRef = useRef<HTMLElement | null>(null);\n const contentRef = useRef<HTMLDivElement | null>(null);\n // top 정렬 상단 여백은 '열릴 때 한 번' 계산해 닫을 때까지 고정한다(내용 변화로 흔들리지 않게).\n const lockedTopRef = useRef<number | null>(null);\n // 레이아웃 측정값:\n // - top: top 정렬 시 상단 여백(px). 팝업 자연 높이에 반비례 — 내용 적으면 크게(최대 120),\n // 내용 많으면 작게(최소 = 하단 여백 16). center 정렬이면 null.\n // - bodyMax: ModalBody 실제 maxHeight(px). header·footer·여백을 빼 화면에 맞춘 값이라\n // footer가 절대 가려지지 않는다(flex 높이 전파에 의존하지 않음).\n const [layout, setLayout] = useState<{\n top: number | null;\n bodyMax: number | null;\n bodyInner: number | null;\n }>({ top: null, bodyMax: null, bodyInner: null });\n\n // ESC 닫기 — 열린 모달 스택의 '맨 위'일 때만 닫는다(중첩 모달 동시 닫힘 방지).\n // closeOnEsc/onClose는 ref로 읽어 effect 재실행(스택 순서 뒤틀림)을 피한다.\n const closeOnEscRef = useRef(closeOnEsc);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n closeOnEscRef.current = closeOnEsc;\n onCloseRef.current = onClose;\n }); // 매 렌더 후 최신값 유지(latest-ref) — 렌더 중 ref 쓰기 금지 규칙 준수\n useEffect(() => {\n if (!open) return;\n const token = {};\n openModalStack.push(token);\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (openModalStack[openModalStack.length - 1] !== token) return; // 맨 위 모달만\n if (closeOnEscRef.current) onCloseRef.current?.();\n };\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('keydown', onKey);\n const i = openModalStack.indexOf(token);\n if (i !== -1) openModalStack.splice(i, 1);\n };\n }, [open]);\n\n // 열렸을 때 body 스크롤 잠금 — 참조 카운트(중첩 모달에서 먼저 닫힌 쪽이 잠금을 풀지 않게)\n useEffect(() => {\n if (!open) return;\n scrollLockCount += 1;\n if (scrollLockCount === 1) {\n scrollLockPrev = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n }\n return () => {\n scrollLockCount -= 1;\n if (scrollLockCount === 0) document.body.style.overflow = scrollLockPrev;\n };\n }, [open]);\n\n // 포커스 트랩 + 복원(접근성) — 열릴 때 박스로 진입, Tab을 박스 안에 가두고, 닫힐 때 원래 포커스 복원.\n // 중첩 모달은 각자 자기 박스에 트랩(스택 조율 불필요), 내부 위젯이 처리한 Tab은 존중한다.\n useFocusTrap(open, boxRef);\n\n // 레이아웃 계산 — header/footer/본문 자연 높이를 측정해 top 여백과 ModalBody maxHeight를 정한다.\n useLayoutEffect(() => {\n if (!open) return;\n const BOTTOM = 16; // 하단 여백(spacing-7) — 고정\n const toPx = (v: number | string | null | undefined) => {\n if (v == null) return Infinity;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n if (s.endsWith('vh')) return (window.innerHeight * parseFloat(s)) / 100;\n if (s.endsWith('px')) return parseFloat(s);\n return Infinity;\n };\n const compute = () => {\n const vh = window.innerHeight;\n const hh = headerRef.current?.offsetHeight ?? 0;\n const fh = footerRef.current?.offsetHeight ?? 0;\n const ch = contentRef.current?.offsetHeight ?? 0; // 본문 natural(미캡) 높이\n const designCap = toPx(bodyMaxHeight); // 디자인 캡(기본 70vh)\n // 본문 '내용' 가용 높이용 — 본문 패딩(py)을 실측해 빼준다(height='fill' 자식 소비, ModalBodyMaxContext)\n const padY = () => {\n const el = contentRef.current;\n if (!el) return 0;\n const cs = getComputedStyle(el);\n return parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);\n };\n\n if (placement === 'top') {\n // 상단 정렬은 % 높이 캡(bodyMaxHeight, 70vh)을 적용하지 않는다 — top 여백 + 화면 가용\n // 높이로만 본문 높이를 정한다. 상단 여백 top은 '열릴 때 한 번'만 계산해 고정(lockedTopRef)\n // → 이후 내용이 바뀌어도 위치가 흔들리지 않는다. bodyMax만 화면 크기에 맞춰 갱신.\n // - 팝업 자연 높이에 반비례: 내용 많음 → top 작게(최소 BOTTOM), 적음 → 크게(최대 MAX_TOP).\n if (lockedTopRef.current == null) {\n // 상단 여백 범위: BOTTOM(16) ~ MAX_TOP(화면높이/2 − 150). 작은 화면에선 BOTTOM로 수렴.\n // 팝업 자연 높이가 화면을 채우는 비율(0~1)에 선형 비례해 top을 MAX_TOP→BOTTOM로 보간.\n // 내용 적음 → top 큼(최대 MAX_TOP), 내용 많음 → top 작음(최소 BOTTOM).\n const MAX_TOP = Math.max(BOTTOM, vh / 2 - 150);\n const effModalH = hh + fh + ch;\n const fill = Math.min(1, Math.max(0, effModalH / vh));\n const top = MAX_TOP - (MAX_TOP - BOTTOM) * fill;\n lockedTopRef.current = Math.round(Math.min(MAX_TOP, Math.max(BOTTOM, top)));\n }\n const top = lockedTopRef.current;\n // -2: header↔body, body↔footer 사이 gap-spacing-1(각 1px 구분선) 공간 확보\n const bodyMax = Math.max(0, vh - top - BOTTOM - hh - fh - 2);\n setLayout({ top, bodyMax, bodyInner: Math.max(0, bodyMax - padY()) });\n } else {\n const bodyMax = Math.max(0, Math.min(designCap, vh - 32 - hh - fh));\n setLayout({ top: null, bodyMax, bodyInner: Math.max(0, bodyMax - padY()) });\n }\n };\n compute();\n const ro = new ResizeObserver(compute);\n [headerRef, footerRef, contentRef].forEach((r) => r.current && ro.observe(r.current));\n window.addEventListener('resize', compute);\n return () => {\n ro.disconnect();\n window.removeEventListener('resize', compute);\n lockedTopRef.current = null; // 닫히면(또는 placement 변경) 고정 해제 → 다음 열림에 재계산\n };\n }, [open, placement, bodyMaxHeight]);\n\n if (!open) return null;\n\n // 푸터 버튼 그룹(footer 미지정 시) — footerFullWidth면 전체 폭 균등 분할\n const defaultFooterEnd = (\n <ButtonGroup width={footerFullWidth ? 'fill' : 'hug'}>\n {showCancel && (\n <Button variant=\"line\" onClick={onCancel ?? onClose}>\n {cancelText}\n </Button>\n )}\n <Button\n variant={confirmVariant}\n type={onSubmit ? 'submit' : 'button'}\n disabled={confirmDisabled}\n loading={confirmLoading}\n onClick={onSubmit ? undefined : onConfirm}\n >\n {confirmText}\n </Button>\n </ButtonGroup>\n );\n\n // 헤더/바디/푸터 묶음 — onSubmit이 있으면 <form>으로 감싼다\n // (타입은 'form'으로 고정 — 'form'|'div' 유니언은 JSX props가 두 요소의 교집합으로 검사돼\n // onSubmit 핸들러 타입이 충돌한다. 런타임 동작은 동일.)\n const Inner = (onSubmit ? 'form' : 'div') as 'form';\n const innerProps = onSubmit\n ? {\n onSubmit: (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n onSubmit(e);\n },\n }\n : {};\n\n // 정렬: center=중앙, top=상단 가변(measure로 layout.top 계산). 첫 페인트 전 fallback=화면높이/2−150.\n const isTop = placement === 'top';\n const topPad = layout.top ?? Math.round(Math.max(16, (typeof window !== 'undefined' ? window.innerHeight : 800) / 2 - 150));\n const overlayLayout = isTop\n ? 'items-start justify-center px-spacing-7'\n : 'items-center justify-center p-spacing-7';\n const overlayStyle = isTop ? { paddingTop: topPad, paddingBottom: 16 } : undefined;\n const boxMaxHStyle = isTop\n ? { maxHeight: `calc(100vh - ${topPad + 16}px)` }\n : { maxHeight: 'calc(100vh - 32px)' };\n // 최소 너비 — fill은 1260px, 그 외는 360px\n const minWClass = size === 'fill' ? 'min-w-[1260px]' : 'min-w-[360px]';\n\n return createPortal(\n <div\n className={`fixed inset-0 z-[1000] flex bg-modal-overlay ${overlayLayout}`}\n style={overlayStyle}\n // dim(오버레이) 영역을 '직접' 클릭했을 때만 닫는다. 박스에 stopPropagation을 걸지 않으므로\n // (걸면 네이티브 mousedown 전파가 막혀 모달 안 Popover의 document 외부클릭 감지가 안 됨)\n // 박스 클릭은 target≠overlay라 여기서 닫히지 않는다.\n onMouseDown={closeOnOverlayClick ? (e) => { if (e.target === e.currentTarget) onClose(); } : undefined}\n >\n <div\n ref={boxRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === 'string' ? title : undefined}\n className={`flex ${minWClass} max-w-[calc(100vw-32px)] flex-col gap-spacing-1 overflow-hidden rounded-round-6 bg-modal-inline shadow-[0px_4px_6px_0px_rgba(0,0,0,0.08)] outline-none ring-1 ring-modal-outline ${SIZE_WIDTH[size]} ${className}`}\n style={boxMaxHStyle}\n {...props}\n >\n <Inner className=\"flex min-h-0 flex-1 flex-col gap-spacing-1\" {...innerProps}>\n {/* Header */}\n {showHeader && (\n <header ref={headerRef} className=\"flex shrink-0 items-center justify-between bg-modal-panel-bg py-spacing-6 pl-spacing-7 pr-spacing-6\">\n <h2 className=\"text-15 font-semibold text-font-icon-5\">{title}</h2>\n {showClose && (\n <Button variant=\"ghost\" size=\"32\" icon={X} onClick={onClose} aria-label=\"닫기\" showTooltip={false} />\n )}\n </header>\n )}\n\n {/* Body — maxHeight는 측정값(bodyMax)으로, header·footer·화면 여백을 뺀 값이라\n footer를 절대 가리지 않는다. 초과 시 ScrollArea 내부 스크롤. */}\n <div className=\"min-h-0 bg-modal-panel-bg\">\n <ScrollArea maxHeight={layout.bodyMax ?? bodyMaxHeight}>\n <div ref={contentRef} className={`${bodyPadding} text-14 text-font-icon-4 ${bodyClassName}`}>\n <ModalBodyMaxContext.Provider value={layout.bodyInner ?? null}>\n {children}\n </ModalBodyMaxContext.Provider>\n </div>\n </ScrollArea>\n </div>\n\n {/* Footer — 좌측 여백은 footerStart 내용 유형(footerStartType)에 따라 text=16/button=12 */}\n {showFooter && (footer || footerStart || confirmText) && (\n <footer\n ref={footerRef}\n className={`flex shrink-0 items-center bg-modal-panel-bg ${\n footerPadding ??\n `${footerStartType === 'button' ? 'pl-spacing-6' : 'pl-spacing-7'} pr-spacing-6 py-spacing-6`\n } ${footerFullWidth ? '' : 'justify-between'}`}\n >\n {!footerFullWidth && <div className=\"min-w-0 flex-1 text-14 text-font-icon-5\">{footerStart}</div>}\n {footer ?? defaultFooterEnd}\n </footer>\n )}\n </Inner>\n </div>\n </div>,\n document.body,\n );\n}\n\n// ConfirmAlertBody — Alert/Confirm 전용 고정 본문 슬롯.\n// 헤더 없이 본문 안에 [타이틀 → 설명 → 2뎁스 상세(회색 박스) → 체크박스(Confirm 전용)] 순으로 배치한다.\n// 슬롯 종류는 고정이며 텍스트만 자유롭게 바꾼다. 비워둔 슬롯은 렌더하지 않는다.\nfunction ConfirmAlertBody({\n title,\n description,\n descriptionDetail,\n checkbox,\n}: {\n title?: ReactNode;\n description?: ReactNode;\n descriptionDetail?: ReactNode;\n checkbox?: ReactNode;\n}) {\n const hasContent = description != null || descriptionDetail != null || checkbox != null;\n return (\n <div className=\"flex flex-col gap-spacing-6\">\n {title != null && (\n <h2 className=\"text-20 font-semibold text-font-icon-5\">{title}</h2>\n )}\n {hasContent && (\n <div className=\"flex flex-col gap-spacing-5\">\n {description != null && (\n <div className=\"whitespace-pre-line text-14 text-font-icon-5\">{description}</div>\n )}\n {descriptionDetail != null && (\n <div className=\"whitespace-pre-line rounded-round-4 bg-modal-description-bg p-spacing-6 text-14 text-font-icon-5\">\n {descriptionDetail}\n </div>\n )}\n {checkbox}\n </div>\n )}\n </div>\n );\n}\n\n// AlertModal — 확인 버튼 하나, 단순 알림. 헤더 없이 본문 고정 슬롯(타이틀/설명/2뎁스 상세).\nexport interface AlertModalProps extends ModalProps {\n /** 설명글(여러 줄은 \\n) */\n description?: ReactNode;\n /** 2뎁스 상세 설명(회색 박스) */\n descriptionDetail?: ReactNode;\n}\n\nexport function AlertModal({\n title,\n description,\n descriptionDetail,\n confirmText = '확인',\n onConfirm,\n onClose,\n size = 'sm', // Alert/Confirm 기본 360px\n placement = 'center', // 알럿은 중앙 기본(필요 시 override)\n ...props\n}: AlertModalProps) {\n return (\n <Modal\n {...props}\n title={title}\n size={size}\n placement={placement}\n onClose={onClose}\n showHeader={false}\n showCancel={false}\n footerFullWidth\n bodyPadding=\"p-spacing-8\"\n footerPadding=\"px-spacing-8 py-spacing-7\"\n confirmText={confirmText}\n onConfirm={onConfirm ?? onClose}\n >\n <ConfirmAlertBody title={title} description={description} descriptionDetail={descriptionDetail} />\n </Modal>\n );\n}\n\n// ConfirmModal — 취소/확인, 의사결정 요구. 헤더 없이 본문 고정 슬롯(타이틀/설명/2뎁스 상세/체크박스).\n// 체크박스는 삭제 등 위험 액션의 재확인 요소 — checkboxLabel을 주면 본문에 노출되고,\n// requireCheck(기본 true)면 체크해야 확인 버튼이 활성화된다.\nexport interface ConfirmModalProps extends ModalProps {\n /** 설명글(여러 줄은 \\n) */\n description?: ReactNode;\n /** 2뎁스 상세 설명(회색 박스) */\n descriptionDetail?: ReactNode;\n /** 주면 본문에 재확인 체크박스 노출 */\n checkboxLabel?: ReactNode;\n /** controlled 체크 상태(미지정 시 내부 상태로 동작) */\n checked?: boolean;\n /** (checked, event) — 체크 변경 콜백 */\n onCheckChange?: (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void;\n /** checkboxLabel 있을 때 체크해야 확인 활성화 */\n requireCheck?: boolean;\n}\n\nexport function ConfirmModal({\n title,\n description,\n descriptionDetail,\n checkboxLabel,\n checked,\n onCheckChange,\n requireCheck = true,\n confirmText = '확인',\n cancelText = '취소',\n onConfirm,\n onCancel,\n onClose,\n confirmVariant = 'fill',\n confirmDisabled = false,\n open,\n size = 'sm', // Alert/Confirm 기본 360px\n placement = 'center', // 컨펌은 중앙 기본(필요 시 override)\n ...props\n}: ConfirmModalProps) {\n const hasCheck = checkboxLabel != null;\n const isControlled = checked !== undefined;\n const [internalChecked, setInternalChecked] = useState(false);\n const isChecked = isControlled ? checked : internalChecked;\n\n // 열림 상태가 바뀌는 순간(렌더 중) 내부 체크 상태 초기화 — 닫았다 다시 열면 해제된 상태로 시작.\n // effect 대신 prop 변화 감지 패턴을 써서 cascading render를 피한다.\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (open && !isControlled) setInternalChecked(false);\n }\n\n const handleCheck = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternalChecked(e.target.checked);\n onCheckChange?.(e.target.checked, e);\n };\n\n const gated = hasCheck && requireCheck && !isChecked;\n\n return (\n <Modal\n {...props}\n title={title}\n open={open}\n size={size}\n placement={placement}\n onClose={onClose}\n showHeader={false}\n showCancel\n footerFullWidth\n bodyPadding=\"p-spacing-8\"\n footerPadding=\"px-spacing-8 py-spacing-7\"\n confirmText={confirmText}\n cancelText={cancelText}\n onConfirm={onConfirm}\n onCancel={onCancel ?? onClose}\n confirmVariant={confirmVariant}\n confirmDisabled={confirmDisabled || gated}\n >\n <ConfirmAlertBody\n title={title}\n description={description}\n descriptionDetail={descriptionDetail}\n checkbox={\n hasCheck ? (\n <Checkbox checked={isChecked} onChange={handleCheck} label={checkboxLabel} />\n ) : null\n }\n />\n </Modal>\n );\n}\n\n// FormModal — 취소/저장(또는 등록), form + 유효성 검사 연동\nexport interface FormModalProps extends ModalProps {\n /** 저장/등록 등 목적별 라벨 */\n submitText?: ReactNode;\n submitDisabled?: boolean;\n loading?: boolean;\n}\n\nexport function FormModal({\n submitText = '저장',\n cancelText = '취소',\n onSubmit,\n onCancel,\n onClose,\n submitDisabled = false,\n loading = false,\n ...props\n}: FormModalProps) {\n return (\n <Modal\n {...props}\n onClose={onClose}\n onSubmit={onSubmit}\n showCancel\n confirmText={submitText}\n cancelText={cancelText}\n onCancel={onCancel ?? onClose}\n confirmDisabled={submitDisabled}\n confirmLoading={loading}\n />\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","// NoticeWritingTemplate — 안내문 작성 템플릿 (Figma \"Notice Writing Template\", state=site/email/sms)\n// 채널 탭(채용사이트/이메일/SMS 안내문) + 제목 입력 + 첨부파일 + 본문 에디터를 한 덩어리로 묶은\n// 완전 옵션형 템플릿(규칙 4 — Tabs·Input·FileUploadButton·Editor 조립).\n// - site/email : 제목 Input + [첨부파일] FileUploadButton(업로드 메뉴 팝오버) + 풀 툴바 Editor(머지필드 포함)\n// - sms : 바이트 안내 문구 + 축약 툴바 Editor(undo/redo·머지필드만, SMS 평문)\n// 채널·제목·본문·첨부는 내부 상태로 동작하며(채널만 controlled 지원), 색·간격은 하위 컴포넌트 토큰을 따른다.\n// 작성 내용은 onChange 스냅샷으로 반출한다(2026-07-07 감사 — 값 반출 계약 없이는 저장/발송이 불가능했음).\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef } from 'react';\nimport { Paperclip } from 'lucide-react';\nimport { Tabs } from './Tabs';\nimport { Input } from './Input';\nimport { FileUploadButton } from './FileUploadButton';\nimport { Editor } from './Editor';\n\n// 채널 값 — 'site' | 'email' | 'sms'\ntype NoticeChannel = 'site' | 'email' | 'sms';\n\n// 채널 정의 — 탭 라벨과 제목 placeholder. sms는 제목 없이 안내 문구를 노출한다.\nconst CHANNELS: { value: NoticeChannel; label: string; titlePlaceholder?: string }[] = [\n { value: 'site', label: '채용사이트 안내문', titlePlaceholder: '채용 사이트안내문 제목을 입력하세요' },\n { value: 'email', label: '이메일 안내문', titlePlaceholder: '이메일 안내문 제목을 입력하세요' },\n { value: 'sms', label: 'SMS 안내문' },\n];\n\nconst SMS_INFO = '90byte를 초과(한글 45자)하면 멀티 문자로 전환되며, 발송 비용이 변경될 수 있습니다.';\n// SMS는 평문이라 서식 없이 실행취소/다시실행 + 머지필드만 노출한다.\nconst SMS_TOOLBAR = ['undo', 'redo', 'mergefield'];\n\n// 첨부 항목 — 표시용 name/size + 원본 File(.file — 소비자가 실제 업로드에 사용)\ninterface NoticeAttachment {\n name: string;\n size: number; // MB(소수 1자리)\n id: string;\n file: File;\n}\n\n// onChange 반출 스냅샷 — { channel, titles: {site,email,sms}, bodies: {site,email,sms},\n// attachments: {site,email,sms} } (attachments 항목에 원본 File은 .file)\ninterface NoticeWritingValues {\n channel: NoticeChannel;\n titles: Record<NoticeChannel, string>;\n bodies: Record<NoticeChannel, string>;\n attachments: Record<NoticeChannel, NoticeAttachment[]>;\n}\n\ninterface NoticeWritingTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n channel?: NoticeChannel; // controlled 채널값('site'|'email'|'sms')\n defaultChannel?: NoticeChannel; // uncontrolled 초기 채널\n onChannelChange?: (channel: NoticeChannel) => void;\n onChange?: (values: NoticeWritingValues) => void; // 작성 내용이 바뀔 때마다 전체 스냅샷 반출\n showTabs?: boolean; // 채널 탭 노출 여부(false면 단일 채널 모드)\n tabVariant?: 'fill' | 'hug'; // 탭 너비 타입: 'fill'(균등 분할, 기본) | 'hug'(내용 폭)\n enabledChannels?: NoticeChannel[]; // 활성 채널 목록(['site','email','sms']) — 미지정 시 전체 활성. 밖의 탭은 disabled\n mergeFields?: (string | { label: string; value: string })[]; // Editor 머지필드 목록\n defaultBodies?: Partial<Record<NoticeChannel, string>>; // 채널별 초기 본문 HTML — { site?, email?, sms? }\n showAttach?: boolean; // 첨부파일 버튼 노출 여부(false면 제목 입력만 전체 폭)\n maxAttachments?: number; // 첨부 최대 개수\n attachGuide?: string; // 업로드 메뉴 안내 문구\n attachAccept?: string; // <input accept> 필터(예: '.pdf,.doc') — guide와 직접 일치시킬 것\n editorMinHeight?: number | string; // 본문 최소 높이(px)\n editorMaxHeight?: number | string; // 지정 시 본문 세로 스크롤(ScrollArea)\n editorReadOnly?: boolean; // 본문 편집 비활성(툴바 숨김·읽기 전용)\n editorPlaceholder?: string; // 본문 빈 안내 문구(Editor 기본값 사용 시 생략)\n}\n\nconst emptyByChannel = (): Record<NoticeChannel, NoticeAttachment[]> => ({ site: [], email: [], sms: [] });\n\nexport function NoticeWritingTemplate({\n channel: channelProp,\n defaultChannel = 'site',\n onChannelChange,\n onChange,\n showTabs = true,\n tabVariant = 'fill',\n enabledChannels,\n mergeFields = [],\n defaultBodies,\n showAttach = true,\n maxAttachments = 5,\n attachGuide = '파일을 첨부하세요 (파일당 최대 10MB).',\n attachAccept,\n editorMinHeight = 360,\n editorMaxHeight,\n editorReadOnly = false,\n editorPlaceholder,\n className = '',\n ...props\n}: NoticeWritingTemplateProps) {\n const controlled = channelProp !== undefined;\n const [internal, setInternal] = useState<NoticeChannel>(defaultChannel);\n const isEnabled = (v: NoticeChannel) => !enabledChannels || enabledChannels.includes(v);\n const rawChannel = controlled ? channelProp : internal;\n // 현재 채널이 비활성이면 첫 활성 채널로 표시 폴백(파생값 — 다시 활성화되면 원래 채널로 복귀)\n const channel = isEnabled(rawChannel)\n ? rawChannel\n : (CHANNELS.find((c) => isEnabled(c.value))?.value ?? rawChannel);\n const setChannel = (v: NoticeChannel) => {\n if (!controlled) setInternal(v);\n onChannelChange?.(v);\n };\n\n // 채널별 제목·본문·첨부는 내부 상태로 유지(채널 전환 시 각자 보존).\n // 변경 시마다 onChange로 전체 스냅샷을 반출한다 — 소비자는 이 값으로 저장/발송한다.\n const [titles, setTitles] = useState<Record<NoticeChannel, string>>({ site: '', email: '', sms: '' });\n const [bodies, setBodies] = useState<Record<NoticeChannel, string>>(() => ({ site: '', email: '', sms: '', ...(defaultBodies || {}) }));\n const [files, setFiles] = useState<Record<NoticeChannel, NoticeAttachment[]>>(emptyByChannel);\n\n const emitChange = (patch: Partial<NoticeWritingValues>) =>\n onChange?.({ channel, titles, bodies, attachments: files, ...patch });\n\n const current = CHANNELS.find((c) => c.value === channel) ?? CHANNELS[0];\n const isSms = channel === 'sms';\n\n const setTitle = (value: string) => {\n const next = { ...titles, [channel]: value };\n setTitles(next);\n emitChange({ titles: next });\n };\n const setBody = (html: string) => {\n const next = { ...bodies, [channel]: html };\n setBodies(next);\n emitChange({ bodies: next });\n };\n const addFiles = (fileList: FileList | File[]) => {\n const next = {\n ...files,\n [channel]: [\n ...files[channel],\n ...Array.from(fileList).map((f) => ({\n name: f.name,\n size: Math.max(0.1, Math.round((f.size / 1048576) * 10) / 10), // MB(소수 1자리)\n id: `${f.name}-${f.size}-${Date.now()}`,\n file: f, // 원본 File — 소비자가 실제 업로드에 사용(표시용 name/size만 남기면 발송 불가)\n })),\n ].slice(0, maxAttachments),\n };\n setFiles(next);\n emitChange({ attachments: next });\n };\n const deleteFile = (_file: unknown, index: number) => {\n const next = { ...files, [channel]: files[channel].filter((_, i) => i !== index) };\n setFiles(next);\n emitChange({ attachments: next });\n };\n\n return (\n <div className={`flex w-full flex-col gap-spacing-6 ${className}`} {...props}>\n {showTabs && (\n <Tabs\n items={CHANNELS.map(({ value, label }) => ({ value, label, disabled: !isEnabled(value) }))}\n value={channel}\n onChange={setChannel}\n variant={tabVariant}\n />\n )}\n\n {isSms ? (\n <p className=\"text-12 text-font-icon-4\">{SMS_INFO}</p>\n ) : (\n <div className=\"flex items-start gap-spacing-6\">\n <div className=\"min-w-0 flex-1\">\n <Input\n width=\"100%\"\n placeholder={current.titlePlaceholder}\n value={titles[channel]}\n onChange={(e) => setTitle(e.target.value)}\n />\n </div>\n {showAttach && (\n <FileUploadButton\n triggerText=\"첨부파일\"\n buttonProps={{ leftIcon: Paperclip }}\n placement=\"auto-right\"\n menuWidth={320}\n files={files[channel]}\n guide={attachGuide}\n accept={attachAccept}\n maxCount={maxAttachments}\n onAdd={addFiles}\n onDelete={deleteFile}\n />\n )}\n </div>\n )}\n\n <Editor\n value={bodies[channel]}\n onChange={setBody}\n mergeFields={mergeFields}\n toolbar={isSms ? SMS_TOOLBAR : undefined}\n showSource={!isSms}\n minHeight={editorMinHeight}\n maxHeight={editorMaxHeight}\n readOnly={editorReadOnly}\n {...(editorPlaceholder ? { placeholder: editorPlaceholder } : {})}\n />\n </div>\n );\n}\n","// Pagination — 페이지네이션 (Figma node 7263:1752839)\n// 구성: [총 N개] · [« ‹ 번호들 › »] · [페이지 행 Select]\n// - « / »(chevrons): 처음/마지막 페이지, ‹ / ›(chevron): 이전/다음 페이지\n// - 현재 페이지: state=\"selected\" (ghost select 토큰 — 회색 원형 배경 + 흰 텍스트)\n// - 경계에서 이동 아이콘은 state=\"disabled\" (ghost disabled 토큰으로 흐림) 처리하고 클릭을 막는다\n// - 페이지 번호는 maxButtons(기본 10) 단위 블록으로 노출(슬라이딩 X, 번호 5개/10개 등)\n// - 색·간격·라운드는 모두 토큰 사용, 인라인 style 금지\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { iconTokens } from '../tokens';\nimport { Select } from './Select';\nimport { useHoverTooltip } from './useHoverTooltip';\n\nconst DEFAULT_PAGE_SIZE_OPTIONS = [5, 10, 20, 50];\n\n// 공통 버튼 박스 — 최소 24px 정사각(min-w/min-h) + 좌우 패딩.\n// 라운드는 모든 상태에서 round-10(24px)로 통일 → 1~2자리는 원형, 3자리(100~)부터\n// 고정폭이 아니라 내용폭만큼 좌우로 늘어난다.\nconst BTN_BASE =\n 'inline-flex items-center justify-center min-w-[24px] min-h-[24px] px-spacing-3 ' +\n 'rounded-round-10 font-pretendard font-normal text-14 transition-colors select-none focus:outline-none';\n\n// hover = ghost-hover-bg, pressed(active) = 투명. 색은 기존 ghost 버튼 시멘틱 토큰 재사용.\nconst BTN_INTERACTIVE =\n 'cursor-pointer hover:bg-button-ghost-hover-bg active:bg-transparent ' +\n 'focus-visible:bg-button-ghost-hover-bg'; // 포커스=호버(2026-07-16)\n\n// 현재 페이지가 속한 '블록'의 번호 목록 계산.\n// maxButtons 단위로 페이지를 고정 그룹핑한다(슬라이딩 X) — 같은 블록 안의 번호를\n// 눌러도 윈도우가 움직이지 않고 selected만 바뀐다. (예: max=5, 총 10 → [1-5],[6-10])\nfunction getPageWindow(current: number, total: number, maxButtons: number) {\n const block = Math.floor((current - 1) / maxButtons);\n const start = block * maxButtons + 1;\n const end = Math.min(start + maxButtons - 1, total);\n return Array.from({ length: end - start + 1 }, (_, i) => start + i);\n}\n\n// 버튼 상태별 스타일 — 배경 라운드는 모든 상태에서 BTN_BASE의 round-10(24px)\n// default : hover 시 ghost-hover-bg, pressed 시 투명 (텍스트색은 type별로 분기)\n// selected: 현재 페이지 — 회색 원형 + 흰 텍스트(고정)\n// disabled: 경계 아이콘 — 흐림 + 클릭 불가\nconst STATE_STYLES = {\n selected: 'cursor-default bg-button-ghost-selected-bg text-button-ghost-selected-text',\n disabled: 'cursor-not-allowed text-button-ghost-disabled-fg',\n};\n\n// default 상태 텍스트색 — 번호는 pagination 토큰(연함)에서 hover/pressed 시 default-fg로 진해지고,\n// 아이콘은 항상 default-fg(진함). (Figma: ghost/gray default text (pagination) = gray.300)\nconst DEFAULT_TEXT = {\n number: 'text-button-ghost-pagination-fg hover:text-button-ghost-default-fg active:text-button-ghost-default-fg focus-visible:text-button-ghost-default-fg',\n icon: 'text-button-ghost-default-fg',\n};\n\n// 페이지네이션 버튼 — number/icon 타입 × state(default/selected/disabled)\ninterface PaginationButtonProps {\n state?: 'default' | 'selected' | 'disabled';\n type?: 'number' | 'icon';\n icon?: LucideIcon;\n label?: string;\n children?: ReactNode;\n onClick?: () => void;\n}\n\nfunction PaginationButton({ state = 'default', type = 'number', icon: Icon, label, children, onClick }: PaginationButtonProps) {\n const interactive = state === 'default';\n const stateStyle =\n state === 'default' ? `${DEFAULT_TEXT[type]} ${BTN_INTERACTIVE}` : STATE_STYLES[state];\n // 아이콘 버튼(첫/이전/다음/마지막)은 명칭이 안 보이므로 hover 툴팁으로 알려준다(base Button과 동일 패턴)\n const hoverTip = useHoverTooltip(type === 'icon' ? label : null);\n return (\n <>\n <button\n type=\"button\"\n className={`${BTN_BASE} ${stateStyle}`}\n aria-current={state === 'selected' ? 'page' : undefined}\n aria-label={type === 'icon' ? label : undefined}\n disabled={state === 'disabled'}\n onClick={interactive ? onClick : undefined}\n onMouseEnter={hoverTip.onMouseEnter}\n onMouseLeave={hoverTip.onMouseLeave}\n >\n {type === 'icon' && Icon ? (\n <Icon size={iconTokens.size} strokeWidth={iconTokens.strokeWidth} />\n ) : (\n children\n )}\n </button>\n {hoverTip.tooltip}\n </>\n );\n}\n\nexport interface PaginationProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n /** 현재 페이지(1-base) — controlled */\n page?: number;\n /** uncontrolled 초기값 */\n defaultPage?: number;\n /** (page: number) => void */\n onChange?: (page: number) => void;\n /** 총 항목 수 — totalPages 미지정 시 페이지 수 계산에 사용 */\n totalCount?: number;\n /** 총 페이지 수(지정 시 totalCount/pageSize보다 우선) */\n totalPages?: number;\n /** 페이지당 행 수 — controlled */\n pageSize?: number;\n /** 페이지당 행 수 초기값(옵션 5/10/20/50 중 기본 10) */\n defaultPageSize?: number;\n /** (size: number) => void */\n onPageSizeChange?: (size: number) => void;\n pageSizeOptions?: number[];\n /** 번호 노출 개수(윈도우) — 보통 10 또는 5 */\n maxButtons?: number;\n /** 좌측 '총 N개' 표시 */\n showTotal?: boolean;\n /** 우측 '페이지 행' Select 표시 */\n showPageSize?: boolean;\n}\n\nexport function Pagination({\n page,\n defaultPage = 1,\n onChange,\n totalCount = 0,\n totalPages,\n pageSize,\n defaultPageSize = 10,\n onPageSizeChange,\n pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,\n maxButtons = 10,\n showTotal = true,\n showPageSize = true,\n className = '',\n ...props\n}: PaginationProps) {\n const isPageControlled = page !== undefined;\n const [internalPage, setInternalPage] = useState(defaultPage);\n const current = isPageControlled ? (page as number) : internalPage;\n\n const isSizeControlled = pageSize !== undefined;\n const [internalSize, setInternalSize] = useState(defaultPageSize);\n const size = isSizeControlled ? (pageSize as number) : internalSize;\n\n // 총 페이지 수 — totalPages가 있으면 우선, 없으면 totalCount/페이지크기로 계산.\n const getTotalPages = (s: number) =>\n totalPages != null ? totalPages : Math.max(1, Math.ceil(totalCount / s));\n const computedTotalPages = getTotalPages(size);\n\n const goTo = (next: number) => {\n const clamped = Math.min(Math.max(next, 1), computedTotalPages);\n if (clamped === current) return;\n if (!isPageControlled) setInternalPage(clamped);\n onChange?.(clamped);\n };\n\n const handleSizeChange = (e: { target: { value: string | number } }) => {\n const next = Number(e.target.value);\n if (!isSizeControlled) setInternalSize(next);\n onPageSizeChange?.(next);\n // 페이지 수가 줄면 현재 페이지가 범위를 벗어날 수 있어 보정\n const nextTotal = getTotalPages(next);\n if (current > nextTotal) goTo(nextTotal);\n };\n\n const pages = getPageWindow(current, computedTotalPages, maxButtons);\n const atFirst = current <= 1;\n const atLast = current >= computedTotalPages;\n\n // totalPages를 외부에서 고정했는데 onPageSizeChange가 없으면 '페이지 행' Select는\n // 바꿔도 아무것도 달라지지 않는 무반응 UI가 된다(기본값 조합 충돌) → 자동 숨김.\n const pageSizeVisible = showPageSize && !(totalPages != null && !onPageSizeChange);\n\n const sizeSelectOptions = pageSizeOptions.map((n) => ({ value: n, label: `${n}개` }));\n\n return (\n <div\n // min-h-[32px]: '페이지 행' Select(32px)가 숨겨져도(showPageSize=false) 행 높이가\n // 번호 버튼(24px) 기준으로 줄지 않도록 고정한다.\n className={`grid min-h-[32px] grid-cols-3 items-center gap-spacing-9 ${className}`}\n {...props}\n >\n {/* 좌: 총 개수 */}\n <div className=\"justify-self-start\">\n {showTotal && (\n <p className=\"text-14 text-font-icon-5\">총 {totalCount}개</p>\n )}\n </div>\n\n {/* 중: 페이지 이동 컨트롤 */}\n <div className=\"flex items-center justify-center gap-spacing-3 justify-self-center\">\n <PaginationButton type=\"icon\" icon={ChevronsLeft} label=\"첫 페이지\" state={atFirst ? 'disabled' : 'default'} onClick={() => goTo(1)} />\n <PaginationButton type=\"icon\" icon={ChevronLeft} label=\"이전 페이지\" state={atFirst ? 'disabled' : 'default'} onClick={() => goTo(current - 1)} />\n {pages.map((p) => (\n <PaginationButton key={p} state={p === current ? 'selected' : 'default'} onClick={() => goTo(p)}>\n {p}\n </PaginationButton>\n ))}\n <PaginationButton type=\"icon\" icon={ChevronRight} label=\"다음 페이지\" state={atLast ? 'disabled' : 'default'} onClick={() => goTo(current + 1)} />\n <PaginationButton type=\"icon\" icon={ChevronsRight} label=\"마지막 페이지\" state={atLast ? 'disabled' : 'default'} onClick={() => goTo(computedTotalPages)} />\n </div>\n\n {/* 우: 페이지 행 선택 */}\n <div className=\"flex items-center gap-spacing-5 justify-self-end\">\n {pageSizeVisible && (\n <>\n <span className=\"whitespace-nowrap text-14 text-font-icon-5\">페이지 행</span>\n <Select\n variant=\"text\"\n size=\"24\"\n placement=\"auto-right\"\n menuWidth={80}\n // 옵션 값은 숫자(pageSizeOptions) 그대로 사용 — Select 타입 계약(string)과 다르지만\n // 런타임 비교(===)는 양쪽 다 숫자라 기존 동작과 동일(타입 단언만, 값 변환 없음)\n options={sizeSelectOptions as unknown as { value: string; label: string }[]}\n value={size as unknown as string}\n onChange={handleSizeChange}\n />\n </>\n )}\n </div>\n </div>\n );\n}\n","// ToolBar — 플로팅 액션 툴바 (Figma tool bar, node 8389:126993) — 공통 컴포넌트\n// 체크박스 선택 등으로 나타나 선택 항목에 대한 액션(셀렉트·버튼)을 담는 얕은 바.\n// - 배경: 위(white)→아래(gray.50) 그라데이션 + gray-900 알파 50 라인(배경 투영) + 그림자\n// - 내용은 children 슬롯 — Select·Button(ghost) 등 DS 컴포넌트를 그대로 배치\n// - 항목 사이 구분선은 ToolBarDivider(16px 세로 strong 라인)\n// 색은 toolbar-* 시멘틱 토큰만 사용. 표시/숨김·위치(absolute 등)는 사용처가 담당한다.\nimport type { ComponentPropsWithoutRef } from 'react';\n\ntype ToolBarProps = ComponentPropsWithoutRef<'div'>;\n\nexport function ToolBar({ children, className = '', ...props }: ToolBarProps) {\n return (\n <div\n role=\"toolbar\"\n // 라인은 outline(바깥 얹기) — 알파 컬러라 border(안쪽)면 자기 그라데이션 배경과 합성돼 뒤 배경이 안 비친다\n className={`inline-flex items-center gap-spacing-4 rounded-round-5 outline outline-1 outline-toolbar-line bg-gradient-to-b from-toolbar-bg-top to-toolbar-bg-bottom p-spacing-5-5 shadow-[0px_2px_5px_0px_rgba(0,0,0,0.12)] ${className}`}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// 툴바 항목 구분선 — 16px 세로 라인(divider strong 토큰)\nexport function ToolBarDivider() {\n return <span aria-hidden className=\"h-spacing-7 w-px shrink-0 bg-divider-strong\" />;\n}\n","// SegmentedTabs — 세그먼트 탭 (Figma segmented tab button 8241:88277 / Segmented Tabs 8241:88288)\n// 회색 트랙(segmented-tab-bg) 안에서 흰색 배경 pill이 선택 탭으로 '슬라이드'해 이동한다.\n// - 탭은 items로 필요한 만큼(2·3·4·5…) 추가 — 모든 탭이 균등 폭(flex-1)이라야 pill 이동 계산이 맞다.\n// - pill = 트랙 안 absolute, 폭 100/N%, translateX(선택 index × 100%) + transition-transform로 슬라이드.\n// - 색은 segmented-* 시멘틱 토큰만. controlled(value)/uncontrolled(defaultValue) 단일 선택.\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { TruncatingText } from './TruncatingText';\n\n// 탭 항목 — [{ value, label, disabled? }]. 값 타입 V는 호출부 리터럴 유니언('points'|'fitness' 등)이\n// 그대로 흐르도록 제네릭 — onChange가 좁은 핸들러(setMode 등)와 호환된다.\ninterface SegmentedTabItem<V extends string | number> {\n value: V;\n label: ReactNode;\n disabled?: boolean;\n}\n\ninterface SegmentedTabsProps<V extends string | number>\n extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: SegmentedTabItem<V>[]; // [{ value, label, disabled? }] — 2개 이상, 개수 가변\n value?: V | null; // controlled 선택값(null=미선택 허용)\n defaultValue?: V; // uncontrolled 초기 선택값\n onChange?: (value: V) => void; // (value) => void\n width?: number | string; // 컨테이너 너비 — 숫자(px)/CSS 길이. 미지정 시 부모 전체 폭(w-full)\n}\n\nexport function SegmentedTabs<V extends string | number>({\n items = [], // [{ value, label, disabled? }] — 2개 이상, 개수 가변\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n width, // 컨테이너 너비 — 숫자(px)/CSS 길이. 미지정 시 부모 전체 폭(w-full)\n className = '',\n ...props\n}: SegmentedTabsProps<V>) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState<V | undefined>(defaultValue);\n // 선택값 폴백은 렌더 파생 — items가 비어 도착해도(비동기) 첫 탭이 선택된다.\n const selected = isControlled ? value : (internal ?? items[0]?.value);\n\n const n = items.length;\n // pill 위치 index — 선택값이 목록에 없으면 0(첫 탭)으로 폴백해 pill이 항상 유효 위치.\n const selIdx = Math.max(0, items.findIndex((it) => it.value === selected));\n\n const select = (val: V) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n // 좌우 방향키로 이동(비활성 탭은 건너뜀)\n const move = (dir: number) => {\n if (n === 0) return;\n let i = selIdx;\n for (let step = 0; step < n; step += 1) {\n i = (i + dir + n) % n;\n if (!items[i].disabled) {\n select(items[i].value);\n return;\n }\n }\n };\n\n const widthStyle = width == null ? undefined : typeof width === 'number' ? `${width}px` : width;\n\n return (\n <div\n role=\"tablist\"\n style={{ width: widthStyle }}\n className={`flex ${width == null ? 'w-full' : ''} rounded-round-4 bg-segmented-tab-bg p-spacing-2 ${className}`}\n {...props}\n >\n <div className=\"relative flex min-w-0 flex-1\">\n {/* 슬라이딩 배경 pill — 트랙 안에서 선택 탭으로 이동. 탭 균등 폭 전제(width 100/N%). */}\n {n > 0 && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 left-0 rounded-round-3 bg-segmented-select-bg ring-1 ring-segmented-select-line shadow-[0px_2px_4px_0px_rgba(0,0,0,0.12)] transition-transform duration-200 ease-out\"\n style={{ width: `${100 / n}%`, transform: `translateX(${selIdx * 100}%)` }}\n />\n )}\n {items.map((it) => {\n const isSel = it.value === selected;\n return (\n <button\n key={it.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isSel}\n disabled={it.disabled}\n tabIndex={it.disabled ? -1 : 0}\n onClick={() => !it.disabled && select(it.value)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n move(1);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n move(-1);\n }\n }}\n className={`relative z-[1] flex min-h-[24px] min-w-0 flex-1 items-center justify-center rounded-round-3 px-spacing-5-5 font-pretendard text-14 leading-24 transition-colors focus:outline-none ${\n it.disabled\n ? 'cursor-not-allowed text-segmented-disabled-text'\n : isSel\n ? /* 선택 탭이 Tab 포커스를 받는다(roving tabindex) — 포커스=호버(파란 텍스트)로 표시,\n 없으면 아웃라인 제거와 겹쳐 Tab이 건너뛰는 것처럼 보인다(2026-07-16) */\n 'cursor-pointer text-segmented-select-text focus-visible:text-segmented-hover-text'\n : 'cursor-pointer text-segmented-unselect-text hover:text-segmented-hover-text focus-visible:text-segmented-hover-text' /* 포커스=호버 */\n }`}\n >\n <TruncatingText as=\"span\" className=\"min-w-0\">\n {it.label}\n </TruncatingText>\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","// ScreeningIndividualSettingModal — 복수 조건(지정) 개별 값 설정 모달 (Figma 8535:10535)\n// ⚠️ 내부용(ScreeningConditionCard 전용) — 지정된 항목(자격증 등)마다 가점/감점(점수) 또는\n// 적합/부적합을 설정하고, 하단에서 적용 함수(SUM 등)를 고른다. 확인 시에만 반영.\n//\n// 구조(모달 템플릿 규칙 — Modal 컴포넌트 조립, 셸 재구현 금지):\n// 바디: [모드 세그먼트 탭(가점/감점|적합/부적합) ── 일괄 적용(셀렉트[+점수]+버튼)]\n// [항목 테이블 — 항목 / 가/감점(텍스트 셀렉트) / 점수(가점·감점 모드만)]\n// 푸터: 취소·확인 (함수 셀렉트는 2026-07-16 제거 — 그룹핑·함수는 수식 영역 툴바가 담당)\n// 저장 페이로드: { type:'individual', mode:'points'|'fitness',\n// items: { [항목값]: { type:'plus'|'minus', points } | { type:'fit'|'unfit' } } }\nimport { useState } from 'react';\nimport { Modal } from './Modal';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { Select } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Table } from './Table';\nimport type { TableColumn, TableRowKey } from './Table';\nimport type { IndividualItemSetting, IndividualSetting, OptionItem } from './formulaFunctions';\n\n// 항목 draft — 가점/감점(points) 모드와 적합/부적합(fitness) 모드 분리 보관용\ntype PointsItem = Extract<IndividualItemSetting, { type: 'plus' | 'minus' }>;\ntype FitItem = Extract<IndividualItemSetting, { type: 'fit' | 'unfit' }>;\n\n// 점수 입력 필터 — 숫자만 허용(2026-07-16 지시)\nconst digitsOnly = (v: string) => v.replace(/[^0-9]/g, '');\n\nconst POINT_TYPES = [\n { value: 'plus', label: '가점' },\n { value: 'minus', label: '감점' },\n];\nconst FIT_TYPES = [\n { value: 'fit', label: '적합' },\n { value: 'unfit', label: '부적합' },\n];\nconst MODE_TABS = (\n fitnessOnly: boolean,\n): { value: 'points' | 'fitness'; label: string; disabled?: boolean }[] => [\n { value: 'points', label: '가점/감점', disabled: fitnessOnly },\n { value: 'fitness', label: '적합/부적합' },\n];\n\ninterface ScreeningIndividualSettingModalProps {\n items?: OptionItem[]; // [{ value, label }] — 지정된 항목(카드의 선택 항목, 옵션 순서)\n value?: IndividualSetting | null; // 저장된 값(위 페이로드) | null — 열 때 draft 시드\n fitnessOnly?: boolean; // 적합/부적합 전용 — IF 함수 안 조건은 가점/감점 불가(2026-07-16)\n onClose: () => void; // Modal 계약상 필수(딤·X·Esc 닫기) // 닫기(취소·X·딤·Esc)\n onConfirm?: (payload: IndividualSetting) => void; // 확인 시에만\n}\n\nexport function ScreeningIndividualSettingModal({\n items = [],\n value = null,\n fitnessOnly = false,\n onClose,\n onConfirm,\n}: ScreeningIndividualSettingModalProps) {\n const [mode, setMode] = useState<IndividualSetting['mode']>(\n fitnessOnly ? 'fitness' : (value?.mode ?? 'points'),\n );\n // 모드별 draft를 분리 보관 — 탭을 오가도 입력이 유지되고, 확인 시 활성 모드만 저장된다\n const seed = <T extends IndividualItemSetting>(m: IndividualSetting['mode'], defaults: T) => {\n const out: Record<string, T> = {};\n items.forEach((it) => {\n const saved = value?.mode === m ? value?.items?.[it.value] : null;\n out[it.value] = (saved as T | null | undefined) ?? defaults;\n });\n return out;\n };\n const [pointsDraft, setPointsDraft] = useState<Record<string, PointsItem>>(() =>\n seed('points', { type: 'plus', points: '' }),\n );\n const [fitDraft, setFitDraft] = useState<Record<string, FitItem>>(() => seed('fitness', { type: 'fit' }));\n // 일괄 적용 — 체크한 행에만 적용(2026-07-16 지시). 모드가 바뀌면 셀렉트 옵션이 달라지므로 초기화\n const [bulkType, setBulkType] = useState('');\n const [bulkPoints, setBulkPoints] = useState('');\n const [checkedIds, setCheckedIds] = useState<TableRowKey[]>([]); // 테이블 체크박스 — 일괄 적용 대상(행 id=항목 값 문자열)\n const changeMode = (m: IndividualSetting['mode']) => {\n setMode(m);\n setBulkType('');\n setBulkPoints('');\n };\n\n const isPoints = mode === 'points';\n // 일괄 적용 그룹은 행을 체크해야 활성화되고(2026-07-16 지시), 값까지 채워져야 적용 가능\n const hasChecked = checkedIds.length > 0;\n const bulkReady = (isPoints ? !!bulkType && !!bulkPoints.trim() : !!bulkType) && hasChecked;\n const applyBulk = () => {\n if (!bulkReady) return;\n const set = isPoints\n ? { type: bulkType as PointsItem['type'], points: bulkPoints.trim() }\n : { type: bulkType as FitItem['type'] };\n const patchChecked = <T extends IndividualItemSetting>(d: Record<string, T>) =>\n ({\n ...d,\n ...Object.fromEntries(checkedIds.map((id) => [id, { ...set }])),\n }) as Record<string, T>;\n if (isPoints) setPointsDraft(patchChecked);\n else setFitDraft(patchChecked);\n };\n\n const patchRow = (val: string, patch: Partial<PointsItem> | Partial<FitItem>) =>\n isPoints\n ? setPointsDraft((d) => ({ ...d, [val]: { ...d[val], ...patch } as PointsItem }))\n : setFitDraft((d) => ({ ...d, [val]: { ...d[val], ...patch } as FitItem }));\n\n // 가점/감점 모드는 모든 항목의 점수가 채워져야 확인 가능(적합/부적합은 기본값으로 항상 완성)\n const complete = isPoints\n ? items.every((it) => !!pointsDraft[it.value]?.points?.trim())\n : true;\n const payload = (): IndividualSetting => ({\n type: 'individual',\n mode,\n items: Object.fromEntries(\n items.map((it): [string, IndividualItemSetting] =>\n isPoints\n ? [it.value, { type: pointsDraft[it.value].type, points: pointsDraft[it.value].points?.trim() }]\n : [it.value, { type: fitDraft[it.value].type }],\n ),\n ),\n });\n\n const columns: TableColumn[] = [\n { key: 'label', label: '항목' },\n {\n key: 'type',\n label: '가/감점',\n width: 120,\n render: (row) => (\n <Select\n variant=\"text\"\n width=\"fill\" /* 셀 전체 폭 — 텍스트 왼쪽·chevron 오른쪽 끝 정렬 */\n options={isPoints ? POINT_TYPES : FIT_TYPES}\n value={(isPoints ? pointsDraft : fitDraft)[String(row.id)]?.type ?? ''}\n onChange={(e) => patchRow(String(row.id), { type: e.target.value as 'plus' | 'minus' | 'fit' | 'unfit' })}\n menuWidth={100}\n />\n ),\n },\n ...(isPoints\n ? ([\n {\n key: 'points',\n label: '점수',\n width: 120,\n render: (row) => (\n <span className=\"flex items-center gap-spacing-4\">\n <Input\n size=\"32\"\n width={70}\n value={pointsDraft[String(row.id)]?.points ?? ''}\n onChange={(e) => patchRow(String(row.id), { points: digitsOnly(e.target.value) })}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"입력\"\n />\n <span className=\"text-14 text-font-icon-5\">점</span>\n </span>\n ),\n },\n ] satisfies TableColumn[])\n : []),\n ];\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"개별설정\"\n size=\"lg\"\n confirmText=\"확인\"\n cancelText=\"취소\"\n confirmDisabled={!complete}\n onConfirm={() => onConfirm?.(payload())}\n >\n <div className=\"flex flex-col gap-spacing-6\">\n {/* 일괄 적용(좌) + 모드 탭(우) — 일괄은 표에서 체크한 행에만 같은 값을 채운다(2026-07-16 위치 교체) */}\n <div className=\"flex items-center justify-between gap-spacing-5\">\n <div className=\"flex items-center gap-spacing-4\">\n {isPoints ? (\n <>\n <Select\n width={90}\n options={POINT_TYPES}\n value={bulkType}\n onChange={(e) => setBulkType(e.target.value)}\n placeholder=\"가/감점\"\n disabled={!hasChecked}\n />\n <Input\n width={70}\n value={bulkPoints}\n onChange={(e) => setBulkPoints(digitsOnly(e.target.value))}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"입력\"\n disabled={!hasChecked}\n />\n <span className={`text-14 ${hasChecked ? 'text-font-icon-5' : 'text-font-icon-2'}`}>\n 점\n </span>\n </>\n ) : (\n <Select\n width={110}\n options={FIT_TYPES}\n value={bulkType}\n onChange={(e) => setBulkType(e.target.value)}\n placeholder=\"적합/부적합\"\n disabled={!hasChecked}\n />\n )}\n <Button variant=\"line\" size=\"32\" disabled={!bulkReady} onClick={applyBulk}>\n 일괄적용\n </Button>\n </div>\n <SegmentedTabs width={200} items={MODE_TABS(fitnessOnly)} value={mode} onChange={changeMode} />\n </div>\n {/* 항목 목록 — 페이지네이션 없이 늘어나는 표(규칙 18: bordered + fill + min-h-0) */}\n <Table\n bordered\n maxHeight=\"fill\"\n className=\"min-h-0\"\n columns={columns}\n rows={items.map((it) => ({ id: it.value, label: it.label }))}\n rowKey=\"id\"\n selectable\n selectedIds={checkedIds}\n onSelectChange={setCheckedIds}\n />\n </div>\n </Modal>\n );\n}\n","// 수식 함수 레지스트리 — ScreeningFormula(컴포넌트)와 ScreeningBuilderTemplate(함수 셀렉트)이 공유.\n// 컴포넌트 파일에 상수 export를 두면 react-refresh 에러가 나서 분리(chipStyles.js 관례).\n// 함수 → 계열 (Figma 함수 조건 색상표 8384:114813 — 계열별 고유 텍스트 컬러는 formula-* 토큰)\n\n// ── 수식 노드 모델 타입(2026-07-18 TS 전환) — 템플릿·수식·툴바가 공유하는 트리 구조 ──\nexport interface OptionItem {\n value: string;\n label: string;\n disabled?: boolean;\n}\nexport interface ConditionTab {\n value: string;\n label: string;\n disableOptions?: boolean; // 옵션 없이 저장 가능(미보유/비대상 등)\n multiSelect?: boolean; // 복수 조건(지정) 탭 — 다중 선택 Select\n placeholder?: string;\n searchPlaceholder?: string;\n bundleLabel?: string; // 수식 묶음 칩 표기(기본=탭 라벨)\n}\nexport interface ConditionMeta {\n tabs: ConditionTab[];\n optionsByTab: Record<string, OptionItem[]>;\n options: OptionItem[];\n}\nexport type IndividualItemSetting = { type: 'plus' | 'minus'; points?: string } | { type: 'fit' | 'unfit' };\nexport interface IndividualSetting {\n type: 'individual';\n mode: 'points' | 'fitness';\n items: Record<string, IndividualItemSetting>;\n fn?: string; // 과거 데이터 호환(함수 셀렉트 제거 전 저장분)\n}\nexport interface FormulaLeafNode {\n id: string;\n kind: 'leaf';\n criteria?: string | null; // 조건 카드 id\n value?: string | null; // 선택 옵션 값\n valueTab?: string | null;\n points?: string; // 부호 포함 문자열('-3' 등)\n scoreType?: 'plus' | 'minus' | 'fit' | 'unfit' | null;\n display?: 'full' | 'compact';\n items?: string[]; // 묶음 leaf(복수 조건) — 지정 항목 값 배열\n individual?: IndividualSetting | null; // 항목별 개별설정\n // 드롭 시점 스냅샷(카드 삭제 대비 폴백 — 표시는 catalog 라이브 우선)\n criteriaOptions?: OptionItem[];\n valueOptions?: OptionItem[];\n valueOptionsByCriteria?: Record<string, OptionItem[]>;\n conditionMetaByCriteria?: Record<string, ConditionMeta>;\n}\nexport interface FormulaGroupNode {\n id: string;\n kind: 'group';\n fn: string;\n children: FormulaNode[];\n trueScore?: string; // IF — 참일 때 점수(기본 '1')\n falseScore?: string; // IF — 거짓일 때 점수(기본 '0')\n capScore?: string; // CAPMAX/CAPMIN — 제한 점수\n compareOp?: string; // FITBYSCORE/UNFITBYSCORE — 비교 연산자(기본 '>=')\n compareScore?: string; // FITBYSCORE/UNFITBYSCORE — 비교값\n}\nexport type FormulaNode = FormulaLeafNode | FormulaGroupNode;\nexport interface FormulaCatalog {\n criteriaOptions: OptionItem[];\n valueOptionsByCriteria: Record<string, OptionItem[]>;\n conditionMetaByCriteria: Record<string, ConditionMeta>;\n}\n\nexport const FORMULA_FN_FAMILY: Record<string, string> = {\n AND: 'logical',\n OR: 'logical',\n IF: 'conditional',\n SUM: 'aggregate',\n MAX: 'aggregate',\n MIN: 'aggregate',\n COUNTIF: 'aggregate',\n CAPMAX: 'score-limit',\n CAPMIN: 'score-limit',\n FITBYSCORE: 'evaluation',\n UNFITBYSCORE: 'evaluation',\n};\n\n// 그룹핑에 쓰는 함수 목록 — 템플릿 함수 셀렉트·함수 변경 팝오버·자연어 함수 칩이 공용.\n// IF 포함(2026-07-15 지시 — 기존엔 leaf 전용으로 제외했었음)\nexport const FORMULA_GROUP_FUNCTIONS = Object.keys(FORMULA_FN_FAMILY);\n\n// ── 스크리닝 키보드 체인 규약(2026-07-15) — 카드·수식·드롭 스트립 공용 ──\n// 정거장(data-formula-chain)을 문서 순서로 잇는다: 조건 카드들 → 수식들 → 드롭 스트립.\n// 한 단계가 완료되면 다음 정거장으로 포커스를 넘긴다(DOM 순서 = 체인 순서).\nexport function focusNextChainStop(currentEl: HTMLElement | null) {\n if (!currentEl) return;\n // 패널 언마운트가 끝난 다음 틱에 이동(포커스 경합 방지)\n setTimeout(() => {\n const all = Array.from(document.querySelectorAll<HTMLElement>('[data-formula-chain]'));\n const next = all[all.indexOf(currentEl) + 1];\n if (!next) return;\n const target = next.matches('[tabindex]') ? next : next.querySelector<HTMLElement>('[tabindex]');\n if (!target) return;\n target.focus();\n // programmatic focus는 :focus-visible에 안 잡힐 수 있어 데이터 속성으로 링을 강제 표시,\n // 포커스가 떠나면 해제(체인 이동 가시성 유지 — 2026-07-16 개정과 세트)\n target.setAttribute('data-chain-focus', '');\n target.addEventListener('blur', () => target.removeAttribute('data-chain-focus'), { once: true });\n }, 0);\n}\n// 체인 트리거 공용 keydown(규칙 20 ⑵) — 포커스 상태에서 Enter/Space/Tab이면 팝오버를 연다.\n// 열린 패널(포털)이 처리한 키(defaultPrevented)는 무시. 카드 조건/가·감점, 수식 값·묶음·조건명 칩이 공유.\n// 사용: onKeyDown={(e) => chainTriggerKey(e, open, () => setOpen(true))} — 이벤트 시점 호출\n// (렌더 중 팩토리 호출은 react-compiler lint가 클로저의 ref 접근을 렌더 접근으로 판정한다)\nexport function chainTriggerKey(\n e: { defaultPrevented: boolean; key: string; shiftKey: boolean; preventDefault: () => void },\n open: boolean,\n onOpen: () => void,\n) {\n if (e.defaultPrevented) return;\n if (e.key === 'Enter' || e.key === ' ' || (e.key === 'Tab' && !e.shiftKey && !open)) {\n e.preventDefault();\n onOpen();\n }\n}\n\n// 가점/감점 값 보유 여부(재귀) — IF 함수는 적합/부적합 조건만, CAP·BYSCORE는 점수 조건만 허용.\n// 툴바(함수별 적용 판정)·수식(함수 변경 게이팅·IF 드롭 값 정리)이 공유(2026-07-18 컴포넌트 파일에서 이동).\nexport function hasPointsScore(node: FormulaNode): boolean {\n if (node.kind === 'group') return node.children.some(hasPointsScore);\n if (node.items?.length) return node.individual?.mode === 'points';\n return node.scoreType === 'plus' || node.scoreType === 'minus' || !!node.points;\n}\n\n// 정거장 공용 링(2026-07-16 개정) — 마우스 클릭/프레스에는 링을 내지 않는다:\n// · focus-visible = 키보드 Tab 포커스(클릭의 :focus에는 미매칭 → 트리거 hover 링과 겹쳐\n// 4px처럼 보이던 프레스 현상 제거)\n// · data-[chain-focus] = 체인의 programmatic focus(위 함수가 부여 — :focus-visible 미매칭 보완)\nexport const CHAIN_STOP_FOCUS =\n 'focus:outline-none focus-visible:ring-2 focus-visible:ring-text-field-focused-line data-[chain-focus]:ring-2 data-[chain-focus]:ring-text-field-focused-line';\n","// usePanelKeyboard — 설정 팝오버 패널의 키보드 규약(규칙 20 ⑶⑷) 공용 훅.\n// ConditionSettingMenu·ScoreSettingMenu(ScreeningConditionCard)가 공유하던 복제 코드를 추출(2026-07-18).\n//\n// 담당:\n// · 마운트 시 패널 포커스 — 패널은 위치 계산 전 visibility:hidden이라 즉시 focus()가 실패한다 → 한 틱 지연.\n// (Tab/Enter 체인이 포커스 없이는 패널에 닿지 않는다)\n// · Tab = 패널 내부 DOM(버튼·인풋·tabindex 요소) 자연 이동. disabled 요소는 수집에서 제외 —\n// 포함하면 실제 마지막 요소에서 경계 처리가 안 걸려 포커스가 패널 밖(GNB)으로 샌다.\n// · 경계 규칙: 마지막 요소에서 Tab → 완성(complete)이면 onCommit(저장+다음 진행) /\n// 미완성이면 onSkip(저장 없이 닫고 다음 정거장) / onSkip 없으면 처음으로 순환.\n// 처음 요소에서 Shift+Tab → 끝으로 순환(팝오버 밖 이탈 방지).\n//\n// 사용: const { menuRef, handleTabKey } = usePanelKeyboard({ complete, onCommit, onSkip });\n// <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>…</div>\nimport { useRef, useEffect } from 'react';\nimport type { KeyboardEvent } from 'react';\n\ninterface UsePanelKeyboardOptions {\n complete: boolean; // 저장 가능 상태 — 마지막 요소 Tab에서 onCommit 실행 조건\n onCommit: () => void; // 저장 + 다음 진행\n onSkip?: (() => void) | null; // 미완성 이탈 — 없으면 처음으로 순환\n}\n\nexport function usePanelKeyboard({ complete, onCommit, onSkip }: UsePanelKeyboardOptions) {\n const menuRef = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const t = setTimeout(() => menuRef.current?.focus(), 0);\n return () => clearTimeout(t);\n }, []);\n\n const focusablesIn = () =>\n Array.from(\n menuRef.current?.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), input:not([disabled]), [tabindex='0']\",\n ) ?? [],\n ).filter((el) => el.offsetParent !== null);\n\n const handleTabKey = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || e.defaultPrevented) return;\n const els = focusablesIn();\n if (!els.length) {\n e.preventDefault();\n if (complete) onCommit();\n else onSkip?.();\n return;\n }\n const i = els.indexOf(e.target as HTMLElement);\n if (!e.shiftKey && i === els.length - 1) {\n e.preventDefault();\n if (complete) onCommit();\n else if (onSkip) onSkip();\n else els[0].focus();\n } else if (e.shiftKey && i <= 0) {\n e.preventDefault();\n els[els.length - 1].focus();\n }\n // 그 외는 브라우저 기본 이동(패널 내부 순서대로)\n };\n\n return { menuRef, handleTabKey };\n}\n","// ScreeningConditionCard — 스크리닝 조건 카드 (Figma ConditionCard, node 8243:88380)\n// ⚠️ 내부용(ScreeningBuilderTemplate 전용) 컴포넌트 — 지원자 정보를 조건화할 때 쓰는 카드.\n// 독립 데모 페이지 없이 ScreeningBuilderTemplate 데모에서 함께 확인한다.\n//\n// 구조: [grip + 카드명] + 삭제 버튼 / [조건 셀렉트(fill) + 가/감점 셀렉트(100px)]\n// - 조건 셀렉트 → tab radio list 팝오버(SegmentedTabs + List radio + 취소/저장).\n// 탭 없이 쓰려면 conditionTabs를 생략하고 conditionOptions(평면 배열)를 준다.\n// 탭에 multiSelect(지정 탭)가 있으면 라디오 대신 다중 선택 Select(검색+확인 푸터)가\n// 나오고, 저장 시 { tab, items:[…] } — 가/감점 셀렉트 자리가 '개별설정' 버튼으로\n// 바뀌어 항목별 가/감점(또는 적합/부적합)+적용 함수를 모달로 설정한다(Figma 8532:4188~).\n// - 가/감점 셀렉트 → radio list 팝오버(가점/감점=입력+점, 적합/부적합). 선택된 라디오의 입력만 활성.\n// - 두 팝오버 모두 저장 시에만 값이 반영된다(취소/외부 클릭은 폐기).\n// 상태: default / hover(CSS) / dragging(drop — 파란 아웃라인·텍스트, DnD 중 유지)\n// 색은 condition-card-* 시멘틱 토큰만 사용. 트리거 비주얼은 Select 박스형과 동일 토큰(text-field-*).\nimport { useState, useRef } from 'react';\nimport type {\n ComponentPropsWithoutRef,\n CSSProperties,\n DragEvent as ReactDragEvent,\n HTMLAttributes,\n ReactNode,\n} from 'react';\nimport { GripVertical, Trash2, ChevronDown, Settings } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { TruncatingText } from './TruncatingText';\nimport { Select } from './Select';\nimport { ScreeningIndividualSettingModal } from './ScreeningIndividualSettingModal';\nimport { focusNextChainStop, chainTriggerKey } from './formulaFunctions';\nimport type { ConditionTab, IndividualSetting, OptionItem } from './formulaFunctions';\nimport { usePanelKeyboard } from './usePanelKeyboard';\n\n// 조건 값/저장 페이로드 — 탭 있으면 { tab, option|null }, 지정(복수)이면 { tab, option:null, items }, 탭 없으면 { option }\nexport interface ScreeningConditionValue {\n tab?: string | null;\n option?: string | null;\n items?: string[];\n}\n// 가/감점 값 — 단일(가점/감점/적합/부적합) 또는 복수 조건의 개별설정(IndividualSetting)\nexport type ScreeningScoreValue = { type: string; points?: string } | IndividualSetting;\n\n// 가/감점 라디오 항목 — 고정 4종(Figma 플로우 컷 기준)\nconst SCORE_TYPES: { value: 'plus' | 'minus' | 'fit' | 'unfit'; label: string; hasPoints: boolean }[] = [\n { value: 'plus', label: '가점', hasPoints: true },\n { value: 'minus', label: '감점', hasPoints: true },\n { value: 'fit', label: '적합', hasPoints: false },\n { value: 'unfit', label: '부적합', hasPoints: false },\n];\nconst SCORE_LABEL = Object.fromEntries(SCORE_TYPES.map((t) => [t.value, t.label]));\n\n// 셀렉트형 트리거 — Select 박스형과 같은 비주얼(text-field-* 토큰). 클릭 토글은 Popover가 담당.\nfunction FieldTrigger({\n children,\n placeholder,\n open,\n}: {\n children?: ReactNode;\n placeholder?: ReactNode;\n open?: boolean;\n}) {\n const filled = children != null && children !== '';\n return (\n <div\n // 포커스=호버(2026-07-16): 포커스는 래퍼(Popover, group/fieldstop)가 받으므로 group 변형으로\n // hover와 같은 인셋 2px 링을 켠다(키보드 focus-visible + 체인 data-chain-focus, 클릭은 표시 없음)\n className={`grid min-h-[32px] w-full cursor-pointer 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 ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 group-focus-visible/fieldstop:ring-2 group-data-[chain-focus]/fieldstop:ring-2 ${\n open ? 'ring-2 ring-text-field-focused-line' : ''\n }`}\n >\n <TruncatingText\n className={`text-14 ${filled ? 'text-text-field-filled-text' : 'text-text-field-default-text'}`}\n >\n {filled ? children : placeholder}\n </TruncatingText>\n <ChevronDown\n size={16}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 text-font-icon-5 transition-transform ${\n open ? 'rotate-180' : ''\n }`}\n />\n </div>\n );\n}\n\ninterface ScreeningConditionCardProps extends ComponentPropsWithoutRef<'div'> {\n cardName?: string;\n // ── 조건 셀렉트(tab radio list) ─────────────────────────\n // conditionTabs — 비우면 탭 없는 radio list.\n // disableOptions 탭(미보유/비대상 등)은 하단 옵션을 disabled로 두고\n // 옵션 선택 없이 저장 가능(값 = { tab, option: null })\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>; // { [tabValue]: [{ value, label }] }\n conditionOptions?: OptionItem[]; // 탭 없이 쓸 때의 평면 옵션 [{ value, label }]\n conditionValue?: ScreeningConditionValue | null; // { tab?, option } | null\n onConditionChange?: (value: ScreeningConditionValue) => void; // 저장 시에만\n conditionPlaceholder?: string;\n // ── 가/감점 셀렉트(radio list) ──────────────────────────\n scoreValue?: ScreeningScoreValue | null; // { type: 'plus'|'minus'|'fit'|'unfit', points?: string } | null\n onScoreChange?: (value: ScreeningScoreValue) => void; // 저장 시에만\n scorePlaceholder?: string;\n // ── 카드 ────────────────────────────────────────────────\n onDelete?: () => void; // 삭제 버튼 클릭\n dragging?: boolean; // 드래그 중 — drop 상태(파란 아웃라인·텍스트)\n dragHandleProps?: HTMLAttributes<HTMLSpanElement>; // grip에 전달(onMouseDown 등 — 핸들 시작 제한)\n width?: number | string; // 카드 너비: 숫자(px) | CSS 길이 | 'fill'(부모 전체 폭)\n // ...props — draggable · onDragStart · onDragOver 등 DnD 이벤트\n}\n\nexport function ScreeningConditionCard({\n cardName = '조건',\n conditionTabs = [],\n conditionOptionsByTab = {},\n conditionOptions = [],\n conditionValue = null,\n onConditionChange,\n conditionPlaceholder = '조건을 선택하세요',\n scoreValue = null,\n onScoreChange,\n scorePlaceholder = '가/감점',\n onDelete,\n dragging = false,\n dragHandleProps = {},\n width = 375,\n className = '',\n ...props\n}: ScreeningConditionCardProps) {\n const hasTabs = conditionTabs.length > 0;\n // 복수 조건(지정) — multiSelect 탭이 있는 카드. 지정 값이 저장되면 가/감점 자리가 '개별설정' 버튼이 된다.\n const multiTab = conditionTabs.find((t) => t.multiSelect);\n const multiOptions = multiTab ? (conditionOptionsByTab[multiTab.value] ?? []) : [];\n const isMultiValue = !!conditionValue?.items?.length;\n\n // 팝오버 열림 — controlled로 들고 트리거 chevron 회전·draft 초기화에 사용\n const [condOpen, setCondOpen] = useState(false);\n const [scoreOpen, setScoreOpen] = useState(false);\n // 개별설정 모달(복수 조건 전용) — 열 때마다 마운트해 draft를 카드 값으로 시드\n const [indivOpen, setIndivOpen] = useState(false);\n const indivBtnRef = useRef<HTMLSpanElement | null>(null);\n // 조건 팝오버 폭 — 기본은 셀렉트(트리거) 폭, 옵션이 길면 내용만큼만 넓어진다(열 때 트리거 폭 측정)\n const condWrapRef = useRef<HTMLDivElement | null>(null);\n const [condMinW, setCondMinW] = useState(0);\n\n const openCondition = (open: boolean) => {\n if (open) setCondMinW(condWrapRef.current?.offsetWidth ?? 0);\n setCondOpen(open);\n };\n\n // 트리거 표시 텍스트\n const tabLabel = (tab?: string | null) => conditionTabs.find((t) => t.value === tab)?.label;\n const optLabel = (tab?: string | null, opt?: string | null) =>\n (hasTabs ? (conditionOptionsByTab[tab as string] ?? []) : conditionOptions).find(\n (o) => o.value === opt,\n )?.label;\n const conditionDisplay = conditionValue\n ? isMultiValue\n ? multiOptions // 지정 — 선택 항목 라벨을 옵션 순서로 나열(Figma 8535:9887)\n .filter((o) => conditionValue.items?.includes(o.value))\n .map((o) => o.label)\n .join(', ')\n : hasTabs\n ? conditionValue.option == null\n ? tabLabel(conditionValue.tab) // disableOptions 탭 — 탭 라벨만 표시(예: \"미보유\")\n : `${tabLabel(conditionValue.tab)}, ${optLabel(conditionValue.tab, conditionValue.option)}`\n : optLabel(undefined, conditionValue.option)\n : null;\n const scoreDisplay = scoreValue\n ? scoreValue.type === 'plus' || scoreValue.type === 'minus'\n ? `${SCORE_LABEL[scoreValue.type]}, ${(scoreValue as { points?: string }).points}점`\n : SCORE_LABEL[scoreValue.type]\n : null;\n\n // 카드 상태 — default(+CSS hover) / dragging=drop 변형(Figma state3)\n // 라인은 border(안쪽) 대신 outline(바깥 얹기) — default는 gray-900 알파(배경 투영, 2026-07-16 지시)\n const cardState =\n 'outline-condition-card-default-card-line bg-condition-card-default-card-bg text-condition-card-default-card-text hover:outline-condition-card-hover-card-line hover:bg-condition-card-hover-card-bg';\n\n const widthStyle =\n width === 'fill' ? '100%' : typeof width === 'number' ? `${width}px` : width;\n\n // 드래그 고스트(마우스 추적 이미지) = drop 변형(Figma state3) — 헤더(grip+카드명)만 남긴 축약형,\n // 파란 아웃라인·텍스트·hug 폭. 목록에 남은 카드는 원래 모습을 유지하고(2026-07-16 지시),\n // dragstart에서 setDragImage로 이 오프스크린 요소를 고스트로 지정한다.\n const ghostRef = useRef<HTMLDivElement | null>(null);\n const handleDragStart = (e: ReactDragEvent<HTMLDivElement>) => {\n if (ghostRef.current && e.dataTransfer) {\n try {\n e.dataTransfer.setDragImage(ghostRef.current, 16, 20);\n } catch {\n /* setDragImage 미지원 브라우저 무시 — 기본 고스트 사용 */\n }\n }\n props.onDragStart?.(e);\n };\n\n return (\n <div\n style={{ width: widthStyle }}\n className={`group/card flex flex-col gap-spacing-5-5 rounded-round-4 outline outline-1 p-spacing-6 shadow-[0_2px_2px_0_rgba(0,0,0,0.12)] transition-colors ${cardState} ${className}`}\n {...props}\n onDragStart={handleDragStart}\n aria-grabbed={dragging || undefined}\n >\n {/* 드래그 고스트 원본 — 화면 밖 고정 배치(display:none이면 setDragImage가 안 먹음) */}\n <div\n ref={ghostRef}\n style={{ position: 'fixed', top: -9999, left: -9999 }}\n className=\"inline-flex w-fit items-center gap-spacing-4 rounded-round-4 border border-condition-card-drop-outline bg-condition-card-hover-card-bg px-spacing-6 py-spacing-4 text-condition-card-drop-text shadow-[0_2px_2px_0_rgba(0,0,0,0.12)]\"\n >\n <GripVertical size={16} strokeWidth={1.8} className=\"shrink-0\" />\n <span className=\"whitespace-nowrap text-14\">{cardName}</span>\n </div>\n {/* 헤더: grip + 카드명 / 삭제 버튼 */}\n <div className=\"flex w-full items-center justify-between\">\n <div className=\"flex min-w-0 items-center gap-spacing-4\">\n {/* 카드 hover 시 grip·카드명은 blue.400(hover-card-text) — group/card로 카드 전체 hover에 연동 */}\n <span\n {...dragHandleProps}\n className=\"shrink-0 cursor-grab text-condition-card-default-card-text transition-colors group-hover/card:text-condition-card-hover-card-text\"\n >\n <GripVertical size={16} strokeWidth={1.8} />\n </span>\n <TruncatingText className=\"min-w-0 text-14 text-condition-card-default-card-text transition-colors group-hover/card:text-condition-card-hover-card-text\">\n {cardName}\n </TruncatingText>\n </div>\n <Button variant=\"ghost\" icon={Trash2} aria-label=\"카드 삭제\" onClick={onDelete} />\n </div>\n\n {/* 셀렉트 행: 조건(fill) + 가/감점(100px) */}\n <div className=\"flex w-full items-center gap-spacing-3\">\n {/* 조건 셀렉트 → tab radio list 팝오버 — 기본 폭=트리거, 옵션이 길면 팝오버만 확장.\n 체인 정거장(data-formula-chain): Tab/Enter로 열고, 가/감점 저장 시 다음 카드로 이동 */}\n <div ref={condWrapRef} data-formula-chain className=\"min-w-0 flex-1\">\n <Popover\n className=\"group/fieldstop w-full rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => openCondition(true))}\n menuWidth={multiTab ? 351 : 'max-content'} /* 복수 조건 카드는 고정 폭(2026-07-16 지시) */\n open={condOpen}\n onOpenChange={openCondition}\n trigger={<FieldTrigger open={condOpen} placeholder={conditionPlaceholder}>{conditionDisplay}</FieldTrigger>}\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={conditionTabs}\n optionsByTab={conditionOptionsByTab}\n options={conditionOptions}\n initialTab={conditionValue?.tab}\n initialOption={conditionValue?.option}\n initialItems={conditionValue?.items}\n style={{ minWidth: condMinW }}\n onCancel={close}\n onSave={(v) => {\n onConditionChange?.(v);\n close();\n if (v.items) {\n // 지정 저장 — 가/감점 자리가 '개별설정' 버튼으로 바뀌므로 그 버튼에 포커스(체인 유지)\n setTimeout(() => indivBtnRef.current?.querySelector('button')?.focus(), 0);\n } else {\n setScoreOpen(true); // 저장(클릭·Enter·Tab 모두) → 바로 가/감점 셀렉트 열림(2026-07-14 지시)\n }\n }}\n onSkip={() => {\n close();\n focusNextChainStop(condWrapRef.current); // 값 미선택 이탈 — 다음 카드로\n }}\n />\n )}\n </Popover>\n </div>\n\n {/* 가/감점 자리 — 복수 조건(지정)이 저장되면 '개별설정' 아이콘 텍스트 버튼으로 바뀐다(Figma 8535:9887) */}\n {isMultiValue ? (\n <span ref={indivBtnRef} className=\"shrink-0\">\n <Button variant=\"line\" size=\"32\" leftIcon={Settings} onClick={() => setIndivOpen(true)}>\n 개별설정\n </Button>\n {indivOpen && (\n <ScreeningIndividualSettingModal\n items={multiOptions.filter((o) => conditionValue?.items?.includes(o.value))}\n value={scoreValue?.type === 'individual' ? (scoreValue as IndividualSetting) : null}\n onClose={() => setIndivOpen(false)}\n onConfirm={(v) => {\n onScoreChange?.(v);\n setIndivOpen(false);\n // 카드 세팅 완료 — 체인 규약대로 다음 정거장(다음 카드)으로\n focusNextChainStop(condWrapRef.current);\n }}\n />\n )}\n </span>\n ) : (\n <Popover\n className=\"group/fieldstop w-[100px] shrink-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, scoreOpen, () => setScoreOpen(true))}\n menuWidth={200}\n placement=\"auto-right\"\n open={scoreOpen}\n onOpenChange={setScoreOpen}\n trigger={<FieldTrigger open={scoreOpen} placeholder={scorePlaceholder}>{scoreDisplay}</FieldTrigger>}\n >\n {(close) => (\n <ScoreSettingMenu\n initialType={scoreValue?.type ?? null}\n initialPoints={(scoreValue as { points?: string } | null)?.points ?? ''}\n onCancel={close}\n onSave={(v) => {\n onScoreChange?.(v);\n close();\n // 카드 세팅 완료 — 체인 규약대로 다음 정거장(다음 카드 조건 셀렉트)으로\n focusNextChainStop(condWrapRef.current);\n }}\n onSkip={() => {\n close();\n focusNextChainStop(condWrapRef.current); // 미선택 이탈 — 다음 카드로\n }}\n />\n )}\n </Popover>\n )}\n </div>\n </div>\n );\n}\n\n// 조건 설정 메뉴 — tab radio list 팝오버 내용(SegmentedTabs? + 라디오 옵션 + 취소/저장).\n// ScreeningConditionCard(조건 셀렉트)와 ScreeningFormula(수식 조건 칩)가 공유한다.\n// disableOptions 탭(미보유/비대상)은 옵션 disabled + 옵션 없이 저장 가능.\n// Popover children으로 열릴 때마다 마운트되므로 draft는 mount 시 initial*로 시드된다.\n// onSave 페이로드: 탭 있으면 { tab, option|null } / 없으면 { option }\ninterface ConditionSettingMenuProps {\n tabs?: ConditionTab[];\n optionsByTab?: Record<string, OptionItem[]>;\n options?: OptionItem[];\n initialTab?: string | null;\n initialOption?: string | null;\n initialItems?: string[] | null; // multiSelect 탭의 저장된 항목 배열(지정된 자격증 등)\n onSave?: (v: ScreeningConditionValue) => void;\n onCancel?: () => void;\n onTabNext?: () => void; // Tab 키 — 저장 후 다음 편집기(값/가감점 팝오버)로 이어가기(2026-07-14 지시)\n onSkip?: () => void; // 값 미선택 상태로 Tab 이탈 — 저장 없이 닫고 다음 정거장으로(2026-07-15)\n style?: CSSProperties;\n}\n\nexport function ConditionSettingMenu({\n tabs = [],\n optionsByTab = {},\n options = [],\n initialTab = null,\n initialOption = null,\n initialItems = null,\n onSave,\n onCancel,\n onTabNext,\n onSkip,\n style,\n}: ConditionSettingMenuProps) {\n const hasTabs = tabs.length > 0;\n const [tab, setTab] = useState<string | null>(initialTab ?? tabs[0]?.value ?? null);\n const [opt, setOpt] = useState<string | null>(initialOption);\n // multiSelect 탭(지정) — 라디오 대신 다중 선택 Select. 다른 탭에서는 같은 Select를 disabled로 보여준다.\n const multiTab = tabs.find((t) => t.multiSelect);\n const onMultiTab = multiTab && tab === multiTab.value;\n const [items, setItems] = useState<string[]>(() =>\n initialTab === multiTab?.value ? (initialItems ?? []) : [],\n );\n const rows = hasTabs ? (optionsByTab[tab as string] ?? []) : options;\n const tabDisables = hasTabs && !!tabs.find((t) => t.value === tab)?.disableOptions;\n const payload = (): ScreeningConditionValue =>\n multiTab\n ? onMultiTab\n ? { tab, option: null, items }\n : { tab, option: null } // 보유/미보유 — 종류 상관없이 적용(옵션 없이 저장)\n : hasTabs\n ? { tab, option: tabDisables ? null : opt }\n : { option: opt };\n const complete = multiTab ? (onMultiTab ? items.length > 0 : true) : tabDisables ? true : !!opt;\n // 패널 포커스 + 경계 Tab 처리(규칙 20 ⑶⑷) — 공용 훅. 라디오 선택은 Space(네이티브), 확인은 Enter.\n const { menuRef, handleTabKey } = usePanelKeyboard({\n complete,\n onCommit: () => {\n onSave?.(payload());\n onTabNext?.();\n },\n onSkip, // 값 미선택 이탈 — 저장 없이 닫고 다음 정거장(다음 카드/수식)으로\n });\n\n return (\n <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>\n <PopoverMenu\n width={multiTab ? 351 : 'auto'} /* 복수 조건 메뉴는 고정 폭 — 항목 라벨 길이에 따라 출렁이지 않게 */\n style={multiTab ? undefined : style}\n footer\n footerButtonsFill\n confirmText=\"저장\"\n confirmDisabled={!complete}\n onCancel={onCancel}\n onConfirm={() => onSave?.(payload())}\n >\n {hasTabs && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <SegmentedTabs\n items={tabs}\n value={tab}\n onChange={(t: string) => {\n setTab(t);\n setOpt(null); // 탭이 바뀌면 이전 탭의 선택은 무효\n }}\n />\n </div>\n )}\n {multiTab ? (\n /* 복수 조건 — 항목 다중 선택 Select(검색+전체 선택/취소/확인 푸터). 지정 탭에서만 활성(Figma 8535:8591).\n PopoverMenu 루트 배경은 gray.100(블록 구분선용)이라 블록마다 흰 배경(list-group-bg)을 직접 깐다 */\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <Select\n width=\"100%\"\n multiple\n confirm\n searchable\n options={optionsByTab[multiTab.value] ?? []}\n value={items}\n onChange={(e) => setItems(e.target.value)}\n disabled={!onMultiTab}\n placeholder={multiTab.placeholder ?? '선택하세요'}\n searchPlaceholder={multiTab.searchPlaceholder ?? '검색어를 입력하세요'}\n />\n </div>\n ) : (\n <ListGroup gap=\"3\">\n {rows.map((o) => (\n <List\n key={o.value}\n radio\n title={o.label}\n checked={opt === o.value}\n disabled={tabDisables}\n onCheckChange={() => setOpt(o.value)}\n />\n ))}\n </ListGroup>\n )}\n </PopoverMenu>\n </div>\n );\n}\n\n// 가/감점 설정 메뉴 — radio list 팝오버 내용(가점/감점=입력+점(선택된 라디오만 활성), 적합/부적합, 취소/저장).\n// ScreeningConditionCard(가/감점 셀렉트)와 ScreeningFormula(수식 점수 인풋)가 공유한다.\n// Popover children으로 열릴 때마다 마운트되므로 draft는 mount 시 initial*로 시드된다.\n// onSave 페이로드: { type: 'plus'|'minus'|'fit'|'unfit', points?: string(양수) }\ninterface ScoreSettingMenuProps {\n initialType?: string | null;\n initialPoints?: string;\n disablePoints?: boolean; // 가점/감점 라디오 비활성 — IF 함수 안 조건은 적합/부적합만(2026-07-16)\n onSave?: (v: { type: string; points?: string }) => void;\n onCancel?: () => void;\n onTabNext?: () => void;\n onSkip?: () => void;\n}\n\nexport function ScoreSettingMenu({\n initialType = null,\n initialPoints = '',\n disablePoints = false,\n onSave,\n onCancel,\n onTabNext,\n onSkip,\n}: ScoreSettingMenuProps) {\n const [type, setType] = useState<string | null>(initialType);\n const [points, setPoints] = useState<Record<'plus' | 'minus', string>>({\n plus: initialType === 'plus' ? initialPoints : '',\n minus: initialType === 'minus' ? initialPoints : '',\n });\n const needsPoints = type === 'plus' || type === 'minus';\n const pointsValue = needsPoints ? points[type as 'plus' | 'minus'] : '';\n const complete = !!type && (!needsPoints || !!pointsValue.trim());\n // 패널 포커스 + 경계 Tab 처리(규칙 20 ⑶⑷) — 공용 훅\n const { menuRef, handleTabKey } = usePanelKeyboard({\n complete,\n onCommit: () => {\n onSave?.(needsPoints ? { type, points: pointsValue.trim() } : { type: type as string });\n onTabNext?.();\n },\n onSkip, // 미선택 이탈 — 닫고 다음 정거장으로\n });\n\n // 가점/감점 라디오 선택 시 점수 입력 자동 포커스(선택 직후 바로 타이핑)\n const onTypeSelect = (value: string, hasPoints: boolean) => {\n setType(value);\n if (hasPoints) {\n setTimeout(() => menuRef.current?.querySelector<HTMLInputElement>('input[inputmode]:not([disabled])')?.focus(), 0);\n }\n };\n\n // 가점 입력칸+단위 — 해당 라디오가 선택됐을 때만 활성(List rightSlot, 행 클릭과 분리)\n const pointsSlot = (key: 'plus' | 'minus') => (\n <span className=\"flex shrink-0 items-center gap-spacing-5\" onClick={(e) => e.stopPropagation()}>\n <Input\n width={56}\n value={points[key]}\n disabled={type !== key}\n // 점수는 숫자만 입력 허용(2026-07-16 지시)\n onChange={(e) => setPoints((p) => ({ ...p, [key]: e.target.value.replace(/[^0-9]/g, '') }))}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"0\"\n />\n <span className={`text-14 ${type === key ? 'text-list-default-text' : 'text-list-disabled-text'}`}>\n 점\n </span>\n </span>\n );\n\n return (\n <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>\n <PopoverMenu\n width=\"100%\"\n footer\n footerButtonsFill\n confirmText=\"저장\"\n confirmDisabled={!complete}\n onCancel={onCancel}\n onConfirm={() =>\n onSave?.(needsPoints ? { type, points: pointsValue.trim() } : { type: type as string })\n }\n >\n <ListGroup gap=\"3\">\n {SCORE_TYPES.map((t) => {\n const rowDisabled = disablePoints && t.hasPoints; // IF 안 조건 — 가점/감점 선택 불가\n return (\n <List\n key={t.value}\n radio\n title={t.label}\n checked={type === t.value}\n disabled={rowDisabled}\n onCheckChange={rowDisabled ? undefined : () => onTypeSelect(t.value, t.hasPoints)}\n rightSlot={t.hasPoints ? pointsSlot(t.value as 'plus' | 'minus') : undefined}\n />\n );\n })}\n </ListGroup>\n </PopoverMenu>\n </div>\n );\n}\n","// ScreeningFormula — 스크리닝 수식 (Figma Formula 8383:112161 · 그룹 8384:114161/114251 · 함수 색상표 8384:114813)\n// ⚠️ 내부용(ScreeningBuilderTemplate 전용) 컴포넌트 — 조건 카드를 수식 영역에 드롭하면 이 수식으로 치환된다.\n// 독립 데모 페이지 없이 ScreeningBuilderTemplate 데모에서 함께 확인한다.\n//\n// 노드 모델(재귀 트리):\n// - leaf : { id, kind:'leaf', criteria, criteriaOptions, value, valueOptions, points, display?('full'|'compact') }\n// → [조건칩] = [값칩] → +N점 / compact = \"기준, 값, 점수\" 요약칩 — 클릭 시 펼침(2026-07-15 표기 개정)\n// - 묶음 leaf(복수 조건, Figma 8535:11956): { …leaf, items:[항목…], individual:{mode,fn,items}|null }\n// → [조건칩] = [지정보유, N개 조건 묶음] → [개별설정] — 항목이 100개여도 한 줄 유지.\n// 묶음 칩 클릭=지정 항목 편집(다중 선택), 개별설정 클릭=항목별 점수 모달.\n// 단수 수식과 같은 골격으로 단독 등록된다(함수 그룹 자동 감싸기 없음 — 2026-07-16 개정,\n// 그룹핑은 일반 수식과 동일하게 체크+툴바로).\n// - group: { id, kind:'group', fn:'SUM'…, children:[node…], trueScore?, falseScore? }\n// → FN( 자식수식 , 자식수식 [, 조건 추가 존] + ) — + 클릭 시에만 '조건 추가' 드롭 존이 나타나고\n// 카드를 드롭하면 그 그룹에 수식이 추가된다. 그룹은 다른 수식·그룹과 다시 그룹핑될 수 있다\n// - IF 그룹(Figma 8537:2175): IF( 자식수식 , [참점수] , [거짓점수] ) — 조건이 참이면 참점수(기본 1),\n// 아니면 거짓점수(기본 0). 조건을 더 못 받으므로 +(조건 추가)·드롭 존이 없다\n// - CAPMAX/CAPMIN 그룹(Figma 8544:36274): FN( 자식수식 , [capScore] ) — 합산 점수의 최대/최소를\n// 제한. 가/감점(점수) 있는 수식에만 적용(적합/부적합·미설정 불가), + 버튼 없음\n// - FITBYSCORE/UNFITBYSCORE 그룹(Figma 8544:36274): FN( 자식수식 , [compareOp] , [compareScore] ) —\n// 점수가 계산식(비교 연산자)·비교값을 만족하면 적합/부적합 처리. 점수 있는 수식 전용, + 버튼 없음\n// 함수 계열별 고유 텍스트 컬러(formula-* 토큰): AND·OR=green / IF=blue / SUM·MAX·MIN·COUNTIF=violet /\n// CAPMAX·CAPMIN=pink / FITBYSCORE·UNFITBYSCORE=orange. hover 아웃라인도 계열 색을 따른다.\n// 체크박스는 최상위(root)에서만 노출 — 그룹핑 대상 선택용(ScreeningBuilderTemplate이 관리).\nimport { useState, useRef, useEffect } from 'react';\nimport type { ChangeEvent, ReactNode } from 'react';\nimport { X, Plus, ChevronDown } from 'lucide-react';\nimport { Checkbox } from './Checkbox';\nimport { SelectChip } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Tag } from './Tag';\nimport { Divider } from './Divider';\nimport { useHoverTooltip } from './useHoverTooltip';\nimport { ScoreSettingMenu, ConditionSettingMenu } from './ScreeningConditionCard';\nimport type { ScreeningConditionValue } from './ScreeningConditionCard';\nimport { ScreeningIndividualSettingModal } from './ScreeningIndividualSettingModal';\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport { FORMULA_FN_FAMILY, FORMULA_GROUP_FUNCTIONS, focusNextChainStop, chainTriggerKey, hasPointsScore } from './formulaFunctions';\nimport type {\n FormulaCatalog,\n FormulaGroupNode,\n FormulaLeafNode,\n FormulaNode,\n OptionItem,\n} from './formulaFunctions';\n\n// 표기 변형 — 'formula'(수식 표기) | 'natural'(자연어 문장 표기)\ntype FormulaVariant = 'formula' | 'natural';\n\n// 계열별 정적 클래스 매핑 (Tailwind purge 안전)\n// 함수 계열 → 칩 color(2026-07-15 — 자연어 그룹 함수 셀렉트 칩 채색)\nconst FAMILY_CHIP_COLOR: Record<string, 'green' | 'blue' | 'violet' | 'pink' | 'orange'> = {\n logical: 'green',\n conditional: 'blue',\n aggregate: 'violet',\n 'score-limit': 'pink',\n evaluation: 'orange',\n};\nconst FAMILY_HOVER_BORDER: Record<string, string> = {\n logical: 'border-formula-hover-logical-outline',\n conditional: 'border-formula-hover-conditional-outline',\n aggregate: 'border-formula-hover-aggregate-outline',\n 'score-limit': 'border-formula-hover-score-limit-outline',\n evaluation: 'border-formula-hover-evaluation-outline',\n};\n\n// 수식 컨테이너 공통 셸 — 흰 bg + 1px 아웃라인(default 공통, hover=함수 계열 색).\n// hover는 CSS :hover 대신 마우스 이벤트로 추적한다 — :hover는 자식 수식 위에 있어도 부모에 걸려\n// 중첩 그룹에서 아웃라인이 겹친다(2026-07-16 지시). mouseover/out은 버블되므로 안쪽 셸이\n// stopPropagation으로 끊으면 가장 안쪽 수식 하나만 호버 상태를 가진다.\nfunction FormulaShell({\n family,\n dense = false,\n comment = null,\n className = '',\n children,\n}: {\n family: string; // 함수 계열(hover 아웃라인 색)\n dense?: boolean;\n comment?: ReactNode;\n className?: string;\n children?: ReactNode;\n}) {\n const [hovered, setHovered] = useState(false);\n const shellRef = useRef<HTMLDivElement | null>(null);\n // 호버 추적은 네이티브 리스너로 — React 합성 이벤트는 portal(팝오버 패널)에서도 컴포넌트 트리로\n // 버블돼, 패널 위 이동이 셸 hover를 갱신하고 패널이 닫히면 mouseout이 없어 hover가 고착된다\n // (조건/값 저장 후 호버 잔류 버그, 2026-07-15). 네이티브는 DOM 트리 기준이라 패널 이벤트가 안 닿는다.\n useEffect(() => {\n const el = shellRef.current;\n if (!el) return;\n // stopPropagation 금지 — 네이티브에서 전파를 끊으면 React 루트 위임까지 막혀\n // 수식 내부 툴팁(useHoverTooltip 등) mouseenter가 전부 죽는다(2026-07-15 회귀).\n // 대신 '이벤트 지점의 가장 가까운 셸이 나인가'로 판정해 가장 안쪽 셸만 hover.\n const over = (e: MouseEvent) =>\n setHovered((e.target as Element | null)?.closest('[data-formula-shell]') === el);\n const out = (e: MouseEvent) => {\n if (!el.contains(e.relatedTarget as Node | null)) setHovered(false);\n };\n el.addEventListener('mouseover', over);\n el.addEventListener('mouseout', out);\n return () => {\n el.removeEventListener('mouseover', over);\n el.removeEventListener('mouseout', out);\n };\n }, []);\n return (\n <div\n ref={shellRef}\n data-formula-shell\n className={`inline-flex max-w-full flex-col gap-spacing-4 rounded-round-4 border py-spacing-5 transition-colors ${\n dense ? 'px-spacing-5-5' : 'px-spacing-6' /* 그룹핑된 조건 수식은 좌우 10, 최상위는 12(2026-07-15) */\n } ${\n hovered\n ? `bg-formula-hover-bg ${FAMILY_HOVER_BORDER[family] ?? 'border-formula-default-outline'}`\n : 'border-formula-default-outline bg-formula-default-bg'\n } ${className}`}\n >\n <div className=\"flex max-w-full flex-wrap items-center gap-spacing-4\">{children}</div>\n {/* 설명 코멘트(Figma commet 옵션, 2026-07-15) — Divider 아래 [설명] 태그 + 요약 텍스트 */}\n {comment && (\n <>\n <Divider />\n <div className=\"flex w-full flex-wrap items-start gap-x-spacing-4 gap-y-spacing-5\">\n <Tag color=\"black\">설명</Tag>\n <p className=\"min-w-0 flex-1 text-12 leading-22 text-formula-default-text\">{comment}</p>\n </div>\n </>\n )}\n </div>\n );\n}\n\nconst Paren = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 font-semibold text-formula-parentheses\">{children}</span>\n);\nconst Comparison = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 font-semibold text-formula-comparison\">{children}</span>\n);\n// 자연어 빌더 연결어 — 레귤러 웨이트(Figma Language 8466:6316)\nconst NaturalText = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 text-formula-comparison\">{children}</span>\n);\n\nconst optLabel = (options: OptionItem[] | null | undefined, v: string | null | undefined) =>\n options?.find((o) => o.value === v)?.label ?? '';\n\n// IF 안으로 들어가는 노드에서 가점/감점 값을 제거(적합/부적합·미설정은 유지)\nfunction stripPointsScore(node: FormulaNode): FormulaNode {\n if (node.kind === 'group') return { ...node, children: node.children.map(stripPointsScore) };\n if (node.items?.length)\n return node.individual?.mode === 'points' ? { ...node, individual: null } : node;\n return node.scoreType === 'plus' || node.scoreType === 'minus' || node.points\n ? { ...node, points: '', scoreType: null }\n : node;\n}\n\n\n// 수식 필드 체인 규약 — 공용 유틸은 formulaFunctions.js(focusNextChainStop/CHAIN_STOP_FOCUS,\n// 카드·수식·드롭 스트립이 공유). 여기서는 정거장 마커(data-formula-chain)만 단다.\n\n// '조건 추가' dashed 드롭 존 (Figma FormulaAdd 8384:113414 — Default/drop 2상태, + 버튼과 별개 컴포넌트).\n// 조건 카드를 이 위에 드롭하면 그 그룹 안에 수식이 추가된다. 내부 X 버튼 클릭 시 영역 삭제(onClose).\n// hover 시 사용법 안내 툴팁(\"조건 카드를 드래그 앤 드롭하세요\")을 띄운다.\nfunction FormulaAddZone({\n onDropLeaf,\n getDropLeaf,\n onClose,\n closable = true,\n}: {\n onDropLeaf: (leaf: FormulaNode) => void;\n getDropLeaf?: (() => FormulaNode | null) | null;\n onClose?: () => void;\n closable?: boolean;\n}) {\n const [over, setOver] = useState(false);\n const droppable = !!getDropLeaf;\n const zoneRef = useRef<HTMLDivElement | null>(null);\n const hoverTip = useHoverTooltip('조건 카드를 드래그 앤 드롭하세요');\n return (\n <div\n ref={zoneRef}\n onMouseEnter={hoverTip.onMouseEnter}\n onMouseLeave={hoverTip.onMouseLeave}\n onDragOver={(e) => {\n if (!droppable || !getDropLeaf?.()) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 수식 영역 드롭 존의 하이라이트/드롭과 분리\n e.dataTransfer.dropEffect = 'move';\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n if (!droppable) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 드롭 존이 최상위 수식으로 중복 추가하지 않게\n setOver(false);\n const leaf = getDropLeaf?.();\n if (leaf) onDropLeaf(leaf);\n }}\n className={`flex h-[38px] shrink-0 items-center gap-spacing-4 rounded-round-4 border border-dashed py-spacing-5 pl-spacing-7 ${\n closable ? 'pr-spacing-5-5' /* X 버튼 자체 여백 감안 */ : 'pr-spacing-7' /* X 없음 — 좌우 동일 */\n } text-12 font-semibold transition-colors ${\n over\n ? 'border-formula-add-drop-outline bg-formula-add-drop-bg text-formula-add-drop-text'\n : 'border-formula-add-outline bg-formula-add-bg text-formula-add-text'\n }`}\n >\n 조건 추가\n {/* X 버튼 위에서는 영역 툴팁을 숨겨 버튼 툴팁과 겹치지 않게 — 벗어나면 다시 표시.\n closable=false(IF 빈 상태 등 조건이 필수인 자리)면 X 없이 상시 노출 */}\n {closable && (\n <span\n className=\"inline-flex shrink-0 items-center\"\n onMouseEnter={hoverTip.onMouseLeave}\n onMouseLeave={() => {\n if (zoneRef.current) hoverTip.onMouseEnter({ currentTarget: zoneRef.current });\n }}\n >\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"영역 삭제\" onClick={onClose} />\n </span>\n )}\n {hoverTip.tooltip}\n </div>\n );\n}\n\n// FITBYSCORE/UNFITBYSCORE 비교 연산자(계산식) — Figma 8544:36274\nconst COMPARE_OPS = [\n { value: '>=', label: '>= (보다 크거나 같을 때)' },\n { value: '>', label: '> (보다 클 때)' },\n { value: '=', label: '= (같을 때)' },\n { value: '<=', label: '<= (보다 작거나 같을 때)' },\n { value: '<', label: '< (보다 작을 때)' },\n];\n// 설명 문장용 관계 표현 — \"N점 이상이면\" 식으로 잇는다('='는 \"N점이면\")\nconst COMPARE_OP_TEXT: Record<string, string> = { '>=': '이상', '>': '초과', '=': '', '<=': '이하', '<': '미만' };\n\n// 그룹 함수 점수 인풋(IF 참/거짓·CAPMAX/CAPMIN 제한값 공용) — 숫자만 입력,\n// 라벨 없는 소형 인풋이라 hover 시 용도 툴팁을 띄운다(2026-07-16 지시)\nfunction GroupScoreInput({\n tooltip,\n value,\n placeholder,\n onChange,\n}: {\n tooltip: string;\n value?: string;\n placeholder?: string;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n}) {\n const tip = useHoverTooltip(tooltip);\n return (\n <span\n className=\"inline-flex shrink-0 items-center\"\n onMouseEnter={tip.onMouseEnter}\n onMouseLeave={tip.onMouseLeave}\n >\n <Input\n size=\"22\"\n width={40}\n value={value}\n placeholder={placeholder}\n onChange={onChange}\n inputProps={{ inputMode: 'numeric', 'aria-label': tooltip }}\n />\n {tip.tooltip}\n </span>\n );\n}\n\n// 자연어 그룹의 상시 드롭 스트립(컴팩트 h38, builder-area 토큰 — Figma 8466:6494 하단)\nfunction NaturalGroupDropStrip({\n getDropLeaf,\n onDropLeaf,\n}: {\n getDropLeaf?: (() => FormulaNode | null) | null;\n onDropLeaf: (leaf: FormulaNode) => void;\n}) {\n const [over, setOver] = useState(false);\n return (\n <div\n onDragOver={(e) => {\n if (!getDropLeaf || !getDropLeaf()) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 존 드롭과 분리\n e.dataTransfer.dropEffect = 'move';\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n if (!getDropLeaf) return;\n e.preventDefault();\n e.stopPropagation();\n setOver(false);\n const leaf = getDropLeaf();\n if (leaf) onDropLeaf(leaf);\n }}\n className={`flex h-[38px] w-full items-center justify-center gap-spacing-3 rounded-round-4 border border-dashed text-14 text-builder-area-add-default-text transition-colors ${\n over\n ? 'border-builder-area-add-hover-outline bg-builder-area-add-hover-bg'\n : 'border-builder-area-add-default-outline bg-builder-area-add-default-bg'\n }`}\n >\n <Plus size={16} strokeWidth={1.8} className=\"shrink-0\" />\n 다른 조건을 여기에 끌어다 놓기\n </div>\n );\n}\n\n// 재귀 수식 렌더러 props — FormulaGroup/FormulaLeaf도 노드 타입만 좁혀 공유한다.\nexport interface ScreeningFormulaProps {\n node: FormulaNode; // leaf | group 노드(위 모델)\n root?: boolean; // 최상위 여부 — 체크박스 노출(그룹핑 대상 선택)\n checked?: boolean;\n onCheckChange?: (checked: boolean) => void; // root 전용\n onChange?: (nextNode: FormulaNode) => void;\n onDelete?: () => void; // X 버튼\n getDropLeaf?: () => FormulaNode | null; // 드래그 중인 카드의 leaf(없으면 null). '조건 추가' 드롭 존이 사용\n // catalog — { criteriaOptions, valueOptionsByCriteria, conditionMetaByCriteria } — 현재 카드 영역\n // 라이브 목록(템플릿이 렌더마다 계산). 지정 시 leaf의 스냅샷보다 우선한다.\n catalog?: FormulaCatalog | null;\n variant?: FormulaVariant; // 'formula'(수식 표기) | 'natural'(자연어 문장 표기 — 같은 노드 모델·기능, 8466:6316)\n inIf?: boolean; // IF 함수 서브트리 안 여부 — 조건 값은 적합/부적합만 허용(2026-07-16)\n className?: string;\n}\n\n// 재귀 수식 렌더러 — root에서만 체크박스, onChange(nextNode)로 불변 갱신을 위로 올린다.\nexport function ScreeningFormula({\n node,\n root = false,\n checked = false,\n onCheckChange,\n onChange,\n onDelete,\n getDropLeaf,\n catalog,\n variant = 'formula',\n inIf = false,\n className = '',\n}: ScreeningFormulaProps) {\n if (node.kind === 'group') {\n return (\n <FormulaGroup\n node={node}\n root={root}\n checked={checked}\n onCheckChange={onCheckChange}\n onChange={onChange}\n onDelete={onDelete}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={inIf}\n className={className}\n />\n );\n }\n\n return (\n <FormulaLeaf\n node={node}\n root={root}\n checked={checked}\n onCheckChange={onCheckChange}\n onChange={onChange}\n onDelete={onDelete}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={inIf}\n className={className}\n />\n );\n}\n\n// 그룹 수식 — FN( 자식 , … [, 조건 추가 존] + ) X\n// '조건 추가' 드롭 존은 평소엔 없고 + 버튼을 클릭했을 때만 나타난다(2026-07-16 지시).\n// 카드를 드롭하면 그 그룹에 수식이 추가되고 존은 다시 사라진다(+ 재클릭으로 닫기도 가능).\nfunction FormulaGroup({ node, root, checked, onCheckChange, onChange, onDelete, getDropLeaf, catalog, variant = 'formula', inIf = false, className }: Omit<ScreeningFormulaProps, 'node'> & { node: FormulaGroupNode }) {\n // 조건 추가 존 목록 — +는 누를 때마다 존을 하나씩 추가(토글 아님, 2026-07-14 지시), 닫기는 각 존의 X\n const [addZoneIds, setAddZoneIds] = useState<string[]>([]);\n const addSeqRef = useRef(0);\n const appendAddZone = () => setAddZoneIds((prev) => [...prev, `add-${++addSeqRef.current}`]);\n const removeAddZone = (id: string) => setAddZoneIds((prev) => prev.filter((x) => x !== id));\n const patch = (p: Partial<FormulaGroupNode>) => onChange?.({ ...node, ...p });\n const family = FORMULA_FN_FAMILY[node.fn];\n // IF 그룹 — 참/거짓 점수 인풋(기본 1/0), 조건 추가 불가(Figma 8537:2175, 2026-07-16)\n const isIf = node.fn === 'IF';\n // CAPMAX/CAPMIN 그룹 — 제한 점수 인풋 하나, 조건 추가 불가(Figma 8544:36274, 2026-07-16)\n const isCap = node.fn === 'CAPMAX' || node.fn === 'CAPMIN';\n const capInput = (\n <GroupScoreInput\n tooltip={node.fn === 'CAPMAX' ? '최대 제한 점수' : '최소 제한 점수'}\n value={node.capScore ?? ''}\n placeholder=\"값\"\n onChange={(e) => patch({ capScore: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n // FITBYSCORE/UNFITBYSCORE 그룹 — 계산식(비교 연산자)+비교값, 조건 추가 불가(Figma 8544:36274)\n const isByScore = node.fn === 'FITBYSCORE' || node.fn === 'UNFITBYSCORE';\n const compareOpSelect = (\n <SelectChip\n options={COMPARE_OPS}\n value={node.compareOp ?? '>='}\n onChange={(e) => patch({ compareOp: e.target.value })}\n menuWidth={210}\n />\n );\n const compareScoreInput = (\n <GroupScoreInput\n tooltip=\"비교 기준 점수\"\n value={node.compareScore ?? ''}\n placeholder=\"값\"\n onChange={(e) => patch({ compareScore: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n // 함수 변경 가능 판정 — 툴바와 같은 규칙(IF=단일·비점수 조건 / CAP·BYSCORE=점수 있는 조건)을\n // 그룹 함수명 팝오버·자연어 함수 칩에도 적용해 우회 전환을 막는다\n const fnDisabled = (fn: string) => {\n if (fn === 'IF') return node.children.length !== 1 || node.children.some(hasPointsScore);\n if (fn === 'CAPMAX' || fn === 'CAPMIN' || fn === 'FITBYSCORE' || fn === 'UNFITBYSCORE')\n return !node.children.some(hasPointsScore);\n return false;\n };\n const ifScoreInput = (key: 'trueScore' | 'falseScore', fallback?: string) => (\n <GroupScoreInput\n tooltip={key === 'trueScore' ? '참일 때 부여 점수' : '거짓일 때 부여 점수'}\n value={node[key] ?? fallback}\n onChange={(e) => patch({ [key]: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n const replaceChild = (idx: number, next: FormulaNode) => {\n const children = node.children.slice();\n children[idx] = next;\n patch({ children });\n };\n\n const removeChild = (idx: number) => patch({ children: node.children.filter((_, i) => i !== idx) });\n // 설명 코멘트 — 최상위에서만, 자식(중첩 포함) 요약을 하나로 합쳐 표시\n const groupComment = root ? nodeSummary(node, catalog) : null;\n\n // ── 자연어 표기(8466:6494/6621) — \"다음 조건들 중 [FN ▾] 반영\" + 자식 문장 스택 + 상시 드롭 스트립.\n // 함수도 셀렉트 칩으로 변경 가능(수식 빌더와 기능 동일, 표기만 다름)\n if (variant === 'natural') {\n return (\n <FormulaShell family={family} className={`w-full ${className}`}>\n <div className=\"flex w-full items-center gap-spacing-4\">\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n <NaturalText>다음 조건들 중</NaturalText>\n <SelectChip\n options={FORMULA_GROUP_FUNCTIONS.map((f) => ({ value: f, label: f, disabled: fnDisabled(f) }))}\n value={node.fn}\n onChange={(e) => patch({ fn: e.target.value })}\n menuWidth={140}\n color={FAMILY_CHIP_COLOR[family] ?? 'gray'}\n weight=\"semibold\"\n />\n <NaturalText>반영</NaturalText>\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </div>\n {node.children.map((child, i) => (\n <ScreeningFormula\n key={child.id}\n node={child}\n onChange={(next) => replaceChild(i, next)}\n onDelete={() => removeChild(i)}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={isIf || inIf}\n className=\"w-full\"\n />\n ))}\n {isIf ? (\n /* IF — 조건 추가 불가(비었을 때만 드롭 스트립 노출). 참/거짓 점수는 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [stripPointsScore(leaf)] })}\n />\n )}\n <div className=\"flex w-full items-center gap-spacing-4\">\n <NaturalText>참이면</NaturalText>\n {ifScoreInput('trueScore', '1')}\n <NaturalText>점, 아니면</NaturalText>\n {ifScoreInput('falseScore', '0')}\n <NaturalText>점 부여</NaturalText>\n </div>\n </>\n ) : isCap ? (\n /* CAPMAX/CAPMIN — 조건 추가 불가(비었을 때만 드롭 스트립). 제한 점수를 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n />\n )}\n <div className=\"flex w-full items-center gap-spacing-4\">\n <NaturalText>{node.fn === 'CAPMAX' ? '최대' : '최소'}</NaturalText>\n {capInput}\n <NaturalText>점으로 제한</NaturalText>\n </div>\n </>\n ) : isByScore ? (\n /* FITBYSCORE/UNFITBYSCORE — 조건 추가 불가. 계산식·비교값을 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n />\n )}\n <div className=\"flex w-full flex-wrap items-center gap-spacing-4\">\n <NaturalText>점수가</NaturalText>\n {compareOpSelect}\n {compareScoreInput}\n <NaturalText>{`점이면 ${node.fn === 'FITBYSCORE' ? '적합' : '부적합'} 처리`}</NaturalText>\n </div>\n </>\n ) : (\n /* 상시 드롭 스트립(컴팩트 h38) — 카드를 끌어다 놓으면 이 그룹에 조건 추가 */\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [...node.children, leaf] })}\n />\n )}\n </FormulaShell>\n );\n }\n\n return (\n <FormulaShell family={family} comment={groupComment} className={className}>\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n {/* 함수명 — underline 텍스트 버튼(계열 색·semibold), 클릭하면 함수 변경 팝오버(2026-07-15) */}\n <Popover\n menuWidth={140}\n trigger={\n <Button variant=\"underline\" size=\"24\" weight=\"semibold\" color={FAMILY_CHIP_COLOR[family] ?? 'black'}>\n {node.fn}\n </Button>\n }\n >\n {(close) => (\n <PopoverMenu width=\"100%\">\n <ListGroup>\n {FORMULA_GROUP_FUNCTIONS.map((fn) => (\n <List\n key={fn}\n title={fn}\n selected={fn === node.fn}\n disabled={fnDisabled(fn)}\n onClick={\n fnDisabled(fn)\n ? undefined\n : () => {\n patch({ fn });\n close();\n }\n }\n />\n ))}\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n <Paren>(</Paren>\n {node.children.map((child, i) => (\n <span key={child.id} className=\"inline-flex max-w-full flex-wrap items-center gap-spacing-4\">\n {i > 0 && <Comparison>,</Comparison>}\n <ScreeningFormula\n node={child}\n onChange={(next) => replaceChild(i, next)}\n onDelete={() => removeChild(i)}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={isIf || inIf}\n />\n </span>\n ))}\n {isIf ? (\n /* IF( 조건 , 참점수 , 거짓점수 ) — 조건 추가(+) 불가(Figma 8537:2175).\n 단 조건이 삭제돼 비면 '조건 추가' 존을 상시 노출(X 없음) — IF는 조건이 필수(2026-07-16 지시) */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [stripPointsScore(leaf)] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {ifScoreInput('trueScore', '1')}\n <Comparison>,</Comparison>\n {ifScoreInput('falseScore', '0')}\n </>\n ) : isCap ? (\n /* CAPMAX/CAPMIN( 조건 , 제한점수 ) — 조건 추가(+) 불가(Figma 8544:36274), 비면 존 상시 노출 */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {capInput}\n </>\n ) : isByScore ? (\n /* FN( 조건 , 계산식 , 비교값 ) — 조건 추가(+) 불가(Figma 8544:36274), 비면 존 상시 노출 */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {compareOpSelect}\n <Comparison>,</Comparison>\n {compareScoreInput}\n </>\n ) : (\n <>\n {/* 조건 추가 존 — + 클릭마다 하나씩 추가되고, 드롭되거나 X를 누르면 그 존만 사라진다 */}\n {addZoneIds.map((zoneId, zi) => (\n <span key={zoneId} className=\"inline-flex items-center gap-spacing-4\">\n {(node.children.length > 0 || zi > 0) && <Comparison>,</Comparison>}\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => {\n patch({ children: [...node.children, leaf] });\n removeAddZone(zoneId);\n }}\n onClose={() => removeAddZone(zoneId)}\n />\n </span>\n ))}\n <Button variant=\"ghost\" size=\"18\" icon={Plus} aria-label=\"조건 추가\" onClick={appendAddZone} />\n </>\n )}\n <Paren>)</Paren>\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </FormulaShell>\n );\n}\n\n// 적합/부적합 라벨 — scoreType이 fit/unfit인 leaf는 점수 입력 대신 글자 표시\nconst SCORE_TEXT: Record<string, string> = { fit: '적합', unfit: '부적합' };\n\n// ── 설명 코멘트 요약(2026-07-15) — 표시는 최상위 수식에서만 하나. 그룹은 자식 요약을 합쳐 한 문장으로. ──\n// 형식: \"{조건명}가 {값}일 경우 가점 N점 부여\" (2026-07-15 문구 지정 — 조사 이/가는 받침 자동 판별)\nconst subjectParticle = (word: string) => {\n const ch = word?.charCodeAt(word.length - 1) ?? 0;\n if (ch < 0xac00 || ch > 0xd7a3) return '가'; // 한글 음절이 아니면 기본 '가'\n return (ch - 0xac00) % 28 > 0 ? '이' : '가';\n};\nfunction leafSummary(node: FormulaLeafNode, catalog?: FormulaCatalog | null) {\n const condMeta =\n catalog?.conditionMetaByCriteria?.[node.criteria ?? ''] ??\n node.conditionMetaByCriteria?.[node.criteria ?? ''] ?? { tabs: [] };\n // 묶음 leaf(복수 조건) — \"{조건명} {묶음라벨} N개 조건의 점수가 합산되어 계산됨\"(Figma 8535:11956)\n if (node.items?.length) {\n const criteriaLabel = optLabel(catalog?.criteriaOptions ?? node.criteriaOptions ?? [], node.criteria);\n if (!criteriaLabel) return null;\n const tabMeta = condMeta.tabs?.find((t) => t.value === node.valueTab);\n const bundleLabel = tabMeta?.bundleLabel ?? tabMeta?.label ?? '';\n return `${criteriaLabel} ${bundleLabel} ${node.items.length}개 조건의 점수가 합산되어 계산됨`;\n }\n const valueOpts =\n catalog?.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptions ??\n [];\n const criteriaOpts = catalog?.criteriaOptions ?? node.criteriaOptions ?? [];\n const criteriaLabel = optLabel(criteriaOpts, node.criteria);\n const tabLabel = condMeta.tabs?.find((t) => t.value === node.valueTab)?.label;\n const valueLabel = node.value != null ? optLabel(valueOpts, node.value) : tabLabel;\n const scoreText = SCORE_TEXT[node.scoreType ?? ''];\n const scoreDesc = scoreText\n ? `${scoreText} 처리`\n : node.points\n ? String(node.points).startsWith('-')\n ? `감점 ${String(node.points).slice(1)}점 부여`\n : `가점 ${node.points}점 부여`\n : null;\n // 값이 정해지면 설명 표시(점수는 정해진 경우에만 문장에 이어 붙음).\n // 탭 라벨(대상 등)은 생략해도 이해에 문제없어 값만 쓴다 — 옵션 없는 탭(미보유 등)은 탭 라벨이 곧 값.\n if (!criteriaLabel || !valueLabel) return null;\n return `${criteriaLabel}${subjectParticle(criteriaLabel)} ${valueLabel}일 경우${scoreDesc ? ` ${scoreDesc}` : ''}`;\n}\nfunction nodeSummary(node: FormulaNode, catalog?: FormulaCatalog | null): string | null {\n if (node.kind === 'group') {\n const parts = node.children.map((c) => nodeSummary(c, catalog)).filter(Boolean);\n const joined = parts.length ? parts.join(', ') : null;\n if (node.fn === 'IF') {\n // IF( 조건, 참, 거짓 ) — 조건이 참이면 참점수, 아니면 거짓점수(Figma 8537:2175)\n const ifPart = `조건이 참이면 ${node.trueScore ?? '1'}점, 아니면 ${node.falseScore ?? '0'}점 부여`;\n return joined ? `${joined} — ${ifPart}` : ifPart;\n }\n if ((node.fn === 'CAPMAX' || node.fn === 'CAPMIN') && node.capScore) {\n // CAPMAX/CAPMIN( 조건, 제한 ) — 합산 점수의 상한/하한 제한(Figma 8544:36274)\n const capPart = `점수를 ${node.fn === 'CAPMAX' ? '최대' : '최소'} ${node.capScore}점으로 제한`;\n return joined ? `${joined} — ${capPart}` : capPart;\n }\n if ((node.fn === 'FITBYSCORE' || node.fn === 'UNFITBYSCORE') && node.compareScore) {\n // FN( 조건, 계산식, 비교값 ) — 점수가 비교식을 만족하면 적합/부적합 처리(Figma 8544:36274)\n const rel = COMPARE_OP_TEXT[node.compareOp ?? '>='];\n const byPart = `점수가 ${node.compareScore}점${rel ? ` ${rel}` : ''}이면 ${\n node.fn === 'FITBYSCORE' ? '적합' : '부적합'\n } 처리`;\n return joined ? `${joined} — ${byPart}` : byPart;\n }\n return joined;\n }\n return leafSummary(node, catalog);\n}\n\n// 칩 모양 팝오버 트리거 — SelectChip 트리거와 동일한 비주얼(chip-* 토큰), 클릭 토글은 Popover가 담당\nfunction ChipTrigger({ children, open }: { children?: ReactNode; open?: boolean }) {\n return (\n <span\n // 포커스 표시=hover 디자인(2026-07-16) — 포커스는 래퍼(Popover, group/chipstop)가 받으므로\n // 키보드(focus-visible)·체인(data-chain-focus) 상태를 group 변형으로 받아 hover 색을 켠다\n className={`inline-flex min-w-0 cursor-pointer items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 text-12 font-normal transition-colors ${CHIP_COLOR_CLASS.gray} group-focus-visible/chipstop:text-chip-gray-hover-text group-focus-visible/chipstop:border-chip-gray-hover-line group-focus-visible/chipstop:bg-chip-gray-hover-bg group-data-[chain-focus]/chipstop:text-chip-gray-hover-text group-data-[chain-focus]/chipstop:border-chip-gray-hover-line group-data-[chain-focus]/chipstop:bg-chip-gray-hover-bg`}\n >\n <span className=\"truncate\">{children}</span>\n <ChevronDown\n size={12}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}\n />\n </span>\n );\n}\n\n// leaf 수식 — IF( 기준 == 값 , 점수 ) / compact = IF( 요약칩 )\nfunction FormulaLeaf({ node, root, checked, onCheckChange, onChange, onDelete, catalog, variant = 'formula', inIf = false, className }: Omit<ScreeningFormulaProps, 'node'> & { node: FormulaLeafNode }) {\n const patch = (p: Partial<FormulaLeafNode>) => onChange?.({ ...node, ...p });\n const startRef = useRef<HTMLSpanElement | null>(null); // 이 수식의 체인 시작점(조건 종류 칩)\n const isCompact = node.display === 'compact';\n // 묶음 leaf(복수 조건) — 항목 배열이 있으면 값·점수 대신 [묶음 칩]과 [개별설정]으로 표기\n const isBundle = !!node.items?.length;\n const [indivOpen, setIndivOpen] = useState(false);\n const [critOpen, setCritOpen] = useState(false); // 묶음 leaf 조건명 칩 팝오버(카드 조건 셀렉트와 동일 메뉴)\n // 묶음 조건 저장(조건명 칩·묶음 칩 공용) — 카드의 조건 셀렉트와 동일한 의미:\n // 지정(items)=항목 갱신 후 개별설정으로, 보유/미보유=묶음 해제(일반 leaf) 후 점수 팝오버로\n // 값 칩(일반 leaf)에서도 같은 메뉴를 쓰므로, 지정(items) 저장 시 일반 leaf가 묶음 수식으로 전환된다\n // 지정(items) 저장=묶음 leaf로, 보유/미보유 저장=일반 leaf로 — 같은 leaf 안에서만 전환된다\n // (2026-07-16 개정: 함수 그룹 자동 감싸기 취소 — 드롭 전/후 설정 모두 묶음 leaf 한 줄로 동일)\n const saveBundleCondition = (v: ScreeningConditionValue, close: () => void) => {\n if (v.items) {\n onChange?.({ ...node, valueTab: v.tab ?? null, value: null, items: v.items });\n close();\n setTimeout(() => document.querySelector<HTMLElement>(`[data-indiv-for=\"${node.id}\"] input`)?.focus(), 0);\n } else {\n onChange?.({\n ...node,\n valueTab: v.tab ?? null,\n value: v.option ?? null,\n items: undefined,\n individual: undefined,\n });\n close();\n setScoreOpen(true);\n }\n };\n const scoreText = SCORE_TEXT[node.scoreType ?? '']; // '적합'|'부적합'|undefined\n // 점수 인풋 클릭 → 카드의 가/감점 셀렉트와 동일한 설정 팝오버(ScoreSettingMenu)\n const [scoreOpen, setScoreOpen] = useState(false);\n const scoreType =\n node.scoreType ?? (node.points ? (String(node.points).startsWith('-') ? 'minus' : 'plus') : null);\n // 값 칩 클릭 → 카드의 조건 셀렉트와 동일한 설정 팝오버(ConditionSettingMenu — 탭+라디오).\n // 조건(기준) 칩은 조건 종류 선택(SelectChip). 기준이 바뀌면 그 카드의 구성을 따라간다.\n const [condOpen, setCondOpen] = useState(false);\n const condMeta = catalog?.conditionMetaByCriteria?.[node.criteria ?? ''] ??\n node.conditionMetaByCriteria?.[node.criteria ?? ''] ?? {\n tabs: [],\n optionsByTab: {},\n options: node.valueOptions ?? [],\n };\n // 카탈로그(현재 카드 영역 라이브 목록) 우선, 없으면 leaf 스냅샷 폴백 — 카드 추가/삭제가 즉시 반영\n const criteriaOpts = catalog?.criteriaOptions ?? node.criteriaOptions ?? [];\n // 값 옵션 — 기준별 맵에서 선택된 기준을 따라간다(카탈로그 → 스냅샷 → 고정 valueOptions 순)\n const valueOpts =\n catalog?.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptions ??\n [];\n // 복수 조건 카드면 조건 팝오버 폭도 카드와 동일하게 고정(351 — 2026-07-16 지시)\n // ⚠️ condMeta 선언 뒤에 있어야 한다 — 앞에서 참조하면 TDZ ReferenceError(드롭 에러 원인이었음)\n const hasMultiTab = !!condMeta.tabs?.some((t) => t.multiSelect);\n const condMenuWidth = hasMultiTab ? 351 : 'max-content';\n // 설명 코멘트 — 단독(최상위) 조건일 때만 자기 요약 표시. 그룹핑되면 그룹이 합쳐진 설명 하나를 담당.\n // 자연어 표기는 문장 자체가 설명이라 코멘트 생략.\n const leafComment = variant === 'natural' ? null : root ? leafSummary(node, catalog) : null;\n return (\n <FormulaShell family=\"conditional\" dense={!root} comment={leafComment} className={className}>\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n {isCompact ? (\n // compact 요약칩 — 파란 텍스트·연파랑 라인. 클릭하면 full로 펼침\n <button\n type=\"button\"\n onClick={() => patch({ display: 'full' })}\n className=\"inline-flex min-w-0 items-center gap-spacing-3 rounded-round-4 border border-formula-compact-chip-line bg-formula-default-bg py-spacing-1 pl-spacing-4 pr-spacing-3 text-12 text-formula-compact-chip-text\"\n >\n <span className=\"truncate\">\n {[\n optLabel(criteriaOpts, node.criteria) || optLabel(node.criteriaOptions, node.criteria),\n isBundle ? `${node.items?.length}개 조건 묶음` : optLabel(valueOpts, node.value),\n isBundle ? null : (scoreText ?? node.points),\n ]\n .filter(Boolean)\n .join(', ')}\n </span>\n <ChevronDown size={12} strokeWidth={1.8} className=\"shrink-0\" />\n </button>\n ) : (\n <>\n {/* 조건(기준) 칩 — 조건 종류(왼쪽 카드 리스트) 선택 + 검색창. 기준이 바뀌면 값·탭 리셋.\n Tab은 하이잭하지 않는다 — 브라우저 기본 순서로 값 칩·점수 인풋에 포커스가 이동(2026-07-15) */}\n <span\n ref={startRef}\n data-formula-chain\n className=\"inline-flex min-w-0 items-center\" /* 인라인 baseline 정렬로 칩이 내려앉는 것 방지 */\n >\n {isBundle ? (\n /* 묶음 leaf의 조건명 칩 — 복수 조건 카드의 조건 셀렉트와 동일한 팝오버(2026-07-16 지시) */\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, critOpen, () => setCritOpen(true))}\n menuWidth={condMenuWidth}\n open={critOpen}\n onOpenChange={setCritOpen}\n trigger={\n <ChipTrigger open={critOpen}>\n {optLabel(criteriaOpts, node.criteria) ||\n optLabel(node.criteriaOptions, node.criteria) ||\n '조건'}\n </ChipTrigger>\n }\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current);\n }}\n />\n )}\n </Popover>\n ) : (\n <SelectChip\n options={criteriaOpts}\n value={node.criteria}\n onChange={(e) =>\n patch({\n criteria: e.target.value,\n value: null,\n valueTab: null,\n items: undefined, // 조건 종류가 바뀌면 묶음(복수 조건) 정보도 리셋\n individual: undefined,\n })\n }\n placeholder=\"조건\"\n searchable\n searchPlaceholder=\"조건 카드 검색\"\n menuWidth={160}\n tabOpens\n />\n )}\n </span>\n {variant === 'natural' ? <NaturalText>이(가)</NaturalText> : <Comparison>=</Comparison>}\n {isBundle ? (\n (() => {\n const tabMeta = condMeta.tabs?.find((t) => t.value === node.valueTab);\n const bundleText = `${tabMeta?.bundleLabel ?? tabMeta?.label ?? ''}, ${node.items?.length}개 조건 묶음`;\n const modalItems = (node.items ?? []).map((v) => ({\n value: v,\n label: optLabel(valueOpts, v) || String(v),\n }));\n return (\n <>\n {/* 묶음 칩 — 클릭하면 카드와 동일한 복수 조건 메뉴(탭+다중 선택)로 지정 항목 편집 */}\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => setCondOpen(true))}\n menuWidth={condMenuWidth}\n open={condOpen}\n onOpenChange={setCondOpen}\n trigger={<ChipTrigger open={condOpen}>{bundleText}</ChipTrigger>}\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current);\n }}\n />\n )}\n </Popover>\n {variant === 'natural' ? <NaturalText>이면,</NaturalText> : <Comparison>→</Comparison>}\n {/* 개별설정 — 점수 인풋 자리의 solid 인풋형 트리거(Figma 8535:11956), 클릭=항목별 점수 모달 */}\n {/* 개별설정 트리거 — data-indiv-for: 묶음 전환(리마운트) 후 포커스 이동용 앵커 */}\n <span data-indiv-for={node.id} className=\"inline-flex shrink-0 items-center\">\n <Input\n size=\"22\"\n width={68}\n value=\"개별설정\"\n readOnly\n className=\"cursor-pointer ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 has-[:focus-visible]:ring-2\"\n inputProps={{\n style: { cursor: 'pointer' },\n onClick: () => setIndivOpen(true),\n onKeyDown: (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setIndivOpen(true);\n }\n },\n }}\n />\n </span>\n {indivOpen && (\n <ScreeningIndividualSettingModal\n items={modalItems}\n value={node.individual ?? null}\n fitnessOnly={inIf} /* IF 안 조건 — 적합/부적합만 */\n onClose={() => setIndivOpen(false)}\n onConfirm={(v) => {\n patch({ individual: v });\n setIndivOpen(false);\n focusNextChainStop(startRef.current); // 묶음 설정 완료 — 다음 수식으로\n }}\n />\n )}\n </>\n );\n })()\n ) : (\n <>\n {/* 값 칩 — 클릭하면 카드의 조건 셀렉트와 동일한 설정 팝오버(탭+라디오), 저장 시 반영 */}\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => setCondOpen(true))}\n menuWidth={condMenuWidth}\n open={condOpen}\n onOpenChange={setCondOpen}\n trigger={\n <ChipTrigger open={condOpen}>\n {(() => {\n // 표시 형식은 카드의 조건 셀렉트와 동일 — 탭이 있으면 \"대상, 심한 장애인\"처럼 탭+옵션\n const tabLabel = condMeta.tabs.find((t) => t.value === node.valueTab)?.label;\n if (node.value != null) {\n const opt = optLabel(valueOpts, node.value);\n return (tabLabel ? `${tabLabel}, ${opt}` : opt) || '조건';\n }\n return tabLabel || '조건'; // disableOptions 탭(미보유 등)은 탭 라벨만\n })()}\n </ChipTrigger>\n }\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialOption={node.value}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)} /* 카드와 동일 메뉴 — 지정 저장=묶음 전환(2026-07-16) */\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current); // 값 미선택 이탈 — 다음 수식으로\n }}\n />\n )}\n </Popover>\n {variant === 'natural' ? <NaturalText>이면,</NaturalText> : <Comparison>→</Comparison>}\n {/* 점수 란 — Input 형태 유지(적합/부적합은 글자 표시). 클릭하면 카드와 동일한\n 가/감점 설정 팝오버(ScoreSettingMenu)가 열리고 저장 시에만 반영된다 */}\n <Popover\n className=\"shrink-0\"\n data-score-for={node.id}\n /* tabIndex 없음 — 포커스는 안의 readOnly 인풋이 받는다(체인 keydown만 공유) */\n onKeyDown={(e) => chainTriggerKey(e, scoreOpen, () => setScoreOpen(true))}\n menuWidth={200}\n open={scoreOpen}\n onOpenChange={setScoreOpen}\n trigger={\n <Input\n size=\"22\"\n width={scoreText ? 56 : 48}\n value={\n scoreText ??\n (node.points\n ? `${String(node.points).startsWith('-') ? '' : '+'}${node.points}점`\n : '')\n }\n readOnly\n placeholder=\"값\"\n // 팝오버 트리거 — 포인터 커서 + hover 링(readOnly라 Input 기본 hover가 꺼져 있어 직접 부여)\n className=\"cursor-pointer ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 has-[:focus-visible]:ring-2\"\n inputProps={{ style: { cursor: 'pointer' } }}\n />\n }\n >\n {(close) => (\n <ScoreSettingMenu\n initialType={scoreType}\n initialPoints={String(node.points ?? '').replace(/^-/, '')}\n disablePoints={inIf} /* IF 안 조건 — 적합/부적합만 */\n onCancel={close}\n onSave={({ type, points }) => {\n const raw = (points ?? '').replace(/^-/, '');\n patch({\n scoreType: type as FormulaLeafNode['scoreType'],\n points: type === 'minus' && raw ? `-${raw}` : type === 'plus' ? raw : '',\n });\n close();\n // 수식 완료 — 체인 규약대로 다음 수식의 시작 필드로 포커스 이동\n focusNextChainStop(startRef.current);\n }}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current); // 미선택 이탈 — 다음 수식으로\n }}\n />\n )}\n </Popover>\n {variant === 'natural' && <NaturalText>{scoreText ? '처리' : '부여'}</NaturalText>}\n </>\n )}\n </>\n )}\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </FormulaShell>\n );\n}\n","// ScreeningBuilderTemplate — 스크리닝 조건 빌더\n// (Figma ConditionCard 8243:88380 + 플로우 8389:117585 + Formula 8383:112161 · 그룹 8384:114161/114251)\n// 지원자 정보를 조건화하는 카드(ScreeningConditionCard)와 수식(ScreeningFormula — 둘 다 내부용)을 관리한다:\n// 좌측 카드 목록 —\n// - 카드마다 조건(tab radio list)·가/감점(radio list)을 세팅, grip 드래그 앤 드롭 순서 변경, 삭제\n// 우측 수식 영역 —\n// - 카드를 수식 영역에 드롭하면 수식이 추가된다(카드는 목록에 남아 반복 드롭 가능).\n// 일반 카드=IF leaf, 복수 조건 카드(지정 items)=묶음 leaf 한 줄(그룹 감싸기 없음 — 2026-07-16 개정)\n// - 수식 체크박스 선택(1개 이상) + 함수 선택 + '함수 적용' 클릭 → FN( 수식, … ) 그룹으로 묶인다\n// - 그룹도 체크박스를 가지므로 다른 수식·그룹과 다시 그룹핑될 수 있다(중첩)\n// 데이터 계약(JobPositionTemplate과 동일 철학):\n// - defaultCards: mount 1회 주입(리셋은 key 리마운트)\n// - onChange(cards) / onFormulaChange(formulas): 변경 시 스냅샷 반출\n// - ref: getCards() · addCard(card) · getFormulas()\nimport { useState, useRef, forwardRef, useImperativeHandle } from 'react';\nimport type { ComponentPropsWithoutRef } from 'react';\nimport { Plus, FolderInput, FolderOutput, X } from 'lucide-react';\nimport { ScrollArea } from './ScrollArea';\nimport { ToolBar, ToolBarDivider } from './ToolBar';\nimport { Accordion, AccordionItem } from './Accordion';\nimport { Popover } from './Popover';\nimport {\n PopoverMenu,\n PopoverMenuColumns,\n PopoverMenuColumn,\n PopoverMenuSection,\n} from './PopoverMenu';\nimport { List } from './List';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { ScreeningConditionCard } from './ScreeningConditionCard';\nimport { ScreeningFormula } from './ScreeningFormula';\nimport { FORMULA_GROUP_FUNCTIONS, hasPointsScore } from './formulaFunctions';\nimport type {\n ConditionTab,\n FormulaCatalog,\n FormulaLeafNode,\n FormulaNode,\n IndividualSetting,\n OptionItem,\n} from './formulaFunctions';\nimport { Select } from './Select';\nimport { Button } from './Button';\n\n// 조건 카드 데이터 — { id, name, conditionTabs?, conditionOptionsByTab?, conditionOptions?, condition?, score? }\nexport interface ScreeningCard {\n id: string;\n name: string;\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>;\n conditionOptions?: OptionItem[];\n conditionPlaceholder?: string;\n condition?: { tab?: string | null; option?: string | null; items?: string[] } | null;\n score?: { type: string; points?: string } | IndividualSetting | null;\n}\n// onChange 스냅샷 — 카드에서 id/name/condition/score만 추린 것\ntype ScreeningCardSnapshot = Pick<ScreeningCard, 'id' | 'name' | 'condition' | 'score'>;\n\n// 수식 영역(존) — 존마다 독립된 수식 트리\ninterface ScreeningZone {\n id: string;\n name: string;\n formulas: FormulaNode[];\n}\n\n// '추가' rich menu 항목/섹션 — [ [ { title, items: [...] } ] ] (컬럼 배열 > 섹션 배열 > 항목)\ninterface ScreeningCardMenuItem {\n name: string;\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>;\n conditionOptions?: OptionItem[];\n conditionPlaceholder?: string;\n}\ninterface ScreeningCardMenuSection {\n title: string;\n items: ScreeningCardMenuItem[];\n}\n\n// ref 핸들 — getCards() · addCard(card) · getFormulas()\nexport interface ScreeningBuilderTemplateHandle {\n getCards: () => ScreeningCardSnapshot[];\n addCard: (card: ScreeningCard) => void;\n getFormulas: () => ScreeningZone[];\n}\n\ninterface ScreeningBuilderTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n defaultCards?: ScreeningCard[]; // 초기 카드 목록(mount 1회) — { id, name, conditionTabs?, conditionOptionsByTab?, conditionOptions?, condition?, score? }\n onChange?: (cards: ScreeningCardSnapshot[]) => void; // 카드 순서/값/삭제 변경 시 스냅샷\n onFormulaChange?: (formulas: ScreeningZone[]) => void; // 수식 트리 변경 시 스냅샷\n cardWidth?: number | string; // 카드 너비: 숫자(px) | CSS 길이 | 'fill'\n formulaArea?: boolean; // 수식 영역 표시 여부 — false면 카드 목록만\n onAddCard?: () => void; // 카드 영역 '추가' 버튼 클릭 — cardMenu 미지정 시에만 사용(없으면 빈 카드 추가)\n // cardMenu — '추가' 버튼의 rich menu 데이터(컬럼 배열 > 섹션 배열 > 항목).\n // 항목 클릭 시 조건 카드가 맨 위에 등록되고,\n // 팝오버는 계속 열려 있어 연속 추가 가능(배경 클릭으로만 닫힘)\n cardMenu?: ScreeningCardMenuSection[][];\n boxHeight?: number; // 빌더 박스 고정 높이(px) — 내용이 넘치면 내부 스크롤(ScrollArea)\n}\n\n// HMR-safe id — 모듈 카운터는 HMR 리셋으로 중복되므로 시간+랜덤(JobPositionTemplate 판례)\nconst newId = (prefix: string) =>\n `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n\n// 카드의 조건 옵션 평면화 — 탭이 있으면 탭별 옵션을 합친다(수식 값 칩 옵션용)\nconst flatOptions = (card: ScreeningCard) =>\n card.conditionTabs?.length\n ? Object.values(card.conditionOptionsByTab ?? {}).flat()\n : (card.conditionOptions ?? []);\n\nexport const ScreeningBuilderTemplate = forwardRef<\n ScreeningBuilderTemplateHandle,\n ScreeningBuilderTemplateProps\n>(function ScreeningBuilderTemplate(\n {\n defaultCards = [],\n onChange,\n onFormulaChange,\n cardWidth = 375,\n formulaArea = true,\n onAddCard,\n cardMenu = [],\n boxHeight = 800,\n className = '',\n ...props\n },\n ref,\n) {\n // defaultCards는 mount 1회 시드(uncontrolled) — 외부에서 리셋하려면 key를 바꿔 리마운트한다.\n const [cards, setCards] = useState<ScreeningCard[]>(() =>\n defaultCards.map((c) => ({ condition: null, score: null, ...c })),\n );\n\n const snapshot = (list: ScreeningCard[]): ScreeningCardSnapshot[] =>\n list.map(({ id, name, condition, score }) => ({ id, name, condition, score }));\n const emit = (next: ScreeningCard[]) => {\n setCards(next);\n onChange?.(snapshot(next));\n };\n\n // 빌더 모드 — '수식'(드래그·그룹핑 빌더) / '자연어'(추후 자연어 생성 방식 — 현재 빈 영역)\n const [builderMode, setBuilderMode] = useState<'formula' | 'natural'>('formula');\n // 수식 영역(존) 목록 — 존마다 아코디언 리스트 하나 + 독립된 수식 트리. '수식 영역 추가'로 늘린다.\n const [zones, setZones] = useState<ScreeningZone[]>(() => [\n { id: newId('zone'), name: '수식 영역 1', formulas: [] },\n ]);\n const [checkedIds, setCheckedIds] = useState<string[]>([]); // 그룹핑 대상 체크(모든 존 공통 — id는 유일)\n const [groupFn, setGroupFn] = useState<string | null>(null);\n const emitZones = (next: ScreeningZone[]) => {\n setZones(next);\n onFormulaChange?.(next.map((z) => ({ id: z.id, name: z.name, formulas: z.formulas })));\n };\n const patchZone = (zoneId: string, fn: (formulas: FormulaNode[]) => FormulaNode[]) =>\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, formulas: fn(z.formulas) } : z)));\n const addZone = () =>\n emitZones([{ id: newId('zone'), name: `수식 영역 ${zones.length + 1}`, formulas: [] }, ...zones]); // 새 존은 맨 위에\n const renameZone = (zoneId: string, name: string) =>\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, name } : z)));\n const removeZone = (zoneId: string) => {\n const target = zones.find((z) => z.id === zoneId);\n const ids = target ? target.formulas.map((f) => f.id) : [];\n const rest = zones.filter((z) => z.id !== zoneId);\n // 마지막 존을 삭제하면(수식 세팅 상태에서만 가능) 빈 기본 존 하나로 대체 — 영역이 0개가 되지 않게\n emitZones(rest.length > 0 ? rest : [{ id: newId('zone'), name: '수식 영역 1', formulas: [] }]);\n setCheckedIds((prev) => prev.filter((x) => !ids.includes(x))); // 삭제된 존의 체크 정리\n };\n\n useImperativeHandle(ref, () => ({\n getCards: () => snapshot(cards),\n addCard: (card) => emit([{ condition: null, score: null, ...card }, ...cards]),\n getFormulas: () => zones.map((z) => ({ id: z.id, name: z.name, formulas: z.formulas })),\n }));\n\n const patchCard = (id: string, patch: Partial<ScreeningCard>) =>\n emit(cards.map((c) => (c.id === id ? { ...c, ...patch } : c)));\n const removeCard = (id: string) => emit(cards.filter((c) => c.id !== id));\n // 카드 영역 '추가' 버튼 — cardMenu가 있으면 rich menu 팝오버(아래), 없으면 onAddCard 위임/빈 카드 추가\n const handleAddCard = () => {\n if (onAddCard) return onAddCard();\n emit([{ id: newId('card'), name: '새 조건', condition: null, score: null }, ...cards]);\n };\n // rich menu 항목 → 조건 카드 등록(맨 위). 팝오버는 닫지 않아 연속 추가 가능.\n const addCardFromMenu = (item: ScreeningCardMenuItem) =>\n emit([\n {\n id: newId('card'),\n name: item.name,\n conditionTabs: item.conditionTabs,\n conditionOptionsByTab: item.conditionOptionsByTab,\n conditionOptions: item.conditionOptions,\n conditionPlaceholder: item.conditionPlaceholder,\n condition: null,\n score: null,\n },\n ...cards,\n ]);\n // rich menu 팝오버 내용 — 헤더 '추가'와 빈 상태 '조건 추가하기' 버튼이 공유\n const richMenuContent = cardMenu.length > 0 && (\n <PopoverMenu width=\"100%\">\n <PopoverMenuColumns>\n {cardMenu.map((column, ci) => (\n <PopoverMenuColumn key={ci}>\n {column.map((section, si) => (\n <PopoverMenuSection\n key={section.title}\n title={section.title}\n grow={si === column.length - 1}\n >\n {section.items.map((item) => (\n <List\n key={item.name}\n title={item.name}\n rightButton\n rightButtonIcon={Plus}\n rightButtonAriaLabel=\"추가\"\n onClick={() => addCardFromMenu(item)}\n onButtonClick={() => addCardFromMenu(item)}\n />\n ))}\n </PopoverMenuSection>\n ))}\n </PopoverMenuColumn>\n ))}\n </PopoverMenuColumns>\n </PopoverMenu>\n );\n\n // ── 카드 드래그 앤 드롭 — 수식 영역 드롭 전용. 카드끼리 순서 변경은 하지 않는다(2026-07-16 지시) ──\n const [dragId, setDragId] = useState<string | null>(null);\n const [dropHoverId, setDropHoverId] = useState<string | null>(null); // 드래그 중 하이라이트된 존 id\n const allowDragRef = useRef(false);\n\n // 카드 → IF leaf 수식 치환(드롭 시). 카드에 세팅된 조건/가점이 있으면 이어받는다.\n // 감점은 수식에서 음수로 표시(가점 10 → 10, 감점 10 → -10), 적합/부적합은 점수 란에 글자로 표시.\n // 조건 칩 옵션/구성은 드롭 시점의 왼쪽 카드 리스트 전체(추가된 카드 포함)를 싣는다.\n // 수식 카탈로그 — 조건 칩·값 칩이 참조하는 '현재 카드 영역' 라이브 목록(렌더마다 최신).\n // 카드가 추가/삭제되면 이미 만들어진 수식의 조건 칩 옵션에도 즉시 반영된다(2026-07-14 지시).\n const formulaCatalog: FormulaCatalog = {\n criteriaOptions: cards.map((c) => ({ value: c.id, label: c.name })),\n valueOptionsByCriteria: Object.fromEntries(cards.map((c) => [c.id, flatOptions(c)])),\n conditionMetaByCriteria: Object.fromEntries(\n cards.map((c) => [\n c.id,\n {\n tabs: c.conditionTabs ?? [],\n optionsByTab: c.conditionOptionsByTab ?? {},\n options: c.conditionOptions ?? [],\n },\n ]),\n ),\n };\n\n // leaf 공통 골격 — 스냅샷은 폴백용(카드가 나중에 삭제돼도 라벨·옵션 유지), 표시는 catalog(라이브) 우선\n const baseLeaf = (card: ScreeningCard): FormulaLeafNode => ({\n id: newId('formula'),\n kind: 'leaf',\n criteria: card.id,\n criteriaOptions: formulaCatalog.criteriaOptions,\n valueOptionsByCriteria: formulaCatalog.valueOptionsByCriteria,\n conditionMetaByCriteria: formulaCatalog.conditionMetaByCriteria,\n });\n const signedPoints = (type: string | null, points?: string) => {\n const raw = (points ?? '').replace(/^-/, '');\n return type === 'minus' && raw ? `-${raw}` : type === 'plus' ? raw : '';\n };\n const cardToLeaf = (card: ScreeningCard): FormulaLeafNode => {\n const type = card.score?.type ?? null;\n return {\n ...baseLeaf(card),\n value: card.condition?.option ?? null,\n valueTab: card.condition?.tab ?? null,\n points: signedPoints(type, (card.score as { points?: string } | null)?.points),\n scoreType: type as FormulaLeafNode['scoreType'], // 'fit'|'unfit'이면 점수 란에 적합/부적합 텍스트 표시\n };\n };\n // 복수 조건(지정) 카드 → '묶음 leaf'로 치환(2026-07-16 개정 — 함수 그룹 감싸기 취소, 단수 수식과 동일 골격):\n // [조건] = [지정보유, N개 조건 묶음] → [개별설정] 한 줄(조건이 100개여도 한 줄).\n // 항목별 점수·적용 함수는 leaf의 individual(개별설정 값)에 담는다. 그룹핑은 일반 수식처럼 툴바로.\n const cardToNode = (card: ScreeningCard): FormulaNode => {\n const items = card.condition?.items;\n if (items?.length) {\n return {\n ...baseLeaf(card),\n valueTab: card.condition?.tab ?? null,\n items: [...items], // 묶음 leaf 마커 — 지정된 항목 배열\n individual: card.score?.type === 'individual' ? (card.score as IndividualSetting) : null,\n };\n }\n return cardToLeaf(card);\n };\n // 카드는 드롭해도 목록에 남는다 — 같은 조건을 여러 번 드롭해 수식을 계속 추가할 수 있다(2026-07-16 지시)\n const dropCardToZone = (zoneId: string) => {\n const card = cards.find((c) => c.id === dragId);\n if (!card) return;\n patchZone(zoneId, (fs) => [...fs, cardToNode(card)]);\n };\n // 그룹 '조건 추가' 드롭 존용 — 드래그 중인 카드의 노드(없으면 null).\n // 복수 조건 카드는 여기서도 함수 그룹으로 들어간다(그룹 안 그룹 — 모델이 중첩 지원)\n const getDropLeaf = () => {\n const card = cards.find((c) => c.id === dragId);\n return card ? cardToNode(card) : null;\n };\n\n // ── 툴바 액션 — 전부 '그 존'의 체크만 보고 동작한다(다른 존 체크에 영향 주고받지 않음, 2026-07-14 지시) ──\n const zoneCheckedIds = (zone: ScreeningZone) =>\n zone.formulas.filter((f) => checkedIds.includes(f.id)).map((f) => f.id);\n // IF 적용 가능 판정 — 조건 1개 + 적합/부적합(또는 미설정) 값일 때만(가점/감점·복수 체크는 불가, 2026-07-16)\n const ifAllowedInZone = (zone: ScreeningZone) => {\n const picked = zone.formulas.filter((f) => checkedIds.includes(f.id));\n return picked.length === 1 && !hasPointsScore(picked[0]);\n };\n // CAPMAX/CAPMIN·FITBYSCORE/UNFITBYSCORE 적용 가능 판정(2026-07-16) — 점수(가/감점)가 있어야 한다.\n // 가/감점이면 단독 조건 수식 하나에도 적용 가능(2026-07-16 지시), 여러 개면 전부 점수 값일 때.\n const capAllowedInZone = (zone: ScreeningZone) => {\n const picked = zone.formulas.filter((f) => checkedIds.includes(f.id));\n return picked.length >= 1 && picked.every(hasPointsScore);\n };\n // 함수별 선택 가능 여부 — 툴바 함수 셀렉트·함수 적용 버튼이 공유.\n // FITBYSCORE/UNFITBYSCORE도 점수가 반환되는 수식 전용이라 CAP과 같은 판정(2026-07-16)\n const fnAllowedInZone = (fn: string, zone: ScreeningZone) => {\n if (fn === 'IF') return ifAllowedInZone(zone);\n if (fn === 'CAPMAX' || fn === 'CAPMIN' || fn === 'FITBYSCORE' || fn === 'UNFITBYSCORE')\n return capAllowedInZone(zone);\n return true;\n };\n\n // 함수 적용 — 이 존에서 체크된 최상위 수식(1개 이상)을 FN(…)으로 묶는다\n // (2026-07-16 지시: 하나만 체크해도 적용 가능 — 단일 수식에 함수를 씌우는 용도)\n const groupCheckedInZone = (zoneId: string) => {\n const zone = zones.find((z) => z.id === zoneId);\n if (!zone || groupFn == null) return;\n if (!fnAllowedInZone(groupFn, zone)) return; // IF·CAPMAX·CAPMIN — 함수별 적용 조건 확인\n const pickedIds = zoneCheckedIds(zone);\n if (pickedIds.length < 1) return;\n const picked = zone.formulas.filter((f) => pickedIds.includes(f.id));\n const rest = zone.formulas.filter((f) => !pickedIds.includes(f.id));\n const insertAt = zone.formulas.findIndex((f) => pickedIds.includes(f.id)); // 첫 체크 위치에 삽입\n const next = rest.slice();\n next.splice(Math.min(insertAt, next.length), 0, {\n id: newId('group'),\n kind: 'group',\n fn: groupFn,\n children: picked,\n });\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, formulas: next } : z)));\n setCheckedIds((prev) => prev.filter((x) => !pickedIds.includes(x))); // 이 존의 체크만 해제\n setGroupFn(null); // 그룹핑 완료 — 함수 셀렉트를 플레이스홀더로 리셋(이전 선택 잔류 방지)\n };\n\n // 함수 그룹 해제 — 이 존에서 체크된 그룹을 풀어 자식을 그 자리에 펼친다(한 겹만, 중첩 자식 유지)\n const ungroupCheckedInZone = (zoneId: string) => {\n const zone = zones.find((z) => z.id === zoneId);\n if (!zone) return;\n const pickedIds = zoneCheckedIds(zone);\n emitZones(\n zones.map((z) =>\n z.id === zoneId\n ? {\n ...z,\n formulas: z.formulas.flatMap((f) =>\n pickedIds.includes(f.id) && f.kind === 'group' ? f.children : [f],\n ),\n }\n : z,\n ),\n );\n setCheckedIds((prev) => prev.filter((x) => !pickedIds.includes(x)));\n };\n\n return (\n // 빌더 전체(카드+수식 영역)를 감싸는 라인 박스 — 흰 bg·gray.100 라인, 고정 높이+내부 스크롤(규칙 9)\n <div\n style={{ height: boxHeight }}\n className={`relative overflow-hidden rounded-round-4 border border-builder-area-box-line bg-builder-area-box-bg ${className}`}\n {...props}\n >\n {/* 카드 영역(gray.25 bg) | 1px gray.100 구분선 | 빌더 영역 — 스크롤은 영역별 분리(각자 ScrollArea) */}\n <div className=\"flex h-full items-stretch\">\n {/* ── 카드 목록 — 독립 스크롤 ── */}\n <div\n className=\"relative shrink-0 border-r border-builder-area-box-line bg-builder-area-card-area-bg\"\n // 카드 사이 여백에서 놓아도 드롭 수락 — 스냅백 방지(카드 쪽 onDragOver와 동일)\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n >\n {/* 고정 헤더 배경 — 수식 영역 헤더와 동일 구조(60px: 위 42px 불투명 + 아래 18px 페이드).\n 카드 영역 배경(gray.25)에 맞춰 card-area-bg → 투명으로 페이드 */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-[60px] flex-col\">\n <div className=\"h-[42px] bg-builder-area-card-area-bg\" />\n <div className=\"h-[18px] bg-gradient-to-b from-builder-area-card-area-bg to-builder-area-header-fade-bottom\" />\n </div>\n {/* 카드 영역 고정 헤더 — 조건 리스트 타이틀 + 추가 라인 버튼(onAddCard 미지정 시 빈 카드 맨 위 추가) */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10\">\n <div className=\"pointer-events-auto flex items-center justify-between px-spacing-7 pb-spacing-6 pt-spacing-7\">\n <span className=\"text-15 font-semibold text-font-icon-5\">조건 리스트</span>\n {cardMenu.length > 0 ? (\n /* rich menu — 항목/+ 클릭 시 카드 등록(팝오버 유지), 배경 클릭으로만 닫힘 */\n <Popover\n menuWidth={804}\n trigger={\n <Button variant=\"line\" size=\"32\" leftIcon={Plus}>\n 추가\n </Button>\n }\n >\n {richMenuContent}\n </Popover>\n ) : (\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={handleAddCard}>\n 추가\n </Button>\n )}\n </div>\n </div>\n <ScrollArea maxHeight={boxHeight - 2 /* 상하 보더 제외 */}>\n {/* 스페이서 — 콘텐츠가 헤더 아래로 지나가며 페이드, thumb 트랙도 헤더 아래에서 시작 */}\n <div data-scroll-sticky-top className=\"h-[60px]\" />\n <div className=\"flex flex-col gap-spacing-6 px-spacing-7 pb-spacing-7\">\n {/* 빈 상태 — 등록된 조건이 없을 때 카피+조건 추가하기 버튼을 영역 가운데 정렬(간격 8) */}\n {cards.length === 0 && (\n <div\n style={{\n width: typeof cardWidth === 'number' ? cardWidth : undefined,\n minHeight: boxHeight - 2 - 60 - 16 /* 보더·헤더·하단 패딩 제외한 가시 영역에 수직 중앙 */,\n }}\n className=\"flex flex-col items-center justify-center gap-spacing-5\"\n >\n <p className=\"text-14 text-font-icon-3\">등록된 조건이 없습니다</p>\n {cardMenu.length > 0 ? (\n <Popover\n menuWidth={804}\n trigger={\n <Button variant=\"line\" size=\"32\" leftIcon={Plus}>\n 조건 추가하기\n </Button>\n }\n >\n {richMenuContent}\n </Popover>\n ) : (\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={handleAddCard}>\n 조건 추가하기\n </Button>\n )}\n </div>\n )}\n {cards.map((card) => (\n <ScreeningConditionCard\n key={card.id}\n cardName={card.name}\n conditionTabs={card.conditionTabs ?? []}\n conditionOptionsByTab={card.conditionOptionsByTab ?? {}}\n conditionOptions={card.conditionOptions ?? []}\n conditionPlaceholder={card.conditionPlaceholder}\n conditionValue={card.condition}\n onConditionChange={(v) => {\n // 단일(가/감점)↔복수(개별설정) 조건 전환 시 기존 점수 설정은 무효 — 함께 리셋\n const wasMulti = card.score?.type === 'individual';\n const isMulti = !!v?.items?.length;\n patchCard(card.id, { condition: v, ...(wasMulti !== isMulti ? { score: null } : {}) });\n }}\n scoreValue={card.score}\n onScoreChange={(v) => patchCard(card.id, { score: v })}\n onDelete={() => removeCard(card.id)}\n width={cardWidth}\n dragging={dragId === card.id}\n dragHandleProps={{\n onMouseDown: () => {\n allowDragRef.current = true;\n },\n onMouseUp: () => {\n allowDragRef.current = false;\n },\n }}\n draggable\n onDragStart={(e) => {\n // grip 밖에서 시작한 드래그(텍스트 등)는 차단 — 핸들에서만 이동\n if (!allowDragRef.current) {\n e.preventDefault();\n return;\n }\n setDragId(card.id);\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(card.id));\n } catch {\n /* 일부 브라우저 setData 미지원 무시 */\n }\n }}\n onDragEnd={() => {\n setDragId(null);\n setDropHoverId(null);\n allowDragRef.current = false;\n }}\n onDragOver={(e) => {\n // 드래그 중엔 자기 자신 위 포함 항상 드롭 허용 — 미허용 상태로 놓으면 크롬 스냅백 재생\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n\n {/* ── 수식 영역 — 독립 스크롤. 존마다 드롭 영역, '수식 영역 추가'로 존을 늘린다 ── */}\n {formulaArea && (\n <div className=\"relative min-w-0 flex-1\">\n {/* 고정 헤더 — 세그먼트 탭·수식 영역 추가는 아래 스크롤과 무관하게 상단 고정.\n 배경(높이 60px): 위 70%는 흰색 불투명, 아래 30%만 흰→투명 페이드(2026-07-14 비율 지정).\n 스크롤 콘텐츠가 아래로 지나가며 사라진다. 오버레이는 클릭 통과(컨트롤만 수신).\n 그룹핑 액션은 체크 시 뜨는 플로팅 ToolBar로 일원화(2026-07-14) */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-[60px] flex-col\">\n <div className=\"h-[42px] bg-builder-area-header-fade-top\" />\n <div className=\"h-[18px] bg-gradient-to-b from-builder-area-header-fade-top to-builder-area-header-fade-bottom\" />\n </div>\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10\">\n {/* 컨트롤 행 높이 = 16 + 32 + 12 = 60px (배경·스크롤 스페이서와 동일) */}\n <div className=\"pointer-events-auto flex items-center px-spacing-7 pb-spacing-6 pt-spacing-7\">\n <div className=\"w-[180px]\">\n <SegmentedTabs\n items={[\n { value: 'formula', label: '수식 빌더' },\n { value: 'natural', label: '자연어 빌더' },\n ]}\n value={builderMode}\n onChange={setBuilderMode}\n />\n </div>\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={addZone} className=\"ml-auto\">\n 수식 영역 추가\n </Button>\n </div>\n </div>\n {/* 스크롤 영역은 박스 전체 — 콘텐츠가 헤더(60px) 아래로 지나가며 그라디언트에 페이드.\n 스페이서에 data-scroll-sticky-top — 세로 thumb 트랙이 헤더(페이드 영역) 아래에서 시작 */}\n <ScrollArea maxHeight={boxHeight - 2}>\n <div data-scroll-sticky-top className=\"h-[60px]\" />\n <div className=\"flex flex-col gap-spacing-5 px-spacing-7 pb-spacing-7\">\n {/* 존 = 아코디언 리스트(2026-07-14 전환) — 이름 편집·삭제는 AccordionItem 옵션,\n 바디에 수식 UI + 드롭 스트립. 수식/자연어 빌더는 같은 존 데이터를 공유하고\n 표기만 variant로 갈린다(2026-07-15) */}\n <Accordion>\n {zones.map((zone) => (\n <AccordionItem\n key={zone.id}\n title={zone.name}\n defaultOpen\n nameEditable\n onTitleChange={(t) => renameZone(zone.id, t)}\n deletable\n // 마지막 존은 비어 있을 때만 삭제 불가 — 수식이 세팅돼 있으면 삭제(=빈 기본 존으로 리셋) 가능\n deleteDisabled={zones.length <= 1 && zone.formulas.length === 0}\n onDelete={() => removeZone(zone.id)}\n >\n <div className=\"flex flex-col gap-spacing-5\">\n {zone.formulas.map((f, i) => (\n <ScreeningFormula\n key={f.id}\n node={f}\n root\n checked={checkedIds.includes(f.id)}\n onCheckChange={(on) =>\n setCheckedIds((prev) => (on ? [...prev, f.id] : prev.filter((x) => x !== f.id)))\n }\n onChange={(next) =>\n patchZone(zone.id, (fs) => fs.map((x, xi) => (xi === i ? next : x)))\n }\n onDelete={() => {\n patchZone(zone.id, (fs) => fs.filter((_, xi) => xi !== i));\n setCheckedIds((prev) => prev.filter((x) => x !== f.id));\n }}\n getDropLeaf={getDropLeaf}\n catalog={formulaCatalog}\n variant={builderMode === 'natural' ? 'natural' : 'formula'}\n />\n ))}\n {/* 드롭 스트립 — 카드를 끌어다 놓는 대상. 드래그 오버 시 hover 상태(bg·라인 진해짐).\n 이 존에 체크된 수식이 있으면 스트립 중앙에 플로팅 툴바(공통 ToolBar)가 뜬다. */}\n <div className=\"relative w-full\">\n <div\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n setDropHoverId(zone.id);\n }}\n onDragLeave={(e) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n setDropHoverId((prev) => (prev === zone.id ? null : prev));\n }}\n onDrop={(e) => {\n e.preventDefault();\n setDropHoverId(null);\n dropCardToZone(zone.id);\n }}\n className={`flex h-[78px] w-full items-center justify-center gap-spacing-3 rounded-round-4 border border-dashed p-spacing-6 text-14 text-builder-area-add-default-text transition-colors ${\n dropHoverId === zone.id ||\n zone.formulas.some((f) => checkedIds.includes(f.id)) // 툴바 표시 중에도 드롭(hover) 상태 디자인\n ? 'border-builder-area-add-hover-outline bg-builder-area-add-hover-bg'\n : 'border-builder-area-add-default-outline bg-builder-area-add-default-bg'\n }`}\n >\n <Plus size={16} strokeWidth={1.8} className=\"shrink-0\" />\n 다른 조건을 여기에 끌어다 놓기\n </div>\n {/* 플로팅 툴바 — 이 존의 체크에만 반응(표시·활성·그룹핑·해제·선택 해제 전부 존 단위) */}\n {zoneCheckedIds(zone).length > 0 && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center\">\n <ToolBar className=\"pointer-events-auto\">\n <Select\n width={200}\n options={FORMULA_GROUP_FUNCTIONS.map((f) => ({\n value: f,\n label: f,\n // IF=단일·적합/부적합 조건만 / CAPMAX·CAPMIN=점수 있는 묶음·그룹·복수 조건만\n disabled: !fnAllowedInZone(f, zone),\n }))}\n value={groupFn ?? ''}\n onChange={(e) => setGroupFn(e.target.value)}\n placeholder=\"함수 선택\"\n disabled={zoneCheckedIds(zone).length < 1}\n />\n <Button\n variant=\"ghost\"\n size=\"32\"\n leftIcon={FolderInput}\n disabled={\n groupFn == null ||\n zoneCheckedIds(zone).length < 1 ||\n !fnAllowedInZone(groupFn, zone) /* 선택 후 조건이 어긋나면 실행도 차단 */\n }\n onClick={() => groupCheckedInZone(zone.id)}\n >\n 함수 적용\n </Button>\n <ToolBarDivider />\n <Button\n variant=\"ghost\"\n size=\"32\"\n leftIcon={FolderOutput}\n disabled={\n !zone.formulas.some((f) => checkedIds.includes(f.id) && f.kind === 'group')\n }\n onClick={() => ungroupCheckedInZone(zone.id)}\n >\n 함수 해제\n </Button>\n <ToolBarDivider />\n <Button\n variant=\"ghost\"\n icon={X}\n aria-label=\"선택 해제\"\n showTooltip={false}\n onClick={() => {\n const ids = zoneCheckedIds(zone);\n setCheckedIds((prev) => prev.filter((x) => !ids.includes(x))); // 이 존만 해제\n }}\n />\n </ToolBar>\n </div>\n )}\n </div>\n </div>\n </AccordionItem>\n ))}\n </Accordion>\n </div>\n </ScrollArea>\n </div>\n )}\n </div>\n </div>\n );\n});\n","// SelectGroup — 셀렉트들을 일정 간격으로 묶는 컨테이너 (간격 규칙은 ButtonGroup과 동일)\n// 셀렉트들을 가로/세로로 모아 일관된 간격(기본 8px = spacing-5)으로 배치한다.\n// 간격은 등록된 spacing 토큰만 사용하도록 lookup으로 제한한다(임의값 금지).\nimport { Children, cloneElement, Fragment, isValidElement } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Select } from './Select';\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 (기본)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\n// fragment·배열·조건부(`{x && <>…</>}`) children을 재귀적으로 평탄화해\n// 실제 자식 요소(Select 등)만 추려낸다. (toArray는 fragment를 펴지 않으므로 직접 처리)\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = [];\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === Fragment) {\n out.push(...flattenChildren((child.props as { children?: ReactNode }).children));\n } else if (child != null && typeof child !== 'boolean') {\n out.push(child);\n }\n });\n return out;\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface SelectGroupProps 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 SelectGroup({\n children,\n direction = 'horizontal', // 'horizontal' | 'vertical'\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 셀렉트들 균등 분할)\n className = '',\n ...props\n}: SelectGroupProps) {\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 // 최종 자식 배열 — 모든 요소에 안정 key를 직접 부여한다.\n // fill이면 각 자식을 flex-1 래퍼로 감싸 컨테이너 폭을 균등 분할하고,\n // Select 자식은 width=\"100%\"로 복제해 래퍼를 채운다(명시된 width는 존중).\n const rendered = flattenChildren(children).map((c, i) => {\n if (!isValidElement(c)) return c;\n const key = c.key ?? `sg-${i}`;\n if (!isFill) return cloneElement(c, { key });\n // Select 자식의 width prop 주입용 좁힘(런타임 동작 동일 — 타입만 명시)\n const sc = c as ReactElement<{ width?: number | string }>;\n const el = c.type === Select ? cloneElement(sc, { width: sc.props.width ?? '100%' }) : c;\n return (\n <div key={key} className=\"min-w-0 flex-1\">\n {el}\n </div>\n );\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","// 한국 전화번호 하이픈 자동 삽입 — 숫자만 남기고 자릿수에 맞춰 구분자를 넣는다.\n// 휴대폰/지역(3자리 국번): 3-3-4(10자리) / 3-4-4(11자리), 서울(02): 2-3-4(9자리) / 2-4-4(10자리)\n// 입력 중(부분 자릿수)에도 점진적으로 포맷한다. 최대 11자리로 제한.\nexport function formatPhoneNumber(raw: string | number | null | undefined): string {\n const d = String(raw).replace(/\\D/g, '').slice(0, 11);\n if (!d) return '';\n if (d.startsWith('02')) {\n if (d.length <= 2) return d;\n if (d.length <= 6) return `${d.slice(0, 2)}-${d.slice(2)}`;\n if (d.length <= 9) return `${d.slice(0, 2)}-${d.slice(2, 5)}-${d.slice(5)}`;\n return `${d.slice(0, 2)}-${d.slice(2, 6)}-${d.slice(6, 10)}`;\n }\n if (d.length <= 3) return d;\n if (d.length <= 7) return `${d.slice(0, 3)}-${d.slice(3)}`;\n if (d.length <= 10) return `${d.slice(0, 3)}-${d.slice(3, 6)}-${d.slice(6)}`;\n return `${d.slice(0, 3)}-${d.slice(3, 7)}-${d.slice(7)}`;\n}\n","// SelectOrInput — \"등록된 값 선택 OR 직접 입력\" 상호 배타 필드 (Select + Input 조립, 규칙 4)\n// 마지막에 사용한 수단이 값을 가지며, 반대쪽은 디폴트 상태로 되돌아간다:\n// - Input에 직접 입력하면 → Select는 선택 전(placeholder) 상태로 리셋\n// - Select에서 값을 선택하면 → Input은 입력 전(빈) 상태로 리셋\n// 값 계약: value/onChange는 최종 문자열 하나(e.target.value). e.target.source('select'|'input'|null)도 함께 전달.\n// controlled일 때 표시 위치는 값이 options에 있으면 Select, 없으면 Input으로 유도된다.\n// EmailField / PhoneField 는 placeholder·inputMode 기본값만 다른 얇은 래퍼.\nimport { useMemo, useState } from 'react';\nimport type { ComponentPropsWithoutRef } from 'react';\nimport { Select } from './Select';\nimport type { SelectPlacement } from './Select';\nimport { Input } from './Input';\nimport { formatPhoneNumber } from '../utils/phone';\nimport type { OptionItem } from './formulaFunctions';\n\n// 값의 출처 — 마지막으로 사용한 수단\ntype SelectOrInputSource = 'select' | 'input' | null;\n\n// ...props는 루트 컨테이너(div)로 전달된다.\ninterface SelectOrInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: { target: { value: string; source: SelectOrInputSource } }) => void; // (e) => void — e.target.value(문자열) · e.target.source('select'|'input'|null)\n options?: (string | OptionItem)[]; // 등록된 값 목록: 문자열 | { value, label }\n showSelect?: boolean; // false면 Select 숨김 — 직접 입력 전용(Input이 전체 폭)\n showInput?: boolean; // false면 Input 숨김 — 등록값 선택 전용(Select가 전체 폭)\n selectPlaceholder?: string;\n inputPlaceholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string; // Input의 에러 툴팁으로 표시\n formatInput?: (raw: string) => string; // (raw) => string — 직접 입력값 변환(예: 전화번호 하이픈). 입력 시마다 적용\n width?: number | string; // 전체 너비: 숫자(px) | CSS 길이('100%' 등)\n selectWidth?: number | string; // Select 너비 — 나머지는 Input이 채움\n menuWidth?: number | string; // Select 드롭다운 너비(미지정 시 트리거와 동일)\n placement?: SelectPlacement;\n searchable?: boolean; // Select 검색 필터\n inputProps?: ComponentPropsWithoutRef<'input'>; // 내부 <input> 속성 통과(inputMode 등)\n}\n\nexport function SelectOrInput({\n value,\n defaultValue = '',\n onChange, // (e) => void — e.target.value(문자열) · e.target.source('select'|'input'|null)\n options = [], // 등록된 값 목록: 문자열 | { value, label }\n showSelect = true, // false면 Select 숨김 — 직접 입력 전용(Input이 전체 폭)\n showInput = true, // false면 Input 숨김 — 등록값 선택 전용(Select가 전체 폭)\n selectPlaceholder = '선택하세요',\n inputPlaceholder = '직접 입력하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '', // Input의 에러 툴팁으로 표시\n formatInput, // (raw) => string — 직접 입력값 변환(예: 전화번호 하이픈). 입력 시마다 적용\n width = 400, // 전체 너비: 숫자(px) | CSS 길이('100%' 등)\n selectWidth = 160, // Select 너비 — 나머지는 Input이 채움\n menuWidth, // Select 드롭다운 너비(미지정 시 트리거와 동일)\n placement = 'auto',\n searchable = false, // Select 검색 필터\n inputProps = {}, // 내부 <input> 속성 통과(inputMode 등)\n className = '',\n ...props\n}: SelectOrInputProps) {\n const opts = useMemo(\n () => options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o)),\n [options],\n );\n const isControlled = value !== undefined;\n // 값만 보고 출처를 추정(초기값·외부 주입값용) — 한쪽만 쓰면 그 수단으로 고정, 아니면 옵션 포함 여부\n const deriveSource = (val: string): SelectOrInputSource => {\n if (!val) return null;\n if (!showSelect) return 'input';\n if (!showInput) return 'select';\n return opts.some((o) => o.value === val) ? 'select' : 'input';\n };\n const [internal, setInternal] = useState(defaultValue);\n // 마지막으로 사용한 수단 — controlled여도 추적한다. 입력 중 값이 우연히 등록 옵션과\n // 일치해도(예: 전화번호 자동 포맷 도중) Select로 하이재킹되지 않도록 추정보다 우선.\n const [lastSource, setLastSource] = useState<SelectOrInputSource>(() => deriveSource(defaultValue));\n\n const current = value !== undefined ? value : internal;\n const source = !current\n ? null\n : !showInput\n ? 'select'\n : !showSelect\n ? 'input'\n : (lastSource ?? deriveSource(current));\n\n const emit = (val: string, src: SelectOrInputSource) => {\n const nextSource = val ? src : null;\n setLastSource(nextSource);\n if (!isControlled) setInternal(val);\n onChange?.({ target: { value: val, source: nextSource } });\n };\n\n // 상호 배타 표시 — 반대쪽 수단은 항상 디폴트 상태\n const selectValue = source === 'select' ? current : '';\n const inputValue = source === 'input' ? current : '';\n\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n\n return (\n <div className={`flex items-start gap-spacing-5 ${className}`} style={{ width: widthStyle }} {...props}>\n {showSelect && (\n <Select\n options={opts}\n value={selectValue}\n onChange={(e) => emit(e.target.value, 'select')}\n placeholder={selectPlaceholder}\n width={showInput ? selectWidth : '100%'} // Input이 없으면 Select가 전체 폭\n menuWidth={menuWidth}\n placement={placement}\n searchable={searchable}\n disabled={disabled}\n readOnly={readOnly}\n // Select 전용 모드에선 에러 툴팁을 Select가 담당(Input이 없으므로)\n error={!showInput && error}\n errorMessage={!showInput ? errorMessage : ''}\n />\n )}\n {showInput && (\n <div className=\"min-w-0 flex-1\">\n <Input\n value={inputValue}\n onChange={(e) => emit(formatInput ? formatInput(e.target.value) : e.target.value, 'input')}\n placeholder={inputPlaceholder}\n width=\"100%\"\n disabled={disabled}\n readOnly={readOnly}\n error={error}\n errorMessage={errorMessage}\n inputProps={inputProps}\n />\n </div>\n )}\n </div>\n );\n}\n\n// EmailField — 등록된 이메일 선택 or 직접 입력\nexport function EmailField({\n selectPlaceholder = '이메일 선택',\n inputPlaceholder = '이메일을 직접 입력하세요',\n inputProps = {},\n ...props\n}: SelectOrInputProps) {\n return (\n <SelectOrInput\n selectPlaceholder={selectPlaceholder}\n inputPlaceholder={inputPlaceholder}\n inputProps={{ inputMode: 'email', ...inputProps }}\n {...props}\n />\n );\n}\n\n// PhoneField — 등록된 전화번호 선택 or 직접 입력(하이픈 자동 삽입 — utils/phone.js)\ninterface PhoneFieldProps extends Omit<SelectOrInputProps, 'formatInput'> {\n formatInput?: ((raw: string) => string) | null; // 기본: 하이픈 자동 포맷(끄려면 null)\n}\n\nexport function PhoneField({\n selectPlaceholder = '전화번호 선택',\n inputPlaceholder = '전화번호를 직접 입력하세요',\n formatInput = formatPhoneNumber, // 기본: 하이픈 자동 포맷(끄려면 null)\n inputProps = {},\n ...props\n}: PhoneFieldProps) {\n return (\n <SelectOrInput\n selectPlaceholder={selectPlaceholder}\n inputPlaceholder={inputPlaceholder}\n formatInput={formatInput ?? undefined}\n inputProps={{ inputMode: 'tel', ...inputProps }}\n {...props}\n />\n );\n}\n","// SideNavigation — 사이드 내비게이션 (Figma side navigation 8200:51519 / side navigation button 8200:51452)\n// 버튼: [아이콘] [라벨] [NewTag] … [chevron ›] 한 행(높이 32, px spacing-6·py spacing-4, 내부 gap spacing-5).\n// state: default / hover(CSS) / select / disabled — 색은 side-nav-* 시멘틱 토큰만 사용.\n// line(attached): 컨테이너 우측 구분선에 붙는 형태(왼쪽만 라운드) / false면 독립형(전체 라운드).\n// select+line은 우측 1px 라인(side-nav-select-text)을 버튼이 직접 가진다(2026-07-06 Figma 개정) —\n// 컨테이너 회색 구분선(내부 absolute 라인) 위에 그 행 높이만큼 정확히 겹쳐 선택 표시가 된다.\n// none line(독립형)에는 없음. 이 라인은 border-r이 아니라 absolute 오버레이로 그린다 —\n// border는 border-box 콘텐츠 폭을 1px 줄여 선택 시 화살표가 왼쪽으로 밀린다(2026-07-06 사용자 지적).\n// 컨테이너: 세로 스택(gap spacing-2), width 180/220/260(또는 임의 px), line=우측 1px 구분선(side-nav-right-line).\n// 구분선은 border가 아니라 내부 absolute 라인으로 그린다 — 행(w-full)이 그 위까지 깔려서\n// 선택 행의 파란 오버레이 라인이 구분선과 같은 픽셀 열에 정확히 겹친다(border-r 방식이면 1px 어긋남).\n// add menu(+)는 컨테이너 내장 기능 — showAdd로 on/off, onAdd로 메뉴 추가 동작 연결(2026-07-06 지시).\n// 메뉴 목록은 ScrollArea(규칙 9) — 상위 DOM이 높이를 제한하면(h-full·고정 px 부모 등) 내비만 독립 스크롤되고,\n// 제한이 없으면 콘텐츠 높이 그대로(스크롤 없음). 추가(+) 행은 스크롤 밖에 고정된다.\nimport { ChevronRight, Plus } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { NewTag } from './Tag';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\n\nconst STATE_STYLE = {\n default:\n 'text-side-nav-default-text hover:bg-side-nav-hover-bg hover:text-side-nav-hover-text cursor-pointer ' +\n 'focus-visible:bg-side-nav-hover-bg focus-visible:text-side-nav-hover-text', // 포커스=호버(2026-07-16)\n select: 'bg-side-nav-select-bg text-side-nav-select-text cursor-pointer',\n disabled: 'text-side-nav-disabled-text cursor-not-allowed',\n};\n\nexport interface SideNavigationButtonProps extends ComponentPropsWithoutRef<'button'> {\n /** 좌측 아이콘(lucide 컴포넌트) — 없으면 생략 */\n icon?: ElementType; // lucide 외 커스텀 아이콘 컴포넌트도 허용\n /** select 상태(파란 텍스트·알파 배경) */\n selected?: boolean;\n /** 라벨 오른쪽 NewTag(N) 표시 */\n showNewTag?: boolean;\n /** NewTag color 패스스루 */\n newTagColor?: 'blue' | 'red' | 'black';\n /** 우측 chevron(›) */\n showArrow?: boolean;\n /** 긴 라벨 처리: 'ellipsis'(말줄임 + hover 전체 툴팁) | 'wrap'(멀티라인 줄바꿈) */\n overflow?: 'ellipsis' | 'wrap';\n /** true=컨테이너 우측 라인에 붙는 형태(왼쪽만 라운드) / false=전체 라운드 */\n line?: boolean;\n}\n\nexport function SideNavigationButton({\n children = 'side menu',\n icon: Icon,\n selected = false,\n disabled = false,\n showNewTag = false,\n newTagColor = 'blue',\n showArrow = true,\n overflow = 'ellipsis',\n line = true,\n onClick,\n className = '',\n ...props\n}: SideNavigationButtonProps) {\n const state = disabled ? 'disabled' : selected ? 'select' : 'default';\n const rounding = line ? 'rounded-l-round-4' : 'rounded-round-4';\n const selectLine = state === 'select' && line;\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onClick}\n aria-current={selected || undefined}\n className={`relative flex min-h-[32px] w-full items-center px-spacing-6 py-spacing-4 text-left transition-colors focus:outline-none ${rounding} ${STATE_STYLE[state]} ${className}`}\n {...props}\n >\n {selectLine && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 right-0 w-spacing-1 bg-side-nav-select-text\"\n />\n )}\n <span className=\"flex min-w-0 flex-1 items-center gap-spacing-5\">\n {Icon && <Icon size={16} strokeWidth={1.8} className=\"shrink-0\" />}\n {overflow === 'wrap' ? (\n <span className=\"min-w-0 whitespace-normal break-words text-14 leading-20\">{children}</span>\n ) : (\n <TruncatingText as=\"span\" className=\"truncate text-14 leading-20\">\n {children}\n </TruncatingText>\n )}\n {showNewTag && <NewTag color={newTagColor} />}\n </span>\n {showArrow && <ChevronRight size={16} strokeWidth={1.8} className=\"shrink-0\" />}\n </button>\n );\n}\n\nexport interface SideNavigationProps extends ComponentPropsWithoutRef<'nav'> {\n /** 180 | 220 | 260 (Figma variants) 또는 임의 px/CSS 길이 */\n width?: number | string;\n /** 우측 1px 구분선(side-nav-right-line) 표시 */\n line?: boolean;\n /** 'add menu'(+) 행 표시 — 메뉴 추가 기능 on/off */\n showAdd?: boolean;\n addLabel?: ReactNode;\n /** 'bottom' | 'top' — 추가 행 위치(템플릿은 top 사용) */\n addPosition?: 'bottom' | 'top';\n /** (+) 클릭 시 메뉴 추가 동작 */\n onAdd?: () => void;\n}\n\nexport function SideNavigation({\n children, // SideNavigationButton들\n width = 180,\n line = true,\n showAdd = true,\n addLabel = 'add menu',\n addPosition = 'bottom',\n onAdd,\n className = '',\n ...props\n}: SideNavigationProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n return (\n <nav\n style={{ width: widthStyle }}\n className={`relative flex flex-col gap-spacing-2 ${className}`}\n {...props}\n >\n {line && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 right-0 w-spacing-1 bg-side-nav-right-line\"\n />\n )}\n {showAdd && addPosition === 'top' && (\n <SideNavigationButton icon={Plus} showArrow={false} line={line} onClick={onAdd}>\n {addLabel}\n </SideNavigationButton>\n )}\n <ScrollArea className=\"min-h-0 flex-1\" contentClassName=\"max-h-full flex flex-col gap-spacing-2\">\n {children}\n </ScrollArea>\n {showAdd && addPosition === 'bottom' && (\n <SideNavigationButton icon={Plus} showArrow={false} line={line} onClick={onAdd}>\n {addLabel}\n </SideNavigationButton>\n )}\n </nav>\n );\n}\n","// SideNavigationTemplate — 좌측 사이드 내비게이션 + 우측 콘텐츠 2단 템플릿\n// (Figma side navigation Template 8202:61044 — [SideNavigation 180 | gap 16(spacing-7) | 콘텐츠 슬롯 FILL])\n// 메뉴는 데이터(menus)로 렌더하고 선택은 내부 상태 또는 controlled(selectedId/onSelect).\n// 추가 행(+)은 SideNavigation 내장 기능을 상단(top)으로 사용 — showAdd/addLabel/onAdd 패스스루.\nimport { useState, useContext } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType } from 'react';\nimport { ModalBodyMaxContext } from './modalContext';\nimport { SideNavigation, SideNavigationButton } from './SideNavigation';\n\n// 메뉴 항목 — [{ id, label, count?, icon?, showNewTag?, newTagColor?, disabled? }] — count는 0이면 표시 안 함\ninterface SideNavigationMenu {\n id: string;\n label: string;\n count?: number;\n icon?: ElementType;\n showNewTag?: boolean;\n newTagColor?: 'blue' | 'red' | 'black';\n disabled?: boolean;\n}\n\ninterface SideNavigationTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'onSelect'> {\n menus?: SideNavigationMenu[];\n selectedId?: string; // controlled 선택 id\n defaultSelectedId?: string; // uncontrolled 초기 선택\n onSelect?: (id: string) => void;\n navWidth?: number; // 좌측 내비 너비 (Figma 180/220/260)\n line?: boolean; // 내비 우측 구분선\n showAdd?: boolean; // 추가(+) 행 — 메뉴 추가 기능 on/off\n addLabel?: string;\n addPosition?: 'top' | 'bottom'; // 'top'(Figma 템플릿 기본) | 'bottom' — 추가 행 위치\n onAdd?: () => void;\n overflow?: 'ellipsis' | 'wrap'; // 메뉴 라벨 긴 경우 처리 — SideNavigationButton 패스스루\n // 템플릿 높이 — 숫자/CSS 길이(고정), 또는 'fill': 모달 안이면 ModalBody 가용 높이(컨텍스트)를\n // '최대치'로 사용(max-height) — 내용이 적으면 자연 높이, 가용 높이에 닿으면 멈추고 내비가 독립 스크롤.\n // 모달 밖 'fill'은 부모 100%. 숫자/CSS는 기존대로 고정 높이(2026-07-06 fill을 height→max-height로 변경).\n height?: number | string;\n}\n\nexport function SideNavigationTemplate({\n menus = [],\n selectedId: selectedProp, // controlled 선택 id\n defaultSelectedId, // uncontrolled 초기 선택\n onSelect,\n navWidth = 180,\n line = true,\n showAdd = true,\n addLabel = 'add menu',\n addPosition = 'top',\n onAdd,\n overflow = 'ellipsis',\n height,\n children, // 우측 콘텐츠 슬롯\n className = '',\n ...props\n}: SideNavigationTemplateProps) {\n const modalBodyMax = useContext(ModalBodyMaxContext);\n // fill=최대치(max-height — 내용 적으면 자연 높이), 숫자/CSS=고정(height)\n const isFill = height === 'fill';\n const fillMax = isFill ? (modalBodyMax != null ? `${modalBodyMax}px` : '100%') : null;\n const fixedHeight = !isFill ? (typeof height === 'number' ? `${height}px` : height) : null;\n\n const controlled = selectedProp !== undefined;\n const [internal, setInternal] = useState<string | undefined>(defaultSelectedId);\n // 첫 메뉴 폴백은 렌더 파생값 — mount 시 menus가 빈 배열(비동기 로드)이어도 도착 후 첫 메뉴가 선택된다\n const selected = controlled ? selectedProp : (internal ?? menus[0]?.id ?? null);\n const select = (id: string) => {\n if (!controlled) setInternal(id);\n onSelect?.(id);\n };\n\n return (\n <div style={fixedHeight ? { height: fixedHeight } : fillMax ? { maxHeight: fillMax } : undefined} className={`flex min-h-0 items-stretch gap-spacing-7 ${className}`} {...props}>\n <SideNavigation\n width={navWidth}\n line={line}\n showAdd={showAdd}\n addLabel={addLabel}\n addPosition={addPosition}\n onAdd={onAdd}\n className=\"shrink-0\"\n >\n {menus.map((m) => (\n <SideNavigationButton\n key={m.id}\n icon={m.icon}\n selected={selected === m.id}\n disabled={m.disabled}\n showNewTag={m.showNewTag}\n newTagColor={m.newTagColor}\n overflow={overflow}\n line={line}\n onClick={() => select(m.id)}\n >\n {m.count ? `${m.label} ${m.count}` : m.label}\n </SideNavigationButton>\n ))}\n </SideNavigation>\n {/* 우측 콘텐츠 슬롯 — 세로 스택(gap 12 = Figma 슬롯 규격) */}\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col gap-spacing-6\">{children}</div>\n </div>\n );\n}\n","// TableTemplate — 목록 페이지 템플릿 (table control + Table + Pagination 조합)\n// 버튼그룹·검색바·테이블·페이지네이션을 한 덩어리로 묶은 완전 옵션형 템플릿(규칙 4).\n// 각 요소를 props로 끄고 켤 수 있다:\n// - actions : 버튼그룹 내용(ReactNode 또는 (ctx) => ReactNode). null이면 버튼그룹 자체를 숨김\n// - title : 테이블 타이틀. 있으면 헤더 좌측(버튼그룹 왼쪽)에 표시(버튼그룹/검색바와 함께 가능)\n// - searchable : 검색바 on/off (false면 숨김)\n// - pagination : 페이지네이션 on/off (false면 전체 행을 한 번에 표시)\n// - bordered : 테이블 외곽선/라운드 타입\n// - selectable : 체크박스 선택 컬럼(+ 전체선택 헤더)\n// 검색 필터 · 페이지네이션 · 선택은 내부 상태로 동작하며, 선택만 selectedIds/onSelectChange로 제어 가능.\n// 좌(셀렉트그룹·버튼그룹) · 우(셀렉트그룹·버튼그룹·검색바) 컨트롤 행은 전부 꺼지면 통째로 사라진다.\n// - rightActions : 우측 슬롯(ReactNode 또는 (ctx)=>) — 검색바 대신/함께 임의 버튼 등을 우측에 배치\n// - selects / rightSelects : 좌/우 셀렉트 그룹 슬롯(ReactNode 또는 (ctx)=>) — SelectGroup으로 감싸 배치.\n// 좌=타이틀→셀렉트그룹→버튼그룹(셀렉트가 버튼그룹 앞), 우=버튼그룹→셀렉트그룹→검색바(셀렉트가 버튼그룹과 검색바 사이)\n// 색·간격·보더는 하위 컴포넌트(Table/Pagination 등)의 토큰을 그대로 따른다.\nimport { useMemo, useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { Table } from './Table';\nimport type { TableColumn } from './Table';\nimport { applyColumnFilters, applySort } from './tableView';\nimport type { TableRowData, TableSort } from './tableView';\nimport { Pagination } from './Pagination';\nimport { SearchBar } from './SearchBar';\nimport { ButtonGroup } from './ButtonGroup';\nimport { SelectGroup } from './SelectGroup';\n\n// 컬럼 정의 — Table 패스스루(headerMenu·sortable·filter 등 추가 옵션 허용)\n// Table의 TableColumn과 동일 계약 + 템플릿 전용 부가 옵션 통과(인덱스 시그니처)\ninterface TableTemplateColumn extends TableColumn {\n [option: string]: unknown;\n}\n\n// actions/rightActions/selects/rightSelects 함수형 슬롯에 넘기는 컨텍스트\ninterface TableTemplateContext {\n selectedIds: (string | number)[];\n clearSelection: () => void;\n visibleRows: TableRowData[];\n query: string;\n}\ntype TableTemplateSlot = ReactNode | ((ctx: TableTemplateContext) => ReactNode);\n\ninterface TableTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n columns?: TableTemplateColumn[];\n rows?: TableRowData[];\n rowKey?: string;\n // 요소 on/off\n actions?: TableTemplateSlot; // 버튼그룹 내용 — null이면 버튼그룹 숨김\n rightActions?: TableTemplateSlot; // 우측 슬롯(ReactNode 또는 (ctx)=>) — 검색바 대신/함께 우측에 버튼 등 배치\n selects?: TableTemplateSlot; // 좌측 셀렉트 그룹(ReactNode 또는 (ctx)=>) — 좌측 버튼그룹 앞(왼쪽)\n rightSelects?: TableTemplateSlot; // 우측 셀렉트 그룹(ReactNode 또는 (ctx)=>) — 우측 버튼그룹과 검색바 사이\n title?: ReactNode; // 테이블 타이틀 — 있으면 헤더 좌측(버튼그룹 왼쪽)에 표시\n searchable?: boolean; // 검색바\n pagination?: boolean; // 페이지네이션\n bordered?: boolean; // 테이블 외곽선 타입\n selectable?: boolean; // 체크박스 선택\n // 검색\n searchPlaceholder?: string;\n searchKeys?: string[]; // 검색 대상 컬럼 key 배열 — 기본: render 없는(텍스트) 컬럼 전체\n searchWidth?: number | string;\n // 페이지네이션\n page?: number; // 현재 페이지(1-base) — 외부 제어 시\n onPageChange?: (page: number) => void; // 페이지 변경 콜백\n defaultPageSize?: number; // 페이지당 행 수 초기값(uncontrolled)\n pageSize?: number; // 페이지당 행 수 — 외부 제어 시\n onPageSizeChange?: (size: number) => void; // 행 수 변경 핸들러\n pageSizeOptions?: number[]; // 행 수 선택 옵션(기본 5/10/20/50) — Pagination 내장 셀렉트에 노출\n showPageSize?: boolean; // Pagination의 '페이지 행' 셀렉트 표시 여부\n showTotal?: boolean; // Pagination의 좌측 '총 N개' 표시 여부\n maxButtons?: number; // 페이지 번호 노출 개수(윈도우)\n paginationClassName?: string; // 페이지네이션 영역 전용 클래스\n // 선택(controlled 옵션)\n selectedIds?: (string | number)[];\n onSelectChange?: (ids: (string | number)[]) => void;\n // 테이블 패스스루\n wrap?: boolean;\n minWidth?: number;\n maxHeight?: number | string;\n loading?: boolean;\n emptyMessage?: ReactNode;\n onRowClick?: (row: TableRowData) => void;\n buttonGroupGap?: '3' | '4' | '5' | '6' | '7'; // 좌/우 버튼그룹 간격 토큰 키 — 기본 '5'(8px, 셀렉트 그룹과 통일)\n selectGroupGap?: '3' | '4' | '5' | '6' | '7'; // 좌/우 셀렉트 그룹 간격 토큰 키 — 기본 '5'(8px, 버튼그룹과 통일)\n}\n\nexport function TableTemplate({\n columns = [],\n rows = [],\n rowKey = 'id',\n // 요소 on/off\n actions = null,\n rightActions = null,\n selects = null,\n rightSelects = null,\n title = null,\n searchable = true,\n pagination = true,\n bordered = false,\n selectable = false,\n // 검색\n searchPlaceholder = '검색어를 입력하세요',\n searchKeys,\n searchWidth = 200,\n // 페이지네이션\n page: pageProp,\n onPageChange,\n defaultPageSize = 10,\n pageSize: pageSizeProp,\n onPageSizeChange,\n pageSizeOptions,\n showPageSize = true,\n showTotal = true,\n maxButtons,\n paginationClassName = '',\n // 선택(controlled 옵션)\n selectedIds: selectedIdsProp,\n onSelectChange,\n // 테이블 패스스루\n wrap = false,\n minWidth = 720,\n maxHeight,\n loading = false,\n emptyMessage,\n onRowClick,\n buttonGroupGap = '5',\n selectGroupGap = '5',\n className = '',\n ...props\n}: TableTemplateProps) {\n const [query, setQuery] = useState('');\n\n // 헤더 필터(filter 컬럼)·정렬(headerMenu sortable) 상태 — 템플릿이 보유.\n // 페이지네이션 전에 전체 행에 적용해야 하므로 여기서 처리하고, 같은 상태를 Table에 controlled로 넘겨 헤더 UI를 동기화한다.\n const [colFilters, setColFilters] = useState<Record<string, unknown>>({});\n const [sort, setSort] = useState<TableSort | null>(null);\n\n // 현재 페이지 — controlled(pageProp) 또는 내부 상태.\n const pageControlled = pageProp !== undefined;\n const [internalPage, setInternalPage] = useState(1);\n const page = pageControlled ? pageProp : internalPage;\n const goToPage = (next: number) => {\n if (!pageControlled) setInternalPage(next);\n onPageChange?.(next);\n };\n\n // 행 수(pageSize) — controlled(pageSizeProp) 또는 내부 상태. 변경 시 1페이지로 리셋.\n const sizeControlled = pageSizeProp !== undefined;\n const [internalSize, setInternalSize] = useState(defaultPageSize);\n const pageSize = sizeControlled ? pageSizeProp : internalSize;\n const changePageSize = (size: number) => {\n if (!sizeControlled) setInternalSize(size);\n onPageSizeChange?.(size);\n goToPage(1);\n };\n\n // 선택 상태 — controlled(selectedIdsProp) 또는 내부 상태\n const selControlled = selectedIdsProp !== undefined;\n const [internalSel, setInternalSel] = useState<(string | number)[]>([]);\n const selectedIds = selControlled ? selectedIdsProp : internalSel;\n const setSelectedIds = (next: (string | number)[]) => {\n if (!selControlled) setInternalSel(next);\n onSelectChange?.(next);\n };\n\n // 검색 대상 key — 미지정 시 render 없는(텍스트) 컬럼 전체\n const keys = useMemo(\n () => searchKeys ?? columns.filter((c) => !c.render).map((c) => c.key),\n [searchKeys, columns],\n );\n\n // 검색 필터(searchable일 때만)\n const searched = useMemo(() => {\n if (!searchable || !query) return rows;\n const q = query.toLowerCase();\n return rows.filter((r) => keys.some((k) => String(r[k] ?? '').toLowerCase().includes(q)));\n }, [rows, query, searchable, keys]);\n\n // 헤더 필터 → 정렬을 전체(검색 후) 행에 적용한 뒤에 페이지를 자른다(헤더 요소가 현재 페이지 안에서만 동작하지 않게).\n const filtered = useMemo(\n () => applySort(applyColumnFilters(searched, columns, colFilters), sort),\n [searched, columns, colFilters, sort],\n );\n\n // 헤더 필터/정렬 변경 — 상태 갱신 + 1페이지로 리셋(보이는 범위가 바뀌므로).\n const handleFilterChange = (next: Record<string, unknown>) => {\n setColFilters(next);\n goToPage(1);\n };\n const handleSortChange = (next: TableSort | null) => {\n setSort(next);\n goToPage(1);\n };\n\n // 페이지네이션이 꺼지면 전체 행을 그대로 노출. 켜지면 현재 페이지 슬라이스만.\n const totalCount = filtered.length;\n const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));\n const safePage = Math.min(page, totalPages); // 필터/삭제로 범위를 벗어나면 보정\n const visibleRows = pagination\n ? filtered.slice((safePage - 1) * pageSize, (safePage - 1) * pageSize + pageSize)\n : filtered;\n\n // actions가 함수면 현재 컨텍스트를 넘겨준다(선택 항목으로 삭제 등 구현 가능).\n const ctx: TableTemplateContext = { selectedIds, clearSelection: () => setSelectedIds([]), visibleRows, query };\n const resolvedActions = typeof actions === 'function' ? actions(ctx) : actions;\n // 우측 슬롯 — 검색바 대신/함께 임의 요소(버튼 등)를 우측에 배치. actions와 동일하게 (ctx) => 함수 지원\n const resolvedRightActions = typeof rightActions === 'function' ? rightActions(ctx) : rightActions;\n // 좌/우 셀렉트 그룹 슬롯 — actions와 동일하게 (ctx) => 함수 지원\n const resolvedSelects = typeof selects === 'function' ? selects(ctx) : selects;\n const resolvedRightSelects = typeof rightSelects === 'function' ? rightSelects(ctx) : rightSelects;\n const showHeader =\n !!resolvedActions || searchable || !!title || !!resolvedRightActions || !!resolvedSelects || !!resolvedRightSelects; // 하나라도 있으면 헤더 렌더\n\n return (\n <div className={`flex flex-col gap-spacing-6 ${className}`} {...props}>\n {showHeader && (\n <div className=\"flex items-center justify-between gap-spacing-6\">\n {/* 좌: 타이틀 → 셀렉트그룹 → 버튼그룹 (셀렉트그룹이 버튼그룹 앞). 전부 없으면 빈 자리로 우측(검색바 등)을 고정.\n 그룹 간 간격 8px(spacing-5) — 그룹 내부 갭과 통일 */}\n <div className=\"flex items-center gap-spacing-5\">\n {title && <h3 className=\"flex h-[32px] items-center text-15 font-semibold text-font-icon-5\">{title}</h3>}\n {resolvedSelects && <SelectGroup gap={selectGroupGap}>{resolvedSelects}</SelectGroup>}\n {resolvedActions && <ButtonGroup gap={buttonGroupGap}>{resolvedActions}</ButtonGroup>}\n </div>\n {/* 우: rightActions 버튼그룹 → 셀렉트그룹 → 검색바(맨 오른쪽) — 셀렉트그룹은 버튼그룹과 검색바 사이.\n 좌 actions처럼 각 그룹으로 감싸 스타일 혼합 요소를 일정 간격으로 배치 */}\n {(searchable || resolvedRightActions || resolvedRightSelects) && (\n <div className=\"flex items-center gap-spacing-5\">\n {resolvedRightActions && (\n <ButtonGroup gap={buttonGroupGap}>{resolvedRightActions}</ButtonGroup>\n )}\n {resolvedRightSelects && (\n <SelectGroup gap={selectGroupGap}>{resolvedRightSelects}</SelectGroup>\n )}\n {searchable && (\n <SearchBar\n value={query}\n placeholder={searchPlaceholder}\n width={searchWidth}\n onChange={(e: ChangeEvent<HTMLInputElement>) => {\n setQuery(e.target.value);\n goToPage(1);\n }}\n />\n )}\n </div>\n )}\n </div>\n )}\n\n <Table\n columns={columns}\n rows={visibleRows}\n rowKey={rowKey}\n bordered={bordered}\n selectable={selectable}\n selectedIds={selectable ? selectedIds : undefined}\n onSelectChange={selectable ? setSelectedIds : undefined}\n filters={colFilters}\n onFilterChange={handleFilterChange}\n sort={sort}\n onSortChange={handleSortChange}\n wrap={wrap}\n minWidth={minWidth}\n maxHeight={maxHeight}\n loading={loading}\n emptyMessage={emptyMessage}\n onRowClick={onRowClick}\n />\n\n {pagination && (\n <Pagination\n page={safePage}\n onChange={goToPage}\n totalCount={totalCount}\n pageSize={pageSize}\n pageSizeOptions={pageSizeOptions}\n showPageSize={showPageSize}\n showTotal={showTotal}\n maxButtons={maxButtons}\n onPageSizeChange={changePageSize}\n className={paginationClassName}\n />\n )}\n </div>\n );\n}\n","// TextArea — 여러 줄 텍스트 입력 (Figma text area, solid 타입)\n// Input의 멀티라인 버전 — 상태(default/hover/focused/filled)는 CSS·실제 입력값으로 자동,\n// disabled/readOnly/error는 props. 하단에 글자수 카운터(Figma countTxt) 옵션.\n// 색·간격·라운드는 Input과 동일한 text-field-* 시멘틱 토큰 사용(새 토큰 없음).\n//\n// 스크롤바(규칙 9): textarea는 ScrollArea(div용 JS 오버레이)로 감쌀 수 없고, 네이티브 스크롤바는\n// hover가 브라우저 한계로 동작 안 한다 → **네이티브 스크롤(커서 추적 유지)은 그대로 두고\n// `.hide-native-scroll`로 기본 바만 숨긴 뒤, ScrollArea와 같은 오버레이 thumb를 textarea 스크롤에\n// 동기화해 얹는다**(scroll-bar 토큰, hover/드래그 동작). 커서 추적·hover 둘 다 만족.\n//\n// 에러 표현 규칙: Input과 동일 — 테두리 대신 박스 아래 absolute 툴팁 오버레이(레이아웃 영향 0).\nimport { useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, MouseEvent as ReactMouseEvent } from 'react';\nimport { listColors } from '../tokens';\nimport { Tooltip } from './Tooltip';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 2px(border-2 토큰), 색은 시멘틱 토큰.\nconst RING =\n 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// 오버레이 thumb 메트릭 — ScrollArea와 동일 상수/색\nconst THUMB_MIN = 24; // thumb 최소 길이(px)\nconst TRACK_PAD = 6; // thumb 양끝 여백(px)\nconst THUMB = { base: listColors['scroll-bar'], active: listColors['scroll-bar-hover'] };\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <textarea> 속성은 textareaProps로 전달한다.\ninterface TextAreaProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;\n placeholder?: string;\n rows?: number; // 기본 높이(줄 수). autoGrow=false면 고정 높이(초과 시 스크롤), true면 최소 높이\n autoGrow?: boolean; // 입력량에 따라 높이 자동 증가\n maxRows?: number; // autoGrow일 때 최대 행(초과 시 스크롤). 미지정 + autoGrow = 무한 증가\n maxLength?: number; // 최대 글자수(입력 제한 + 카운터 분모)\n showCount?: boolean; // 글자수 카운터 표시(Figma countTxt) — maxLength 있으면 'N/max', 없으면 'N'\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열. 미지정 시 320px\n textareaProps?: ComponentPropsWithoutRef<'textarea'>;\n}\n\nexport function TextArea({\n value,\n defaultValue,\n onChange,\n placeholder = '텍스트를 입력하세요',\n rows = 4, // 기본 높이(줄 수). autoGrow=false면 고정 높이(초과 시 스크롤), true면 최소 높이\n autoGrow = false, // 입력량에 따라 높이 자동 증가\n maxRows, // autoGrow일 때 최대 행(초과 시 스크롤). 미지정 + autoGrow = 무한 증가\n maxLength, // 최대 글자수(입력 제한 + 카운터 분모)\n showCount = true, // 글자수 카운터 표시(Figma countTxt) — maxLength 있으면 'N/max', 없으면 'N'\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 320, // 너비: 숫자(px) 또는 CSS 길이 문자열. 미지정 시 320px\n className = '',\n textareaProps = {},\n ...props\n}: TextAreaProps) {\n const interactive = !disabled && !readOnly;\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const taRef = useRef<HTMLTextAreaElement | null>(null);\n const dragRef = useRef(false);\n const [internalLen, setInternalLen] = useState((defaultValue ?? '').length);\n const len = value != null ? value.length : internalLen;\n const [thumb, setThumb] = useState({ pos: 0, size: 0, visible: false });\n const [active, setActive] = useState(false); // hover 또는 드래그 중\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면 플레이스홀더도 더 연하게(Figma disabled = 연한 회색)\n const placeholderColor = disabled\n ? 'placeholder:text-text-field-disabled-text'\n : 'placeholder:text-text-field-default-text';\n\n // 네이티브 스크롤 메트릭 → 오버레이 thumb 위치/크기 (ScrollArea 세로 thumb와 동일 계산)\n const updateThumb = useCallback(() => {\n const el = taRef.current;\n if (!el) return;\n const { scrollHeight, clientHeight, scrollTop } = el;\n if (scrollHeight <= clientHeight + 1) {\n setThumb((t) => (t.visible ? { ...t, visible: false } : t));\n return;\n }\n const trackH = clientHeight - TRACK_PAD * 2;\n const size = Math.max((clientHeight / scrollHeight) * trackH, THUMB_MIN);\n const pos = TRACK_PAD + (scrollTop / (scrollHeight - clientHeight)) * (trackH - size);\n setThumb({ pos, size, visible: true });\n }, []);\n\n // autoGrow: 내용에 맞춰 height 조정(최소 rows, autoGrow=false면 rows 고정, maxRows 초과 시 그 높이로 캡→스크롤).\n const autoSize = useCallback(() => {\n const el = taRef.current;\n if (!el) return;\n if (!autoGrow) {\n el.style.height = ''; // rows 속성이 높이를 제어\n return;\n }\n el.style.height = 'auto';\n const cs = getComputedStyle(el);\n const lh = parseFloat(cs.lineHeight) || 24;\n const pad = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);\n let h = Math.max(el.scrollHeight, rows * lh + pad); // 최소 rows\n if (maxRows != null) h = Math.min(h, maxRows * lh + pad); // 최대 maxRows\n el.style.height = `${h}px`;\n }, [autoGrow, maxRows, rows]);\n\n useLayoutEffect(() => {\n autoSize();\n updateThumb();\n const el = taRef.current;\n if (!el) return undefined;\n // RO는 읽기 전용(updateThumb)만 — autoSize가 height를 바꾸므로 RO에서 호출하면 루프\n const ro = new ResizeObserver(updateThumb);\n ro.observe(el);\n const onResize = () => {\n autoSize();\n updateThumb();\n };\n window.addEventListener('resize', onResize);\n return () => {\n ro.disconnect();\n window.removeEventListener('resize', onResize);\n };\n }, [autoSize, updateThumb, value, rows]);\n\n const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n if (value == null) setInternalLen(e.target.value.length);\n autoSize();\n updateThumb();\n onChange?.(e);\n };\n\n // 드래그 도중 언마운트 시 window 리스너·body userSelect 잔존 방지(ScrollArea와 동일, 2026-07-07 감사)\n const dragCleanupRef = useRef<(() => void) | null>(null);\n useEffect(() => () => dragCleanupRef.current?.(), []);\n\n // thumb 드래그 → textarea.scrollTop 동기화 (ScrollArea와 동일 방식)\n const startDrag = (e: ReactMouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n const el = taRef.current;\n if (!el) return;\n dragRef.current = true;\n setActive(true);\n const start = e.clientY;\n const startScroll = el.scrollTop;\n const maxScroll = el.scrollHeight - el.clientHeight;\n const trackH = el.clientHeight - TRACK_PAD * 2;\n // 트랙이 thumb보다 짧으면 분모 0/음수 → NaN 스크롤 방지(이동 무시)\n const denom = trackH - thumb.size;\n const ratio = denom > 0 ? maxScroll / denom : 0;\n const onMove = (ev: MouseEvent) => {\n el.scrollTop = startScroll + (ev.clientY - start) * 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 return (\n <div\n style={{ width: widthStyle }}\n className={`relative rounded-round-4 bg-text-field-default-bg pl-spacing-6 pr-spacing-4 transition-shadow ${\n interactive ? RING : 'cursor-not-allowed'\n } ${className}`}\n {...props}\n >\n {/* textarea가 박스 전체(상·하단 flush)를 채우는 스크롤 영역 — 위·아래 여백은 안쪽 패딩(py-spacing-4).\n 카운터는 우하단 오버레이라 입력 텍스트와 겹칠 수 있다(의도). */}\n <textarea\n ref={taRef}\n value={value}\n defaultValue={defaultValue}\n onChange={handleChange}\n onScroll={updateThumb}\n placeholder={placeholder}\n disabled={disabled}\n readOnly={readOnly}\n rows={rows}\n maxLength={maxLength}\n aria-invalid={error || undefined}\n className={`hide-native-scroll block w-full resize-none overflow-y-auto bg-transparent pt-spacing-4 pb-spacing-12 text-14 outline-none disabled:cursor-not-allowed read-only:cursor-default ${placeholderColor} ${textColor}`}\n {...textareaProps}\n />\n {/* 오버레이 thumb — 항상 렌더, opacity로 표시 제어(콘텐츠 폭을 줄이지 않음).\n 시각 6px + 클릭 영역 10px(투명 ::before 사방 2px 확장) — ScrollArea thumb와 동일(2026-07-06) */}\n <div\n onMouseDown={startDrag}\n onMouseEnter={() => setActive(true)}\n onMouseLeave={() => {\n if (!dragRef.current) setActive(false);\n }}\n className=\"absolute right-spacing-3 w-spacing-4 cursor-pointer rounded-round-00 before:absolute before:-inset-spacing-2 before:content-['']\"\n style={{\n top: `${thumb.pos}px`,\n height: `${thumb.size}px`,\n opacity: thumb.visible ? 1 : 0,\n pointerEvents: thumb.visible ? 'auto' : 'none',\n backgroundColor: active ? THUMB.active : THUMB.base,\n }}\n />\n\n {/* 글자수 카운터 — 우하단 오버레이(입력 텍스트와 겹칠 수 있음). readOnly면 숨김(Figma read only 변형) */}\n {showCount && !readOnly && (\n <p className=\"pointer-events-none absolute bottom-spacing-4 right-spacing-6 text-12 text-font-icon-3\">\n {len}\n {maxLength != null ? `/${maxLength}` : ''}\n </p>\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","// TimeField — 시간 입력 트리거 + 직접 입력 필드 (DateField의 시간 버전)\n// 시계 아이콘 + 'HH:MM' 값. 누르면 인풋 영역 없는 시/분 2depth 목록(TwoDepthList showInput=false)이 뜬다.\n// - 직접 타이핑: \"12:34\"·\"1:1\"·\"1230\"·\"011\"(→01:01) → Enter/포커스 아웃 시 'HH:MM'로 정규화.\n// - 숫자·콜론만 입력 가능. 필드 상태(default·hover·focused·filled·disabled·readOnly·error)는 tf-* 토큰.\nimport { useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Clock as ClockIcon } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { TwoDepthList } from './TwoDepthList';\nimport { Tooltip } from './Tooltip';\n\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\nconst pad2 = (n: number) => String(n).padStart(2, '0');\nconst ALLOWED_CHARS = /[\\d:]/;\nconst HOURS = Array.from({ length: 24 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}시` }));\nconst MINUTES = Array.from({ length: 60 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}분` }));\n\n// \"12:34\"·\"1:1\"(콜론) / \"1230\"·\"011\"(무구분, 시 2자리 + 분 1~2자리) → 'HH:MM' | null\nfunction parseTime(t: string | null | undefined): string | null {\n const s = (t ?? '').trim();\n const m = s.match(/^(\\d{1,2}):(\\d{1,2})$/) || s.match(/^(\\d{2})(\\d{1,2})$/);\n if (!m) return null;\n const h = Number(m[1]);\n const mn = Number(m[2]);\n if (h > 23 || mn > 59) return null;\n return `${pad2(h)}:${pad2(mn)}`;\n}\n\n// ...props는 Popover로 전달된다(팝오버 옵션 통과).\ninterface TimeFieldProps {\n value?: string | null; // 'HH:MM' | null(값 없음)\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n formatErrorMessage?: string;\n // 너비: 'hug' | 'fill' | 숫자(px) | CSS 길이. 미지정 시 120. 좁으면 말줄임(…).\n width?: number | string;\n className?: string;\n}\n\nexport function TimeField({\n value,\n defaultValue = null,\n onChange,\n placeholder = '시간을 선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n formatErrorMessage = 'HH:MM 형식으로 입력하세요',\n // 너비: 'hug' | 'fill' | 숫자(px) | CSS 길이. 미지정 시 120. 좁으면 말줄임(…).\n width,\n className = '',\n ...props\n}: TimeFieldProps) {\n const interactive = !disabled && !readOnly;\n const [open, setOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [text, setText] = useState('');\n const [parseError, setParseError] = useState(false);\n // Enter/ESC의 동기 blur가 onBlur commit을 중복/오발화시키지 않게 1회 건너뛰는 플래그(DateField와 동일)\n const skipBlurCommitRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [truncRect, setTruncRect] = useState<DOMRect | null>(null);\n\n // 값 (controlled/uncontrolled)\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState(defaultValue);\n const current = value !== undefined ? value : internalValue;\n const applyValue = (v: string | null) => {\n if (!isControlled) setInternalValue(v);\n onChange?.(v);\n setParseError(false);\n };\n\n const display = current || '';\n const isEmpty = !display;\n const [hh, mm] = (current || '').split(':');\n\n // 직접 입력 확정(Enter/blur)\n const commit = (raw: string) => {\n const t = raw.trim();\n if (t === '') {\n applyValue(null);\n return;\n }\n const v = parseTime(t);\n if (!v) {\n setParseError(true);\n return;\n }\n applyValue(v);\n };\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 const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : readOnly\n ? 'text-text-field-readonly-icon'\n : isEmpty\n ? 'text-text-field-default-text'\n : 'text-font-icon-5';\n\n const showErr = error || parseError;\n const errMsg = parseError ? formatErrorMessage : errorMessage;\n\n // 너비 — 미지정 시 120. hug/fill 지원.\n const effWidth = width ?? 120;\n const isHug = effWidth === 'hug';\n const isFill = effWidth === 'fill';\n const fixedWidth = isHug || isFill ? undefined : typeof effWidth === 'number' ? `${effWidth}px` : effWidth;\n const shown = focused ? text : display;\n const hugSize = Math.max((shown || placeholder).length + 1, 1);\n\n const trigger = (\n <div\n className={`relative ${isFill ? 'w-full' : isHug ? 'w-fit' : ''}`}\n style={fixedWidth ? { width: fixedWidth } : undefined}\n >\n <div\n className={`group flex min-h-[32px] ${isHug ? 'w-fit' : 'w-full'} items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n interactive ? `cursor-text ${RING}` : disabled ? 'cursor-not-allowed' : 'cursor-default'\n }`}\n >\n <ClockIcon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />\n <input\n ref={inputRef}\n type=\"text\"\n value={focused ? text : display}\n placeholder={placeholder}\n readOnly={!interactive}\n disabled={disabled}\n size={isHug ? hugSize : undefined}\n aria-invalid={showErr || undefined}\n onClick={(e) => e.stopPropagation()}\n onMouseEnter={(e) => {\n const el = e.currentTarget;\n if (!focused && display && el.scrollWidth > el.clientWidth + 1) {\n setTruncRect(el.getBoundingClientRect());\n }\n }}\n onMouseLeave={() => setTruncRect(null)}\n onFocus={(e) => {\n if (!interactive) return;\n setFocused(true);\n setParseError(false);\n setTruncRect(null);\n setText(display);\n setOpen(true);\n e.target.select();\n }}\n onChange={(e) => {\n const v = Array.from(e.target.value).filter((c) => ALLOWED_CHARS.test(c)).join('');\n setText(v);\n if (parseError) setParseError(false);\n }}\n onBlur={() => {\n if (!interactive) return;\n setFocused(false);\n // Enter(이미 커밋)/ESC(취소) 직후의 blur는 커밋을 건너뛴다\n if (skipBlurCommitRef.current) {\n skipBlurCommitRef.current = false;\n return;\n }\n commit(text);\n }}\n onKeyDown={(e) => {\n if (!interactive) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n commit(text);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n // 취소: 초안을 버리고 기존 표시값으로 복원(커밋 금지)\n setText(display);\n setParseError(false);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n }\n }}\n className={`bg-transparent text-14 outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed read-only:cursor-pointer ${textColor} ${\n isHug ? 'w-auto' : 'min-w-0 flex-1 text-ellipsis'\n }`}\n />\n </div>\n {showErr && errMsg && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errMsg}\n </Tooltip>\n </div>\n )}\n {truncRect &&\n !focused &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: truncRect.left,\n top: truncRect.top > 40 ? truncRect.top - 6 : truncRect.bottom + 6,\n transform: truncRect.top > 40 ? 'translateY(-100%)' : undefined,\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {display}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n\n return (\n <Popover\n open={open}\n onOpenChange={(o: boolean) => {\n setOpen(o);\n // ESC 닫힘 시 입력 편집도 해제(취소+blur) — 바로 재클릭하면 focus로 즉시 재활성(DateField와 동일)\n if (!o && inputRef.current && document.activeElement === inputRef.current) {\n setText(display);\n setParseError(false);\n skipBlurCommitRef.current = true;\n inputRef.current.blur();\n }\n }}\n disabled={!interactive}\n placement=\"auto\"\n menuWidth={140}\n trigger={trigger}\n className={`${isFill ? 'w-full' : ''} ${className}`}\n {...props}\n >\n {() => (\n // 인풋 영역 없는 시/분 2depth 목록(트리거가 입력을 가지므로 showInput=false)\n <TwoDepthList\n showInput={false}\n width={140}\n leftOptions={HOURS}\n leftValue={hh}\n onLeftChange={(h: string | number) => applyValue(`${h}:${mm || '00'}`)}\n rightOptions={MINUTES}\n rightValue={mm}\n onRightChange={(m: string | number) => applyValue(`${hh || '00'}:${m}`)}\n maxVisible={5}\n />\n )}\n </Popover>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/components/Divider.tsx","../src/components/Accordion.tsx","../src/components/CalendarDayButton.tsx","../src/components/Chip.tsx","../src/components/ConditionOrderSlot.tsx","../src/components/TwoDepthList.tsx","../src/components/DatePicker.tsx","../src/utils/datetime.ts","../src/components/DateField.tsx","../src/components/ErrorBoundary.tsx","../src/components/Label.tsx","../src/components/Field.tsx","../src/components/FormTemplate.tsx","../src/components/ImageUploadMenu.tsx","../src/components/ImageUploadButton.tsx","../src/components/tableView.ts","../src/components/Table.tsx","../src/components/JobPositionTemplate.tsx","../src/components/useFocusTrap.ts","../src/components/modalContext.ts","../src/components/Modal.tsx","../src/components/Pagination.tsx","../src/components/ToolBar.tsx","../src/components/SegmentedTabs.tsx","../src/components/ScreeningIndividualSettingModal.tsx","../src/components/formulaFunctions.ts","../src/components/usePanelKeyboard.ts","../src/components/ScreeningConditionCard.tsx","../src/components/ScreeningFormula.tsx","../src/components/ScreeningBuilderTemplate.tsx","../src/components/SelectGroup.tsx","../src/utils/phone.ts","../src/components/SelectOrInput.tsx","../src/components/SideNavigation.tsx","../src/components/SideNavigationTemplate.tsx","../src/components/TableTemplate.tsx","../src/components/TextArea.tsx","../src/components/TimeField.tsx"],"sourcesContent":["// Divider — 구분선 (Figma Divider: Direction Horizontal/Vertical × color Subtle/Default/Strong)\n// 1px 선을 채운 div로 그린다. 색은 divider 시멘틱 토큰 경유(subtle/default/strong).\n// - horizontal: 가로선(높이 1px, 부모 전체 폭)\n// - vertical: 세로선(너비 1px) — self-stretch로 flex 행 높이만큼 늘어난다.\n// (h-full 방식은 부모 높이가 auto면 0으로 계산돼 안 보였음 — 2026-07-07 개선.\n// flex/grid 밖에서 쓰면 className으로 높이를 직접 지정할 것)\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst COLOR = {\n subtle: 'bg-divider-subtle',\n default: 'bg-divider-default',\n strong: 'bg-divider-strong',\n};\n\ninterface DividerProps extends ComponentPropsWithoutRef<'div'> {\n direction?: 'horizontal' | 'vertical';\n color?: keyof typeof COLOR;\n}\n\nexport function Divider({\n direction = 'horizontal',\n color = 'default',\n className = '',\n ...props\n}: DividerProps) {\n const isVertical = direction === 'vertical';\n const shape = isVertical ? 'w-px self-stretch' : 'h-px w-full';\n return (\n <div\n role=\"separator\"\n aria-orientation={isVertical ? 'vertical' : 'horizontal'}\n className={`shrink-0 ${shape} ${COLOR[color] ?? COLOR.default} ${className}`}\n {...props}\n />\n );\n}\n","// Accordion — 아코디언 (Figma accordion 8416:35290 / accordion list SET 8416:35043)\n// 항목을 세로로 쌓고 위·아래·사이를 Divider(default)로 구분한다.\n// - Accordion : 컨테이너 — children(AccordionItem들)을 Divider로 감싸 배치\n// - AccordionItem : 항목 — [∨/∧ + 리스트 네임] 헤더 + 본문(펼침/접힘)\n// 옵션(완전 옵션화):\n// - nameEditable + onTitleChange : 이름 편집(SquarePen → 헤더가 Input+취소/저장으로 전환 — Figma name edit 변형)\n// - deletable + onDelete : 내부 삭제 버튼(Trash2)\n// - open/defaultOpen/onOpenChange: controlled/uncontrolled 펼침\n// - keepMounted / maxHeight : 볼륨 콘텐츠 대응 — 본문은 첫 펼침 때 마운트 후 유지(lazy),\n// keepMounted=true면 접힌 상태에서도 미리 마운트. maxHeight 지정 시\n// 본문 내부 스크롤(ScrollArea, 규칙 9)\n// 펼침 애니메이션은 grid-rows(0fr→1fr) — 콘텐츠 높이와 무관하게 부드럽고 측정 JS 불필요.\n// 색은 accordion-*(list-name)·divider 시멘틱 토큰만 사용. 항목 추가는 외부(사용처 버튼)에서 배열로 관리.\nimport { Children, Fragment, useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement } from 'react';\nimport { ChevronDown, SquarePen, Trash2 } from 'lucide-react';\nimport { Button } from './Button';\nimport { Input } from './Input';\nimport { Divider } from './Divider';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\n\nexport function Accordion({ children, className = '', ...props }: ComponentPropsWithoutRef<'div'>) {\n const items = Children.toArray(children) as ReactElement[];\n return (\n <div className={`flex w-full flex-col ${className}`} {...props}>\n <Divider />\n {items.map((child) => (\n <Fragment key={child.key ?? undefined}>\n {child}\n <Divider />\n </Fragment>\n ))}\n </div>\n );\n}\n\nexport interface AccordionItemProps extends ComponentPropsWithoutRef<'div'> {\n title?: string;\n defaultOpen?: boolean;\n /** controlled 펼침(선택) */\n open?: boolean;\n /** (open) => void */\n onOpenChange?: (open: boolean) => void;\n /** 이름 편집 버튼(SquarePen) 노출 */\n nameEditable?: boolean;\n /** (title) => void — 저장 시에만(취소/Esc는 폐기) */\n onTitleChange?: (title: string) => void;\n /** 삭제 버튼(Trash2) 노출 */\n deletable?: boolean;\n /** 삭제 버튼 비활성(예: 마지막 항목은 삭제 불가) */\n deleteDisabled?: boolean;\n /** () => void */\n onDelete?: () => void;\n /** 접힌 상태에서도 본문 마운트(폼 상태 유지 등) */\n keepMounted?: boolean;\n /** 본문 내부 스크롤 상한(px) — 미지정 시 자연 확장(페이지 스크롤) */\n maxHeight?: number | string;\n}\n\nexport function AccordionItem({\n title = '',\n children,\n defaultOpen = false,\n open: openProp,\n onOpenChange,\n nameEditable = false,\n onTitleChange,\n deletable = false,\n deleteDisabled = false,\n onDelete,\n keepMounted = false,\n maxHeight,\n className = '',\n ...props\n}: AccordionItemProps) {\n const controlled = openProp !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = controlled ? (openProp as boolean) : internalOpen;\n const setOpen = (v: boolean) => {\n if (!controlled) setInternalOpen(v);\n onOpenChange?.(v);\n };\n\n // 볼륨 콘텐츠 대응 — 본문은 첫 펼침 때 마운트하고 이후 유지(닫힘 애니메이션 중 내용 유지 + 초기 비용 지연)\n const [openedOnce, setOpenedOnce] = useState(defaultOpen || keepMounted);\n if (open && !openedOnce) setOpenedOnce(true); // 렌더 파생 래치(controlled 열림도 포착)\n const mounted = keepMounted || openedOnce;\n\n // 이름 편집 — 헤더가 Input + 취소/저장(ghost 텍스트 버튼)으로 전환(Figma name edit 변형)\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const startEdit = () => {\n setDraft(title ?? '');\n setEditing(true);\n };\n const cancelEdit = () => setEditing(false);\n const saveEdit = () => {\n setEditing(false);\n const next = draft.trim();\n if (next && next !== title) onTitleChange?.(next);\n };\n\n return (\n <div className={`w-full ${className}`} {...props}>\n {/* 헤더 */}\n {editing ? (\n <div className=\"flex w-full items-start gap-spacing-5 py-spacing-5\">\n <div className=\"min-w-0 flex-1\">\n <Input\n width=\"100%\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n inputProps={{\n autoFocus: true,\n onKeyDown: (e) => {\n if (e.key === 'Enter') saveEdit();\n if (e.key === 'Escape') cancelEdit();\n },\n }}\n />\n </div>\n <div className=\"flex shrink-0 items-center gap-spacing-3\">\n <Button variant=\"ghost\" size=\"32\" onClick={cancelEdit}>\n 취소\n </Button>\n <Button variant=\"ghost\" size=\"32\" onClick={saveEdit}>\n 저장\n </Button>\n </div>\n </div>\n ) : (\n <div className=\"flex w-full items-start gap-spacing-3 p-spacing-5\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n className=\"flex h-[32px] min-w-0 flex-1 cursor-pointer items-center gap-spacing-5 text-left\"\n >\n {/* Figma: 열림=∨(chevron-down) / 닫힘=∧(180° 회전) */}\n <ChevronDown\n size={16}\n strokeWidth={1.8}\n className={`shrink-0 text-font-icon-5 transition-transform ${open ? '' : 'rotate-180'}`}\n />\n <TruncatingText className=\"min-w-0 text-14 text-accordion-list-name\">{title}</TruncatingText>\n </button>\n {nameEditable && (\n <Button variant=\"ghost\" icon={SquarePen} aria-label=\"이름 편집\" onClick={startEdit} />\n )}\n {deletable && (\n <Button\n variant=\"ghost\"\n icon={Trash2}\n aria-label=\"리스트 삭제\"\n disabled={deleteDisabled}\n onClick={onDelete}\n />\n )}\n </div>\n )}\n\n {/* 본문 — grid 0fr→1fr 전환(콘텐츠 높이 무관 부드러운 펼침) */}\n <div\n className={`grid transition-[grid-template-rows] duration-200 ${\n open ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]'\n }`}\n >\n <div className=\"min-h-0 overflow-hidden\">\n {mounted && (\n <div className=\"pb-spacing-6\">\n {maxHeight ? <ScrollArea maxHeight={maxHeight}>{children}</ScrollArea> : children}\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n","// CalendarDayButton — 캘린더(DatePicker)의 날짜 한 칸 (Figma date picker / calendar day button, node 7592:3061)\n// 36×24 셀: 뒤쪽 좌/우 반쪽 배경(범위 연속 효과) + 중앙 24px 원형 위에 날짜 텍스트.\n// - 범위(start~end)를 가로로 끊김 없이 칠하기 위해, 셀을 좌/우 18px 두 반쪽으로 나눠\n// 반쪽 배경(range-bg)을 이어 붙이고, 그 위에 원형 하이라이트를 올린다.\n// 상태(state):\n// default — 이번 달 선택 가능 날짜(검정 텍스트, hover 시 회색 원)\n// muted — 흐린 회색 날짜(DatePicker에선 이전/다음 달 또는 선택 불가(disablePast 등))\n// today — 오늘(진회색 원 + 흰 텍스트)\n// selected — 단일 선택 / 범위 시작=끝(파란 원 + 흰 텍스트)\n// range-start — 범위 시작(파란 원 + 오른쪽 반쪽 range-bg)\n// range-end — 범위 끝(파란 원 + 왼쪽 반쪽 range-bg)\n// in-range — 범위 안(연한 파란 배경 + 파란 텍스트)\n// 색은 cal-* 시멘틱 토큰만 사용.\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\n// 좌/우 반쪽 배경 — 범위 연속 칠을 위한 lookup. (range-bg를 칠할지 day-bg(투명)로 둘지)\nconst HALF_FILL = {\n 'default': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'muted': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'today': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'selected': { left: 'bg-calendar-day-bg', right: 'bg-calendar-day-bg' },\n 'range-start': { left: 'bg-calendar-day-bg', right: 'bg-calendar-range-bg' },\n 'range-end': { left: 'bg-calendar-range-bg', right: 'bg-calendar-day-bg' },\n 'in-range': { left: 'bg-calendar-range-bg', right: 'bg-calendar-range-bg' },\n};\n\n// 중앙 원형 배경 + 텍스트 색 lookup.\nconst CIRCLE_STYLE = {\n 'default': { circle: 'bg-calendar-day-bg', text: 'text-calendar-day-text' },\n 'muted': { circle: 'bg-transparent', text: 'text-calendar-muted-text' },\n 'today': { circle: 'bg-calendar-today-bg', text: 'text-calendar-today-text' },\n 'selected': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'range-start': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'range-end': { circle: 'bg-calendar-selected-bg', text: 'text-calendar-selected-text' },\n 'in-range': { circle: 'bg-calendar-range-bg', text: 'text-calendar-range-text' },\n};\n\n// hover 시 원에 회색 배경을 줄 수 있는(=아직 강조되지 않은) 상태\nconst HOVERABLE = new Set(['default', 'muted']);\n\n// ...props는 루트 <button>으로 전달된다.\ninterface CalendarDayButtonProps extends ComponentPropsWithoutRef<'button'> {\n state?: keyof typeof HALF_FILL; // 'default' | 'muted' | 'today' | 'selected' | 'range-start' | 'range-end' | 'in-range'\n edge?: 'left' | 'right'; // 'left'(행 첫 칸) | 'right'(행 마지막 칸) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n}\n\nexport function CalendarDayButton({\n children,\n state = 'default', // 'default' | 'muted' | 'today' | 'selected' | 'range-start' | 'range-end' | 'in-range'\n edge, // 'left'(행 첫 칸) | 'right'(행 마지막 칸) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n disabled = false,\n onClick,\n className = '',\n ...props\n}: CalendarDayButtonProps) {\n const half = HALF_FILL[state] ?? HALF_FILL.default;\n const circle = CIRCLE_STYLE[state] ?? CIRCLE_STYLE.default;\n // 포커스=호버(2026-07-16) — group=버튼 자신이므로 group-focus-visible로 같은 배경\n const hover =\n !disabled && HOVERABLE.has(state)\n ? 'group-hover:bg-calendar-hover-bg group-focus-visible:bg-calendar-hover-bg'\n : '';\n // muted는 이미 회색(비활성)으로 읽히므로, disabled여도 추가 dimming(opacity)을 주지 않는다.\n const dimmed = disabled && state !== 'muted';\n // 행 가장자리 + 해당 반쪽이 범위색일 때만 바깥 모서리를 둥글게(줄바꿈으로 각지게 끊기는 것 방지)\n const leftRound =\n edge === 'left' && half.left.includes('range-bg') ? 'rounded-l-round-00' : '';\n const rightRound =\n edge === 'right' && half.right.includes('range-bg') ? 'rounded-r-round-00' : '';\n\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={disabled ? undefined : onClick}\n aria-disabled={disabled || undefined}\n className={`group relative flex h-[24px] w-[36px] items-center justify-center focus:outline-none ${\n disabled ? 'cursor-not-allowed' : 'cursor-pointer'\n } ${dimmed ? 'opacity-40' : ''} ${className}`}\n {...props}\n >\n {/* 뒤쪽 좌/우 반쪽 배경 — 범위 연속 칠 (행 가장자리는 바깥 모서리 둥글게) */}\n <span className={`absolute inset-y-0 left-0 w-1/2 ${half.left} ${leftRound}`} aria-hidden=\"true\" />\n <span className={`absolute inset-y-0 right-0 w-1/2 ${half.right} ${rightRound}`} aria-hidden=\"true\" />\n {/* 중앙 원형 하이라이트 + 날짜 텍스트 */}\n <span\n className={`relative z-10 flex size-[24px] items-center justify-center rounded-round-00 transition-colors ${circle.circle} ${hover}`}\n >\n <span className={`text-12 ${circle.text}`}>{children}</span>\n </span>\n </button>\n );\n}\n","// Chip — 제거형 칩 (Figma chip: state Default/hover/pressed × size 22 × color gray/red/blue/black\n// + 함수 계열 대응 green/violet/pink/orange — 2026-07-15 추가)\n// 텍스트 + X(삭제) 버튼. 색은 chip 시멘틱 토큰 경유(default/hover, pressed=default 재사용).\n// - color: gray(기본) · red · blue · black — 분류/의미 색(variant). recolor 금지, prop으로 표현(규칙 5)\n// - onRemove가 있으면 X 노출(클릭 시 삭제, 칩 onClick과 분리)\n// - onClick 주면 칩 전체가 클릭 가능(선택 등)\n// (disabled 변형은 Figma에 없어 미제공 — 필요 시 추후 추가)\nimport { X } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, MouseEvent } from 'react';\n// 칩 색 클래스 맵 — SelectChip(Select variant=\"chip\")과 공유(별도 파일, react-refresh 규칙 회피)\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport type { ChipColor } from './chipStyles';\n\ninterface ChipProps extends ComponentPropsWithoutRef<'span'> {\n onRemove?: (e: MouseEvent<HTMLButtonElement>) => void; // X 클릭 콜백 — 있으면 X 노출\n color?: ChipColor; // gray | red | blue | black\n removeAriaLabel?: string;\n}\n\nexport function Chip({\n children,\n onRemove, // X 클릭 콜백 — 있으면 X 노출\n onClick, // 칩 전체 클릭(선택 등)\n color = 'gray', // gray | red | blue | black\n removeAriaLabel = '삭제',\n className = '',\n ...props\n}: ChipProps) {\n const clickable = !!onClick;\n const base =\n 'inline-flex items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 ' +\n 'font-pretendard font-normal text-12 transition-colors';\n\n return (\n <span\n onClick={clickable ? onClick : undefined}\n className={`${base} ${CHIP_COLOR_CLASS[color] ?? CHIP_COLOR_CLASS.gray} ${clickable ? 'cursor-pointer' : ''} ${className}`}\n {...props}\n >\n <span className=\"whitespace-nowrap\">{children}</span>\n {onRemove && (\n <button\n type=\"button\"\n aria-label={removeAriaLabel}\n onClick={(e) => {\n e.stopPropagation();\n onRemove(e);\n }}\n className=\"flex shrink-0 items-center justify-center\"\n >\n <X size={12} strokeWidth={1.8} />\n </button>\n )}\n </span>\n );\n}\n","// ConditionOrderSlot / ConditionSlotCard — 조건 정렬 슬롯\n// (Figma ConditionSlotCard 8219:80364 / ConditionOrderSlot 8219:80583 — condition-slot-* 토큰)\n//\n// 카드(ConditionSlotCard): [⠿ grip + \"조건 N.\"] … [Switch] 헤더 + 세부 Select 스택(children).\n// - 세부 select는 children으로 필요만큼 추가한다(2개 고정 아님).\n// - state: default / hover(CSS) / pressed — pressed는 드래그 앤 드롭 중 유지되는 상태\n// (blue.400 라인·텍스트·아이콘, 2026-07-07 지시).\n// 컨테이너(ConditionOrderSlot): 회색 슬롯(bg gray.50, p 12) 안에 카드들을 배치하고 사이에 커넥터.\n// - direction: 'vertical'(↓ 커넥터) | 'horizontal'(› 커넥터) — 레이아웃 전환\n// - 드래그 앤 드롭(grip에서만 시작)으로 카드 순서 변경\n// - 카드별 Switch로 사용/미사용 — 미사용 카드는 적용 순번에서 빠지고 다음 조건이 번호를\n// 당겨받는다(카드의 물리적 위치는 유지). 제목 번호는 표시 순서상 '사용 중' 카드 기준 1..k\n// 자동 부여, 미사용 카드는 \"조건 -.\"로 표기.\n// - items 배열로 조건 카드를 필요만큼 추가한다.\nimport { Children, Fragment, cloneElement, isValidElement, useRef, useState } from 'react';\nimport type { ComponentPropsWithoutRef, HTMLAttributes, ReactElement, ReactNode } from 'react';\nimport { ChevronDown, ChevronRight, GripVertical } from 'lucide-react';\nimport { Switch } from './Switch';\n\n// 미사용(스위치 off) 카드의 세부 컨트롤에 disabled를 강제 주입한다(2026-07-07 지시).\n// body를 <>…</>(fragment)로 감싸 넘기는 경우가 많아 fragment는 한 겹씩 펴서 내려간다.\nfunction withDisabled(nodes: ReactNode): ReactNode {\n return Children.map(nodes, (c) => {\n if (!isValidElement(c)) return c;\n if (c.type === Fragment)\n return <Fragment>{withDisabled((c.props as { children?: ReactNode }).children)}</Fragment>;\n return cloneElement(c as ReactElement<{ disabled?: boolean }>, { disabled: true });\n });\n}\n\n// 카드 상태별 클래스 — hover는 CSS, pressed(드래그 중)는 상태로 적용. 아이콘·텍스트는 currentColor 상속.\nconst CARD_DEFAULT =\n 'border-condition-slot-card-default-line bg-condition-slot-card-default-bg text-condition-slot-card-default-text ' +\n 'hover:border-condition-slot-card-hover-line hover:bg-condition-slot-card-hover-bg hover:text-condition-slot-card-hover-text';\nconst CARD_PRESSED =\n 'border-condition-slot-card-pressed-line bg-condition-slot-card-pressed-bg text-condition-slot-card-pressed-text';\n\ninterface ConditionSlotCardProps extends ComponentPropsWithoutRef<'div'> {\n title?: string; // 헤더 제목 — 컨테이너가 활성 순번으로 자동 부여\n enabled?: boolean; // 사용/미사용(Switch) — 미사용이면 제목 dim + 세부 영역 반투명\n onEnabledChange?: (checked: boolean) => void;\n dragging?: boolean; // pressed 상태(드래그 앤 드롭 중 유지)\n width?: number | string; // Figma 기본 202px — 숫자(px) | CSS 길이\n dragHandleProps?: HTMLAttributes<HTMLSpanElement> | null; // grip에 붙는 핸들러(컨테이너가 주입 — 드래그 시작 허용 판정)\n children?: ReactNode; // 세부 Select들(세로 스택 gap 4=spacing-3) — 필요만큼 추가\n}\n\nexport function ConditionSlotCard({\n title = '조건 1.',\n enabled = true,\n onEnabledChange,\n dragging = false,\n width = 202,\n dragHandleProps = null,\n children,\n className = '',\n ...props\n}: ConditionSlotCardProps) {\n return (\n <div\n style={{ width: typeof width === 'number' ? `${width}px` : width }}\n className={`flex shrink-0 flex-col gap-spacing-6 rounded-round-4 border p-spacing-6 shadow-[0_2px_2px_0_rgba(0,0,0,0.12)] transition-colors ${\n dragging ? CARD_PRESSED : CARD_DEFAULT\n } ${className}`}\n {...props}\n >\n <div className=\"flex w-full items-center justify-between gap-spacing-4\">\n <div className=\"flex min-w-0 items-center gap-spacing-4\">\n <span\n aria-label=\"순서 변경\"\n className=\"flex shrink-0 cursor-grab items-center active:cursor-grabbing\"\n {...(dragHandleProps || {})}\n >\n <GripVertical size={16} strokeWidth={1.8} />\n </span>\n <span className={`truncate text-14 ${enabled ? '' : 'text-font-icon-3'}`}>{title}</span>\n </div>\n <Switch checked={enabled} onChange={(e) => onEnabledChange?.(e.target.checked)} />\n </div>\n {/* 세부 select 스택(gap 4px=spacing-3, Figma Slot) —\n 미사용(스위치 off)이면 자식 컨트롤에 disabled를 주입해 Select 자체 disabled 상태로 표시(값은 보존) */}\n <div className=\"flex w-full flex-col gap-spacing-3\">\n {enabled ? children : withDisabled(children)}\n </div>\n </div>\n );\n}\n\n// 조건 카드 항목 — [{ id, body }] — body=카드 안 세부 Select들(ReactNode)\ninterface ConditionOrderItem {\n id: string;\n body?: ReactNode;\n}\n\ninterface ConditionOrderSlotProps extends ComponentPropsWithoutRef<'div'> {\n items?: ConditionOrderItem[];\n direction?: 'vertical' | 'horizontal'; // 'vertical'(↓) | 'horizontal'(›)\n order?: string[]; // id[] — 카드 배치 순서(controlled). DnD로 변경\n defaultOrder?: string[]; // 초기 순서(uncontrolled) — 미지정 시 items 순서\n onOrderChange?: (ids: string[]) => void;\n enabledIds?: string[]; // id[] — 사용 중 조건(controlled)\n onEnabledChange?: (nextEnabledIds: string[], info: { id: string; enabled: boolean }) => void;\n titlePrefix?: string; // 카드 제목 접두(\"조건 N.\")\n cardWidth?: number | string;\n}\n\nexport function ConditionOrderSlot({\n items = [],\n direction = 'vertical',\n order,\n defaultOrder,\n onOrderChange,\n enabledIds,\n onEnabledChange,\n titlePrefix = '조건',\n cardWidth = 202,\n className = '',\n ...props\n}: ConditionOrderSlotProps) {\n const vertical = direction === 'vertical';\n const ids = items.map((it) => it.id);\n\n // 순서 — controlled/uncontrolled. 렌더 파생으로 items 변동(카드 추가/삭제)을 보정:\n // 사라진 id는 제거, 새 id는 뒤에 붙는다(비동기/동적 추가 대응).\n const orderControlled = order !== undefined;\n const [internalOrder, setInternalOrder] = useState<string[] | null>(defaultOrder ?? null);\n const rawOrder = orderControlled ? order : (internalOrder ?? ids);\n const effOrder = [\n ...rawOrder.filter((id) => ids.includes(id)),\n ...ids.filter((id) => !rawOrder.includes(id)),\n ];\n\n // 사용/미사용 — 내부적으로는 '미사용 목록'으로 추적해 새로 추가된 카드가 기본 사용 상태가 되게 한다.\n const enControlled = enabledIds !== undefined;\n const [internalDisabled, setInternalDisabled] = useState<string[]>([]);\n const isEnabled = (id: string) =>\n enControlled ? enabledIds.includes(id) : !internalDisabled.includes(id);\n const toggleEnabled = (id: string, next: boolean) => {\n if (!enControlled) {\n setInternalDisabled((prev) => (next ? prev.filter((d) => d !== id) : [...prev, id]));\n }\n const nextEnabled = effOrder.filter((oid) => (oid === id ? next : isEnabled(oid)));\n onEnabledChange?.(nextEnabled, { id, enabled: next });\n };\n\n // 드래그 앤 드롭 — grip에서만 시작(allowDragRef), 드래그 중 카드가 pressed 상태를 유지하고\n // 다른 카드 위로 들어가면 그 자리로 라이브 재배치된다.\n const [dragId, setDragId] = useState<string | null>(null);\n const allowDragRef = useRef(false);\n const moveTo = (srcId: string, dstId: string) => {\n if (srcId === dstId) return;\n const si = effOrder.indexOf(srcId);\n const di = effOrder.indexOf(dstId);\n if (si < 0 || di < 0) return;\n // 이동 방향에 따라 hover 카드의 앞/뒤에 삽입한다. 항상 '앞'에 꽂으면 앞→뒤 이동 시\n // src 제거로 dst가 한 칸 당겨져 제자리에 되꽂혀 순서가 안 바뀐다(2026-07-07 사용자 지적).\n const next = effOrder.filter((x) => x !== srcId);\n const base = next.indexOf(dstId);\n next.splice(si < di ? base + 1 : base, 0, srcId);\n if (!orderControlled) setInternalOrder(next);\n onOrderChange?.(next);\n };\n\n const Connector = vertical ? ChevronDown : ChevronRight;\n\n // 제목 번호 — 표시 순서상 '사용 중' 카드에만 1..k 부여(미사용은 번호에서 빠지고 다음이 당겨받음)\n let seq = 0;\n\n return (\n <div\n className={`inline-flex ${vertical ? 'flex-col' : 'flex-row'} items-center gap-spacing-2 rounded-round-4 bg-condition-slot-slot-bg p-spacing-6 ${className}`}\n {...props}\n // 커넥터·여백 위에서 놓아도 드롭 수락 — 카드 밖 드롭의 스냅백 방지(카드 쪽 onDragOver와 동일)\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n >\n {effOrder.map((id, i) => {\n const item = items.find((it) => it.id === id);\n if (!item) return null;\n const enabled = isEnabled(id);\n const title = enabled ? `${titlePrefix} ${++seq}.` : `${titlePrefix} -.`;\n return (\n <div key={id} className={`flex ${vertical ? 'flex-col' : 'flex-row'} items-center gap-spacing-2`}>\n {i > 0 && (\n <Connector\n size={16}\n strokeWidth={1.8}\n className=\"shrink-0 text-condition-slot-card-default-icon\"\n />\n )}\n <ConditionSlotCard\n title={title}\n enabled={enabled}\n onEnabledChange={(next) => toggleEnabled(id, next)}\n dragging={dragId === id}\n width={cardWidth}\n dragHandleProps={{\n onMouseDown: () => {\n allowDragRef.current = true;\n },\n onMouseUp: () => {\n allowDragRef.current = false;\n },\n }}\n draggable\n onDragStart={(e) => {\n // grip 밖에서 시작한 드래그(텍스트 등)는 차단 — 핸들에서만 이동\n if (!allowDragRef.current) {\n e.preventDefault();\n return;\n }\n setDragId(id);\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(id));\n } catch {\n /* 일부 브라우저(구IE 계열) setData 미지원 무시 */\n }\n }}\n onDragEnd={() => {\n setDragId(null);\n allowDragRef.current = false;\n }}\n onDragOver={(e) => {\n // 드래그 중엔 자기 자신 위 포함 항상 드롭 허용 — 미허용 상태로 놓으면\n // 크롬이 고스트를 원위치로 되돌리는 스냅백 애니메이션을 재생한다(2026-07-08 지적)\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n onDragEnter={() => {\n if (dragId != null && dragId !== id) moveTo(dragId, id);\n }}\n >\n {item.body}\n </ConditionSlotCard>\n </div>\n );\n })}\n </div>\n );\n}\n","// TwoDepthList — 입력 영역 + 좌/우 2개 컬럼 목록 팝오버 (Figma 2depth list, node 7595:6736 / 7595:7798)\n// 상단 input area는 좌/우 \"분리 인풋 + 구분자(., :)\" 구조 — [Input] . [Input] (2026-07-02 디자인 개정).\n// 그 아래 두 개의 ListGroup을 1px 틈(gap-spacing-1)으로 나란히 둔다.\n// - year_month(폭 160): 좌=연도, 우=월, 구분자 '.' / time(폭 140): 좌=시, 우=분, 구분자 ':'\n// - 각 컬럼은 ListGroup(maxVisible 초과 시 내부 스크롤). 선택 행은 List selected(파란 텍스트).\n// 외부 계약은 합성 문자열 그대로 유지: inputValue('2026.7'), onInputApply('2026.7')→정규화 문자열.\n// 컨테이너 색은 list-popover-* / list-group-* 시멘틱 토큰(PopoverMenu와 동일 규칙).\nimport { useEffect, useRef, useState } from 'react';\nimport type {\n ChangeEvent,\n ComponentPropsWithoutRef,\n FocusEvent,\n KeyboardEvent,\n ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Input } from './Input';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Tooltip } from './Tooltip';\n\n// 컬럼 옵션 한 개 — { value, label, disabled? }\ninterface TwoDepthOption {\n value: string | number;\n label: ReactNode;\n disabled?: boolean;\n}\n\n// onInputApply 실패 반환형 — 해당 파트 인풋 아래에 그 문구로 툴팁\ninterface TwoDepthApplyError {\n error: 'left' | 'right';\n message?: string;\n}\n\ninterface ColumnProps {\n options: TwoDepthOption[];\n value?: TwoDepthOption['value'];\n onChange?: (value: TwoDepthOption['value']) => void;\n maxVisible?: number;\n}\n\n// 한 컬럼 — 옵션 목록 + 선택 행 자동 스크롤(열릴 때 선택값이 보이도록).\nfunction Column({ options, value, onChange, maxVisible }: ColumnProps) {\n const ref = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const el = ref.current?.querySelector('[aria-selected=\"true\"]');\n el?.scrollIntoView({ block: 'nearest' });\n }, [value]);\n\n return (\n <div ref={ref} className=\"min-w-0 flex-1\">\n <ListGroup maxVisible={maxVisible}>\n {options.map((opt) => (\n <List\n key={opt.value}\n title={opt.label}\n selected={opt.value === value}\n disabled={opt.disabled}\n onClick={() => onChange?.(opt.value)}\n data-value={opt.value}\n />\n ))}\n </ListGroup>\n </div>\n );\n}\n\n// 합성 문자열 ↔ 좌/우 파트 변환 — 첫 구분자 기준 2조각\nconst splitParts = (text: string, separator: string): [string, string] => {\n const i = String(text).indexOf(separator);\n return i < 0 ? [String(text), ''] : [String(text).slice(0, i), String(text).slice(i + 1)];\n};\n\ninterface TwoDepthListProps extends ComponentPropsWithoutRef<'div'> {\n // 상단 입력 영역 — 현재 선택을 좌/우 분리 인풋으로 보여주며, 기본적으로 직접 입력(타이핑)도 가능하다.\n inputValue?: string;\n separator?: string; // 좌/우 파트 구분자 표시·합성 기준 — year_month '.' / time ':'\n showInput?: boolean; // 상단 입력 영역 표시 여부(false면 좌/우 컬럼만 — 트리거가 따로 입력을 가질 때)\n editable?: boolean; // 상단 input 직접 입력 허용(false면 readOnly로 값만 표시)\n onInputChange?: (e: ChangeEvent<HTMLInputElement>) => void; // (e) => void — 입력 변경(raw 이벤트)\n // (text) => string | null — 합성 텍스트('2026.7')로 좌/우 값을 적용하고 \"정규화된 표시 문자열\"을 반환.\n // 형식이 틀리면 null/false. 파싱은 호출부가 담당(연.월/시:분 등 도메인별).\n // 타이핑 즉시(라이브) 적용되고, 입력 영역을 떠날 때 반환된 정규화 문자열로 표시값을 보정한다.\n // 실패 시 null(마지막 편집 인풋에 errorMessage 툴팁) 또는 { error:'left'|'right', message? }\n // (해당 파트 인풋 아래에 그 문구로 툴팁 — 파트별 문구는 호출부가 지정).\n onInputApply?: (text: string) => string | boolean | TwoDepthApplyError | null | undefined;\n errorMessage?: string; // 파트 지정이 없을 때의 기본 툴팁 메시지\n // 파트 입력 허용 문자(문자 1개씩 test) — 기본은 숫자만(구분자는 UI가 표시하므로 입력 불필요).\n allowedChars?: RegExp | null;\n inputPlaceholder?: string; // 'YYYY.MM'·'HH:MM'처럼 구분자 포함 문자열 — 구분자로 쪼개 좌/우 placeholder로 사용\n inputProps?: ComponentPropsWithoutRef<'input'>;\n // 좌측 컬럼\n leftOptions?: TwoDepthOption[]; // [{ value, label, disabled? }]\n leftValue?: TwoDepthOption['value'];\n onLeftChange?: (value: TwoDepthOption['value']) => void;\n // 우측 컬럼\n rightOptions?: TwoDepthOption[]; // [{ value, label, disabled? }]\n rightValue?: TwoDepthOption['value'];\n onRightChange?: (value: TwoDepthOption['value']) => void;\n maxVisible?: number; // 각 컬럼 표시 행 수(초과 시 내부 스크롤)\n width?: number | string;\n}\n\nexport function TwoDepthList({\n // 상단 입력 영역 — 현재 선택을 좌/우 분리 인풋으로 보여주며, 기본적으로 직접 입력(타이핑)도 가능하다.\n inputValue = '',\n separator = ':', // 좌/우 파트 구분자 표시·합성 기준 — year_month '.' / time ':'\n showInput = true, // 상단 입력 영역 표시 여부(false면 좌/우 컬럼만 — 트리거가 따로 입력을 가질 때)\n editable = true, // 상단 input 직접 입력 허용(false면 readOnly로 값만 표시)\n onInputChange, // (e) => void — 입력 변경(raw 이벤트)\n // (text) => string | null — 합성 텍스트('2026.7')로 좌/우 값을 적용하고 \"정규화된 표시 문자열\"을 반환.\n // 형식이 틀리면 null/false. 파싱은 호출부가 담당(연.월/시:분 등 도메인별).\n // 타이핑 즉시(라이브) 적용되고, 입력 영역을 떠날 때 반환된 정규화 문자열로 표시값을 보정한다.\n // 실패 시 null(마지막 편집 인풋에 errorMessage 툴팁) 또는 { error:'left'|'right', message? }\n // (해당 파트 인풋 아래에 그 문구로 툴팁 — 파트별 문구는 호출부가 지정).\n onInputApply,\n errorMessage = '형식이 올바르지 않습니다.', // 파트 지정이 없을 때의 기본 툴팁 메시지\n // 파트 입력 허용 문자(문자 1개씩 test) — 기본은 숫자만(구분자는 UI가 표시하므로 입력 불필요).\n allowedChars = /\\d/,\n inputPlaceholder = '', // 'YYYY.MM'·'HH:MM'처럼 구분자 포함 문자열 — 구분자로 쪼개 좌/우 placeholder로 사용\n inputProps = {},\n // 좌측 컬럼\n leftOptions = [], // [{ value, label, disabled? }]\n leftValue,\n onLeftChange,\n // 우측 컬럼\n rightOptions = [], // [{ value, label, disabled? }]\n rightValue,\n onRightChange,\n maxVisible = 5, // 각 컬럼 표시 행 수(초과 시 내부 스크롤)\n width = 160,\n className = '',\n ...props\n}: TwoDepthListProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const [lPlaceholder, rPlaceholder] = splitParts(inputPlaceholder, separator);\n\n // 직접 입력용 좌/우 파트 텍스트.\n // - 적용은 타이핑 즉시(라이브) — 값을 친 뒤 팝오버 바깥을 눌러 닫혀도 이미 반영돼 있다.\n // - 포커스가 입력 영역 안에 있는 동안엔 정규화 값으로 덮어쓰지 않는다 → \"2:2 → 2:20\"처럼 마저 입력 가능.\n // 포커스가 영역을 떠나면(두 인풋 모두 blur) 정규화된 표시값으로 보정한다.\n // - 외부에서 값(컬럼 선택 등)으로 inputValue가 바뀌면, 포커스가 없을 때만 텍스트를 동기화한다.\n const [focused, setFocused] = useState(false);\n const [parts, setParts] = useState<[string, string]>(() => splitParts(inputValue, separator));\n const [error, setError] = useState<{ side: 'left' | 'right'; message: string } | null>(null); // { side:'left'|'right', message } | null\n // 에러 툴팁은 포털(fixed)로 띄운다(팝오버 overflow-hidden에 잘리지 않게). 위치 기준은 실패한 파트 인풋 rect.\n const inputAreaRef = useRef<HTMLDivElement | null>(null);\n const leftBoxRef = useRef<HTMLDivElement | null>(null);\n const rightBoxRef = useRef<HTMLDivElement | null>(null);\n const lastSideRef = useRef<'left' | 'right'>('left'); // 파트 미지정 실패(null) 시 툴팁을 붙일 마지막 편집 인풋\n const [errorRect, setErrorRect] = useState<DOMRect | null>(null);\n const [prevInput, setPrevInput] = useState(inputValue);\n if (inputValue !== prevInput) {\n setPrevInput(inputValue);\n if (!focused) {\n setParts(splitParts(inputValue, separator));\n setError(null);\n }\n }\n\n const compose = (l: string, r: string) => `${l}${separator}${r}`;\n\n const handlePartChange = (side: 'left' | 'right') => (e: ChangeEvent<HTMLInputElement>) => {\n // 허용 문자만 남긴다(기본 숫자만 — 구분자는 UI가 항상 표시).\n const v = allowedChars\n ? Array.from(e.target.value).filter((c) => allowedChars.test(c)).join('')\n : e.target.value;\n const next: [string, string] = side === 'left' ? [v, parts[1]] : [parts[0], v];\n lastSideRef.current = side;\n setParts(next);\n onInputChange?.(e);\n if (error) setError(null); // 타이핑하면 에러 해제(검증은 영역 이탈 시)\n if ((next[0] + next[1]).trim() !== '') onInputApply?.(compose(next[0], next[1])); // 유효하면 즉시 적용(무효면 호출부가 무시)\n };\n\n const handleFocus = (side: 'left' | 'right') => (e: FocusEvent<HTMLInputElement>) => {\n lastSideRef.current = side;\n setFocused(true);\n inputProps.onFocus?.(e);\n e.target.select(); // 포커스 시 전체 선택 — 기존 값을 바로 덮어쓰기 쉽게\n };\n\n // 입력 영역 이탈(두 인풋 모두 blur) — 형식 검증 + 정규화된 표시값으로 보정.\n // 좌↔우 인풋 사이 이동(relatedTarget이 영역 안)은 이탈로 보지 않는다.\n const handleBlur = (e: FocusEvent<HTMLInputElement>) => {\n inputProps.onBlur?.(e);\n if (inputAreaRef.current?.contains(e.relatedTarget)) return;\n setFocused(false);\n const t = compose(parts[0].trim(), parts[1].trim());\n if (parts[0].trim() === '' && parts[1].trim() === '') {\n setParts(splitParts(inputValue, separator));\n setError(null);\n return;\n }\n const norm = onInputApply ? onInputApply(t) : t; // 유효=정규화 문자열/true, 무효=null 또는 {error, message?}\n if (norm && !(typeof norm === 'object' && norm.error)) {\n setParts(splitParts(typeof norm === 'string' ? norm : inputValue, separator));\n setError(null);\n } else {\n const side = (typeof norm === 'object' && norm !== null && norm.error) || lastSideRef.current;\n const box = side === 'right' ? rightBoxRef.current : leftBoxRef.current;\n setErrorRect(box?.getBoundingClientRect() ?? inputAreaRef.current?.getBoundingClientRect() ?? null);\n setError({ side, message: (typeof norm === 'object' && norm !== null && norm.message) || errorMessage });\n }\n };\n\n // Enter = 확정(blur 처리로 정규화)\n const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {\n inputProps.onKeyDown?.(e);\n if (e.key === 'Enter') {\n e.preventDefault();\n e.currentTarget.blur();\n }\n };\n\n const partInput = (side: 'left' | 'right', value: string, placeholder: string) => (\n <div ref={side === 'left' ? leftBoxRef : rightBoxRef} className=\"min-w-0 flex-1\">\n <Input\n value={editable ? value : splitParts(inputValue, separator)[side === 'left' ? 0 : 1]}\n onChange={editable ? handlePartChange(side) : undefined}\n readOnly={!editable}\n placeholder={placeholder}\n width=\"100%\"\n inputProps={\n editable\n ? { ...inputProps, onFocus: handleFocus(side), onBlur: handleBlur, onKeyDown: handleKeyDown }\n : inputProps\n }\n />\n </div>\n );\n\n return (\n <div\n style={{ width: widthStyle }}\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 {/* input area — 흰 배경, [좌 Input] 구분자 [우 Input] (gap 4px). popover(gray100)와 1px 틈이 구분선.\n 에러 툴팁은 Input 내장(absolute)을 쓰면 팝오버 overflow-hidden에 잘리므로,\n 여기선 끄고 아래에서 포털(fixed)로 직접 띄운다. showInput=false면 영역 자체를 숨긴다. */}\n {showInput && (\n <div ref={inputAreaRef} className=\"flex w-full items-center gap-spacing-3 bg-list-group-bg p-spacing-5\">\n {partInput('left', parts[0], lPlaceholder)}\n {/* 구분자 — 아래 정렬(마침표가 인풋 텍스트 하단선에 붙도록 self-end + 인풋 py와 동일한 pb) */}\n <span className=\"shrink-0 self-end pb-spacing-3 text-14 text-text-field-filled-text\">{separator}</span>\n {partInput('right', parts[1], rPlaceholder)}\n </div>\n )}\n {/* 2 컬럼 목록 — 1px 틈(gap-spacing-1)이 두 컬럼 사이 구분선이 된다. */}\n <div className=\"flex w-full gap-spacing-1\">\n <Column options={leftOptions} value={leftValue} onChange={onLeftChange} maxVisible={maxVisible} />\n <Column options={rightOptions} value={rightValue} onChange={onRightChange} maxVisible={maxVisible} />\n </div>\n\n {/* 에러 툴팁 — 포털(fixed)로 입력 행 아래에 띄워 overflow-hidden에 잘리지 않게 한다. */}\n {showInput &&\n error &&\n errorRect &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{ position: 'fixed', top: errorRect.bottom + 6, left: errorRect.left }}\n >\n <Tooltip variant=\"error\" beak=\"top\">\n {error.message}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n","// DatePicker — 캘린더 날짜 선택 팝오버 (Figma date picker, node 7594:4368)\n// 구성(위→아래, 각 영역은 gap-spacing-1(1px) 틈으로 구분):\n// 1) date area — 상단 네비게이션(캘린더 아이콘 + 연.월 선택 / 이전·오늘·다음 버튼) + 요일 헤더\n// 2) calender area — 날짜 그리드(CalendarDayButton). scrollNavigate면 ScrollArea로 주 단위\n// 무한 세로 스크롤(페이지처럼 연속 이동, 스크롤 따라 헤더 연.월 갱신), 아니면 정적 6주 그리드.\n// 3) time area — (showTime) 시작/마감 시간 선택(시:분 2depth 목록)\n// 연.월 선택과 시간 선택은 TwoDepthList(2depth 목록)를 Popover로 띄워 고른다.\n//\n// 선택 모드:\n// - mode='single' : value=Date|null\n// - mode='range' : value={ start: Date|null, end: Date|null }\n// controlled(value/onChange) · uncontrolled(defaultValue) 모두 지원. 표시 월은 month/defaultMonth.\n// 색은 cal-* / list-* / font-icon-* 시멘틱 토큰만 사용.\nimport { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Calendar as CalendarIcon, ChevronLeft, ChevronRight } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { Popover } from './Popover';\nimport { InlineFieldTrigger } from './InlineFieldTrigger';\nimport { TwoDepthList } from './TwoDepthList';\nimport { CalendarDayButton } from './CalendarDayButton';\nimport { ScrollArea } from './ScrollArea';\nimport { Tooltip } from './Tooltip';\n\n// ── 타입 ─────────────────────────────────────────────────────────────────\n// 범위 선택값 — mode='range'의 value 형태\nexport interface DateRange {\n start: Date | null;\n end: Date | null;\n}\n// 선택값(단일=Date|null, 범위=DateRange)\nexport type DatePickerValue = Date | DateRange | null;\n// 연/월·시/분 2depth 목록 옵션\ninterface DepthOption {\n value: string;\n label: string;\n}\n\n// ── 날짜 헬퍼 (외부 라이브러리 없이 native Date) ─────────────────────────\nconst pad2 = (n: number) => String(n).padStart(2, '0');\nconst startOfDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate());\nconst isSameDay = (a: Date | null | undefined, b: Date | null | undefined) =>\n !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\nconst addMonths = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth() + n, 1);\nconst fmtYearMonth = (d: Date) => `${d.getFullYear()}.${pad2(d.getMonth() + 1)}`;\n\n// 직접 입력 파싱 — 연.월. 구분자 있으면 연도 2/4자리·월 1~2자리(예: \"2025.05\" · \"25.3\"),\n// 구분자 없으면 YYMM(4자리, 예 \"2505\") 또는 YYYYMM(6자리, 예 \"202505\"). 2자리 연도는 20xx로 보정.\nconst DEFAULT_WEEKDAYS = ['일', '월', '화', '수', '목', '금', '토'];\n\n// 표시 월의 6주 × 7일 그리드(앞뒤 달 채움)\nfunction buildWeeks(viewDate: Date, weekStartsOn: number) {\n const y = viewDate.getFullYear();\n const m = viewDate.getMonth();\n const firstDow = (new Date(y, m, 1).getDay() - weekStartsOn + 7) % 7;\n const gridStart = new Date(y, m, 1 - firstDow);\n return Array.from({ length: 6 }, (_, w) =>\n Array.from({ length: 7 }, (_, d) => new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + w * 7 + d)),\n );\n}\n\n// 날짜 셀 라벨 — 매월 1일은 \"M.1\"로 표기해 달 경계를 구분(Figma \"7.1\"·\"8.1\").\nconst dayLabel = (date: Date) => (date.getDate() === 1 ? `${date.getMonth() + 1}.1` : date.getDate());\n\n// ── 주(week) 헬퍼 — 무한 스크롤 캘린더용 ─────────────────────────────────\nconst monthKey = (d: Date) => d.getFullYear() * 12 + d.getMonth();\nconst addWeeks = (d: Date, n: number) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + n * 7);\nconst startOfWeek = (d: Date, weekStartsOn: number) => {\n const x = startOfDay(d);\n const diff = (x.getDay() - weekStartsOn + 7) % 7;\n return new Date(x.getFullYear(), x.getMonth(), x.getDate() - diff);\n};\nconst firstWeekOfMonth = (monthDate: Date, weekStartsOn: number) =>\n startOfWeek(new Date(monthDate.getFullYear(), monthDate.getMonth(), 1), weekStartsOn);\n\n// 스크롤 메트릭(셀 24 + 행 간격 12 = 한 주 36px) — idx ↔ scrollTop 환산에 사용\nconst ROW_UNIT = 36;\nconst GRID_PAD = 12; // 날짜 영역 상하 패딩(spacing-6) — 정적 그리드(p-spacing-6)와 동일\nconst VISIBLE_HEIGHT = 6 * 24 + 5 * 12 + 2 * GRID_PAD; // 6주+상하 패딩 = 228px (정적 그리드와 동일 높이)\nconst WIN_BEFORE = 20; // 앵커 달 첫 주 기준 위로 채울 주\nconst WIN_AFTER = 26; // 아래로 채울 주\nconst BATCH = 10; // 끝 근처에서 한 번에 덧붙일 주\nconst MAX_WEEKS = 160; // DOM 상한(반대편을 잘라 유지)\n\n// 앵커 달의 첫 주를 index WIN_BEFORE에 두는 주(week-start) 배열\nfunction buildWindow(monthDate: Date, weekStartsOn: number) {\n const fw = firstWeekOfMonth(monthDate, weekStartsOn);\n return Array.from({ length: WIN_BEFORE + WIN_AFTER }, (_, i) => addWeeks(fw, i - WIN_BEFORE));\n}\n\n// CalendarScroller — 주 단위 무한 세로 스크롤 캘린더(페이지 스크롤처럼 연속 이동).\n// ScrollArea(오버레이 스크롤바)로 감싸고, 끝에 가까워지면 주를 prepend/append해 끝없이 이어 붙인다.\n// 보이는 중앙 주의 달을 헤더로 보고(onVisibleMonthChange), 외부에서 달이 바뀌면(anchorKey) 그 달로 스크롤한다.\ninterface CalendarScrollerProps {\n anchorMonth: Date;\n anchorKey: number;\n weekStartsOn: number;\n onVisibleMonthChange: (month: Date) => void;\n renderDay: (date: Date) => ReactNode;\n}\n\nfunction CalendarScroller({ anchorMonth, anchorKey, weekStartsOn, onVisibleMonthChange, renderDay }: CalendarScrollerProps) {\n const vpRef = useRef<HTMLDivElement | null>(null);\n const setVp = useCallback((el: HTMLDivElement | null) => {\n vpRef.current = el;\n }, []);\n const [weeks, setWeeks] = useState<Date[]>(() => buildWindow(anchorMonth, weekStartsOn));\n const lastKeyRef = useRef(anchorKey); // 마지막으로 헤더에 보고/반영한 달\n const adjustRef = useRef<'anchor' | number>('anchor'); // 다음 레이아웃에서 scrollTop 보정: 'anchor' | px(number) | 0\n const busyRef = useRef(false); // prepend/append 진행 중 중복 방지\n\n // 외부에서 달이 바뀌면(버튼·연월 선택) 그 달 기준으로 윈도우 재구성 + 상단 정렬\n useEffect(() => {\n if (anchorKey === lastKeyRef.current) return; // 스크롤로 인한 변경이면 이미 동기화됨\n lastKeyRef.current = anchorKey;\n adjustRef.current = 'anchor';\n setWeeks(buildWindow(anchorMonth, weekStartsOn));\n }, [anchorKey, anchorMonth, weekStartsOn]);\n\n // weeks가 바뀐 직후 scrollTop 보정(앵커 정렬 / prepend·trim 위치 유지)\n useLayoutEffect(() => {\n const el = vpRef.current;\n if (!el) return;\n if (adjustRef.current === 'anchor') el.scrollTop = WIN_BEFORE * ROW_UNIT; // 콘텐츠 py 패딩 덕에 첫 주 위 12px 여백(정적과 동일)\n else if (adjustRef.current) el.scrollTop += adjustRef.current;\n adjustRef.current = 0;\n busyRef.current = false;\n }, [weeks]);\n\n const handleScroll = () => {\n const el = vpRef.current;\n if (!el) return;\n // 보이는 중앙 주의 목요일이 속한 달을 현재 달로 본다\n const idx = Math.min(Math.max(Math.floor((el.scrollTop - GRID_PAD + el.clientHeight / 2) / ROW_UNIT), 0), weeks.length - 1);\n const wk = weeks[idx];\n const mid = new Date(wk.getFullYear(), wk.getMonth(), wk.getDate() + 3);\n const key = monthKey(mid);\n if (key !== lastKeyRef.current) {\n lastKeyRef.current = key;\n onVisibleMonthChange(new Date(mid.getFullYear(), mid.getMonth(), 1));\n }\n // 끝 근처면 무한 확장(+ 반대편 trim)\n if (busyRef.current) return;\n const NEAR = ROW_UNIT * 4;\n if (el.scrollTop < NEAR) {\n busyRef.current = true;\n setWeeks((prev) => {\n const fw = prev[0];\n let next = [...Array.from({ length: BATCH }, (_, i) => addWeeks(fw, i - BATCH)), ...prev];\n adjustRef.current = BATCH * ROW_UNIT; // 위에 끼운 만큼 아래로 보정\n if (next.length > MAX_WEEKS) next = next.slice(0, MAX_WEEKS); // 아래쪽 잘라냄(스크롤 영향 없음)\n return next;\n });\n } else if (el.scrollTop + el.clientHeight > el.scrollHeight - NEAR) {\n busyRef.current = true;\n setWeeks((prev) => {\n const last = prev[prev.length - 1];\n let next = [...prev, ...Array.from({ length: BATCH }, (_, i) => addWeeks(last, i + 1))];\n if (next.length > MAX_WEEKS) {\n const drop = next.length - MAX_WEEKS;\n next = next.slice(drop);\n adjustRef.current = -drop * ROW_UNIT; // 앞쪽 잘라낸 만큼 위로 보정\n }\n return next;\n });\n }\n };\n\n return (\n <ScrollArea\n maxHeight={VISIBLE_HEIGHT}\n onViewport={setVp}\n onScroll={handleScroll}\n className=\"w-full bg-list-group-bg\"\n contentClassName=\"flex select-none flex-col gap-spacing-6 overscroll-contain p-spacing-6\"\n >\n {weeks.map((wkStart) => (\n <div key={wkStart.getTime()} className=\"flex items-center\">\n {Array.from({ length: 7 }, (_, d) =>\n renderDay(new Date(wkStart.getFullYear(), wkStart.getMonth(), wkStart.getDate() + d)),\n )}\n </div>\n ))}\n </ScrollArea>\n );\n}\n\n// ── 연.월 / 시간 선택을 띄우는 셀렉트형 트리거 (텍스트 + chevron, hover 밑줄) ──────\ninterface DualSelectFieldProps {\n icon?: LucideIcon;\n display?: ReactNode;\n panelWidth?: number;\n placement?: string;\n disabled?: boolean;\n // ── 이하 TwoDepthList 패스스루 (TwoDepthListProps와 시그니처 일치) ──\n separator?: string;\n inputValue?: string;\n inputPlaceholder?: string;\n /** 직접 입력 적용 — 정규화 문자열 반환 또는 { error: 'left'|'right', message } */\n onInputApply?: (text: string) => string | { error: 'left' | 'right'; message: string };\n leftOptions?: DepthOption[];\n leftValue?: string;\n onLeftChange?: (value: string | number) => void;\n rightOptions?: DepthOption[];\n rightValue?: string;\n onRightChange?: (value: string | number) => void;\n maxVisible?: number;\n}\n\nfunction DualSelectField({ icon: Icon, display, panelWidth = 160, placement = 'auto-left', disabled = false, ...panelProps }: DualSelectFieldProps) {\n // 트리거 비주얼은 공유 프리미티브 InlineFieldTrigger 사용(Select 인라인 텍스트형과 동일).\n // 열림 상태(텍스트 회색·chevron 회전)를 반영하려고 Popover를 controlled로 둔다.\n const [open, setOpen] = useState(false);\n return (\n <Popover\n placement={placement}\n menuWidth={panelWidth}\n disabled={disabled}\n open={open}\n onOpenChange={setOpen}\n trigger={\n <InlineFieldTrigger icon={Icon} open={open} disabled={disabled}>\n {display}\n </InlineFieldTrigger>\n }\n >\n <TwoDepthList width={panelWidth} {...panelProps} />\n </Popover>\n );\n}\n\n// 시간 영역 한 칸(시작/마감) — 라벨 + 시:분 셀렉트. disabled면 비활성(해당 날짜 미선택 시).\n// justify: 'center'(범위 2칸, 기본) | 'between'(단일 1칸 — 라벨 왼쪽·셀렉터 오른쪽 정렬)\ninterface TimeFieldProps {\n label?: ReactNode;\n value?: string;\n onChange?: (time: string) => void;\n hourOptions: DepthOption[];\n minuteOptions: DepthOption[];\n placement?: string;\n disabled?: boolean;\n justify?: 'center' | 'between';\n}\n\nfunction TimeField({ label, value, onChange, hourOptions, minuteOptions, placement = 'auto-right', disabled = false, justify = 'center' }: TimeFieldProps) {\n const [hh = '00', mm = '00'] = (value || '').split(':');\n const set = (h: string | number, m: string | number) => onChange?.(`${h}:${m}`);\n return (\n <div\n className={`flex h-[44px] flex-1 items-center gap-spacing-6 bg-list-group-bg px-spacing-6 py-spacing-5 ${\n justify === 'between' ? 'justify-between' : 'justify-center'\n }`}\n >\n <span className={`whitespace-nowrap text-12 ${disabled ? 'text-font-icon-2' : 'text-font-icon-5'}`}>{label}</span>\n <DualSelectField\n disabled={disabled}\n placement={placement}\n panelWidth={140}\n separator=\":\"\n display={`${hh}:${mm}`}\n inputValue={`${hh}:${mm}`}\n inputPlaceholder=\"HH:MM\"\n onInputApply={(t) => {\n const [hs, ms] = t.split(':');\n const hh = Number(hs);\n const mm = Number(ms);\n if (hs.trim() === '' || Number.isNaN(hh) || hh > 23) return { error: 'left', message: '시는 0~23으로 입력하세요' };\n if (ms.trim() === '' || Number.isNaN(mm) || mm > 59) return { error: 'right', message: '분은 0~59로 입력하세요' };\n const v = `${String(hh).padStart(2, '0')}:${String(mm).padStart(2, '0')}`;\n onChange?.(v);\n return v;\n }}\n leftOptions={hourOptions}\n leftValue={hh}\n onLeftChange={(h) => set(h, mm)}\n rightOptions={minuteOptions}\n rightValue={mm}\n onRightChange={(m) => set(hh, m)}\n maxVisible={5}\n />\n </div>\n );\n}\n\nexport interface DatePickerProps\n extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'defaultValue'> {\n /** 'single' | 'range' */\n mode?: 'single' | 'range';\n value?: DatePickerValue;\n defaultValue?: DatePickerValue;\n onChange?: (value: DatePickerValue) => void;\n // 선택 제한 — 지나간 날짜/미래 날짜/범위 밖 날짜를 선택 불가(비활성)로\n /** 오늘 이전 날짜 선택 불가(오늘은 선택 가능) */\n disablePast?: boolean;\n /** 오늘 이후 날짜 선택 불가(오늘은 선택 가능) */\n disableFuture?: boolean;\n /** 이 날짜 이전은 비활성 (Date) */\n minDate?: Date;\n /** 이 날짜 이후는 비활성 (Date) */\n maxDate?: Date;\n /** (date) => boolean — 커스텀 비활성 판정 */\n disabledDate?: (date: Date) => boolean;\n /** 표시 월(controlled) — Date */\n month?: Date;\n defaultMonth?: Date;\n onMonthChange?: (month: Date) => void;\n /** 하단 시간 영역 표시 — 범위=시작/마감 2칸 · 단일=시간 1칸(startTime 사용, 마감 없음) */\n showTime?: boolean;\n startTime?: string;\n endTime?: string;\n onStartTimeChange?: (time: string) => void;\n onEndTimeChange?: (time: string) => void;\n /** 단일 모드 시간 칸 라벨 */\n timeLabel?: ReactNode;\n /** 0=일요일 시작 */\n weekStartsOn?: number;\n weekdayLabels?: string[];\n todayLabel?: string;\n /** 날짜 영역에서 휠 세로 스크롤로 이전/다음 달 이동 */\n scrollNavigate?: boolean;\n // 범위 모드에서 날짜 hover 시 \"시작일/마감일\" 안내 툴팁(찍는 날짜가 무엇인지 인지용)\n rangeTooltip?: boolean;\n rangeStartLabel?: string;\n rangeEndLabel?: string;\n /** 연도 선택 범위(미지정 시 표시 연도 ±12) */\n minYear?: number;\n maxYear?: number;\n width?: number | string;\n}\n\nexport function DatePicker({\n mode = 'single',\n value,\n defaultValue,\n onChange,\n disablePast = false,\n disableFuture = false,\n minDate,\n maxDate,\n disabledDate,\n month,\n defaultMonth,\n onMonthChange,\n showTime = false,\n startTime = '00:00',\n endTime = '23:59',\n onStartTimeChange,\n onEndTimeChange,\n timeLabel = '시간 입력',\n weekStartsOn = 0,\n weekdayLabels = DEFAULT_WEEKDAYS,\n todayLabel = '오늘',\n scrollNavigate = true,\n rangeTooltip = true,\n rangeStartLabel = '시작일',\n rangeEndLabel = '마감일',\n minYear,\n maxYear,\n width = 276,\n className = '',\n ...props\n}: DatePickerProps) {\n const today = startOfDay(new Date());\n\n // ── 선택값 (controlled/uncontrolled) ─────────────────────────\n const isRange = mode === 'range';\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState<DatePickerValue>(\n defaultValue ?? (isRange ? { start: null, end: null } : null),\n );\n const current = isControlled ? (value as DatePickerValue) : internalValue;\n const range = isRange ? (current as DateRange | null) ?? { start: null, end: null } : null;\n const single = !isRange ? (current as Date | null) ?? null : null;\n\n // 범위 모드 hover 안내 툴팁 — 호버한 셀 위에 \"시작일/마감일\"을 띄운다(포털, 클리핑 방지).\n const [hover, setHover] = useState<{ rect: DOMRect; label: string } | null>(null); // { rect, label } | null\n const showRangeTooltip = isRange && rangeTooltip;\n\n const commit = (next: DatePickerValue) => {\n if (!isControlled) setInternalValue(next);\n onChange?.(next);\n };\n\n // ── 표시 월 (controlled/uncontrolled) ────────────────────────\n const initialMonth =\n defaultMonth ?? (isRange ? range?.start : single) ?? today;\n const [internalMonth, setInternalMonth] = useState(() => startOfDay(initialMonth));\n const viewDate = month ?? internalMonth;\n const setView = (next: Date) => {\n if (month === undefined) setInternalMonth(next);\n onMonthChange?.(next);\n };\n\n // ── 선택 제한(비활성) 판정 ───────────────────────────────────\n const isDayDisabled = (date: Date) => {\n const d = startOfDay(date);\n if (disablePast && d < today) return true;\n if (disableFuture && d > today) return true;\n if (minDate && d < startOfDay(minDate)) return true;\n if (maxDate && d > startOfDay(maxDate)) return true;\n return !!disabledDate && !!disabledDate(d);\n };\n\n // ── 날짜 클릭 ────────────────────────────────────────────────\n // 범위 클릭 규칙 — 완성된 범위는 풀리지 않고 한쪽만 이동한다:\n // 시작일 앞을 찍으면 시작일만 이동(마감 유지), 마감일 뒤를 찍으면 마감일만 이동(시작 유지),\n // 범위 안(시작~마감, 경계 포함)은 첫 클릭=시작일 이동, 두 번째 클릭=마감일 이동(교대).\n // 미완성(시작만) 상태는 기존대로 — 시작 앞을 찍으면 시작 재지정, 뒤를 찍으면 마감 확정.\n const insideNextRef = useRef<'start' | 'end'>('start'); // 범위 안 클릭이 다음에 잡을 쪽(교대 상태)\n const nextRange = (d: Date): DateRange => {\n const { start, end } = range as DateRange;\n if (!start) return { start: d, end: null };\n if (end) {\n if (d < start) return { start: d, end };\n if (d > end) return { start, end: d };\n return insideNextRef.current === 'start' ? { start: d, end } : { start, end: d };\n }\n if (d < start) return { start: d, end: null };\n return { start, end: d };\n };\n\n const handleDayClick = (date: Date) => {\n if (isDayDisabled(date)) return; // 비활성 날짜는 무시\n const d = startOfDay(date);\n if (!isRange) {\n commit(d);\n return;\n }\n const { start, end } = range as DateRange;\n const inside = !!(start && end) && d >= (start as Date) && d <= (end as Date);\n commit(nextRange(d));\n // 교대 상태 갱신 — 범위 안 클릭이면 다음 쪽으로 토글, 그 외 클릭은 처음(시작)으로 리셋\n insideNextRef.current = inside && insideNextRef.current === 'start' ? 'end' : 'start';\n };\n\n // 이 날짜를 클릭하면 시작일을 잡는지 마감일을 잡는지 — nextRange와 동일한 분기.\n // r을 넘기면 그 범위 기준으로 판정(클릭 직후 다음 역할 계산용 — state 반영 전).\n const dayRole = (date: Date, r: DateRange = range as DateRange) => {\n const d = startOfDay(date);\n const { start, end } = r;\n if (!start) return 'start';\n if (end) {\n if (d < start) return 'start';\n if (d > end) return 'end';\n return insideNextRef.current;\n }\n return d < start ? 'start' : 'end';\n };\n\n // ── 날짜 셀 상태 계산 ────────────────────────────────────────\n // 우선순위: 비활성(muted) → 선택/범위 → today → 이전·다음 달(muted) → default.\n // - 비활성(disablePast 등) 날짜는 muted(선택 불가).\n // - 선택 가능한 상태에서는 현재(표시) 달은 default, 이전/다음 달은 muted로 구분한다.\n // → 차이: \"이번 달 지난 날짜\"가 선택 가능하면 default, disablePast면 muted가 된다.\n const dayState = (date: Date) => {\n if (isDayDisabled(date)) return 'muted';\n if (isRange) {\n const { start, end } = range as DateRange;\n const isStart = isSameDay(date, start);\n const isEnd = isSameDay(date, end);\n if (isStart && isEnd) return 'selected';\n if (isStart) return end ? 'range-start' : 'selected';\n if (isEnd) return 'range-end';\n if (start && end && date > start && date < end) return 'in-range';\n } else if (isSameDay(date, single)) {\n return 'selected';\n }\n if (isSameDay(date, today)) return 'today';\n if (monthKey(date) !== monthKey(viewDate)) return 'muted'; // 이전/다음 달\n return 'default';\n };\n\n // ── 연/월 옵션 ───────────────────────────────────────────────\n const viewYear = viewDate.getFullYear();\n const lo = Math.min(minYear ?? viewYear - 12, viewYear);\n const hi = Math.max(maxYear ?? viewYear + 12, viewYear);\n const yearOptions = Array.from({ length: hi - lo + 1 }, (_, i) => ({\n value: String(lo + i),\n label: `${lo + i}년`,\n }));\n const monthOptions = Array.from({ length: 12 }, (_, i) => ({\n value: String(i + 1),\n label: `${i + 1}월`,\n }));\n\n // ── 시/분 옵션 ── 값은 'HH'/'MM', 라벨은 \"00시\"/\"00분\"\n const hourOptions = Array.from({ length: 24 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}시` }));\n const minuteOptions = Array.from({ length: 60 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}분` }));\n\n // 정렬용 요일 라벨(weekStartsOn 반영)\n const weekdays = weekdayLabels.map((_, i) => weekdayLabels[(i + weekStartsOn) % 7]);\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n\n // 날짜 셀 1칸 — 정적 그리드/무한 스크롤이 공유한다.\n const renderDay = (date: Date) => {\n const disabled = isDayDisabled(date);\n // 비활성 날짜는 안내 툴팁도 띄우지 않는다(선택 불가이므로 시작일/마감일 안내가 무의미).\n const tip = showRangeTooltip && !disabled;\n // 행 가장자리 칸(주 시작=왼쪽 끝·주 끝=오른쪽 끝) — 범위 띠가 줄에서 끊길 때 바깥 모서리를 둥글게\n const col = (date.getDay() - weekStartsOn + 7) % 7;\n const edge = col === 0 ? 'left' : col === 6 ? 'right' : undefined;\n return (\n <CalendarDayButton\n key={date.toISOString()}\n state={dayState(date)}\n edge={edge}\n disabled={disabled}\n onClick={(e) => {\n // dayRole은 range(범위)에서만 의미 있고 단일 모드에선 range가 null이라 호출 금지.\n // 클릭 직후의 다음 역할은 커밋될 범위(nextRange) 기준으로 계산한다(state 반영 전).\n const nr = tip && !disabled ? nextRange(startOfDay(date)) : null;\n handleDayClick(date);\n if (nr) {\n setHover({\n rect: e.currentTarget.getBoundingClientRect(),\n label: dayRole(date, nr) === 'start' ? rangeStartLabel : rangeEndLabel,\n });\n }\n }}\n onMouseEnter={\n tip\n ? (e) =>\n setHover({\n rect: e.currentTarget.getBoundingClientRect(),\n label: dayRole(date) === 'start' ? rangeStartLabel : rangeEndLabel,\n })\n : undefined\n }\n onMouseLeave={tip ? () => setHover(null) : undefined}\n >\n {dayLabel(date)}\n </CalendarDayButton>\n );\n };\n\n return (\n <div\n style={{ width: widthStyle }}\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 {/* 1) date area — 네비게이션 + 요일 헤더 */}\n <div className=\"flex w-full flex-col gap-spacing-5 bg-list-group-bg p-spacing-6\">\n {/* navigation */}\n <div className=\"flex h-[32px] items-center justify-between pl-spacing-5\">\n {/* 연.월 선택 (캘린더 아이콘 + selected-text → 연/월 2depth 목록) */}\n <DualSelectField\n icon={CalendarIcon}\n separator=\".\"\n display={fmtYearMonth(viewDate)}\n inputValue={fmtYearMonth(viewDate)}\n inputPlaceholder=\"YYYY.MM\"\n onInputApply={(t) => {\n const [ys, ms] = t.split('.');\n if (!/^(\\d{2}|\\d{4})$/.test(ys.trim()))\n return { error: 'left', message: '연도는 YYYY 형식으로 입력하세요 (예: 2026 · 26)' };\n const mo = Number(ms);\n if (ms.trim() === '' || Number.isNaN(mo) || mo < 1 || mo > 12)\n return { error: 'right', message: '월은 1~12로 입력하세요' };\n const yr = ys.trim().length === 2 ? 2000 + Number(ys) : Number(ys);\n const next = new Date(yr, mo - 1, 1);\n setView(next);\n return fmtYearMonth(next); // 정규화된 'YYYY.MM'\n }}\n leftOptions={yearOptions}\n leftValue={String(viewYear)}\n onLeftChange={(y) => setView(new Date(Number(y), viewDate.getMonth(), 1))}\n rightOptions={monthOptions}\n rightValue={String(viewDate.getMonth() + 1)}\n onRightChange={(m) => setView(new Date(viewYear, Number(m) - 1, 1))}\n maxVisible={5}\n />\n {/* 이전 / 오늘 / 다음 */}\n <ButtonGroup gap=\"3\" className=\"shrink-0\">\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={ChevronLeft}\n aria-label=\"이전 달\"\n onClick={() => setView(addMonths(viewDate, -1))}\n />\n <Button variant=\"ghost\" size=\"24\" onClick={() => setView(startOfDay(new Date()))}>\n {todayLabel}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"24\"\n icon={ChevronRight}\n aria-label=\"다음 달\"\n onClick={() => setView(addMonths(viewDate, 1))}\n />\n </ButtonGroup>\n </div>\n {/* 요일 헤더 */}\n <div className=\"flex items-center\">\n {weekdays.map((w) => (\n <div key={w} className=\"flex h-[24px] w-[36px] items-center justify-center\">\n <span className=\"text-12 text-font-icon-5\">{w}</span>\n </div>\n ))}\n </div>\n </div>\n\n {/* 2) calender area — scrollNavigate면 주 단위 무한 세로 스크롤(스크롤바·페이지처럼 연속 이동),\n 아니면 표시 월의 정적 6주 그리드. */}\n {scrollNavigate ? (\n <CalendarScroller\n anchorMonth={viewDate}\n anchorKey={monthKey(viewDate)}\n weekStartsOn={weekStartsOn}\n onVisibleMonthChange={setView}\n renderDay={renderDay}\n />\n ) : (\n <div className=\"flex w-full flex-col gap-spacing-6 bg-list-group-bg p-spacing-6\">\n {buildWeeks(viewDate, weekStartsOn).map((week, wi) => (\n <div key={wi} className=\"flex items-center\">\n {week.map((date) => renderDay(date))}\n </div>\n ))}\n </div>\n )}\n\n {/* 3) time area — 시간 선택. 해당 날짜를 고르기 전엔 비활성.\n 범위 모드=시작/마감 2칸, 단일 모드=시간 1칸(마감 시간 없음 — 값은 startTime 그대로 사용,\n DateField 단일 표기 \"YY.MM.DD (HH:MM)\"와 일치). */}\n {showTime &&\n (isRange ? (\n <div className=\"flex w-full gap-spacing-1\">\n <TimeField\n label=\"시작 시간\"\n value={startTime}\n onChange={onStartTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!range?.start}\n />\n <TimeField\n label=\"마감 시간\"\n value={endTime}\n onChange={onEndTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!range?.end}\n />\n </div>\n ) : (\n <div className=\"flex w-full gap-spacing-1\">\n <TimeField\n label={timeLabel}\n justify=\"between\"\n value={startTime}\n onChange={onStartTimeChange}\n hourOptions={hourOptions}\n minuteOptions={minuteOptions}\n disabled={!single}\n />\n </div>\n ))}\n\n {/* 범위 hover 안내 툴팁 — 말줄임(TruncatingText)과 동일한 디자인(normal·beak 없음).\n 포털(fixed)로 띄워 컨테이너 overflow-hidden에 잘리지 않게 하고, 셀 위 가운데 정렬한다. */}\n {showRangeTooltip &&\n hover &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: hover.rect.left + hover.rect.width / 2,\n top: hover.rect.top - 6,\n transform: 'translate(-50%, -100%)',\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {hover.label}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n","// 날짜·시간 표기 유틸 (디자인 시스템 공통 포맷)\n// 규칙: 날짜·시간 표기는 항상 아래 형식으로 통일한다. 연·월·일·시·분 모두 2자리 0 패딩(연도는 뒤 2자리).\n// - 날짜+시간 1건 : \"YY.MM.DD (HH:MM)\" 예) 25.05.20 (12:00)\n// - 날짜만 : \"YY.MM.DD\" 예) 25.05.20\n// - 범위(둘 다) : \"YY.MM.DD (HH:MM)~YY.MM.DD (HH:MM)\" (틸드 양옆 공백 없음)\n// - 범위(시작만) : \"YY.MM.DD (HH:MM) ~ 마감일 없음\"\n// - 범위(마감만) : \"시작일 없음 ~ YY.MM.DD (HH:MM)\"\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n\n// 'HH:MM' 문자열 또는 Date에서 시:분 문자열 추출\nconst toTimeStr = (time?: string | Date | null) => {\n if (!time) return '';\n if (typeof time === 'string') return time; // 이미 'HH:MM'\n return `${pad2(time.getHours())}:${pad2(time.getMinutes())}`;\n};\n\n// 날짜 → \"YY.MM.DD\" (2자리 연·월·일)\nexport function formatDate(date?: Date | null): string {\n if (!date) return '';\n return `${pad2(date.getFullYear() % 100)}.${pad2(date.getMonth() + 1)}.${pad2(date.getDate())}`;\n}\n\n// 날짜+시간 → \"YY.MM.DD (HH:MM)\". time은 'HH:MM' 문자열 또는 Date. 시간이 없으면 날짜만.\nexport function formatDateTime(date?: Date | null, time?: string | Date | null): string {\n if (!date) return '';\n const t = toTimeStr(time);\n return t ? `${formatDate(date)} (${t})` : formatDate(date);\n}\n\n// 날짜+시간 범위\n// - 둘 다 : \"A~B\" (공백 없는 틸드)\n// - 시작만: \"A ~ 마감일 없음\" / 마감만: \"시작일 없음 ~ B\"\n// - 라벨은 emptyStartLabel/emptyEndLabel로 변경 가능. 둘 다 없으면 ''.\nexport function formatDateTimeRange(\n start?: Date | null,\n startTime?: string | Date | null,\n end?: Date | null,\n endTime?: string | Date | null,\n { emptyStartLabel = '시작일 없음', emptyEndLabel = '마감일 없음' }: { emptyStartLabel?: string; emptyEndLabel?: string } = {},\n): string {\n const s = formatDateTime(start, startTime);\n const e = formatDateTime(end, endTime);\n if (start && end) return `${s}~${e}`;\n if (start) return `${s} ~ ${emptyEndLabel}`;\n if (end) return `${emptyStartLabel} ~ ${e}`;\n return '';\n}\n","// DateField — DatePicker를 여는 트리거 + 직접 입력 필드 (Figma date picker / Input, node 7593:4139)\n// 캘린더 아이콘 + 날짜(범위)·시간 값. 아이콘/필드를 누르면 DatePicker 팝오버가 열리고,\n// 입력칸에 직접 타이핑한 뒤 Enter 또는 포커스 아웃하면 파싱해 반영한다.\n// - 표기/입력 형식은 utils/datetime 규칙: \"YY.MM.DD (HH:MM)\", 범위 \"A~B\" / 시작만 \"A ~ 마감일 없음\".\n// - 범위에서 마감일을 안 적고 확정하면(시작만 입력) end=null → \"마감일 없음\"으로 표시된다.\n// - 필드 외형/상태(default·hover·focused·filled·disabled·readOnly·error 툴팁)는 tf-* 토큰.\nimport { useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Calendar as CalendarIcon } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { DatePicker } from './DatePicker';\nimport { Tooltip } from './Tooltip';\nimport { formatDateTime, formatDateTimeRange } from '../utils/datetime';\n\n// 편집 가능 상태의 테두리(ring) — SearchBar/Input과 동일(hover/focus-within 2px, tf-* 토큰)\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\nconst pad2 = (n: number) => String(n).padStart(2, '0');\n// 입력 허용 문자 — 숫자·구분자(. - / : ~ 공백)·괄호만.\nconst ALLOWED_CHARS = /[\\d.\\-/:~() ]/;\n\n// 값 모델 — single: Date | null / range: { start, end }(각각 null 허용). DatePickerValue와 동일 구조.\ntype DateRange = { start: Date | null; end: Date | null };\ntype DateFieldValue = Date | null | DateRange;\n\n// 날짜 파트 → Date | null. 구분자 형식과 무구분자(YYYYMMDD/YYMMDD/MMDD) 모두 허용.\n// \"25.05.20\"·\"2025.05.20\" / \"20250520\"(→2025) / \"250520\"(→2025) / \"0520\"(→올해)\nfunction parseDatePart(s: string): Date | null {\n const str = s.trim();\n const sep = str.match(/^(\\d{2}|\\d{4})\\D+(\\d{1,2})\\D+(\\d{1,2})$/); // 구분자 있음\n const d8 = str.match(/^(\\d{4})(\\d{2})(\\d{2})$/); // YYYYMMDD\n const d6 = str.match(/^(\\d{2})(\\d{2})(\\d{2})$/); // YYMMDD\n const d4 = str.match(/^(\\d{2})(\\d{2})$/); // MMDD(올해)\n let year: number;\n let mo: number;\n let day: number;\n if (sep) {\n year = sep[1].length === 2 ? 2000 + Number(sep[1]) : Number(sep[1]);\n mo = Number(sep[2]);\n day = Number(sep[3]);\n } else if (d8) {\n year = Number(d8[1]);\n mo = Number(d8[2]);\n day = Number(d8[3]);\n } else if (d6) {\n year = 2000 + Number(d6[1]);\n mo = Number(d6[2]);\n day = Number(d6[3]);\n } else if (d4) {\n year = new Date().getFullYear();\n mo = Number(d4[1]);\n day = Number(d4[2]);\n } else {\n return null;\n }\n if (mo < 1 || mo > 12 || day < 1 || day > 31) return null;\n const date = new Date(year, mo - 1, day);\n if (date.getMonth() !== mo - 1 || date.getDate() !== day) return null; // 실재 날짜 검증\n return date;\n}\n\n// 시간 파트 → 'HH:MM' | null. 콜론(\"0:10\"·\"00:10\") / 무구분(시 2자리 + 분 1~2자리: \"0010\"→00:10, \"011\"→01:01).\nfunction parseTimePart(t: string): string | null {\n const str = t.trim();\n const colon = str.match(/^(\\d{1,2}):(\\d{1,2})$/);\n const compact = str.match(/^(\\d{2})(\\d{1,2})$/);\n const m = colon || compact;\n if (!m) return null;\n const h = Number(m[1]);\n const mn = Number(m[2]);\n if (h > 23 || mn > 59) return null;\n return `${pad2(h)}:${pad2(mn)}`;\n}\n\n// 날짜 토큰 파싱 — \"<날짜>[ <시간>]\" 또는 \"<날짜> (시간)\".\n// → { date, time } / 'empty'(빈 문자열·\"마감일 없음\"·\"시작일 없음\") / null(형식 오류)\nfunction parseDateToken(raw: string | null | undefined): { date: Date; time: string | null } | 'empty' | null {\n let s = (raw ?? '').trim();\n if (s === '' || s === '마감일 없음' || s === '시작일 없음') return 'empty';\n let timeRaw = null;\n const paren = s.match(/\\(([^)]*)\\)\\s*$/); // 끝의 (시간)\n if (paren) {\n timeRaw = paren[1].trim();\n s = s.slice(0, paren.index).trim();\n } else {\n const sp = s.match(/^(.*\\S)\\s+([\\d:]+)$/); // 공백 뒤 시간(숫자·콜론)\n if (sp) {\n s = sp[1].trim();\n timeRaw = sp[2];\n }\n }\n const date = parseDatePart(s);\n if (!date) return null;\n let time = null;\n if (timeRaw) {\n time = parseTimePart(timeRaw);\n if (!time) return null;\n }\n return { date, time };\n}\n\n// 입력 전체 파싱 → { value, startTime?, endTime? } | null(형식 오류)\nfunction parseDateFieldInput(\n text: string,\n isRange: boolean,\n): { value: DateFieldValue; startTime?: string | null; endTime?: string | null } | null {\n const t = text.trim();\n if (!isRange) {\n if (t === '') return { value: null };\n const tok = parseDateToken(t);\n if (tok === null) return null;\n if (tok === 'empty') return { value: null };\n return { value: tok.date, startTime: tok.time };\n }\n // 범위 — '~'로 시작/마감 분리. '~'가 없으면 시작만 입력한 것으로 본다(마감 없음).\n if (t === '') return { value: { start: null, end: null } };\n const idx = t.indexOf('~');\n const startPart = idx >= 0 ? t.slice(0, idx) : t;\n const endPart = idx >= 0 ? t.slice(idx + 1) : '';\n const s = parseDateToken(startPart);\n const e = parseDateToken(endPart);\n if (s === null || e === null) return null;\n return {\n value: { start: s === 'empty' ? null : s.date, end: e === 'empty' ? null : e.date },\n startTime: s === 'empty' ? null : s.time,\n endTime: e === 'empty' ? null : e.time,\n };\n}\n\n// ...props는 Popover로 전달된다(팝오버 옵션 통과).\ninterface DateFieldProps {\n mode?: 'single' | 'range';\n value?: DateFieldValue;\n defaultValue?: DateFieldValue;\n onChange?: (value: DateFieldValue) => void;\n showTime?: boolean;\n startTime?: string;\n defaultStartTime?: string;\n onStartTimeChange?: (time: string) => void;\n endTime?: string;\n defaultEndTime?: string;\n onEndTimeChange?: (time: string) => void;\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n formatErrorMessage?: string; // 직접 입력 형식 오류 메시지\n // 캘린더 선택 완료 시 자동 닫기 — 기본: 단일은 날짜 클릭 즉시 닫힘, 범위는 배경 클릭으로만 닫힘.\n // (showTime이면 시간도 골라야 하므로 true여도 닫지 않음)\n closeOnSelect?: boolean;\n // 너비: 'hug'(콘텐츠 폭) | 'fill'(부모 100%) | 숫자(px) | CSS 길이.\n // 미지정 시 기본 = showTime이면 260, 아니면 180. 폭이 좁으면 텍스트는 말줄임(…).\n width?: number | string;\n className?: string;\n // DatePicker 패스스루\n disablePast?: boolean;\n disableFuture?: boolean;\n minDate?: Date;\n maxDate?: Date;\n disabledDate?: (date: Date) => boolean;\n weekStartsOn?: number;\n minYear?: number;\n maxYear?: number;\n}\n\nexport function DateField({\n mode = 'single', // 'single' | 'range'\n value,\n defaultValue,\n onChange,\n showTime = false,\n startTime,\n defaultStartTime = '00:00',\n onStartTimeChange,\n endTime,\n defaultEndTime = '23:59',\n onEndTimeChange,\n placeholder = '날짜를 선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n formatErrorMessage = '날짜 형식이 올바르지 않습니다.', // 직접 입력 형식 오류 메시지\n // 캘린더 선택 완료 시 자동 닫기 — 기본: 단일은 날짜 클릭 즉시 닫힘, 범위는 배경 클릭으로만 닫힘.\n // (showTime이면 시간도 골라야 하므로 true여도 닫지 않음)\n closeOnSelect = mode !== 'range',\n // 너비: 'hug'(콘텐츠 폭) | 'fill'(부모 100%) | 숫자(px) | CSS 길이.\n // 미지정 시 기본 = showTime이면 260, 아니면 180. 폭이 좁으면 텍스트는 말줄임(…).\n width,\n className = '',\n // DatePicker 패스스루\n disablePast,\n disableFuture,\n minDate,\n maxDate,\n disabledDate,\n weekStartsOn,\n minYear,\n maxYear,\n ...props\n}: DateFieldProps) {\n const isRange = mode === 'range';\n const interactive = !disabled && !readOnly;\n const [open, setOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [text, setText] = useState(''); // 포커스 중 직접 입력 텍스트\n const [parseError, setParseError] = useState(false);\n // Enter/ESC가 blur()를 동기 발화시키므로, onBlur의 commit을 1회 건너뛰는 플래그.\n // 없으면 ESC(취소)가 편집 중 초안을 커밋하고, Enter는 onChange가 2회 발화한다(2026-07-07 감사).\n const skipBlurCommitRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n // 말줄임 hover 툴팁(규칙 5) — 값이 폭을 넘쳐 잘릴 때 전체 값을 보여준다(TruncatingText와 동일 방식).\n const [truncRect, setTruncRect] = useState<DOMRect | null>(null);\n\n // 값 (controlled/uncontrolled)\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState<DateFieldValue>(\n defaultValue ?? (isRange ? { start: null, end: null } : null),\n );\n const current = value !== undefined ? value : internalValue;\n const applyValue = (v: DateFieldValue) => {\n if (!isControlled) setInternalValue(v);\n onChange?.(v);\n setParseError(false); // 값이 정해지면(캘린더 선택·직접 입력 성공) 형식 에러 해제\n };\n\n // 시간 (controlled/uncontrolled)\n const stControlled = startTime !== undefined;\n const [intSt, setIntSt] = useState(defaultStartTime);\n const curSt = startTime !== undefined ? startTime : intSt;\n const setSt = (v: string) => {\n if (!stControlled) setIntSt(v);\n onStartTimeChange?.(v);\n };\n const etControlled = endTime !== undefined;\n const [intEt, setIntEt] = useState(defaultEndTime);\n const curEt = endTime !== undefined ? endTime : intEt;\n const setEt = (v: string) => {\n if (!etControlled) setIntEt(v);\n onEndTimeChange?.(v);\n };\n\n // 표시 문자열 — 규칙 통일(utils/datetime). 비어 있으면 placeholder.\n const display = isRange\n ? formatDateTimeRange(\n (current as DateRange | null)?.start,\n showTime ? curSt : undefined,\n (current as DateRange | null)?.end,\n showTime ? curEt : undefined,\n )\n : formatDateTime(current as Date | null, showTime ? curSt : undefined);\n const isEmpty = !display;\n\n // 캘린더에서 선택 — 완료 시 자동 닫기(closeOnSelect)\n const handlePickerChange = (v: DateFieldValue) => {\n applyValue(v);\n if (closeOnSelect && !showTime) {\n if (!isRange) setOpen(false);\n else if ((v as DateRange | null)?.start && (v as DateRange | null)?.end) setOpen(false);\n }\n };\n\n // 직접 입력 확정(Enter/blur) — 파싱해 값·시간 반영. 형식 틀리면 에러.\n const commit = (raw: string) => {\n const parsed = parseDateFieldInput(raw, isRange);\n if (!parsed) {\n setParseError(true);\n return;\n }\n setParseError(false);\n applyValue(parsed.value);\n if (showTime) {\n if (parsed.startTime != null) setSt(parsed.startTime);\n if (parsed.endTime != null) setEt(parsed.endTime);\n }\n };\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 const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : readOnly\n ? 'text-text-field-readonly-icon'\n : isEmpty\n ? 'text-text-field-default-text' // 값 없음 → 회색 #878787 (Figma)\n : 'text-font-icon-5'; // 값 있음 → 진한색\n\n const showErr = error || parseError;\n const errMsg = parseError ? formatErrorMessage : errorMessage;\n\n // 너비 — 미지정 시 showTime이면 260, 아니면 180. 'hug'=콘텐츠 폭, 'fill'=부모 100%.\n const effWidth = width ?? (showTime ? 260 : 180);\n const isHug = effWidth === 'hug';\n const isFill = effWidth === 'fill';\n const fixedWidth = isHug || isFill ? undefined : typeof effWidth === 'number' ? `${effWidth}px` : effWidth;\n // hug: input을 콘텐츠 폭만큼. size는 라틴 평균 글자폭 기준이라 한글(전각)은 2배로 세고 +1 여유.\n const shown = focused ? text : display;\n const hugSize = Math.max(\n Array.from(shown || placeholder).reduce((n, c) => {\n // 한글·CJK·전각 글자는 전각폭이라 2칸, 그 외 1칸. 끝 글자 잘림 방지로 초기값 +1.\n const code = c.charCodeAt(0);\n const wide =\n (code >= 0x1100 && code <= 0x11ff) ||\n (code >= 0x3000 && code <= 0x9fff) ||\n (code >= 0xac00 && code <= 0xd7ff) ||\n (code >= 0xff00 && code <= 0xffef);\n return n + (wide ? 2 : 1);\n }, 1),\n 1,\n );\n\n const trigger = (\n <div\n className={`relative ${isFill ? 'w-full' : isHug ? 'w-fit' : ''}`}\n style={fixedWidth ? { width: fixedWidth } : undefined}\n >\n <div\n className={`group flex min-h-[32px] ${isHug ? 'w-fit' : 'w-full'} items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n interactive ? `cursor-text ${RING}` : disabled ? 'cursor-not-allowed' : 'cursor-default'\n }`}\n >\n <CalendarIcon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />\n <input\n ref={inputRef}\n type=\"text\"\n value={focused ? text : display}\n placeholder={placeholder}\n readOnly={!interactive}\n disabled={disabled}\n size={isHug ? hugSize : undefined}\n aria-invalid={showErr || undefined}\n onClick={(e) => e.stopPropagation()} // 입력칸 클릭은 팝오버 토글하지 않음\n onMouseEnter={(e) => {\n // 잘렸을 때만(scrollWidth>clientWidth) 전체 값 툴팁. 편집 중(focused)엔 표시 안 함.\n const el = e.currentTarget;\n if (!focused && display && el.scrollWidth > el.clientWidth + 1) {\n setTruncRect(el.getBoundingClientRect());\n }\n }}\n onMouseLeave={() => setTruncRect(null)}\n onFocus={(e) => {\n if (!interactive) return;\n setFocused(true);\n setParseError(false);\n setTruncRect(null);\n setText(display);\n setOpen(true);\n e.target.select();\n }}\n onChange={(e) => {\n // 숫자·구분자·괄호 외 문자는 입력 단계에서 걸러낸다.\n const v = Array.from(e.target.value).filter((c) => ALLOWED_CHARS.test(c)).join('');\n setText(v);\n if (parseError) setParseError(false);\n }}\n onBlur={() => {\n if (!interactive) return; // readOnly/disabled은 commit 안 함(값 보존)\n setFocused(false);\n // Enter(이미 커밋)/ESC(취소) 직후의 blur는 커밋을 건너뛴다\n if (skipBlurCommitRef.current) {\n skipBlurCommitRef.current = false;\n return;\n }\n commit(text);\n }}\n onKeyDown={(e) => {\n if (!interactive) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n commit(text);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n // 취소: 초안을 버리고 기존 표시값으로 복원(커밋 금지)\n setText(display);\n setParseError(false);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n }\n }}\n className={`bg-transparent text-14 outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed read-only:cursor-pointer ${textColor} ${\n isHug ? 'w-auto' : 'min-w-0 flex-1 text-ellipsis'\n }`}\n />\n </div>\n {/* 에러 툴팁 — 닫혔을 때만(열리면 picker와 겹침). 필드 아래 오버레이. */}\n {showErr && errMsg && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errMsg}\n </Tooltip>\n </div>\n )}\n {/* 말줄임 hover 툴팁 — 잘린 전체 값(규칙 5). 포털(fixed)로 필드 위에 띄운다(위 공간 없으면 아래로). */}\n {truncRect &&\n !focused &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: truncRect.left,\n top: truncRect.top > 40 ? truncRect.top - 6 : truncRect.bottom + 6,\n transform: truncRect.top > 40 ? 'translateY(-100%)' : undefined,\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {display}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n\n return (\n <Popover\n open={open}\n onOpenChange={(o: boolean) => {\n setOpen(o);\n // ESC로 닫힐 때: Popover의 capture 리스너가 키를 선점해 인풋 ESC 핸들러가 못 받으므로\n // 여기서 입력 편집을 해제한다 — 초안은 버리고(취소) blur해서, 필드를 바로 다시\n // 클릭하면 focus 이벤트로 즉시 재활성된다(2026-07-07 사용자 지적).\n // 외부 클릭 닫힘은 useOutsideDismiss가 먼저 blur를 복원해(activeElement가 이미 아님) 여기 안 탄다.\n if (!o && inputRef.current && document.activeElement === inputRef.current) {\n setText(display);\n setParseError(false);\n skipBlurCommitRef.current = true;\n inputRef.current.blur();\n }\n }}\n disabled={!interactive}\n placement=\"auto\"\n menuWidth={276}\n trigger={trigger}\n className={`${isFill ? 'w-full' : ''} ${className}`}\n {...props}\n >\n {() => (\n <DatePicker\n mode={mode}\n value={current}\n onChange={handlePickerChange}\n showTime={showTime}\n startTime={curSt}\n onStartTimeChange={setSt}\n endTime={curEt}\n onEndTimeChange={setEt}\n disablePast={disablePast}\n disableFuture={disableFuture}\n minDate={minDate}\n maxDate={maxDate}\n disabledDate={disabledDate}\n weekStartsOn={weekStartsOn}\n minYear={minYear}\n maxYear={maxYear}\n />\n )}\n </Popover>\n );\n}\n","import { Component } from 'react';\nimport type { ErrorInfo, ReactNode } from 'react';\nimport { Button } from './Button';\n\n// ErrorBoundary — 자식(페이지) 렌더 중 던져진 에러를 잡아, 앱 전체(레이아웃·좌측 메뉴)가\n// 언마운트되는 것을 막는다. 에러가 난 자리에만 안내를 표시하고, 나머지 셸은 그대로 유지된다.\n// React에서 getDerivedStateFromError/componentDidCatch는 클래스 컴포넌트에서만 동작하므로 클래스로 둔다.\ninterface ErrorBoundaryProps {\n children?: ReactNode;\n // resetKey: 이 값이 바뀌면(=다른 페이지로 이동) 에러를 자동 해제해 새 자식을 정상 렌더한다.\n resetKey?: unknown;\n // title: 에러 화면 제목.\n title?: ReactNode;\n // description: 제목 아래 보조 안내 문구.\n description?: ReactNode;\n // fallback: ({ error, reset }) => node — 기본 UI 대신 직접 렌더하고 싶을 때.\n fallback?: (args: { error: Error; reset: () => void }) => ReactNode;\n // onError: (error, info) => void — 에러 발생 시 콜백(로깅 등).\n onError?: (error: Error, info: ErrorInfo) => void;\n}\n\ninterface ErrorBoundaryState {\n error: Error | null;\n prevResetKey: unknown;\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { error: null, prevResetKey: props.resetKey };\n this.reset = this.reset.bind(this);\n }\n\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n // resetKey가 바뀌면(페이지 전환 등) 에러 상태를 비워 새 자식이 다시 렌더되게 한다.\n static getDerivedStateFromProps(props: ErrorBoundaryProps, state: ErrorBoundaryState) {\n if (props.resetKey !== state.prevResetKey) {\n return { error: null, prevResetKey: props.resetKey };\n }\n return null;\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n // 원본 스택을 콘솔에 남겨 디버깅을 돕는다.\n console.error('[ErrorBoundary]', error, info);\n this.props.onError?.(error, info);\n }\n\n reset() {\n this.setState({ error: null });\n }\n\n render() {\n const { error } = this.state;\n const {\n children,\n title = '페이지를 표시할 수 없습니다.',\n description = '이 페이지를 렌더하는 중 오류가 발생했습니다. 다른 메뉴는 정상적으로 이용할 수 있습니다.',\n fallback,\n } = this.props;\n\n if (!error) return children;\n if (fallback) return fallback({ error, reset: this.reset });\n\n return (\n <div className=\"px-spacing-7 py-spacing-10 text-left\">\n <h2 className=\"text-16 font-semibold text-font-icon-5\">{title}</h2>\n <p className=\"mt-spacing-3 text-14 text-font-icon-3\">{description}</p>\n <pre className=\"mt-spacing-6 overflow-auto whitespace-pre-wrap rounded-round-4 border border-gray-200 bg-gray-50 px-spacing-6 py-spacing-5 font-mono text-12 text-font-icon-4\">\n {String(error?.message || error)}\n </pre>\n <div className=\"mt-spacing-6\">\n <Button variant=\"line\" size=\"32\" onClick={this.reset}>\n 다시 시도\n </Button>\n </div>\n </div>\n );\n }\n}\n","// Label — 폼 라벨 텍스트 (Figma label 컴포넌트: Property 1 = 12~16 사이즈 변형)\n//\n// Figma 원본은 \"사이즈 변형(12/13/14/15/16)만\" 가진 텍스트 컴포넌트다.\n// 코드는 실제 폼 사용을 위해 required(*)·disabled·htmlFor 를 추가로 노출한다\n// (규칙 11: 코드가 Figma보다 옵션이 많아도 무방 — 코드를 Figma에 맞춰 깎지 않는다).\n//\n// 단독으로도 쓰고(<Label htmlFor=…>), Field 내부에서도 재사용한다\n// (Checkbox가 단독 + List 내부에서 재사용되는 것과 동일한 합성 패턴).\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nconst SIZE_STYLES = {\n '12': 'text-12',\n '13': 'text-13',\n '14': 'text-14',\n '15': 'text-15',\n '16': 'text-16',\n};\n\ninterface LabelProps extends ComponentPropsWithoutRef<'label'> {\n size?: keyof typeof SIZE_STYLES; // '12' | '13' | '14' | '15' | '16' (Figma size variant)\n required?: boolean; // 필수 표시(빨강 점) — Figma state=required\n disabled?: boolean; // 비활성 — Figma state=disabled\n}\n\nexport function Label({\n children,\n htmlFor,\n size = '14', // '12' | '13' | '14' | '15' | '16' (Figma size variant)\n required = false, // 필수 표시(빨강 점) — Figma state=required\n disabled = false, // 비활성 — Figma state=disabled\n className = '',\n ...props\n}: LabelProps) {\n // 색은 label-field 시멘틱 토큰 경유(Figma label-field/* 변수와 1:1)\n const textColor = disabled ? 'text-label-field-disabled-text' : 'text-label-field-default-text';\n\n return (\n <label\n htmlFor={htmlFor}\n className={`inline-flex items-center gap-spacing-3 font-pretendard font-normal ${SIZE_STYLES[size]} ${textColor} ${className}`}\n {...props}\n >\n {children}\n {required && (\n // 필수 표시 — 빨강 6px 점(Figma label-field/required mark)\n <span\n className=\"h-spacing-4 w-spacing-4 shrink-0 rounded-round-00 bg-label-field-required-mark\"\n aria-hidden=\"true\"\n />\n )}\n </label>\n );\n}\n","// Field — 라벨 + 컨트롤(Input/Select/DateField/TimeField/Picker 등)을 묶는 폼 행\n//\n// 컨트롤은 손으로 다시 짜지 않고 기존 DS 컴포넌트를 children으로 그대로 받는다(규칙 4 조립):\n// <Field label=\"이름\" htmlFor=\"name\" required>\n// <Input inputProps={{ id: 'name' }} />\n// </Field>\n//\n// 복합 필드(한 필드에 컨트롤 2개+ — 인풋+셀렉트·셀렉트2개·인풋+데이트픽커 등)는\n// controlsDirection=\"row\"로 컨트롤을 한 줄에 나란히(등분) 배치한다:\n// <Field label=\"기간\" controlsDirection=\"row\"><Select/><DateField/></Field>\n//\n// 역할 분담:\n// - Field 가 소유: 라벨(내부 Label 재사용)·필수표시·보조설명(description)·레이아웃(세로/가로)\n// - 에러 표시는 소유하지 않는다 — 기존 컨트롤의 error/errorMessage 툴팁을 그대로 사용한다\n// (Input/Select/DateField 등이 이미 하단 오버레이 툴팁을 갖고 있어 중복을 피한다).\n\nimport { Children } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Label } from './Label';\n\n// 라벨↔컨트롤 간격 — Tailwind JIT가 인식하도록 \"완성 클래스\"를 리터럴로 둔다(동적 조합 금지).\nconst GAP_CLASS = {\n 'spacing-2': 'gap-spacing-2',\n 'spacing-3': 'gap-spacing-3',\n 'spacing-5': 'gap-spacing-5',\n 'spacing-6': 'gap-spacing-6',\n 'spacing-7': 'gap-spacing-7',\n};\nconst DEFAULT_GAP: Record<'vertical' | 'horizontal', keyof typeof GAP_CLASS> = {\n vertical: 'spacing-3',\n horizontal: 'spacing-6',\n};\n\n// ...props는 루트 컨테이너(div)로 전달된다.\ninterface FieldProps extends ComponentPropsWithoutRef<'div'> {\n label?: ReactNode; // string | ReactNode — 주면 내부 Label 렌더\n htmlFor?: string; // 컨트롤 id와 연결(라벨 클릭 시 포커스). 컨트롤에 같은 id 부여 필요\n required?: boolean;\n disabled?: boolean; // 라벨 비활성 스타일(컨트롤 disabled는 컨트롤에 직접 전달)\n description?: ReactNode; // 보조 설명(helper) — 컨트롤 아래 회색 텍스트\n direction?: 'vertical' | 'horizontal'; // 라벨↔컨트롤 배치\n controlsDirection?: 'column' | 'row'; // 컨트롤 여러 개일 때 배치(복합 필드, row=한 줄 등분)\n labelWidth?: number | string; // horizontal일 때 라벨 영역 너비(number=px | CSS 문자열)\n labelSize?: '12' | '13' | '14' | '15' | '16'; // Label size (12~16). 미지정 시 '14' (2026-07-06 세로도 12→14 통일)\n gap?: keyof typeof GAP_CLASS; // 라벨↔컨트롤 간격 토큰 키 override (예: 'spacing-5')\n}\n\nexport function Field({\n label, // string | ReactNode — 주면 내부 Label 렌더\n htmlFor, // 컨트롤 id와 연결(라벨 클릭 시 포커스). 컨트롤에 같은 id 부여 필요\n required = false,\n disabled = false, // 라벨 비활성 스타일(컨트롤 disabled는 컨트롤에 직접 전달)\n description, // 보조 설명(helper) — 컨트롤 아래 회색 텍스트\n direction = 'vertical', // 'vertical' | 'horizontal' (라벨↔컨트롤 배치)\n controlsDirection = 'column', // 'column' | 'row' — 컨트롤 여러 개일 때 배치(복합 필드, row=한 줄 등분)\n labelWidth, // horizontal일 때 라벨 영역 너비(number=px | CSS 문자열)\n labelSize, // Label size (12~16). 미지정 시 '14' (2026-07-06 세로도 12→14 통일)\n gap, // 라벨↔컨트롤 간격 토큰 키 override (예: 'spacing-5')\n className = '',\n children, // 컨트롤\n ...props\n}: FieldProps) {\n const isHorizontal = direction === 'horizontal';\n const isRowControls = controlsDirection === 'row';\n // 라벨 기본 사이즈는 layout에 따라 다름(가로=14 / 세로=12), 명시 지정 시 그것 우선\n const resolvedLabelSize = labelSize ?? '14';\n const gapClass =\n GAP_CLASS[gap ?? DEFAULT_GAP[isHorizontal ? 'horizontal' : 'vertical']];\n\n const wrapper = isHorizontal\n ? `flex flex-row items-start ${gapClass}`\n : `flex flex-col ${gapClass}`;\n\n const labelWidthStyle =\n isHorizontal && labelWidth != null\n ? { width: typeof labelWidth === 'number' ? `${labelWidth}px` : labelWidth }\n : undefined;\n\n return (\n <div className={`${wrapper} ${className}`} {...props}>\n {label != null && (\n <div\n style={labelWidthStyle}\n className={isHorizontal ? 'shrink-0 pt-spacing-3' : undefined}\n >\n <Label htmlFor={htmlFor} size={resolvedLabelSize} required={required} disabled={disabled}>\n {label}\n </Label>\n </div>\n )}\n\n {/* 컨트롤 영역 — controls 컨테이너(가로 등분/세로 스택, 컨트롤 간 8px) + 그 아래 helper(4px).\n 가로 레이아웃에선 남은 폭을 채운다 */}\n <div className={`flex min-w-0 flex-col gap-spacing-3 ${isHorizontal ? 'flex-1' : ''}`}>\n {isRowControls ? (\n // 복합 필드(row): 컨트롤을 한 줄에 등분(각 min-w-0 flex-1), 컨트롤 간 8px\n <div className=\"flex flex-row items-start gap-spacing-5\">\n {/* toArray — 조건부 자식(false/null)을 걸러 유령 등분 컬럼이 생기지 않게 */}\n {Children.toArray(children).map((c) => (\n <div key={(c as ReactElement).key} className=\"min-w-0 flex-1\">\n {c}\n </div>\n ))}\n </div>\n ) : (\n // column: 컨트롤 세로 스택, 컨트롤 간 8px(row와 동일)\n <div className=\"flex flex-col gap-spacing-5\">{children}</div>\n )}\n {description != null && (\n <p className=\"text-12 text-label-field-helper-text\">{description}</p>\n )}\n </div>\n </div>\n );\n}\n","// FormTemplate — 폼 템플릿 (Figma \"form template\", Property 1=1/2/3/mixed column)\n// 12칸 그리드(Figma \"12 grid\") 위에 Field(라벨+컨트롤)를 span으로 배치하는 조립형 템플릿(규칙 4).\n// - columns: 1/2/3 — 필드 기본 폭(12/columns칸). 혼합 배치는 필드별 span(12칸 기준)으로 지정\n// - columnGap/rowGap: 그리드 가로/세로 간격 — 16/20/24/28/32(px, spacing 토큰 7~11)\n// - labelSize: 모든 필드 라벨 크기 일괄 변경('12'~'16', 미지정 시 Field 기본)\n// 컨트롤은 소비자가 field.control로 그대로 주입(Input/Select/DateField/EmailField 등 — 손으로 안 짬).\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { Field } from './Field';\n\n// 간격(px) → 등록 spacing 토큰 클래스 (16=7 · 20=8 · 24=9 · 28=10 · 32=11)\nconst GAP_X = {\n 16: 'gap-x-spacing-7',\n 20: 'gap-x-spacing-8',\n 24: 'gap-x-spacing-9',\n 28: 'gap-x-spacing-10',\n 32: 'gap-x-spacing-11',\n};\nconst GAP_Y = {\n 16: 'gap-y-spacing-7',\n 20: 'gap-y-spacing-8',\n 24: 'gap-y-spacing-9',\n 28: 'gap-y-spacing-10',\n 32: 'gap-y-spacing-11',\n};\n\n// 폼 필드 한 항목 — { key, label, required?, disabled?, description?, span?(1~12), control: ReactNode }\ninterface FormTemplateField {\n key: string;\n label?: ReactNode;\n required?: boolean;\n disabled?: boolean;\n description?: ReactNode;\n span?: number; // 1~12 (12칸 기준)\n control?: ReactNode;\n}\n\ninterface FormTemplateProps extends ComponentPropsWithoutRef<'div'> {\n fields?: FormTemplateField[]; // [{ key, label, required?, disabled?, description?, span?(1~12), control: ReactNode }]\n columns?: 1 | 2 | 3; // 필드 기본 폭(12/columns칸). mixed는 필드별 span으로\n columnGap?: keyof typeof GAP_X; // 가로 간격(px): 16 | 20 | 24 | 28 | 32\n rowGap?: keyof typeof GAP_Y; // 세로 간격(px): 16 | 20 | 24 | 28 | 32\n labelSize?: '12' | '13' | '14' | '15' | '16'; // 라벨 크기 일괄('12'~'16') — 미지정 시 Field 기본(세로 12)\n}\n\nexport function FormTemplate({\n fields = [],\n columns = 1,\n columnGap = 16,\n rowGap = 16,\n labelSize,\n className = '',\n ...props\n}: FormTemplateProps) {\n const cols = Math.min(Math.max(columns, 1), 3);\n const defaultSpan = 12 / cols; // 1→12 · 2→6 · 3→4\n const gapX = GAP_X[columnGap] ?? GAP_X[16];\n const gapY = GAP_Y[rowGap] ?? GAP_Y[16];\n\n return (\n <div className={`grid grid-cols-12 ${gapX} ${gapY} ${className}`} {...props}>\n {fields.map((f) => {\n const span = Math.min(Math.max(f.span ?? defaultSpan, 1), 12);\n return (\n // span은 12칸 기준 — Tailwind purge를 피해 인라인 grid-column으로 지정\n <div key={f.key} style={{ gridColumn: `span ${span} / span ${span}` }}>\n <Field\n label={f.label}\n required={f.required}\n disabled={f.disabled}\n description={f.description}\n {...(labelSize ? { labelSize } : {})}\n >\n {f.control}\n </Field>\n </div>\n );\n })}\n </div>\n );\n}\n","// ImageUploadMenu — 이미지 업로드 팝오버 (Figma popover menu / type=upload image)\n//\n// PopoverMenu 컨테이너를 조립한다(규칙 4): 안내 박스 + 이미지 미리보기 + 푸터.\n// 프레젠테이셔널(controlled) — image(미리보기 URL)는 호출부가 소유.\n// - image 있음: 미리보기 + 푸터 [파일 삭제(line) · 파일 찾기(fill)]\n// - image 없음(empty): 미리보기·삭제 없이 전체폭 '파일 찾기'만\n//\n// ⚠️ 주의: guide(화면 안내 문구)와 accept(실제 파일 선택창 필터)는 **별개 prop**이라\n// 자동 연동되지 않는다. 표시 확장자와 실제 허용 확장자가 어긋나지 않도록 **둘을 직접 일치**시킬 것.\n// (예: guide '… .jpeg, .png, .webp …' ↔ accept '.jpeg,.png,.webp')\nimport { useRef } from 'react';\nimport type { ChangeEvent } from 'react';\nimport { PopoverMenu } from './PopoverMenu';\nimport type { PopoverMenuProps } from './PopoverMenu';\n\n// onSelect는 DOM select 이벤트 핸들러와 이름이 겹쳐 Omit 후 파일 콜백으로 재선언\nexport interface ImageUploadMenuProps extends Omit<PopoverMenuProps, 'onSelect'> {\n /** 미리보기 이미지 URL (없으면 empty) */\n image?: string;\n /** 안내 문구(여러 줄은 \\n) */\n guide?: string;\n /** <input accept> 실제 필터 (예: '.jpeg,.png,.webp'). ⚠️ guide 표시와 직접 일치시킬 것 */\n accept?: string;\n /** (File) => void — '파일 찾기'로 선택 */\n onSelect?: (file: File) => void;\n /** () => void — '파일 삭제' */\n onRemove?: () => void;\n findText?: string;\n removeText?: string;\n imageAlt?: string;\n}\n\nexport function ImageUploadMenu({\n image,\n guide,\n accept,\n onSelect,\n onRemove,\n findText = '파일 찾기',\n removeText = '파일 삭제',\n imageAlt = '업로드 이미지 미리보기',\n width = 320,\n className = '',\n ...props\n}: ImageUploadMenuProps) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n const openPicker = () => inputRef.current?.click();\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n if (e.target.files?.[0]) onSelect?.(e.target.files[0]);\n e.target.value = '';\n };\n\n return (\n <PopoverMenu\n width={width}\n className={className}\n footer\n footerButtonsFill\n showCancel={!!image}\n cancelText={removeText}\n onCancel={onRemove}\n confirmText={findText}\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 {image && (\n <div className=\"bg-list-group-bg p-spacing-5\">\n {/* 높이 360px 초과 시 360에서 잘라 보여준다(clip, 가운데 기준 — 위·아래 균등 크롭) */}\n <div className=\"flex max-h-[360px] w-full items-center overflow-hidden rounded-round-4\">\n <img src={image} alt={imageAlt} className=\"block w-full shrink-0\" />\n </div>\n </div>\n )}\n\n <input ref={inputRef} type=\"file\" accept={accept} hidden onChange={handleChange} />\n </PopoverMenu>\n );\n}\n","// ImageUploadButton — 버튼 트리거 + 이미지 업로드 팝오버 (Figma butto+ image upload)\n//\n// Button을 트리거로 Popover에 ImageUploadMenu를 붙인다(규칙 4 조립).\n// - 모든 Button 스타일 사용 가능: buttonProps로 variant/size/leftIcon 등 그대로 통과.\n// 이미지 상태는 호출부가 소유(컨트롤드) — ImageUploadMenu props(image/onSelect/onRemove 등)를 그대로 받는다.\nimport { Image as ImageIcon } from 'lucide-react';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { ImageUploadMenu } from './ImageUploadMenu';\nimport type { ImageUploadMenuProps } from './ImageUploadMenu';\nimport type { UploadTriggerButtonProps } from './FileUploadButton';\n\nexport interface ImageUploadButtonProps extends Omit<ImageUploadMenuProps, 'width'> {\n // ── 트리거 버튼 ──\n triggerText?: string;\n /** 모든 Button 스타일 props 통과. 기본 variant=line·leftIcon=Image */\n buttonProps?: UploadTriggerButtonProps;\n // ── 팝오버 ──\n placement?: string;\n /** 팝오버(=ImageUploadMenu) 너비 */\n menuWidth?: number | string;\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n}\n\nexport function ImageUploadButton({\n triggerText = '이미지 등록',\n buttonProps = {},\n placement = 'bottom-left',\n menuWidth = 320,\n open,\n onOpenChange,\n disabled = false,\n className = '',\n // ── 나머지는 ImageUploadMenu props(image/guide/accept/onSelect/onRemove/...) ──\n ...menuProps\n}: ImageUploadButtonProps) {\n const { leftIcon = ImageIcon, 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 {triggerText}\n </Button>\n }\n >\n <ImageUploadMenu width=\"100%\" {...menuProps} />\n </Popover>\n );\n}\n","// tableView — Table 헤더 요소(필터·정렬)의 순수 데이터 헬퍼 + 셀 치수 계산.\nexport type TableRowData = Record<string, unknown> & { id?: string | number };\nexport interface TableSort {\n key: string;\n dir: 'asc' | 'desc';\n}\nimport { spacing } from '../tokens';\n// Table 내부와 TableTemplate(페이지 자르기 전 전체 행)이 같은 규칙을 공유하도록 컴포넌트 파일과 분리해 둔다.\n\n// 정렬 비교 — 둘 다 숫자로 해석되면 수치 비교, 아니면 한국어 로케일 문자열 비교. null은 뒤로.\nexport function compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n const na = Number(a);\n const nb = Number(b);\n if (!Number.isNaN(na) && !Number.isNaN(nb) && a !== '' && b !== '') return na - nb;\n return String(a).localeCompare(String(b), 'ko');\n}\n\n// 헤더 필터(filter 컬럼) 적용 — filters={[colKey]:value}, ''/없음이면 그 컬럼 필터 해제. render 셀도 원본 값(row[key]) 기준.\nexport function applyColumnFilters(\n rows: TableRowData[],\n columns: { key: string; filter?: unknown }[],\n filters: Record<string, unknown> = {},\n) {\n return rows.filter((row) =>\n columns.every((c) => {\n const v = filters[c.key];\n return c.filter == null || v == null || v === '' || String(row[c.key]) === String(v);\n }),\n );\n}\n\n// 헤더 메뉴 정렬(sort={key,dir}) 적용 — 원본 배열은 건드리지 않게 복사 후 정렬. sort 없으면 그대로.\nexport function applySort(rows: TableRowData[], sort?: TableSort | null) {\n if (sort?.key == null) return rows;\n return [...rows].sort((a, b) => {\n const r = compareValues(a[sort.key], b[sort.key]);\n return sort.dir === 'desc' ? -r : r;\n });\n}\n\n// 아이콘 전용 셀 너비 계산 — 고스트 아이콘 버튼만 단독으로 들어가는 컬럼의 width를\n// \"셀 좌우 패딩(spacing-5-5×2) + 버튼 폭 합 + 버튼 사이 간격 합\"으로 정확히 산출한다(임의 폭 금지).\n// 예: iconCellWidth(2) = 10+10 + 24×2 + 8 = 76px (ghost size 24 버튼 2개, gap spacing-5)\nconst CELL_PAD_X = parseInt(spacing['spacing-5-5'], 10); // Table 바디/헤더 셀 좌우 패딩과 동일 토큰(10px)\n\nexport function iconCellWidth(\n count = 1,\n { buttonSize = 24, gap = parseInt(spacing['spacing-5'], 10) }: { buttonSize?: number; gap?: number } = {},\n) {\n return CELL_PAD_X * 2 + buttonSize * count + gap * Math.max(0, count - 1);\n}\n","// Table — 데이터 테이블 (Figma table, node 7257:1925)\n// 컬럼 정의(columns)와 데이터(rows)를 받아 렌더하는 완전 옵션형 테이블.\n// - columns: [{ key, label, width?, align?, render?, renderHeader?, filter?, headerMenu? }] width 없으면 가변(fill). fill 컬럼은 최소 40px 유지\n// renderHeader: () => ReactNode — 헤더 셀(라벨 영역)을 직접 렌더(가장 우선). 없으면 filter → label 순으로 처리\n// filter: { options:[{value,label}], allLabel?, placeholder? } — 헤더 라벨 자리에 인라인 텍스트형 Select(size 20)를\n// 넣고, 선택값으로 그 컬럼(row[key])을 기준으로 행을 거른다. 옵션 맨 앞에 전체 옵션(value '')이 자동 추가되고\n// 그걸 고르면 필터가 해제된다. 전체 옵션 라벨은 allLabel(기본 '{컬럼명} 전체')로 정한다.\n// 필터 상태는 filters/onFilterChange로 controlled, 미지정 시 내부 상태.\n// headerMenu: { sortable?, items?, icon?, width? } — 헤더 우측에 ghost 아이콘 버튼(size 24)을 두고, 클릭 시 Popover 메뉴를 연다.\n// sortable=true면 '오름차순/내림차순 정렬' 항목이 자동 추가되고 그 컬럼(row[key]) 기준으로 Table이 내부 정렬한다(현재 정렬은 항목에 selected 표시).\n// items=[{ key?, label, icon?, onClick(column) }] 로 임의 기능을 더 넣을 수 있다. 정렬 상태는 sort/onSortChange로 controlled, 미지정 시 내부 상태.\n// sortable도 items도 없으면(빈 메뉴) 버튼을 렌더하지 않는다.\n// - selectable: 체크박스 선택 컬럼(전체선택 헤더 포함). selectedIds/onSelectChange로 controlled, 미지정 시 내부 상태\n// - bordered: 외곽선 + 라운드(true) / 외곽선 없는 라운드 헤더(false)\n// - wrap: 본문 셀 텍스트 줄바꿈 여부. false(기본)=말줄임+행 높이 고정(잘리면 hover 툴팁) / true=줄바꿈으로 행이 세로로 늘어남\n// (render로 직접 렌더한 셀은 사용자 책임이라 wrap 영향을 받지 않음)\n// - maxHeight: 지정 시 본문 세로 스크롤 + 헤더 고정(sticky) + 헤더 하단 구분선\n// - minWidth: 테이블 최소 너비. 실제 최소 너비는 max(minWidth, 컬럼 최소폭 합)이며,\n// 컨테이너가 이보다 좁아지면 가로 스크롤이 자동 활성화되고 fill 컬럼은 40px를 유지한다\n// - scrollX: minWidth 없이도 가로 스크롤을 켜는 수동 옵션. 세로·가로 모두 ScrollArea 오버레이 스크롤바\n// - loading / emptyMessage: 로딩 · 빈 상태 처리\n// 색·간격·보더는 table-*/spacing-*/border-* 토큰만 사용(하드코딩 금지).\n// sticky 헤더의 하단 구분선은 border-collapse 환경에서 스크롤 시 사라지는 브라우저 버그를 피하려\n// box-shadow(토큰 색 인라인)로 그린다. — Tooltip/ScrollArea의 토큰값 인라인 적용과 동일한 예외.\nimport { useMemo, useState } from 'react';\nimport type { CSSProperties, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { LoaderCircle, MoreVertical } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { Checkbox } from './Checkbox';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\nimport { Select } from './Select';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { applyColumnFilters, applySort } from './tableView';\nimport type { TableRowData, TableSort } from './tableView';\nimport { tableColors } from '../tokens';\n\n// 행 선택 키(rowKey 값 또는 인덱스 폴백)\nexport type TableRowKey = string | number;\n\n// 헤더 필터(filter 컬럼) 옵션 — Select options 계약과 동일(값·라벨 문자열)\nexport interface TableColumnFilterOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n\n// 헤더 메뉴(items) 항목 — sortable 자동 항목 외 임의 기능 추가용\nexport interface TableHeaderMenuItem {\n key?: string | number;\n label: ReactNode;\n icon?: LucideIcon;\n onClick?: (column: TableColumn) => void;\n}\n\n// 컬럼 정의 — 상단 주석의 columns 스펙과 1:1\nexport interface TableColumn {\n key: string;\n label?: ReactNode;\n /** 없으면 가변(fill). fill 컬럼은 최소 40px 유지 */\n width?: number;\n align?: 'left' | 'center' | 'right';\n /** 본문 셀 직접 렌더 */\n render?: (row: TableRowData) => ReactNode;\n /** 헤더 셀(라벨 영역)을 직접 렌더(가장 우선). 없으면 filter → label 순으로 처리 */\n renderHeader?: () => ReactNode;\n /** 헤더 라벨 자리 인라인 텍스트형 Select(size 20) 필터 */\n filter?: {\n options: TableColumnFilterOption[];\n allLabel?: string;\n placeholder?: string;\n };\n /** 헤더 우측 ghost 아이콘 버튼(size 24) + Popover 메뉴 */\n headerMenu?: {\n sortable?: boolean;\n items?: TableHeaderMenuItem[];\n icon?: LucideIcon;\n width?: number;\n };\n /** 셀 세로 병합 — (row, rowIndex, rows) => n. n>1=아래로 n행 병합, 0=위 셀 병합 범위에 포함(td 생략), 1/미지정=일반 셀 */\n cellSpan?: (row: TableRowData, rowIndex: number, rows: TableRowData[]) => number;\n}\n\nconst ALIGN_STYLE = {\n left: 'text-left justify-start',\n center: 'text-center justify-center',\n right: 'text-right justify-end',\n};\n\nconst CHECKBOX_COL_WIDTH = 44;\nconst COL_MIN_WIDTH = 40; // 가변(fill) 컬럼이 표가 좁아져도 유지하는 최소 너비(px)\n\n// HeaderMenu — 헤더 우측 ghost 아이콘 버튼(size 24) + Popover 메뉴.\n// sortable면 오름차순/내림차순 정렬 항목을 자동 제공(현재 정렬은 selected로 표시), items로 임의 기능 추가.\nfunction HeaderMenu({\n column,\n sort,\n onSort,\n}: {\n column: TableColumn;\n sort: TableSort | null;\n onSort: (sort: TableSort) => void;\n}) {\n const menu = column.headerMenu!; // headerMenu가 있을 때만 렌더된다(호출부 가드)\n const TriggerIcon = menu.icon ?? MoreVertical;\n const sortDir = sort?.key === column.key ? sort.dir : null;\n const items = menu.items ?? [];\n return (\n <Popover\n placement=\"bottom-right\"\n menuWidth={menu.width ?? 120}\n trigger={<Button variant=\"ghost\" size=\"24\" icon={TriggerIcon} aria-label={`${column.label ?? ''} 컬럼 메뉴`} showTooltip={false} />}\n >\n {(close) => (\n <PopoverMenu width=\"100%\">\n <ListGroup>\n {menu.sortable && [\n <List\n key=\"__asc\"\n title=\"오름차순 정렬\"\n selected={sortDir === 'asc'}\n onClick={() => {\n onSort({ key: column.key, dir: 'asc' });\n close();\n }}\n />,\n <List\n key=\"__desc\"\n title=\"내림차순 정렬\"\n selected={sortDir === 'desc'}\n onClick={() => {\n onSort({ key: column.key, dir: 'desc' });\n close();\n }}\n />,\n ]}\n {items.map((it, i) => (\n <List\n key={it.key ?? i}\n title={it.label}\n onClick={() => {\n it.onClick?.(column);\n close();\n }}\n />\n ))}\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n );\n}\n\nexport interface TableProps extends ComponentPropsWithoutRef<'div'> {\n columns?: TableColumn[];\n rows?: TableRowData[];\n rowKey?: string;\n selectable?: boolean;\n selectedIds?: TableRowKey[];\n onSelectChange?: (selectedIds: TableRowKey[]) => void;\n filters?: Record<string, unknown>;\n onFilterChange?: (filters: Record<string, unknown>) => void;\n sort?: TableSort | null;\n onSortChange?: (sort: TableSort | null) => void;\n bordered?: boolean;\n wrap?: boolean;\n /** number | 'fill' — fill=부모 flex 높이를 상한으로(모달 등), 내용 적으면 자연 높이 */\n maxHeight?: number | string;\n /** 바디 스크롤 영역 최소 높이(px|CSS) — 내용이 적어도 이 높이는 확보 */\n minHeight?: number | string;\n minWidth?: number;\n scrollX?: boolean;\n loading?: boolean;\n loadingMessage?: ReactNode;\n emptyMessage?: ReactNode;\n onRowClick?: (row: TableRowData) => void;\n}\n\nexport function Table({\n columns = [],\n rows = [],\n rowKey = 'id',\n selectable = false,\n selectedIds,\n onSelectChange,\n filters,\n onFilterChange,\n sort,\n onSortChange,\n bordered = false,\n wrap = false,\n maxHeight,\n minHeight,\n minWidth,\n scrollX = false,\n loading = false,\n loadingMessage = '불러오는 중…',\n emptyMessage = '데이터가 없습니다.',\n onRowClick,\n className = '',\n ...props\n}: TableProps) {\n // 헤더 필터 상태 — controlled(filters+onFilterChange) 또는 내부 상태. { [colKey]: value }, ''=전체(해제)\n const filtersControlled = filters !== undefined;\n const [internalFilters, setInternalFilters] = useState<Record<string, unknown>>({});\n const activeFilters = filtersControlled ? (filters as Record<string, unknown>) : internalFilters;\n const setFilter = (key: string, val: unknown) => {\n const next = { ...activeFilters, [key]: val };\n if (!filtersControlled) setInternalFilters(next);\n onFilterChange?.(next);\n };\n // 활성 필터를 적용해 행을 거른다(공유 헬퍼). 메모 — 안 하면 선택 토글 등 무관한 state\n // 변경마다 전체 행 재필터+재정렬이 돌아 수천 행에서 클릭마다 지연된다(2026-07-07 감사).\n const filteredRows = useMemo(\n () => applyColumnFilters(rows, columns, activeFilters),\n [rows, columns, activeFilters],\n );\n\n // 정렬 상태 — controlled(sort+onSortChange) 또는 내부 상태. { key, dir:'asc'|'desc' } | null\n const sortControlled = sort !== undefined;\n const [internalSort, setInternalSort] = useState<TableSort | null>(null);\n const activeSort = sortControlled ? (sort as TableSort | null) : internalSort;\n const setSort = (next: TableSort | null) => {\n if (!sortControlled) setInternalSort(next);\n onSortChange?.(next);\n };\n // 정렬 적용(필터 뒤, 공유 헬퍼) — 헤더 메뉴 항목 기준(row[key]). 메모(위와 동일 이유).\n const displayRows = useMemo(() => applySort(filteredRows, activeSort), [filteredRows, activeSort]);\n\n // 선택 상태 — controlled(selectedIds+onSelectChange) 또는 내부 상태\n const isControlled = selectedIds !== undefined;\n const [internalSel, setInternalSel] = useState<TableRowKey[]>([]);\n const selected = isControlled ? (selectedIds as TableRowKey[]) : internalSel;\n const selectedSet = new Set(selected); // O(1) 조회 — 행마다 includes 하지 않게\n\n const getKey = (row: TableRowData, i: number) => (row[rowKey] ?? i) as TableRowKey;\n // 선택 + 인덱스 key 폴백 + 정렬/필터가 겹치면 체크가 다른 행으로 옮겨간다 — 개발 중에만 경고\n const meta = import.meta as { env?: { DEV?: boolean } };\n if (meta.env?.DEV && selectable && rows.length > 0 && rows[0]?.[rowKey] == null) {\n console.warn(\n `[Table] selectable인데 rows에 rowKey('${rowKey}')가 없어 인덱스로 선택을 추적합니다 — 정렬/필터 시 선택이 다른 행으로 이동합니다. 고유 키 필드를 지정하세요.`,\n );\n }\n const emit = (next: TableRowKey[]) => {\n if (!isControlled) setInternalSel(next);\n onSelectChange?.(next);\n };\n // 전체선택은 현재 보이는(필터·정렬된) 행 기준\n const allKeys = displayRows.map(getKey);\n const allChecked = displayRows.length > 0 && allKeys.every((k) => selectedSet.has(k));\n const toggleAll = () => emit(allChecked ? [] : allKeys);\n const toggleRow = (key: TableRowKey) =>\n emit(selectedSet.has(key) ? selected.filter((k) => k !== key) : [...selected, key]);\n\n const totalCols = columns.length + (selectable ? 1 : 0);\n const vMax = maxHeight;\n const fillV = vMax === 'fill'; // 세로 상한을 부모 flex 높이로\n const hasVScroll = vMax != null;\n // 가로+세로 스크롤 중첩 시(헤더 분리형) 바디 스크롤 요소 — 세로 thumb는 가로 스크롤을 안 받는\n // 바깥 가로 ScrollArea(vScrollEl 연동)가 그린다. 바디 자체 ScrollArea로 그리면 thumb가\n // 콘텐츠 전체 폭 우측(뷰포트 밖)에 붙어 가로 스크롤 위치에 따라 안 보인다(2026-07-06 지시).\n const [bodyScrollEl, setBodyScrollEl] = useState<HTMLDivElement | null>(null);\n\n // 컬럼 최소 너비 보장 — 가변(width 미지정=fill) 컬럼은 COL_MIN_WIDTH(40px), 고정 컬럼은 지정 폭.\n // 이들의 합을 테이블 최소 너비로 삼아, 그보다 좁아지면 fill 컬럼이 40px 아래로 줄지 않고 가로 스크롤이 생긴다.\n const hasFill = columns.some((c) => c.width == null);\n const contentMinWidth =\n (selectable ? CHECKBOX_COL_WIDTH : 0) +\n columns.reduce((sum, c) => sum + (c.width ?? COL_MIN_WIDTH), 0);\n\n // 최소 너비(minWidth)·가변 컬럼·scrollX 중 하나라도 있으면 가로 스크롤(오버레이)을 활성화한다.\n const hasHScroll = scrollX || minWidth != null || hasFill;\n const tableMinWidth = hasHScroll ? Math.max(minWidth ?? 0, contentMinWidth) : minWidth;\n const needsScroll = hasVScroll || hasHScroll;\n\n // 셀 공통 — 마지막 컬럼 제외 우측 구분선. 헤더는 한 단계 진한 header-line 토큰(2026-07-03 분리).\n const cellLine = (isLast: boolean) => (isLast ? '' : 'border-r border-table-cell-line');\n const headLine = (isLast: boolean) => (isLast ? '' : 'border-r border-table-header-line');\n\n // empty/loading 행: noneline은 데이터 행처럼 하단 구분선, bordered는 외곽선이 처리하므로 생략.\n const stateLine = bordered ? '' : 'border-b border-table-cell-line';\n\n // 헤더 행 하단 구분선 — bordered 타입에만 적용한다.\n // noneline(외곽선 없음)은 가이드상 둥근 회색 바만 있고 헤더 언더라인이 없어야 하므로,\n // 세로 스크롤(sticky 헤더)이어도 구분선을 긋지 않는다(회색 바 배경으로 본문과 구분).\n // border-collapse 환경에서 스크롤 시 사라지는 버그를 피하려 box-shadow로 안정적으로 그린다.\n // 색은 외곽선(outline)과 동일하게 맞춘다(2026-07-08 지시 — header-line #d6d6d6 → outline #e3e3e3).\n const headDivider: CSSProperties | undefined = bordered\n ? { boxShadow: `inset 0 -1px 0 ${tableColors['outline']}` }\n : undefined;\n\n // noneline(외곽선 없음) 테이블은 헤더가 위·아래 모두 둥근 회색 바 → 헤더 바깥 셀에 좌/우 라운드.\n // bordered는 래퍼 overflow-clip이 코너를 처리하므로 셀 라운드를 주지 않는다.\n const headCorner = (isFirst: boolean, isLast: boolean) =>\n bordered ? '' : `${isFirst ? 'rounded-l-round-4' : ''} ${isLast ? 'rounded-r-round-4' : ''}`;\n\n // 헤더 셀(<th>) 공통 — 구분선·코너·패딩·하단 구분선(box-shadow). 체크박스/라벨 셀이 함께 사용.\n // 오른쪽 패딩은 spacing-4(6px) — 헤더 우측 메뉴 버튼이 가장자리에 너무 떨어지지 않게.\n // 높이는 32px 고정(상하 패딩 없음 — 본문 h-[45px]처럼 컴포넌트 고정 치수).(상하 패딩 없음 — 본문 h-[45px]처럼 컴포넌트 고정 치수).\n const headCellProps = (isFirst: boolean, isLast: boolean, width?: number) => ({\n className: `${headLine(isLast)} ${headCorner(isFirst, isLast)} pl-spacing-5-5 pr-spacing-4 h-[32px] align-middle`,\n style: { ...(width ? { width } : null), ...headDivider },\n });\n\n const headCell = (c: TableColumn, isFirst: boolean, isLast: boolean) => (\n <th key={c.key} {...headCellProps(isFirst, isLast, c.width)}>\n {/* 좌: 라벨 영역(renderHeader → filter → label) / 우: headerMenu(ghost 아이콘 버튼 + Popover). */}\n <div className=\"flex items-center gap-spacing-3\">\n <div className={`flex min-w-0 flex-1 items-center ${ALIGN_STYLE[c.align as keyof typeof ALIGN_STYLE] ?? ALIGN_STYLE.left}`}>\n {/* 우선순위: renderHeader(직접 렌더) → filter(인라인 텍스트형 Select, size 20) → label(말줄임).\n 컬럼이 좁아도 줄바꿈으로 세로로 늘어나지 않게. 잘리면 hover 툴팁. */}\n {c.renderHeader ? (\n c.renderHeader()\n ) : c.filter ? (\n (() => {\n // 전체(필터 해제) 옵션 라벨 — allLabel이 있으면 그대로, 없으면 '{컬럼명} 전체'(label 없으면 '전체')\n const allLabel = c.filter.allLabel ?? (c.label ? `${c.label} 전체` : '전체');\n return (\n <Select\n variant=\"text\"\n size=\"20\"\n value={(activeFilters[c.key] ?? '') as string}\n onChange={(e) => setFilter(c.key, e.target.value)}\n options={[{ value: '', label: allLabel }, ...c.filter.options]}\n placeholder={c.filter.placeholder ?? allLabel}\n />\n );\n })()\n ) : (\n <TruncatingText as=\"span\" className=\"min-w-0 text-12 font-normal text-font-icon-5\">\n {c.label}\n </TruncatingText>\n )}\n </div>\n {/* headerMenu는 정렬(sortable)이나 항목(items)이 하나라도 있을 때만 버튼을 렌더(빈 메뉴 방지).\n flex 래퍼 — inline-flex(Popover 루트)의 베이스라인 갭(하단 2px)을 없애 상하 여백을 대칭으로. */}\n {c.headerMenu && (c.headerMenu.sortable || (c.headerMenu.items?.length ?? 0) > 0) && (\n <div className=\"flex shrink-0 items-center\">\n <HeaderMenu column={c} sort={activeSort} onSort={setSort} />\n </div>\n )}\n </div>\n </th>\n );\n\n // 세로 스크롤이면 타입과 무관하게 헤더를 스크롤 영역 밖으로 분리한다(2026-07-06, noneline도 통일) —\n // sticky 헤더는 알파 배경(header-bg) 뒤/위로 행이 비쳐 보이므로, 세로 스크롤 영역을 '바디 높이'로 한정.\n const splitHeader = hasVScroll;\n\n const colgroupEl = (\n <colgroup>\n {selectable && <col style={{ width: CHECKBOX_COL_WIDTH }} />}\n {columns.map((c) => (\n <col key={c.key} style={c.width ? { width: c.width } : undefined} />\n ))}\n </colgroup>\n );\n const tableClass = 'w-full table-fixed border-separate';\n const tableStyle: CSSProperties = { borderSpacing: 0, ...(tableMinWidth ? { minWidth: tableMinWidth } : null) };\n\n const theadEl = (\n <thead>\n <tr className=\"bg-table-header-bg\">\n {selectable && (\n // 체크박스 컬럼 헤더만 상하좌우 패딩을 8px(spacing-5)로 통일 — 공통 headCellProps의 좌(spacing-6) 패딩 대신.\n <th\n className={`${cellLine(false)} ${headCorner(true, false)} p-spacing-5 align-middle`}\n style={{ width: CHECKBOX_COL_WIDTH, ...headDivider }}\n >\n <div className=\"flex items-center justify-center\">\n <Checkbox checked={allChecked} onChange={toggleAll} />\n </div>\n </th>\n )}\n {columns.map((c, i) =>\n headCell(c, !selectable && i === 0, i === columns.length - 1),\n )}\n </tr>\n </thead>\n );\n\n const tbodyEl = (\n <tbody>\n {loading ? (\n <tr className=\"bg-table-row-bg\">\n <td colSpan={totalCols} className={`${stateLine} px-spacing-5-5 py-spacing-12`}>\n <div className=\"flex items-center justify-center gap-spacing-4 text-14 text-font-icon-3\">\n <LoaderCircle size={16} strokeWidth={1.8} className=\"animate-spin\" />\n {loadingMessage}\n </div>\n </td>\n </tr>\n ) : displayRows.length === 0 ? (\n <tr className=\"bg-table-row-bg\">\n <td colSpan={totalCols} className={`${stateLine} px-spacing-5-5 py-spacing-12 text-center text-14 text-font-icon-3`}>\n {emptyMessage}\n </td>\n </tr>\n ) : (\n displayRows.map((row, ri) => {\n const key = getKey(row, ri);\n const isLastRow = ri === displayRows.length - 1;\n // 행 구분선은 td에 적용한다(border-separate에선 tr 보더가 렌더되지 않음).\n // 바닥이 이미 닫히는 경우(bordered=외곽선 / noneline+세로스크롤=컨테이너 하단선)엔\n // 마지막 행 구분선을 빼 이중선(2px)이 되지 않게 한다.\n const bottomClosed = bordered || hasVScroll;\n const rowLine = bottomClosed && isLastRow ? '' : 'border-b border-table-cell-line';\n return (\n <tr\n key={key}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={`bg-table-row-bg transition-colors hover:bg-table-row-hover-bg ${\n onRowClick ? 'cursor-pointer' : ''\n }`}\n >\n {selectable && (\n <td className={`${cellLine(false)} ${rowLine} h-[45px] px-spacing-5-5 py-spacing-4 align-middle`}>\n <div className=\"flex items-center justify-center\">\n <Checkbox\n checked={selectedSet.has(key)}\n onChange={() => toggleRow(key)}\n onClick={(e) => e.stopPropagation()}\n />\n </div>\n </td>\n )}\n {columns.map((c, ci) => {\n // 셀 세로 병합 — 컬럼 cellSpan((row, rowIndex, rows) => n): n>1=아래로 n행 병합,\n // 0=위 셀의 병합 범위에 포함(이 행에선 td 생략), 1/미지정=일반 셀.\n const span = c.cellSpan ? c.cellSpan(row, ri, displayRows) : 1;\n if (span === 0) return null;\n // 병합 셀의 행 구분선은 셀이 '끝나는' 행 기준으로 판정(마지막 행에서 끝나면 이중선 방지)\n const endsLast = ri + Math.max(span, 1) - 1 >= displayRows.length - 1;\n const spanRowLine =\n bottomClosed && endsLast ? '' : 'border-b border-table-cell-line';\n return (\n <td\n key={c.key}\n rowSpan={span > 1 ? span : undefined}\n className={`${cellLine(ci === columns.length - 1)} ${spanRowLine} h-[45px] px-spacing-5-5 py-spacing-4 align-middle`}\n >\n <div className={`flex items-center ${ALIGN_STYLE[c.align as keyof typeof ALIGN_STYLE] ?? ALIGN_STYLE.left} min-w-0 text-14 text-font-icon-5`}>\n {/* wrap=false면 말줄임(행이 세로로 안 늘어남), wrap=true면 줄바꿈으로 늘어남. */}\n {c.render ? (\n c.render(row)\n ) : wrap ? (\n <span className=\"break-words\">{row[c.key] as ReactNode}</span>\n ) : (\n <TruncatingText as=\"span\" className=\"min-w-0\">\n {row[c.key] as ReactNode}\n </TruncatingText>\n )}\n </div>\n </td>\n );\n })}\n </tr>\n );\n })\n )}\n </tbody>\n );\n\n const table = (\n <table className={tableClass} style={tableStyle}>\n {colgroupEl}\n {theadEl}\n {tbodyEl}\n </table>\n );\n\n // 외곽: bordered → 외곽선+라운드+클립(코너 처리)\n // noneline → 외곽 없음(헤더 셀이 직접 라운드). 단, 세로 스크롤이 생기면 하단에\n // 구분선을 그어 스크롤 영역의 끝을 표시한다. (마지막 행 구분선은 위 rowLine에서 제거되고,\n // 그 선은 스크롤돼야만 보이므로, 스크롤 위치와 무관하게 항상 보이도록 컨테이너에 고정 underline을 둔다.)\n const shell = bordered\n ? 'overflow-clip rounded-round-4 border border-table-outline'\n : hasVScroll\n ? 'border-b border-table-cell-line'\n : '';\n\n // 스크롤 래핑 — splitHeader(bordered+세로)면 헤더는 고정하고 바디만 maxHeight 세로 스크롤.\n // 컬럼 정렬은 동일 colgroup + table-fixed + 동일 최소폭 래퍼로 보장하고,\n // 가로 스크롤은 헤더·바디가 같이 움직이도록 바깥에서 함께 감싼다.\n let body;\n if (splitHeader) {\n // 빈 상태/로딩은 스크롤 영역 자체를 두지 않는다(빈 목록에 세로 썸이 뜨는 아티팩트 방지)\n const bodyTable = (\n <table className={tableClass} style={tableStyle}>\n {colgroupEl}\n {tbodyEl}\n </table>\n );\n const scrollableBody = !loading && displayRows.length > 0;\n const fillActive = fillV && scrollableBody; // 데이터 없을 땐 최대 높이(fill) 미적용 — 자연 높이(2026-07-06)\n const minHStyle = minHeight\n ? { minHeight: typeof minHeight === 'number' ? `${minHeight}px` : minHeight }\n : undefined;\n // fill: 바디 스크롤 상한 = 부모 flex 높이(뷰포트 max-h-full — SideNavigation 독립 스크롤과 동일 패턴)\n const region = scrollableBody ? (\n hasHScroll ? (\n // 가로 스크롤 중첩 — 바디는 thumb 없는 스크롤 요소만 두고, 세로 thumb는 바깥\n // 가로 ScrollArea가 vScrollEl 연동으로 그린다(가로 스크롤 영향 없이 항상 보임)\n fillActive ? (\n <div\n ref={setBodyScrollEl}\n className=\"hide-native-scroll min-h-0 flex-1 overflow-y-auto\"\n style={minHStyle}\n >\n {bodyTable}\n </div>\n ) : (\n <div\n ref={setBodyScrollEl}\n className=\"hide-native-scroll overflow-y-auto\"\n style={{ ...minHStyle, maxHeight: typeof vMax === 'number' ? `${vMax}px` : vMax }}\n >\n {bodyTable}\n </div>\n )\n ) : fillActive ? (\n <ScrollArea className=\"min-h-0 flex-1\" contentClassName=\"max-h-full\" style={minHStyle}>\n {bodyTable}\n </ScrollArea>\n ) : (\n <ScrollArea maxHeight={vMax} style={minHStyle}>{bodyTable}</ScrollArea>\n )\n ) : (\n <div>{bodyTable}</div> // 빈/로딩 상태 — minHeight도 미적용(무조건 hug)\n );\n const split = (\n <div\n className={fillActive ? 'flex min-h-0 flex-1 flex-col' : undefined}\n style={tableMinWidth ? { minWidth: tableMinWidth } : undefined}\n >\n <table className={`${tableClass} shrink-0`} style={tableStyle}>\n {colgroupEl}\n {theadEl}\n </table>\n {region}\n </div>\n );\n body = hasHScroll ? (\n <ScrollArea\n horizontal\n vScrollEl={bodyScrollEl}\n className={fillActive ? 'min-h-0 flex-1' : ''}\n contentClassName={fillActive ? 'flex h-full min-h-0 flex-col' : ''}\n >\n {split}\n </ScrollArea>\n ) : (\n split\n );\n } else if (needsScroll) {\n body = (\n <ScrollArea maxHeight={vMax} horizontal={hasHScroll}>\n {table}\n </ScrollArea>\n );\n } else {\n body = table;\n }\n\n return (\n <div className={`${shell} ${fillV && !loading && displayRows.length > 0 ? 'flex min-h-0 flex-col' : ''} ${className}`} {...props}>\n {body}\n </div>\n );\n}\n","// JobPositionTemplate — 채용 직무 설정 템플릿 (Figma \"JobPositionTemplate\" 8220:82935)\n// Step 01(좌): ConditionOrderSlot — 조건 카드(기준 Select + 값 Select)들을 드래그 정렬·사용/미사용.\n// Step 02(우): 활성 조건의 선택 값이 read-only Input에 \"값1 / 값2 / …\"로 실시간 표시되고,\n// [추가] 클릭 시 하단 외곽선(bordered) 무한 스크롤 테이블(규칙 18)에 로우가 추가된다(최신이 위).\n// 로우의 SelectChip을 클릭하면 그 자리에서 값을 변경할 수 있다.\n// 조립(규칙 4): ConditionOrderSlot + Select + Input + Button + Table + SelectChip — 손 구현 0.\n// 작성 데이터는 onChange(rows)로 반출한다(템플릿 값 반출 계약 — 2026-07-07 감사 교훈).\nimport { useImperativeHandle, useState } from 'react';\nimport type { ComponentPropsWithoutRef, Ref } from 'react';\nimport { ChevronRight, Copy, Plus, Trash2 } from 'lucide-react';\nimport { ConditionOrderSlot } from './ConditionOrderSlot';\nimport { Select, SelectChip } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Table } from './Table';\nimport type { TableColumn } from './Table';\nimport { iconCellWidth } from './tableView';\nimport { Divider } from './Divider';\n\n// 로우 id 생성 — 모듈 카운터(let seq)는 HMR로 모듈이 재평가되면 0으로 리셋돼 기존 로우 id와\n// 충돌하고, React 중복 key로 새 로우가 화면에 안 나타난다(2026-07-07 복사 미동작 원인).\n// 시간+랜덤 조합으로 defaultRows·리마운트·HMR 어떤 경우에도 충돌하지 않게 한다.\nconst newRowId = () =>\n `row-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n\n// 옵션 항목 — [{ value, label }] (로우 칩에서 disabled 파생 부여)\ninterface JobOption {\n value: string;\n label: string;\n disabled?: boolean;\n}\n// 로우의 조건 항목 — { criteria, value }\ninterface JobRowItem {\n criteria: string;\n value: string;\n}\n// 테이블 로우 — { id, items: [{ criteria, value }] }\ninterface JobRow {\n id: string;\n items: JobRowItem[];\n}\n// 카드별 (기준, 값) 선택 — 다중 카드는 값이 배열\ninterface JobSelection {\n criteria: string;\n value: string | string[];\n}\n\n// (선택) 저장 API(React 19 ref-as-prop) —\n// validate(): 미선택 칩에 에러 표시 + 통과 여부(false면 저장 중단)\n// getRows(): 저장 시점 최신 로우 조회('변경 없이 저장'도 안전)\nexport interface JobPositionTemplateHandle {\n validate: () => boolean;\n getRows: () => JobRow[];\n}\n\ninterface JobPositionTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n criteriaOptions?: JobOption[]; // 기준 목록 [{ value, label }] — 카드의 첫 Select(예: 지역/고용형태/경력/직무)\n valueOptions?: Record<string, JobOption[]>; // 기준별 값 목록 { [criteriaValue]: [{ value, label }] } — 카드의 둘째 Select·로우 칩 공용\n conditionCount?: number; // 조건 카드 수\n defaultDisabledIds?: string[]; // 초기 미사용(스위치 off) 조건 id 목록 — 예: ['cond-4'] (id=`cond-1`..`cond-N`)\n // multiLastValue — 마지막 조건(표시 순서상 맨 아래 카드)의 값을 체크박스 다중 선택으로.\n // 추가 시 선택한 값마다 행을 하나씩 생성(각 행=단일 조합, 중복은 건너뜀).\n multiLastValue?: boolean;\n defaultRows?: JobRow[]; // 초기 로우 [{ id, items: [{ criteria, value }] }]\n onChange?: (rows: JobRow[]) => void; // 로우 추가/삭제/칩 변경 시 전체 스냅샷 반출\n step1Title?: string;\n step2Title?: string;\n addLabel?: string;\n inputPlaceholder?: string;\n orderLabel?: string;\n jobLabel?: string;\n manageLabel?: string;\n tableHeight?: number | 'fill'; // 테이블 세로 — 'fill'(내용만큼 계속 확장, 넘치면 모달 바디 스크롤) | 숫자(px 고정 상한+바디 스크롤)\n emptyMessage?: string;\n duplicateMessage?: string; // 중복 추가 시도 시 인풋 에러 툴팁 문구\n emptyValueMessage?: string; // 저장 검증 — 미선택 칩 에러 툴팁 문구\n ref?: Ref<JobPositionTemplateHandle>; // (선택) 저장 API(React 19 ref-as-prop)\n}\n\nexport function JobPositionTemplate({\n criteriaOptions = [],\n valueOptions = {},\n conditionCount = 4,\n defaultDisabledIds = [],\n multiLastValue = false,\n defaultRows = [],\n onChange,\n step1Title = '1. 조건 조합 설정',\n step2Title = '2. 채용 직무 추가',\n addLabel = '추가',\n inputPlaceholder = '조건을 선택하세요',\n orderLabel = '순서',\n jobLabel = '채용 직무',\n manageLabel = '관리',\n tableHeight = 'fill',\n emptyMessage = '추가된 채용 직무가 없습니다.',\n duplicateMessage = '이미 추가된 조건 조합입니다.',\n emptyValueMessage = '값을 선택하세요.',\n ref,\n className = '',\n ...props\n}: JobPositionTemplateProps) {\n // Step 01 상태 — 카드 순서/사용 여부는 ConditionOrderSlot controlled로 추적하고,\n // 카드별 (기준, 값) 선택은 템플릿이 소유한다(미리보기·추가 스냅샷 계산용).\n const cardIds = Array.from({ length: conditionCount }, (_, i) => `cond-${i + 1}`);\n const [order, setOrder] = useState(cardIds);\n const [enabledIds, setEnabledIds] = useState(() =>\n cardIds.filter((id) => !defaultDisabledIds.includes(id)),\n );\n const [selections, setSelections] = useState<Record<string, JobSelection>>({}); // { [cardId]: { criteria, value } }\n const effOrder = [\n ...order.filter((id) => cardIds.includes(id)),\n ...cardIds.filter((id) => !order.includes(id)),\n ];\n\n // 마지막 '사용 중' 조건 — multiLastValue면 이 카드의 값 셀렉트가 다중 선택(배열 값)이 된다.\n // 물리적 맨 아래가 아니라 활성 순번상 마지막(제목 \"조건 N.\"의 최대 N)이어야 한다\n // (조건 3·4를 끄면 조건 2가 마지막 → 조건 2가 체크박스, 2026-07-08 지적).\n const activeOrder = effOrder.filter((id) => enabledIds.includes(id));\n const lastCardId = activeOrder[activeOrder.length - 1];\n\n const setCardCriteria = (id: string, criteria: string) =>\n setSelections((p) => ({ ...p, [id]: { criteria, value: '' } })); // 기준 변경 시 값 리셋\n const setCardValue = (id: string, value: string | string[]) =>\n setSelections((p) => ({ ...p, [id]: { ...(p[id] ?? { criteria: '' }), value } }));\n\n // 값 정규화 — 다중 카드는 배열, 그 외는 문자열로 강제한다. 재정렬로 마지막 카드가 바뀌면\n // 이전 카드에 남은 배열 값은 여기서 ''로 취급돼(단일화) 조합·미리보기가 꼬이지 않는다.\n const isMultiCard = (id: string) => multiLastValue && id === lastCardId;\n const valueOf = (id: string): string | string[] => {\n const raw = selections[id]?.value;\n if (isMultiCard(id)) return Array.isArray(raw) ? raw : raw ? [raw] : [];\n return Array.isArray(raw) ? '' : (raw ?? '');\n };\n\n const labelOf = (criteria: string, value: string) =>\n (valueOptions[criteria] ?? []).find((o) => o.value === value)?.label ?? '';\n\n // 다른 '사용 중' 카드가 선택한 기준은 이 카드에서 비활성(중복 조건 방지, 2026-07-07 지시).\n // 앞 카드만 잠그면 뒤 카드가 먼저 고른 기준을 앞 카드가 다시 고를 수 있어(사용자 재현) 양방향 잠금.\n const usedByOthers = (id: string) => {\n const used = new Set<string>();\n for (const pid of effOrder) {\n if (pid === id || !enabledIds.includes(pid)) continue;\n const c = selections[pid]?.criteria;\n if (c) used.add(c);\n }\n return used;\n };\n\n // 활성 조건 중 (기준+값) 모두 선택된 것 — 표시 순서(effOrder) 기준.\n // 다중 값(배열)은 하나 이상 골랐을 때만 완성으로 본다(빈 배열=미완성).\n const activeComplete = effOrder\n .filter((id) => enabledIds.includes(id))\n .map((id) => ({ criteria: selections[id]?.criteria, value: valueOf(id) }))\n .filter((s): s is JobSelection => !!(s.criteria && (Array.isArray(s.value) ? s.value.length > 0 : s.value)));\n // 미리보기 — 다중 값 세그먼트는 라벨을 ', '로 잇는다(예: \"서울 / 신입 / 프론트엔드, 백엔드\")\n const valueLabel = (s: JobSelection) =>\n Array.isArray(s.value)\n ? s.value.map((v) => labelOf(s.criteria, v)).join(', ')\n : labelOf(s.criteria, s.value);\n const preview = activeComplete.map(valueLabel).join(' / ');\n\n // Step 02 로우 — 최신이 위(순서 내림차순). onChange로 전체 스냅샷 반출.\n const [rows, setRows] = useState<JobRow[]>(defaultRows);\n const applyRows = (next: JobRow[]) => {\n setRows(next);\n onChange?.(next);\n };\n\n // 중복 검증(2026-07-07 지시) — 조건 조합(기준+값)이 이미 테이블에 있으면 추가 불가.\n // 순서는 무관하다(2026-07-08 지시): \"정규직>신입…\"과 \"신입>정규직…\"은 값 집합이 같으므로 같은 조합.\n // → 각 (기준:값) 쌍을 정렬한 뒤 join해 순서 독립 키를 만든다.\n // 에러는 추가 시도 시 표시하고, 조합·로우가 바뀌어 중복이 해소되면 자동으로 사라진다(렌더 파생).\n const comboKey = (items: JobRowItem[]) =>\n items\n .map((it) => `${it.criteria}:${it.value}`)\n .sort()\n .join('|');\n // 현재 조건을 추가 가능한 '단일 조합 행'들로 펼친다 — 마지막 조건이 다중이면 값마다 한 조합.\n // 다중 아님: 조합 1개 / 다중(값 N개): 조합 N개(각 행은 값 하나짜리 단일 조합).\n const expandCombos = (): JobRowItem[][] => {\n const idx = activeComplete.findIndex((s) => Array.isArray(s.value));\n if (idx === -1)\n return [activeComplete.map((s) => ({ criteria: s.criteria, value: s.value as string }))];\n return (activeComplete[idx].value as string[]).map((v) =>\n activeComplete.map((s, i) => ({ criteria: s.criteria, value: i === idx ? v : (s.value as string) })),\n );\n };\n const combos = activeComplete.length > 0 ? expandCombos() : [];\n // 추가할 게 하나도 없으면(전부 이미 존재) 중복 — 인풋 에러로 알린다.\n const isDuplicate =\n combos.length > 0 && combos.every((items) => rows.some((r) => comboKey(r.items) === comboKey(items)));\n const [dupAttempted, setDupAttempted] = useState(false);\n const showDupError = dupAttempted && isDuplicate;\n\n const addRow = () => {\n if (activeComplete.length === 0) return;\n // 이미 존재하는 조합은 건너뛰고 새 조합만 추가(다중 값의 일부만 새것일 수 있음)\n const fresh = combos.filter((items) => !rows.some((r) => comboKey(r.items) === comboKey(items)));\n if (fresh.length === 0) {\n setDupAttempted(true); // 추가하지 않고 인풋에 에러 툴팁 표시\n return;\n }\n setDupAttempted(false);\n applyRows([...fresh.map((items) => ({ id: newRowId(), items })), ...rows]);\n };\n const removeRow = (id: string) => applyRows(rows.filter((r) => r.id !== id));\n // 로우 복사(2026-07-07 지시) — 값을 그대로 복제하되 완전 중복을 피하도록 마지막 칩만 미선택으로.\n // 새 로우는 '추가'와 동일하게 맨 위(최대 순번)로 들어간다. 복사는 항상 새 로우를 만든다 —\n // 미완성 중복은 저장 검증(빈 칩)과 칩 편집 잠금(같은 조합 완성 불가)이 걸러준다.\n const copyRow = (row: JobRow) =>\n applyRows([\n {\n id: newRowId(),\n items: row.items.map((it, i) =>\n i === row.items.length - 1 ? { ...it, value: '' } : { ...it },\n ),\n },\n ...rows,\n ]);\n const setRowValue = (rowId: string, itemIndex: number, value: string) => {\n applyRows(\n rows.map((r) =>\n r.id === rowId\n ? { ...r, items: r.items.map((it, i) => (i === itemIndex ? { ...it, value } : it)) }\n : r,\n ),\n );\n // 값이 채워지면 해당 칩의 저장 검증 에러 해제\n setInvalidKeys((p) => {\n const key = `${rowId}:${itemIndex}`;\n if (!p.has(key)) return p;\n const next = new Set(p);\n next.delete(key);\n return next;\n });\n };\n\n // 저장 검증(2026-07-07 지시) — 미선택 칩(복사 직후 등)에 에러 툴팁을 걸고 저장을 막는다.\n // 소비자가 저장 버튼에서 ref.current.validate()를 호출: 통과 시 true.\n const [invalidKeys, setInvalidKeys] = useState<Set<string>>(() => new Set());\n useImperativeHandle(ref, () => ({\n validate: () => {\n const invalid = new Set<string>();\n rows.forEach((r) =>\n r.items.forEach((it, i) => {\n if (!it.value) invalid.add(`${r.id}:${i}`);\n }),\n );\n setInvalidKeys(invalid);\n return invalid.size === 0;\n },\n // 저장 시점 최신 로우 조회 — onChange 스냅샷을 따로 보관하지 않아도 되고,\n // '변경 없이 바로 저장'(onChange 미발화) 케이스에서도 defaultRows 포함 최신 상태를 준다.\n getRows: () => rows,\n }));\n\n // 순서 번호 = 내림차순(맨 위가 가장 큼) — 렌더 파생\n const displayRows = rows.map((r, i) => ({ ...r, orderNo: rows.length - i }));\n\n // 로우 칩 편집 검증(2026-07-07 지시) — 이 값으로 바꾸면 다른 로우와 같은 조합이 되는 옵션은\n // 드롭다운에서 비활성(사전 차단, 추가 시 인풋 검증과 일관된 옵션 disabled 패턴)\n const wouldDuplicate = (row: JobRow, itemIndex: number, value: string) => {\n const key = comboKey(\n row.items.map((it, i) => (i === itemIndex ? { ...it, value } : it)),\n );\n return rows.some((r) => r.id !== row.id && comboKey(r.items) === key);\n };\n\n const columns = [\n { key: 'orderNo', label: orderLabel, width: 60 },\n {\n key: 'items',\n label: jobLabel,\n render: (row: JobRow) => (\n <div className=\"flex min-w-0 flex-wrap items-center gap-spacing-3\">\n {row.items.map((it: JobRowItem, i: number) => (\n <span key={`${it.criteria}-${i}`} className=\"flex items-center gap-spacing-3\">\n {i > 0 && (\n <ChevronRight size={12} strokeWidth={1.8} className=\"shrink-0 text-font-icon-3\" />\n )}\n <SelectChip\n options={(valueOptions[it.criteria] ?? []).map((o) =>\n o.value !== it.value && wouldDuplicate(row, i, o.value)\n ? { ...o, disabled: true }\n : o,\n )}\n value={it.value}\n searchable // 값 목록이 길어질 수 있어 팝오버 상단 검색 제공(2026-07-07 지시)\n searchPlaceholder=\"검색어 입력\" // 좁은 칩 드롭다운(160)에 맞춘 짧은 문구\n menuWidth={160} // 검색바가 들어가므로 칩 기본(120)보다 넓게\n error={invalidKeys.has(`${row.id}:${i}`)}\n errorMessage={emptyValueMessage}\n onChange={(e) => setRowValue(row.id, i, e.target.value)}\n />\n </span>\n ))}\n </div>\n ),\n },\n {\n key: 'manage',\n label: manageLabel,\n // 고스트 아이콘 버튼 2개(복사·삭제) — width는 임의값 대신 계산(규칙 17): 셀 패딩 + 버튼×2 + 간격\n width: iconCellWidth(2, { buttonSize: 32 }),\n render: (row: JobRow) => (\n <div className=\"flex items-center gap-spacing-5\">\n <Button variant=\"ghost\" size=\"32\" icon={Copy} aria-label=\"복사\" onClick={() => copyRow(row)} />\n <Button variant=\"ghost\" size=\"32\" icon={Trash2} aria-label=\"삭제\" onClick={() => removeRow(row.id)} />\n </div>\n ),\n },\n ];\n\n return (\n <div className={`flex w-full items-stretch gap-spacing-7 ${className}`} {...props}>\n {/* Step 01 — 조건 조합 설정 */}\n <div className=\"flex shrink-0 flex-col gap-spacing-6\">\n <p className=\"text-14 text-font-icon-5\">{step1Title}</p>\n <ConditionOrderSlot\n items={cardIds.map((id) => {\n const sel = selections[id] ?? { criteria: '', value: '' };\n const used = usedByOthers(id);\n return {\n id,\n body: (\n <>\n <Select\n width=\"100%\"\n options={criteriaOptions.map((o) =>\n used.has(o.value) ? { ...o, disabled: true } : o,\n )}\n value={sel.criteria}\n label=\"기준\" // 내부 라벨 — 선택 시 \"기준 ⋮ 지역\"으로 표시(2026-07-07 지시)\n placeholder=\"기준 선택\"\n onChange={(e) => setCardCriteria(id, e.target.value)}\n />\n <Select\n width=\"100%\"\n options={valueOptions[sel.criteria] ?? []}\n label=\"값\" // 내부 라벨 — 선택 시 \"값 ⋮ 서울\"으로 표시(2026-07-07 지시)\n placeholder=\"값 선택\"\n disabled={!sel.criteria}\n // 마지막 조건은 체크박스 다중 선택(값 여러 개) — multiple이 동적 boolean이라\n // 판별 유니언(SelectProps)에 스프레드로 한 번 좁혀 전달(런타임 동일)\n {...({\n multiple: isMultiCard(id),\n value: valueOf(id),\n onChange: (e: { target: { value: string | string[] } }) =>\n setCardValue(id, e.target.value),\n } as unknown as Parameters<typeof Select>[0])}\n />\n </>\n ),\n };\n })}\n order={order}\n onOrderChange={setOrder}\n enabledIds={enabledIds}\n onEnabledChange={(nextEnabled, { id: toggledId, enabled }) => {\n setEnabledIds(nextEnabled);\n // 다시 켠 카드가 양보(2026-07-07 지적) — 미사용 동안 다른 활성 카드가 같은 기준을\n // 선택했을 수 있으므로, 켜는 순간 기준이 이미 사용 중이면 이 카드의 선택을 리셋한다.\n // (이미 활성인 카드는 건드리지 않아 동작이 예측 가능)\n if (enabled) {\n const c = selections[toggledId]?.criteria;\n const taken =\n c &&\n nextEnabled.some(\n (oid) => oid !== toggledId && selections[oid]?.criteria === c,\n );\n if (taken) setSelections((p) => ({ ...p, [toggledId]: { criteria: '', value: '' } }));\n }\n }}\n />\n </div>\n\n <Divider direction=\"vertical\" />\n\n {/* Step 02 — 채용 직무 추가 */}\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col gap-spacing-6\">\n <p className=\"text-14 text-font-icon-5\">{step2Title}</p>\n <div className=\"flex items-center gap-spacing-5\">\n <div className=\"min-w-0 flex-1\">\n <Input\n width=\"100%\"\n readOnly\n value={preview}\n placeholder={inputPlaceholder}\n error={showDupError}\n errorMessage={duplicateMessage}\n />\n </div>\n <Button variant=\"line\" leftIcon={Plus} disabled={activeComplete.length === 0} onClick={addRow}>\n {addLabel}\n </Button>\n </div>\n {/* 규칙 18 — 모달 안 무한 스크롤 테이블: bordered + maxHeight='fill' + min-h-0(shrink 상한) */}\n <Table\n /* render 파라미터를 구체 행 타입(JobRow)으로 쓰므로 Table 계약(TableRowData)으로 한 번 캐스팅 —\n rows에 JobRow[]만 넘겨 런타임 안전 */\n columns={columns as unknown as TableColumn[]}\n rows={displayRows}\n rowKey=\"id\"\n bordered\n maxHeight={tableHeight}\n className={tableHeight === 'fill' ? 'min-h-0' : ''}\n emptyMessage={emptyMessage}\n />\n </div>\n </div>\n );\n}\n","// useFocusTrap — 열린 오버레이(Dialog/Modal 등)의 접근성 포커스 관리(2026-07-20, Radix FocusScope 참고·자체 구현).\n// ① 진입: 열릴 때 컨테이너(또는 첫 포커스 요소)로 포커스를 옮기고, 열기 직전 포커스 위치를 기억한다.\n// ② 트랩: Tab/Shift+Tab이 컨테이너 밖으로 나가지 않게 처음↔끝을 순환시킨다.\n// ③ 복원: 닫힐 때(언마운트·active=false) 열기 직전 포커스로 되돌린다.\n// 중첩 모달은 각자 자기 컨테이너에 트랩을 걸므로 별도 스택 조율이 필요 없다(inner 이벤트는 outer로 안 샌다).\n// 컨테이너 안 위젯(Select 옵션 순회·설정 패널 키보드 등)이 Tab을 이미 처리(preventDefault)하면 존중해 건너뛴다.\nimport { useEffect } from 'react';\nimport type { RefObject } from 'react';\n\nconst FOCUSABLE = [\n 'a[href]',\n 'button:not([disabled])',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n '[tabindex]:not([tabindex=\"-1\"])',\n].join(',');\n\nexport interface UseFocusTrapOptions {\n /** 진입 포커스 대상 — 'container'(기본, 컨테이너 자체) | 'first'(첫 포커스 가능 요소) */\n initialFocus?: 'container' | 'first';\n}\n\nexport function useFocusTrap(\n active: boolean,\n containerRef: RefObject<HTMLElement | null>,\n { initialFocus = 'container' }: UseFocusTrapOptions = {},\n) {\n useEffect(() => {\n if (!active) return;\n const container = containerRef.current;\n if (!container) return;\n const previouslyFocused = document.activeElement as HTMLElement | null;\n\n const focusables = () =>\n Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(\n (el) => el.offsetParent !== null, // 화면에 보이는 것만\n );\n\n // ① 진입 포커스\n if (initialFocus === 'first') {\n (focusables()[0] ?? container).focus();\n } else {\n container.focus();\n }\n\n // ② Tab 트랩 — 컨테이너 안 위젯이 이미 처리한 Tab(defaultPrevented)은 존중해 건너뛴다\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || e.defaultPrevented) return;\n const els = focusables();\n if (els.length === 0) {\n e.preventDefault();\n container.focus();\n return;\n }\n const first = els[0];\n const last = els[els.length - 1];\n const activeEl = document.activeElement;\n if (activeEl && !container.contains(activeEl)) {\n // 어쩌다 포커스가 밖에 있으면 안으로 회수\n e.preventDefault();\n first.focus();\n } else if (!e.shiftKey && activeEl === last) {\n e.preventDefault();\n first.focus();\n } else if (e.shiftKey && (activeEl === first || activeEl === container)) {\n e.preventDefault();\n last.focus();\n }\n // 그 외(내부 순방향/역방향 이동)는 브라우저 기본 동작에 맡긴다\n };\n container.addEventListener('keydown', onKeyDown);\n\n return () => {\n container.removeEventListener('keydown', onKeyDown);\n // ③ 포커스 복원 — 열기 직전 요소가 아직 문서에 살아 있을 때만\n if (previouslyFocused && document.contains(previouslyFocused)) {\n previouslyFocused.focus();\n }\n };\n }, [active, containerRef, initialFocus]);\n}\n","// ModalBodyMaxContext — Modal이 계산한 본문 '내용' 가용 높이(px, 본문 패딩 제외).\n// 모달 본문을 꽉 채우려는 자식(예: SideNavigationTemplate height='fill')이 소비한다.\n// 모달 밖에서는 null — 소비자는 부모 100% 등으로 폴백한다.\n// (컴포넌트 파일과 분리 — react-refresh only-export-components 회피, tableView/utils 전례)\nimport { createContext } from 'react';\n\nexport const ModalBodyMaxContext = createContext<number | null>(null);\n","// Modal — 오버레이 다이얼로그 (base + 변형)\n// Figma: h9jZFkEHfcHUGok1TZjjlP — modal (node 7349:1758474)\n//\n// 구조 (모달 설계 가이드):\n// ModalOverlay (딤)\n// └── ModalBox (size로 width 고정, 내부 요소는 width:100%)\n// ├── ModalHeader (타이틀 + 닫기 버튼)\n// ├── ModalBody (ScrollArea — maxHeight 70vh, 내부 스크롤)\n// │ └── children (ModalContent — 외부 주입)\n// └── ModalFooter (좌측 텍스트/컴포넌트 + 버튼 그룹)\n//\n// ModalBox 배경은 modal-inline(gray-100) + gap 1px → 헤더/바디/푸터(white) 사이가\n// 1px 구분선으로 비친다. 외곽선은 modal-outline 1px ring, round-6(10px), 그림자.\n//\n// 변형(아래 export):\n// - FormModal(취소/저장 + form) — 기본 Modal 구조(헤더 + 자유 본문 + 푸터) 그대로.\n// - AlertModal(확인 1개) · ConfirmModal(취소/확인) — 헤더 없이 본문에 고정 슬롯\n// [타이틀 → 설명 → 2뎁스 상세(회색 박스) → 체크박스(Confirm 전용)]만 둔다.\n// 슬롯 종류는 고정(텍스트만 자유 수정), 푸터 버튼은 전체 폭 균등 분할.\n// 체크박스는 삭제 등 위험 액션의 재확인 요소(ConfirmModal에서만 사용).\nimport { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport type {\n ChangeEvent,\n ComponentPropsWithoutRef,\n FormEvent,\n ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { useFocusTrap } from './useFocusTrap';\nimport { X } from 'lucide-react';\nimport { Button } from './Button';\nimport { ButtonGroup } from './ButtonGroup';\nimport { ScrollArea } from './ScrollArea';\nimport { ModalBodyMaxContext } from './modalContext';\nimport { Checkbox } from './Checkbox';\n\n// 중첩 모달 전역 관리(2026-07-07 감사) —\n// openModalStack: ESC는 '맨 위' 모달만 닫는다(없으면 ESC 한 번에 중첩 모달이 전부 닫혀 폼 유실).\n// closeOnEsc=false 모달도 스택에는 올라 아래 모달의 ESC를 막는다(맨 위가 안 닫히는 게 맞는 동작).\n// scrollLock: body 스크롤 잠금 참조 카운트 — 인스턴스별 prev 저장/복원은 A→B 순서로 열고 A를\n// 먼저 닫으면 B가 떠 있는데 잠금이 풀리므로, 첫 잠금의 원래 값을 마지막 해제 때만 복원한다.\nconst openModalStack: object[] = [];\nlet scrollLockCount = 0;\nlet scrollLockPrev = '';\n\n// ModalBox width 규격 — sm:360 md:480 lg:600 xl:720 2xl:840 3xl:960 4xl:1260\n// fill = 브라우저 너비에서 좌우 16px씩 뺀 값(calc(100vw-32px)), 단 최소 너비 1260px\nconst SIZE_WIDTH = {\n sm: 'w-[360px]',\n md: 'w-[480px]',\n lg: 'w-[600px]',\n xl: 'w-[720px]',\n '2xl': 'w-[840px]',\n '3xl': 'w-[960px]',\n '4xl': 'w-[1260px]',\n fill: 'w-[calc(100vw-32px)]',\n};\n\nexport interface ModalProps\n extends Omit<ComponentPropsWithoutRef<'div'>, 'title' | 'onSubmit'> {\n open: boolean;\n onClose: () => void;\n title?: ReactNode;\n /** 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | 'fill'(좌우 16씩 제외) */\n size?: keyof typeof SIZE_WIDTH;\n /** 'center'(브라우저 중앙) | 'top'(상단 정렬, 여백 16 ~ 화면높이/2−150 가변). 일반 팝업·FormModal 기본=top, Alert/Confirm은 center */\n placement?: 'center' | 'top';\n showHeader?: boolean;\n /** 헤더 우측 닫기(X) 버튼 */\n showClose?: boolean;\n showFooter?: boolean;\n /** 푸터 전체 커스텀 노드(주면 아래 버튼 props 무시) */\n footer?: ReactNode;\n /** 푸터 좌측 영역 — 새로고침/불러오기 버튼, 안내글, 유효성 메시지 등(텍스트/컴포넌트) */\n footerStart?: ReactNode;\n /** 'text' | 'button' — footerStart 내용 유형. 좌측 여백 결정: text=16(spacing-7) / button=12(spacing-6, 버튼 자체 여백 감안) — 2026-07-06 지시 */\n footerStartType?: 'text' | 'button';\n // 푸터 기본 버튼 — footer 미지정 시 사용\n confirmText?: ReactNode;\n cancelText?: ReactNode;\n onConfirm?: () => void;\n /** 미지정 시 onClose */\n onCancel?: () => void;\n /** 주동작 버튼 variant */\n confirmVariant?: 'fill' | 'line' | 'ghost' | 'underline';\n confirmDisabled?: boolean;\n confirmLoading?: boolean;\n /** 취소(보조) 버튼 노출 여부 */\n showCancel?: boolean;\n /** 푸터 버튼을 전체 폭으로 균등 분할(footerStart 없는 단순 액션용 — Alert/Confirm) */\n footerFullWidth?: boolean;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n /** 본문 최대 높이(초과 시 내부 스크롤) */\n bodyMaxHeight?: number | string;\n /** 본문 패딩(토큰 클래스). Alert/Confirm은 p-spacing-8 */\n bodyPadding?: string;\n /** 푸터 패딩 직접 지정(토큰 클래스) — 미지정 시 footerStartType 기반 기본값(좌 16/12·우 12·상하 12). Alert/Confirm은 px-spacing-8 py-spacing-7 */\n footerPadding?: string | null;\n bodyClassName?: string;\n /** 주면 본문+푸터를 <form>으로 감싸고 주동작 버튼 type=submit */\n onSubmit?: (e: FormEvent<HTMLFormElement>) => void;\n}\n\nexport function Modal({\n open,\n onClose,\n title,\n children, // ModalContent — 본문에 주입할 내용\n size = 'md',\n placement = 'top',\n showHeader = true,\n showClose = true,\n showFooter = true,\n footer,\n footerStart = null,\n footerStartType = 'text',\n confirmText = '확인',\n cancelText = '취소',\n onConfirm,\n onCancel,\n confirmVariant = 'fill',\n confirmDisabled = false,\n confirmLoading = false,\n showCancel = true,\n footerFullWidth = false,\n closeOnOverlayClick = true,\n closeOnEsc = true,\n bodyMaxHeight = '70vh',\n bodyPadding = 'p-spacing-7',\n footerPadding = null,\n bodyClassName = '',\n onSubmit,\n className = '',\n ...props\n}: ModalProps) {\n const boxRef = useRef<HTMLDivElement | null>(null);\n const headerRef = useRef<HTMLElement | null>(null);\n const footerRef = useRef<HTMLElement | null>(null);\n const contentRef = useRef<HTMLDivElement | null>(null);\n // top 정렬 상단 여백은 '열릴 때 한 번' 계산해 닫을 때까지 고정한다(내용 변화로 흔들리지 않게).\n const lockedTopRef = useRef<number | null>(null);\n // 레이아웃 측정값:\n // - top: top 정렬 시 상단 여백(px). 팝업 자연 높이에 반비례 — 내용 적으면 크게(최대 120),\n // 내용 많으면 작게(최소 = 하단 여백 16). center 정렬이면 null.\n // - bodyMax: ModalBody 실제 maxHeight(px). header·footer·여백을 빼 화면에 맞춘 값이라\n // footer가 절대 가려지지 않는다(flex 높이 전파에 의존하지 않음).\n const [layout, setLayout] = useState<{\n top: number | null;\n bodyMax: number | null;\n bodyInner: number | null;\n }>({ top: null, bodyMax: null, bodyInner: null });\n\n // ESC 닫기 — 열린 모달 스택의 '맨 위'일 때만 닫는다(중첩 모달 동시 닫힘 방지).\n // closeOnEsc/onClose는 ref로 읽어 effect 재실행(스택 순서 뒤틀림)을 피한다.\n const closeOnEscRef = useRef(closeOnEsc);\n const onCloseRef = useRef(onClose);\n useEffect(() => {\n closeOnEscRef.current = closeOnEsc;\n onCloseRef.current = onClose;\n }); // 매 렌더 후 최신값 유지(latest-ref) — 렌더 중 ref 쓰기 금지 규칙 준수\n useEffect(() => {\n if (!open) return;\n const token = {};\n openModalStack.push(token);\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return;\n if (openModalStack[openModalStack.length - 1] !== token) return; // 맨 위 모달만\n if (closeOnEscRef.current) onCloseRef.current?.();\n };\n document.addEventListener('keydown', onKey);\n return () => {\n document.removeEventListener('keydown', onKey);\n const i = openModalStack.indexOf(token);\n if (i !== -1) openModalStack.splice(i, 1);\n };\n }, [open]);\n\n // 열렸을 때 body 스크롤 잠금 — 참조 카운트(중첩 모달에서 먼저 닫힌 쪽이 잠금을 풀지 않게)\n useEffect(() => {\n if (!open) return;\n scrollLockCount += 1;\n if (scrollLockCount === 1) {\n scrollLockPrev = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n }\n return () => {\n scrollLockCount -= 1;\n if (scrollLockCount === 0) document.body.style.overflow = scrollLockPrev;\n };\n }, [open]);\n\n // 포커스 트랩 + 복원(접근성) — 열릴 때 박스로 진입, Tab을 박스 안에 가두고, 닫힐 때 원래 포커스 복원.\n // 중첩 모달은 각자 자기 박스에 트랩(스택 조율 불필요), 내부 위젯이 처리한 Tab은 존중한다.\n useFocusTrap(open, boxRef);\n\n // 레이아웃 계산 — header/footer/본문 자연 높이를 측정해 top 여백과 ModalBody maxHeight를 정한다.\n useLayoutEffect(() => {\n if (!open) return;\n const BOTTOM = 16; // 하단 여백(spacing-7) — 고정\n const toPx = (v: number | string | null | undefined) => {\n if (v == null) return Infinity;\n if (typeof v === 'number') return v;\n const s = String(v).trim();\n if (s.endsWith('vh')) return (window.innerHeight * parseFloat(s)) / 100;\n if (s.endsWith('px')) return parseFloat(s);\n return Infinity;\n };\n const compute = () => {\n const vh = window.innerHeight;\n const hh = headerRef.current?.offsetHeight ?? 0;\n const fh = footerRef.current?.offsetHeight ?? 0;\n const ch = contentRef.current?.offsetHeight ?? 0; // 본문 natural(미캡) 높이\n const designCap = toPx(bodyMaxHeight); // 디자인 캡(기본 70vh)\n // 본문 '내용' 가용 높이용 — 본문 패딩(py)을 실측해 빼준다(height='fill' 자식 소비, ModalBodyMaxContext)\n const padY = () => {\n const el = contentRef.current;\n if (!el) return 0;\n const cs = getComputedStyle(el);\n return parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);\n };\n\n if (placement === 'top') {\n // 상단 정렬은 % 높이 캡(bodyMaxHeight, 70vh)을 적용하지 않는다 — top 여백 + 화면 가용\n // 높이로만 본문 높이를 정한다. 상단 여백 top은 '열릴 때 한 번'만 계산해 고정(lockedTopRef)\n // → 이후 내용이 바뀌어도 위치가 흔들리지 않는다. bodyMax만 화면 크기에 맞춰 갱신.\n // - 팝업 자연 높이에 반비례: 내용 많음 → top 작게(최소 BOTTOM), 적음 → 크게(최대 MAX_TOP).\n if (lockedTopRef.current == null) {\n // 상단 여백 범위: BOTTOM(16) ~ MAX_TOP(화면높이/2 − 150). 작은 화면에선 BOTTOM로 수렴.\n // 팝업 자연 높이가 화면을 채우는 비율(0~1)에 선형 비례해 top을 MAX_TOP→BOTTOM로 보간.\n // 내용 적음 → top 큼(최대 MAX_TOP), 내용 많음 → top 작음(최소 BOTTOM).\n const MAX_TOP = Math.max(BOTTOM, vh / 2 - 150);\n const effModalH = hh + fh + ch;\n const fill = Math.min(1, Math.max(0, effModalH / vh));\n const top = MAX_TOP - (MAX_TOP - BOTTOM) * fill;\n lockedTopRef.current = Math.round(Math.min(MAX_TOP, Math.max(BOTTOM, top)));\n }\n const top = lockedTopRef.current;\n // -2: header↔body, body↔footer 사이 gap-spacing-1(각 1px 구분선) 공간 확보\n const bodyMax = Math.max(0, vh - top - BOTTOM - hh - fh - 2);\n setLayout({ top, bodyMax, bodyInner: Math.max(0, bodyMax - padY()) });\n } else {\n const bodyMax = Math.max(0, Math.min(designCap, vh - 32 - hh - fh));\n setLayout({ top: null, bodyMax, bodyInner: Math.max(0, bodyMax - padY()) });\n }\n };\n compute();\n const ro = new ResizeObserver(compute);\n [headerRef, footerRef, contentRef].forEach((r) => r.current && ro.observe(r.current));\n window.addEventListener('resize', compute);\n return () => {\n ro.disconnect();\n window.removeEventListener('resize', compute);\n lockedTopRef.current = null; // 닫히면(또는 placement 변경) 고정 해제 → 다음 열림에 재계산\n };\n }, [open, placement, bodyMaxHeight]);\n\n if (!open) return null;\n\n // 푸터 버튼 그룹(footer 미지정 시) — footerFullWidth면 전체 폭 균등 분할\n const defaultFooterEnd = (\n <ButtonGroup width={footerFullWidth ? 'fill' : 'hug'}>\n {showCancel && (\n <Button variant=\"line\" onClick={onCancel ?? onClose}>\n {cancelText}\n </Button>\n )}\n <Button\n variant={confirmVariant}\n type={onSubmit ? 'submit' : 'button'}\n disabled={confirmDisabled}\n loading={confirmLoading}\n onClick={onSubmit ? undefined : onConfirm}\n >\n {confirmText}\n </Button>\n </ButtonGroup>\n );\n\n // 헤더/바디/푸터 묶음 — onSubmit이 있으면 <form>으로 감싼다\n // (타입은 'form'으로 고정 — 'form'|'div' 유니언은 JSX props가 두 요소의 교집합으로 검사돼\n // onSubmit 핸들러 타입이 충돌한다. 런타임 동작은 동일.)\n const Inner = (onSubmit ? 'form' : 'div') as 'form';\n const innerProps = onSubmit\n ? {\n onSubmit: (e: FormEvent<HTMLFormElement>) => {\n e.preventDefault();\n onSubmit(e);\n },\n }\n : {};\n\n // 정렬: center=중앙, top=상단 가변(measure로 layout.top 계산). 첫 페인트 전 fallback=화면높이/2−150.\n const isTop = placement === 'top';\n const topPad = layout.top ?? Math.round(Math.max(16, (typeof window !== 'undefined' ? window.innerHeight : 800) / 2 - 150));\n const overlayLayout = isTop\n ? 'items-start justify-center px-spacing-7'\n : 'items-center justify-center p-spacing-7';\n const overlayStyle = isTop ? { paddingTop: topPad, paddingBottom: 16 } : undefined;\n const boxMaxHStyle = isTop\n ? { maxHeight: `calc(100vh - ${topPad + 16}px)` }\n : { maxHeight: 'calc(100vh - 32px)' };\n // 최소 너비 — fill은 1260px, 그 외는 360px\n const minWClass = size === 'fill' ? 'min-w-[1260px]' : 'min-w-[360px]';\n\n return createPortal(\n <div\n className={`fixed inset-0 z-[1000] flex bg-modal-overlay ${overlayLayout}`}\n style={overlayStyle}\n // dim(오버레이) 영역을 '직접' 클릭했을 때만 닫는다. 박스에 stopPropagation을 걸지 않으므로\n // (걸면 네이티브 mousedown 전파가 막혀 모달 안 Popover의 document 외부클릭 감지가 안 됨)\n // 박스 클릭은 target≠overlay라 여기서 닫히지 않는다.\n onMouseDown={closeOnOverlayClick ? (e) => { if (e.target === e.currentTarget) onClose(); } : undefined}\n >\n <div\n ref={boxRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === 'string' ? title : undefined}\n className={`flex ${minWClass} max-w-[calc(100vw-32px)] flex-col gap-spacing-1 overflow-hidden rounded-round-6 bg-modal-inline shadow-[0px_4px_6px_0px_rgba(0,0,0,0.08)] outline-none ring-1 ring-modal-outline ${SIZE_WIDTH[size]} ${className}`}\n style={boxMaxHStyle}\n {...props}\n >\n <Inner className=\"flex min-h-0 flex-1 flex-col gap-spacing-1\" {...innerProps}>\n {/* Header */}\n {showHeader && (\n <header ref={headerRef} className=\"flex shrink-0 items-center justify-between bg-modal-panel-bg py-spacing-6 pl-spacing-7 pr-spacing-6\">\n <h2 className=\"text-15 font-semibold text-font-icon-5\">{title}</h2>\n {showClose && (\n <Button variant=\"ghost\" size=\"32\" icon={X} onClick={onClose} aria-label=\"닫기\" showTooltip={false} />\n )}\n </header>\n )}\n\n {/* Body — maxHeight는 측정값(bodyMax)으로, header·footer·화면 여백을 뺀 값이라\n footer를 절대 가리지 않는다. 초과 시 ScrollArea 내부 스크롤. */}\n <div className=\"min-h-0 bg-modal-panel-bg\">\n <ScrollArea maxHeight={layout.bodyMax ?? bodyMaxHeight}>\n <div ref={contentRef} className={`${bodyPadding} text-14 text-font-icon-4 ${bodyClassName}`}>\n <ModalBodyMaxContext.Provider value={layout.bodyInner ?? null}>\n {children}\n </ModalBodyMaxContext.Provider>\n </div>\n </ScrollArea>\n </div>\n\n {/* Footer — 좌측 여백은 footerStart 내용 유형(footerStartType)에 따라 text=16/button=12 */}\n {showFooter && (footer || footerStart || confirmText) && (\n <footer\n ref={footerRef}\n className={`flex shrink-0 items-center bg-modal-panel-bg ${\n footerPadding ??\n `${footerStartType === 'button' ? 'pl-spacing-6' : 'pl-spacing-7'} pr-spacing-6 py-spacing-6`\n } ${footerFullWidth ? '' : 'justify-between'}`}\n >\n {!footerFullWidth && <div className=\"min-w-0 flex-1 text-14 text-font-icon-5\">{footerStart}</div>}\n {footer ?? defaultFooterEnd}\n </footer>\n )}\n </Inner>\n </div>\n </div>,\n document.body,\n );\n}\n\n// ConfirmAlertBody — Alert/Confirm 전용 고정 본문 슬롯.\n// 헤더 없이 본문 안에 [타이틀 → 설명 → 2뎁스 상세(회색 박스) → 체크박스(Confirm 전용)] 순으로 배치한다.\n// 슬롯 종류는 고정이며 텍스트만 자유롭게 바꾼다. 비워둔 슬롯은 렌더하지 않는다.\nfunction ConfirmAlertBody({\n title,\n description,\n descriptionDetail,\n checkbox,\n}: {\n title?: ReactNode;\n description?: ReactNode;\n descriptionDetail?: ReactNode;\n checkbox?: ReactNode;\n}) {\n const hasContent = description != null || descriptionDetail != null || checkbox != null;\n return (\n <div className=\"flex flex-col gap-spacing-6\">\n {title != null && (\n <h2 className=\"text-20 font-semibold text-font-icon-5\">{title}</h2>\n )}\n {hasContent && (\n <div className=\"flex flex-col gap-spacing-5\">\n {description != null && (\n <div className=\"whitespace-pre-line text-14 text-font-icon-5\">{description}</div>\n )}\n {descriptionDetail != null && (\n <div className=\"whitespace-pre-line rounded-round-4 bg-modal-description-bg p-spacing-6 text-14 text-font-icon-5\">\n {descriptionDetail}\n </div>\n )}\n {checkbox}\n </div>\n )}\n </div>\n );\n}\n\n// AlertModal — 확인 버튼 하나, 단순 알림. 헤더 없이 본문 고정 슬롯(타이틀/설명/2뎁스 상세).\nexport interface AlertModalProps extends ModalProps {\n /** 설명글(여러 줄은 \\n) */\n description?: ReactNode;\n /** 2뎁스 상세 설명(회색 박스) */\n descriptionDetail?: ReactNode;\n}\n\nexport function AlertModal({\n title,\n description,\n descriptionDetail,\n confirmText = '확인',\n onConfirm,\n onClose,\n size = 'sm', // Alert/Confirm 기본 360px\n placement = 'center', // 알럿은 중앙 기본(필요 시 override)\n ...props\n}: AlertModalProps) {\n return (\n <Modal\n {...props}\n title={title}\n size={size}\n placement={placement}\n onClose={onClose}\n showHeader={false}\n showCancel={false}\n footerFullWidth\n bodyPadding=\"p-spacing-8\"\n footerPadding=\"px-spacing-8 py-spacing-7\"\n confirmText={confirmText}\n onConfirm={onConfirm ?? onClose}\n >\n <ConfirmAlertBody title={title} description={description} descriptionDetail={descriptionDetail} />\n </Modal>\n );\n}\n\n// ConfirmModal — 취소/확인, 의사결정 요구. 헤더 없이 본문 고정 슬롯(타이틀/설명/2뎁스 상세/체크박스).\n// 체크박스는 삭제 등 위험 액션의 재확인 요소 — checkboxLabel을 주면 본문에 노출되고,\n// requireCheck(기본 true)면 체크해야 확인 버튼이 활성화된다.\nexport interface ConfirmModalProps extends ModalProps {\n /** 설명글(여러 줄은 \\n) */\n description?: ReactNode;\n /** 2뎁스 상세 설명(회색 박스) */\n descriptionDetail?: ReactNode;\n /** 주면 본문에 재확인 체크박스 노출 */\n checkboxLabel?: ReactNode;\n /** controlled 체크 상태(미지정 시 내부 상태로 동작) */\n checked?: boolean;\n /** (checked, event) — 체크 변경 콜백 */\n onCheckChange?: (checked: boolean, e: ChangeEvent<HTMLInputElement>) => void;\n /** checkboxLabel 있을 때 체크해야 확인 활성화 */\n requireCheck?: boolean;\n}\n\nexport function ConfirmModal({\n title,\n description,\n descriptionDetail,\n checkboxLabel,\n checked,\n onCheckChange,\n requireCheck = true,\n confirmText = '확인',\n cancelText = '취소',\n onConfirm,\n onCancel,\n onClose,\n confirmVariant = 'fill',\n confirmDisabled = false,\n open,\n size = 'sm', // Alert/Confirm 기본 360px\n placement = 'center', // 컨펌은 중앙 기본(필요 시 override)\n ...props\n}: ConfirmModalProps) {\n const hasCheck = checkboxLabel != null;\n const isControlled = checked !== undefined;\n const [internalChecked, setInternalChecked] = useState(false);\n const isChecked = isControlled ? checked : internalChecked;\n\n // 열림 상태가 바뀌는 순간(렌더 중) 내부 체크 상태 초기화 — 닫았다 다시 열면 해제된 상태로 시작.\n // effect 대신 prop 변화 감지 패턴을 써서 cascading render를 피한다.\n const [prevOpen, setPrevOpen] = useState(open);\n if (open !== prevOpen) {\n setPrevOpen(open);\n if (open && !isControlled) setInternalChecked(false);\n }\n\n const handleCheck = (e: ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setInternalChecked(e.target.checked);\n onCheckChange?.(e.target.checked, e);\n };\n\n const gated = hasCheck && requireCheck && !isChecked;\n\n return (\n <Modal\n {...props}\n title={title}\n open={open}\n size={size}\n placement={placement}\n onClose={onClose}\n showHeader={false}\n showCancel\n footerFullWidth\n bodyPadding=\"p-spacing-8\"\n footerPadding=\"px-spacing-8 py-spacing-7\"\n confirmText={confirmText}\n cancelText={cancelText}\n onConfirm={onConfirm}\n onCancel={onCancel ?? onClose}\n confirmVariant={confirmVariant}\n confirmDisabled={confirmDisabled || gated}\n >\n <ConfirmAlertBody\n title={title}\n description={description}\n descriptionDetail={descriptionDetail}\n checkbox={\n hasCheck ? (\n <Checkbox checked={isChecked} onChange={handleCheck} label={checkboxLabel} />\n ) : null\n }\n />\n </Modal>\n );\n}\n\n// FormModal — 취소/저장(또는 등록), form + 유효성 검사 연동\nexport interface FormModalProps extends ModalProps {\n /** 저장/등록 등 목적별 라벨 */\n submitText?: ReactNode;\n submitDisabled?: boolean;\n loading?: boolean;\n}\n\nexport function FormModal({\n submitText = '저장',\n cancelText = '취소',\n onSubmit,\n onCancel,\n onClose,\n submitDisabled = false,\n loading = false,\n ...props\n}: FormModalProps) {\n return (\n <Modal\n {...props}\n onClose={onClose}\n onSubmit={onSubmit}\n showCancel\n confirmText={submitText}\n cancelText={cancelText}\n onCancel={onCancel ?? onClose}\n confirmDisabled={submitDisabled}\n confirmLoading={loading}\n />\n );\n}\n","// Pagination — 페이지네이션 (Figma node 7263:1752839)\n// 구성: [총 N개] · [« ‹ 번호들 › »] · [페이지 행 Select]\n// - « / »(chevrons): 처음/마지막 페이지, ‹ / ›(chevron): 이전/다음 페이지\n// - 현재 페이지: state=\"selected\" (ghost select 토큰 — 회색 원형 배경 + 흰 텍스트)\n// - 경계에서 이동 아이콘은 state=\"disabled\" (ghost disabled 토큰으로 흐림) 처리하고 클릭을 막는다\n// - 페이지 번호는 maxButtons(기본 10) 단위 블록으로 노출(슬라이딩 X, 번호 5개/10개 등)\n// - 색·간격·라운드는 모두 토큰 사용, 인라인 style 금지\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { iconTokens } from '../tokens';\nimport { Select } from './Select';\nimport { useHoverTooltip } from './useHoverTooltip';\n\nconst DEFAULT_PAGE_SIZE_OPTIONS = [5, 10, 20, 50];\n\n// 공통 버튼 박스 — 최소 24px 정사각(min-w/min-h) + 좌우 패딩.\n// 라운드는 모든 상태에서 round-10(24px)로 통일 → 1~2자리는 원형, 3자리(100~)부터\n// 고정폭이 아니라 내용폭만큼 좌우로 늘어난다.\nconst BTN_BASE =\n 'inline-flex items-center justify-center min-w-[24px] min-h-[24px] px-spacing-3 ' +\n 'rounded-round-10 font-pretendard font-normal text-14 transition-colors select-none focus:outline-none';\n\n// hover = ghost-hover-bg, pressed(active) = 투명. 색은 기존 ghost 버튼 시멘틱 토큰 재사용.\nconst BTN_INTERACTIVE =\n 'cursor-pointer hover:bg-button-ghost-hover-bg active:bg-transparent ' +\n 'focus-visible:bg-button-ghost-hover-bg'; // 포커스=호버(2026-07-16)\n\n// 현재 페이지가 속한 '블록'의 번호 목록 계산.\n// maxButtons 단위로 페이지를 고정 그룹핑한다(슬라이딩 X) — 같은 블록 안의 번호를\n// 눌러도 윈도우가 움직이지 않고 selected만 바뀐다. (예: max=5, 총 10 → [1-5],[6-10])\nfunction getPageWindow(current: number, total: number, maxButtons: number) {\n const block = Math.floor((current - 1) / maxButtons);\n const start = block * maxButtons + 1;\n const end = Math.min(start + maxButtons - 1, total);\n return Array.from({ length: end - start + 1 }, (_, i) => start + i);\n}\n\n// 버튼 상태별 스타일 — 배경 라운드는 모든 상태에서 BTN_BASE의 round-10(24px)\n// default : hover 시 ghost-hover-bg, pressed 시 투명 (텍스트색은 type별로 분기)\n// selected: 현재 페이지 — 회색 원형 + 흰 텍스트(고정)\n// disabled: 경계 아이콘 — 흐림 + 클릭 불가\nconst STATE_STYLES = {\n selected: 'cursor-default bg-button-ghost-selected-bg text-button-ghost-selected-text',\n disabled: 'cursor-not-allowed text-button-ghost-disabled-fg',\n};\n\n// default 상태 텍스트색 — 번호는 pagination 토큰(연함)에서 hover/pressed 시 default-fg로 진해지고,\n// 아이콘은 항상 default-fg(진함). (Figma: ghost/gray default text (pagination) = gray.300)\nconst DEFAULT_TEXT = {\n number: 'text-button-ghost-pagination-fg hover:text-button-ghost-default-fg active:text-button-ghost-default-fg focus-visible:text-button-ghost-default-fg',\n icon: 'text-button-ghost-default-fg',\n};\n\n// 페이지네이션 버튼 — number/icon 타입 × state(default/selected/disabled)\ninterface PaginationButtonProps {\n state?: 'default' | 'selected' | 'disabled';\n type?: 'number' | 'icon';\n icon?: LucideIcon;\n label?: string;\n children?: ReactNode;\n onClick?: () => void;\n}\n\nfunction PaginationButton({ state = 'default', type = 'number', icon: Icon, label, children, onClick }: PaginationButtonProps) {\n const interactive = state === 'default';\n const stateStyle =\n state === 'default' ? `${DEFAULT_TEXT[type]} ${BTN_INTERACTIVE}` : STATE_STYLES[state];\n // 아이콘 버튼(첫/이전/다음/마지막)은 명칭이 안 보이므로 hover 툴팁으로 알려준다(base Button과 동일 패턴)\n const hoverTip = useHoverTooltip(type === 'icon' ? label : null);\n return (\n <>\n <button\n type=\"button\"\n className={`${BTN_BASE} ${stateStyle}`}\n aria-current={state === 'selected' ? 'page' : undefined}\n aria-label={type === 'icon' ? label : undefined}\n disabled={state === 'disabled'}\n onClick={interactive ? onClick : undefined}\n onMouseEnter={hoverTip.onMouseEnter}\n onMouseLeave={hoverTip.onMouseLeave}\n >\n {type === 'icon' && Icon ? (\n <Icon size={iconTokens.size} strokeWidth={iconTokens.strokeWidth} />\n ) : (\n children\n )}\n </button>\n {hoverTip.tooltip}\n </>\n );\n}\n\nexport interface PaginationProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n /** 현재 페이지(1-base) — controlled */\n page?: number;\n /** uncontrolled 초기값 */\n defaultPage?: number;\n /** (page: number) => void */\n onChange?: (page: number) => void;\n /** 총 항목 수 — totalPages 미지정 시 페이지 수 계산에 사용 */\n totalCount?: number;\n /** 총 페이지 수(지정 시 totalCount/pageSize보다 우선) */\n totalPages?: number;\n /** 페이지당 행 수 — controlled */\n pageSize?: number;\n /** 페이지당 행 수 초기값(옵션 5/10/20/50 중 기본 10) */\n defaultPageSize?: number;\n /** (size: number) => void */\n onPageSizeChange?: (size: number) => void;\n pageSizeOptions?: number[];\n /** 번호 노출 개수(윈도우) — 보통 10 또는 5 */\n maxButtons?: number;\n /** 좌측 '총 N개' 표시 */\n showTotal?: boolean;\n /** 우측 '페이지 행' Select 표시 */\n showPageSize?: boolean;\n}\n\nexport function Pagination({\n page,\n defaultPage = 1,\n onChange,\n totalCount = 0,\n totalPages,\n pageSize,\n defaultPageSize = 10,\n onPageSizeChange,\n pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,\n maxButtons = 10,\n showTotal = true,\n showPageSize = true,\n className = '',\n ...props\n}: PaginationProps) {\n const isPageControlled = page !== undefined;\n const [internalPage, setInternalPage] = useState(defaultPage);\n const current = isPageControlled ? (page as number) : internalPage;\n\n const isSizeControlled = pageSize !== undefined;\n const [internalSize, setInternalSize] = useState(defaultPageSize);\n const size = isSizeControlled ? (pageSize as number) : internalSize;\n\n // 총 페이지 수 — totalPages가 있으면 우선, 없으면 totalCount/페이지크기로 계산.\n const getTotalPages = (s: number) =>\n totalPages != null ? totalPages : Math.max(1, Math.ceil(totalCount / s));\n const computedTotalPages = getTotalPages(size);\n\n const goTo = (next: number) => {\n const clamped = Math.min(Math.max(next, 1), computedTotalPages);\n if (clamped === current) return;\n if (!isPageControlled) setInternalPage(clamped);\n onChange?.(clamped);\n };\n\n const handleSizeChange = (e: { target: { value: string | number } }) => {\n const next = Number(e.target.value);\n if (!isSizeControlled) setInternalSize(next);\n onPageSizeChange?.(next);\n // 페이지 수가 줄면 현재 페이지가 범위를 벗어날 수 있어 보정\n const nextTotal = getTotalPages(next);\n if (current > nextTotal) goTo(nextTotal);\n };\n\n const pages = getPageWindow(current, computedTotalPages, maxButtons);\n const atFirst = current <= 1;\n const atLast = current >= computedTotalPages;\n\n // totalPages를 외부에서 고정했는데 onPageSizeChange가 없으면 '페이지 행' Select는\n // 바꿔도 아무것도 달라지지 않는 무반응 UI가 된다(기본값 조합 충돌) → 자동 숨김.\n const pageSizeVisible = showPageSize && !(totalPages != null && !onPageSizeChange);\n\n const sizeSelectOptions = pageSizeOptions.map((n) => ({ value: n, label: `${n}개` }));\n\n return (\n <div\n // min-h-[32px]: '페이지 행' Select(32px)가 숨겨져도(showPageSize=false) 행 높이가\n // 번호 버튼(24px) 기준으로 줄지 않도록 고정한다.\n className={`grid min-h-[32px] grid-cols-3 items-center gap-spacing-9 ${className}`}\n {...props}\n >\n {/* 좌: 총 개수 */}\n <div className=\"justify-self-start\">\n {showTotal && (\n <p className=\"text-14 text-font-icon-5\">총 {totalCount}개</p>\n )}\n </div>\n\n {/* 중: 페이지 이동 컨트롤 */}\n <div className=\"flex items-center justify-center gap-spacing-3 justify-self-center\">\n <PaginationButton type=\"icon\" icon={ChevronsLeft} label=\"첫 페이지\" state={atFirst ? 'disabled' : 'default'} onClick={() => goTo(1)} />\n <PaginationButton type=\"icon\" icon={ChevronLeft} label=\"이전 페이지\" state={atFirst ? 'disabled' : 'default'} onClick={() => goTo(current - 1)} />\n {pages.map((p) => (\n <PaginationButton key={p} state={p === current ? 'selected' : 'default'} onClick={() => goTo(p)}>\n {p}\n </PaginationButton>\n ))}\n <PaginationButton type=\"icon\" icon={ChevronRight} label=\"다음 페이지\" state={atLast ? 'disabled' : 'default'} onClick={() => goTo(current + 1)} />\n <PaginationButton type=\"icon\" icon={ChevronsRight} label=\"마지막 페이지\" state={atLast ? 'disabled' : 'default'} onClick={() => goTo(computedTotalPages)} />\n </div>\n\n {/* 우: 페이지 행 선택 */}\n <div className=\"flex items-center gap-spacing-5 justify-self-end\">\n {pageSizeVisible && (\n <>\n <span className=\"whitespace-nowrap text-14 text-font-icon-5\">페이지 행</span>\n <Select\n variant=\"text\"\n size=\"24\"\n placement=\"auto-right\"\n menuWidth={80}\n // 옵션 값은 숫자(pageSizeOptions) 그대로 사용 — Select 타입 계약(string)과 다르지만\n // 런타임 비교(===)는 양쪽 다 숫자라 기존 동작과 동일(타입 단언만, 값 변환 없음)\n options={sizeSelectOptions as unknown as { value: string; label: string }[]}\n value={size as unknown as string}\n onChange={handleSizeChange}\n />\n </>\n )}\n </div>\n </div>\n );\n}\n","// ToolBar — 플로팅 액션 툴바 (Figma tool bar, node 8389:126993) — 공통 컴포넌트\n// 체크박스 선택 등으로 나타나 선택 항목에 대한 액션(셀렉트·버튼)을 담는 얕은 바.\n// - 배경: 위(white)→아래(gray.50) 그라데이션 + gray-900 알파 50 라인(배경 투영) + 그림자\n// - 내용은 children 슬롯 — Select·Button(ghost) 등 DS 컴포넌트를 그대로 배치\n// - 항목 사이 구분선은 ToolBarDivider(16px 세로 strong 라인)\n// 색은 toolbar-* 시멘틱 토큰만 사용. 표시/숨김·위치(absolute 등)는 사용처가 담당한다.\nimport type { ComponentPropsWithoutRef } from 'react';\n\ntype ToolBarProps = ComponentPropsWithoutRef<'div'>;\n\nexport function ToolBar({ children, className = '', ...props }: ToolBarProps) {\n return (\n <div\n role=\"toolbar\"\n // 라인은 outline(바깥 얹기) — 알파 컬러라 border(안쪽)면 자기 그라데이션 배경과 합성돼 뒤 배경이 안 비친다\n className={`inline-flex items-center gap-spacing-4 rounded-round-5 outline outline-1 outline-toolbar-line bg-gradient-to-b from-toolbar-bg-top to-toolbar-bg-bottom p-spacing-5-5 shadow-[0px_2px_5px_0px_rgba(0,0,0,0.12)] ${className}`}\n {...props}\n >\n {children}\n </div>\n );\n}\n\n// 툴바 항목 구분선 — 16px 세로 라인(divider strong 토큰)\nexport function ToolBarDivider() {\n return <span aria-hidden className=\"h-spacing-7 w-px shrink-0 bg-divider-strong\" />;\n}\n","// SegmentedTabs — 세그먼트 탭 (Figma segmented tab button 8241:88277 / Segmented Tabs 8241:88288)\n// 회색 트랙(segmented-tab-bg) 안에서 흰색 배경 pill이 선택 탭으로 '슬라이드'해 이동한다.\n// - 탭은 items로 필요한 만큼(2·3·4·5…) 추가 — 모든 탭이 균등 폭(flex-1)이라야 pill 이동 계산이 맞다.\n// - pill = 트랙 안 absolute, 폭 100/N%, translateX(선택 index × 100%) + transition-transform로 슬라이드.\n// - 색은 segmented-* 시멘틱 토큰만. controlled(value)/uncontrolled(defaultValue) 단일 선택.\nimport { useState } from 'react';\nimport type { ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { TruncatingText } from './TruncatingText';\n\n// 탭 항목 — [{ value, label, disabled? }]. 값 타입 V는 호출부 리터럴 유니언('points'|'fitness' 등)이\n// 그대로 흐르도록 제네릭 — onChange가 좁은 핸들러(setMode 등)와 호환된다.\ninterface SegmentedTabItem<V extends string | number> {\n value: V;\n label: ReactNode;\n disabled?: boolean;\n}\n\ninterface SegmentedTabsProps<V extends string | number>\n extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n items?: SegmentedTabItem<V>[]; // [{ value, label, disabled? }] — 2개 이상, 개수 가변\n value?: V | null; // controlled 선택값(null=미선택 허용)\n defaultValue?: V; // uncontrolled 초기 선택값\n onChange?: (value: V) => void; // (value) => void\n width?: number | string; // 컨테이너 너비 — 숫자(px)/CSS 길이. 미지정 시 부모 전체 폭(w-full)\n}\n\nexport function SegmentedTabs<V extends string | number>({\n items = [], // [{ value, label, disabled? }] — 2개 이상, 개수 가변\n value, // controlled 선택값\n defaultValue, // uncontrolled 초기 선택값\n onChange, // (value) => void\n width, // 컨테이너 너비 — 숫자(px)/CSS 길이. 미지정 시 부모 전체 폭(w-full)\n className = '',\n ...props\n}: SegmentedTabsProps<V>) {\n const isControlled = value !== undefined;\n const [internal, setInternal] = useState<V | undefined>(defaultValue);\n // 선택값 폴백은 렌더 파생 — items가 비어 도착해도(비동기) 첫 탭이 선택된다.\n const selected = isControlled ? value : (internal ?? items[0]?.value);\n\n const n = items.length;\n // pill 위치 index — 선택값이 목록에 없으면 0(첫 탭)으로 폴백해 pill이 항상 유효 위치.\n const selIdx = Math.max(0, items.findIndex((it) => it.value === selected));\n\n const select = (val: V) => {\n if (!isControlled) setInternal(val);\n onChange?.(val);\n };\n\n // 좌우 방향키로 이동(비활성 탭은 건너뜀)\n const move = (dir: number) => {\n if (n === 0) return;\n let i = selIdx;\n for (let step = 0; step < n; step += 1) {\n i = (i + dir + n) % n;\n if (!items[i].disabled) {\n select(items[i].value);\n return;\n }\n }\n };\n\n const widthStyle = width == null ? undefined : typeof width === 'number' ? `${width}px` : width;\n\n return (\n <div\n role=\"tablist\"\n style={{ width: widthStyle }}\n className={`flex ${width == null ? 'w-full' : ''} rounded-round-4 bg-segmented-tab-bg p-spacing-2 ${className}`}\n {...props}\n >\n <div className=\"relative flex min-w-0 flex-1\">\n {/* 슬라이딩 배경 pill — 트랙 안에서 선택 탭으로 이동. 탭 균등 폭 전제(width 100/N%). */}\n {n > 0 && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 left-0 rounded-round-3 bg-segmented-select-bg ring-1 ring-segmented-select-line shadow-[0px_2px_4px_0px_rgba(0,0,0,0.12)] transition-transform duration-200 ease-out\"\n style={{ width: `${100 / n}%`, transform: `translateX(${selIdx * 100}%)` }}\n />\n )}\n {items.map((it) => {\n const isSel = it.value === selected;\n return (\n <button\n key={it.value}\n type=\"button\"\n role=\"tab\"\n aria-selected={isSel}\n disabled={it.disabled}\n tabIndex={it.disabled ? -1 : 0}\n onClick={() => !it.disabled && select(it.value)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') {\n e.preventDefault();\n move(1);\n } else if (e.key === 'ArrowLeft') {\n e.preventDefault();\n move(-1);\n }\n }}\n className={`relative z-[1] flex min-h-[24px] min-w-0 flex-1 items-center justify-center rounded-round-3 px-spacing-5-5 font-pretendard text-14 leading-24 transition-colors focus:outline-none ${\n it.disabled\n ? 'cursor-not-allowed text-segmented-disabled-text'\n : isSel\n ? /* 선택 탭이 Tab 포커스를 받는다(roving tabindex) — 포커스=호버(파란 텍스트)로 표시,\n 없으면 아웃라인 제거와 겹쳐 Tab이 건너뛰는 것처럼 보인다(2026-07-16) */\n 'cursor-pointer text-segmented-select-text focus-visible:text-segmented-hover-text'\n : 'cursor-pointer text-segmented-unselect-text hover:text-segmented-hover-text focus-visible:text-segmented-hover-text' /* 포커스=호버 */\n }`}\n >\n <TruncatingText as=\"span\" className=\"min-w-0\">\n {it.label}\n </TruncatingText>\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","// ScreeningIndividualSettingModal — 복수 조건(지정) 개별 값 설정 모달 (Figma 8535:10535)\n// ⚠️ 내부용(ScreeningConditionCard 전용) — 지정된 항목(자격증 등)마다 가점/감점(점수) 또는\n// 적합/부적합을 설정하고, 하단에서 적용 함수(SUM 등)를 고른다. 확인 시에만 반영.\n//\n// 구조(모달 템플릿 규칙 — Modal 컴포넌트 조립, 셸 재구현 금지):\n// 바디: [모드 세그먼트 탭(가점/감점|적합/부적합) ── 일괄 적용(셀렉트[+점수]+버튼)]\n// [항목 테이블 — 항목 / 가/감점(텍스트 셀렉트) / 점수(가점·감점 모드만)]\n// 푸터: 취소·확인 (함수 셀렉트는 2026-07-16 제거 — 그룹핑·함수는 수식 영역 툴바가 담당)\n// 저장 페이로드: { type:'individual', mode:'points'|'fitness',\n// items: { [항목값]: { type:'plus'|'minus', points } | { type:'fit'|'unfit' } } }\nimport { useState } from 'react';\nimport { Modal } from './Modal';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { Select } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Table } from './Table';\nimport type { TableColumn, TableRowKey } from './Table';\nimport type { IndividualItemSetting, IndividualSetting, OptionItem } from './formulaFunctions';\n\n// 항목 draft — 가점/감점(points) 모드와 적합/부적합(fitness) 모드 분리 보관용\ntype PointsItem = Extract<IndividualItemSetting, { type: 'plus' | 'minus' }>;\ntype FitItem = Extract<IndividualItemSetting, { type: 'fit' | 'unfit' }>;\n\n// 점수 입력 필터 — 숫자만 허용(2026-07-16 지시)\nconst digitsOnly = (v: string) => v.replace(/[^0-9]/g, '');\n\nconst POINT_TYPES = [\n { value: 'plus', label: '가점' },\n { value: 'minus', label: '감점' },\n];\nconst FIT_TYPES = [\n { value: 'fit', label: '적합' },\n { value: 'unfit', label: '부적합' },\n];\nconst MODE_TABS = (\n fitnessOnly: boolean,\n): { value: 'points' | 'fitness'; label: string; disabled?: boolean }[] => [\n { value: 'points', label: '가점/감점', disabled: fitnessOnly },\n { value: 'fitness', label: '적합/부적합' },\n];\n\ninterface ScreeningIndividualSettingModalProps {\n items?: OptionItem[]; // [{ value, label }] — 지정된 항목(카드의 선택 항목, 옵션 순서)\n value?: IndividualSetting | null; // 저장된 값(위 페이로드) | null — 열 때 draft 시드\n fitnessOnly?: boolean; // 적합/부적합 전용 — IF 함수 안 조건은 가점/감점 불가(2026-07-16)\n onClose: () => void; // Modal 계약상 필수(딤·X·Esc 닫기) // 닫기(취소·X·딤·Esc)\n onConfirm?: (payload: IndividualSetting) => void; // 확인 시에만\n}\n\nexport function ScreeningIndividualSettingModal({\n items = [],\n value = null,\n fitnessOnly = false,\n onClose,\n onConfirm,\n}: ScreeningIndividualSettingModalProps) {\n const [mode, setMode] = useState<IndividualSetting['mode']>(\n fitnessOnly ? 'fitness' : (value?.mode ?? 'points'),\n );\n // 모드별 draft를 분리 보관 — 탭을 오가도 입력이 유지되고, 확인 시 활성 모드만 저장된다\n const seed = <T extends IndividualItemSetting>(m: IndividualSetting['mode'], defaults: T) => {\n const out: Record<string, T> = {};\n items.forEach((it) => {\n const saved = value?.mode === m ? value?.items?.[it.value] : null;\n out[it.value] = (saved as T | null | undefined) ?? defaults;\n });\n return out;\n };\n const [pointsDraft, setPointsDraft] = useState<Record<string, PointsItem>>(() =>\n seed('points', { type: 'plus', points: '' }),\n );\n const [fitDraft, setFitDraft] = useState<Record<string, FitItem>>(() => seed('fitness', { type: 'fit' }));\n // 일괄 적용 — 체크한 행에만 적용(2026-07-16 지시). 모드가 바뀌면 셀렉트 옵션이 달라지므로 초기화\n const [bulkType, setBulkType] = useState('');\n const [bulkPoints, setBulkPoints] = useState('');\n const [checkedIds, setCheckedIds] = useState<TableRowKey[]>([]); // 테이블 체크박스 — 일괄 적용 대상(행 id=항목 값 문자열)\n const changeMode = (m: IndividualSetting['mode']) => {\n setMode(m);\n setBulkType('');\n setBulkPoints('');\n };\n\n const isPoints = mode === 'points';\n // 일괄 적용 그룹은 행을 체크해야 활성화되고(2026-07-16 지시), 값까지 채워져야 적용 가능\n const hasChecked = checkedIds.length > 0;\n const bulkReady = (isPoints ? !!bulkType && !!bulkPoints.trim() : !!bulkType) && hasChecked;\n const applyBulk = () => {\n if (!bulkReady) return;\n const set = isPoints\n ? { type: bulkType as PointsItem['type'], points: bulkPoints.trim() }\n : { type: bulkType as FitItem['type'] };\n const patchChecked = <T extends IndividualItemSetting>(d: Record<string, T>) =>\n ({\n ...d,\n ...Object.fromEntries(checkedIds.map((id) => [id, { ...set }])),\n }) as Record<string, T>;\n if (isPoints) setPointsDraft(patchChecked);\n else setFitDraft(patchChecked);\n };\n\n const patchRow = (val: string, patch: Partial<PointsItem> | Partial<FitItem>) =>\n isPoints\n ? setPointsDraft((d) => ({ ...d, [val]: { ...d[val], ...patch } as PointsItem }))\n : setFitDraft((d) => ({ ...d, [val]: { ...d[val], ...patch } as FitItem }));\n\n // 가점/감점 모드는 모든 항목의 점수가 채워져야 확인 가능(적합/부적합은 기본값으로 항상 완성)\n const complete = isPoints\n ? items.every((it) => !!pointsDraft[it.value]?.points?.trim())\n : true;\n const payload = (): IndividualSetting => ({\n type: 'individual',\n mode,\n items: Object.fromEntries(\n items.map((it): [string, IndividualItemSetting] =>\n isPoints\n ? [it.value, { type: pointsDraft[it.value].type, points: pointsDraft[it.value].points?.trim() }]\n : [it.value, { type: fitDraft[it.value].type }],\n ),\n ),\n });\n\n const columns: TableColumn[] = [\n { key: 'label', label: '항목' },\n {\n key: 'type',\n label: '가/감점',\n width: 120,\n render: (row) => (\n <Select\n variant=\"text\"\n width=\"fill\" /* 셀 전체 폭 — 텍스트 왼쪽·chevron 오른쪽 끝 정렬 */\n options={isPoints ? POINT_TYPES : FIT_TYPES}\n value={(isPoints ? pointsDraft : fitDraft)[String(row.id)]?.type ?? ''}\n onChange={(e) => patchRow(String(row.id), { type: e.target.value as 'plus' | 'minus' | 'fit' | 'unfit' })}\n menuWidth={100}\n />\n ),\n },\n ...(isPoints\n ? ([\n {\n key: 'points',\n label: '점수',\n width: 120,\n render: (row) => (\n <span className=\"flex items-center gap-spacing-4\">\n <Input\n size=\"32\"\n width={70}\n value={pointsDraft[String(row.id)]?.points ?? ''}\n onChange={(e) => patchRow(String(row.id), { points: digitsOnly(e.target.value) })}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"입력\"\n />\n <span className=\"text-14 text-font-icon-5\">점</span>\n </span>\n ),\n },\n ] satisfies TableColumn[])\n : []),\n ];\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"개별설정\"\n size=\"lg\"\n confirmText=\"확인\"\n cancelText=\"취소\"\n confirmDisabled={!complete}\n onConfirm={() => onConfirm?.(payload())}\n >\n <div className=\"flex flex-col gap-spacing-6\">\n {/* 일괄 적용(좌) + 모드 탭(우) — 일괄은 표에서 체크한 행에만 같은 값을 채운다(2026-07-16 위치 교체) */}\n <div className=\"flex items-center justify-between gap-spacing-5\">\n <div className=\"flex items-center gap-spacing-4\">\n {isPoints ? (\n <>\n <Select\n width={90}\n options={POINT_TYPES}\n value={bulkType}\n onChange={(e) => setBulkType(e.target.value)}\n placeholder=\"가/감점\"\n disabled={!hasChecked}\n />\n <Input\n width={70}\n value={bulkPoints}\n onChange={(e) => setBulkPoints(digitsOnly(e.target.value))}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"입력\"\n disabled={!hasChecked}\n />\n <span className={`text-14 ${hasChecked ? 'text-font-icon-5' : 'text-font-icon-2'}`}>\n 점\n </span>\n </>\n ) : (\n <Select\n width={110}\n options={FIT_TYPES}\n value={bulkType}\n onChange={(e) => setBulkType(e.target.value)}\n placeholder=\"적합/부적합\"\n disabled={!hasChecked}\n />\n )}\n <Button variant=\"line\" size=\"32\" disabled={!bulkReady} onClick={applyBulk}>\n 일괄적용\n </Button>\n </div>\n <SegmentedTabs width={200} items={MODE_TABS(fitnessOnly)} value={mode} onChange={changeMode} />\n </div>\n {/* 항목 목록 — 페이지네이션 없이 늘어나는 표(규칙 18: bordered + fill + min-h-0) */}\n <Table\n bordered\n maxHeight=\"fill\"\n className=\"min-h-0\"\n columns={columns}\n rows={items.map((it) => ({ id: it.value, label: it.label }))}\n rowKey=\"id\"\n selectable\n selectedIds={checkedIds}\n onSelectChange={setCheckedIds}\n />\n </div>\n </Modal>\n );\n}\n","// 수식 함수 레지스트리 — ScreeningFormula(컴포넌트)와 ScreeningBuilderTemplate(함수 셀렉트)이 공유.\n// 컴포넌트 파일에 상수 export를 두면 react-refresh 에러가 나서 분리(chipStyles.js 관례).\n// 함수 → 계열 (Figma 함수 조건 색상표 8384:114813 — 계열별 고유 텍스트 컬러는 formula-* 토큰)\n\n// ── 수식 노드 모델 타입(2026-07-18 TS 전환) — 템플릿·수식·툴바가 공유하는 트리 구조 ──\nexport interface OptionItem {\n value: string;\n label: string;\n disabled?: boolean;\n}\nexport interface ConditionTab {\n value: string;\n label: string;\n disableOptions?: boolean; // 옵션 없이 저장 가능(미보유/비대상 등)\n multiSelect?: boolean; // 복수 조건(지정) 탭 — 다중 선택 Select\n placeholder?: string;\n searchPlaceholder?: string;\n bundleLabel?: string; // 수식 묶음 칩 표기(기본=탭 라벨)\n}\nexport interface ConditionMeta {\n tabs: ConditionTab[];\n optionsByTab: Record<string, OptionItem[]>;\n options: OptionItem[];\n}\nexport type IndividualItemSetting = { type: 'plus' | 'minus'; points?: string } | { type: 'fit' | 'unfit' };\nexport interface IndividualSetting {\n type: 'individual';\n mode: 'points' | 'fitness';\n items: Record<string, IndividualItemSetting>;\n fn?: string; // 과거 데이터 호환(함수 셀렉트 제거 전 저장분)\n}\nexport interface FormulaLeafNode {\n id: string;\n kind: 'leaf';\n criteria?: string | null; // 조건 카드 id\n value?: string | null; // 선택 옵션 값\n valueTab?: string | null;\n points?: string; // 부호 포함 문자열('-3' 등)\n scoreType?: 'plus' | 'minus' | 'fit' | 'unfit' | null;\n display?: 'full' | 'compact';\n items?: string[]; // 묶음 leaf(복수 조건) — 지정 항목 값 배열\n individual?: IndividualSetting | null; // 항목별 개별설정\n // 드롭 시점 스냅샷(카드 삭제 대비 폴백 — 표시는 catalog 라이브 우선)\n criteriaOptions?: OptionItem[];\n valueOptions?: OptionItem[];\n valueOptionsByCriteria?: Record<string, OptionItem[]>;\n conditionMetaByCriteria?: Record<string, ConditionMeta>;\n}\nexport interface FormulaGroupNode {\n id: string;\n kind: 'group';\n fn: string;\n children: FormulaNode[];\n trueScore?: string; // IF — 참일 때 점수(기본 '1')\n falseScore?: string; // IF — 거짓일 때 점수(기본 '0')\n capScore?: string; // CAPMAX/CAPMIN — 제한 점수\n compareOp?: string; // FITBYSCORE/UNFITBYSCORE — 비교 연산자(기본 '>=')\n compareScore?: string; // FITBYSCORE/UNFITBYSCORE — 비교값\n}\nexport type FormulaNode = FormulaLeafNode | FormulaGroupNode;\nexport interface FormulaCatalog {\n criteriaOptions: OptionItem[];\n valueOptionsByCriteria: Record<string, OptionItem[]>;\n conditionMetaByCriteria: Record<string, ConditionMeta>;\n}\n\nexport const FORMULA_FN_FAMILY: Record<string, string> = {\n AND: 'logical',\n OR: 'logical',\n IF: 'conditional',\n SUM: 'aggregate',\n MAX: 'aggregate',\n MIN: 'aggregate',\n COUNTIF: 'aggregate',\n CAPMAX: 'score-limit',\n CAPMIN: 'score-limit',\n FITBYSCORE: 'evaluation',\n UNFITBYSCORE: 'evaluation',\n};\n\n// 그룹핑에 쓰는 함수 목록 — 템플릿 함수 셀렉트·함수 변경 팝오버·자연어 함수 칩이 공용.\n// IF 포함(2026-07-15 지시 — 기존엔 leaf 전용으로 제외했었음)\nexport const FORMULA_GROUP_FUNCTIONS = Object.keys(FORMULA_FN_FAMILY);\n\n// ── 스크리닝 키보드 체인 규약(2026-07-15) — 카드·수식·드롭 스트립 공용 ──\n// 정거장(data-formula-chain)을 문서 순서로 잇는다: 조건 카드들 → 수식들 → 드롭 스트립.\n// 한 단계가 완료되면 다음 정거장으로 포커스를 넘긴다(DOM 순서 = 체인 순서).\nexport function focusNextChainStop(currentEl: HTMLElement | null) {\n if (!currentEl) return;\n // 패널 언마운트가 끝난 다음 틱에 이동(포커스 경합 방지)\n setTimeout(() => {\n const all = Array.from(document.querySelectorAll<HTMLElement>('[data-formula-chain]'));\n const next = all[all.indexOf(currentEl) + 1];\n if (!next) return;\n const target = next.matches('[tabindex]') ? next : next.querySelector<HTMLElement>('[tabindex]');\n if (!target) return;\n target.focus();\n // programmatic focus는 :focus-visible에 안 잡힐 수 있어 데이터 속성으로 링을 강제 표시,\n // 포커스가 떠나면 해제(체인 이동 가시성 유지 — 2026-07-16 개정과 세트)\n target.setAttribute('data-chain-focus', '');\n target.addEventListener('blur', () => target.removeAttribute('data-chain-focus'), { once: true });\n }, 0);\n}\n// 체인 트리거 공용 keydown(규칙 20 ⑵) — 포커스 상태에서 Enter/Space/Tab이면 팝오버를 연다.\n// 열린 패널(포털)이 처리한 키(defaultPrevented)는 무시. 카드 조건/가·감점, 수식 값·묶음·조건명 칩이 공유.\n// 사용: onKeyDown={(e) => chainTriggerKey(e, open, () => setOpen(true))} — 이벤트 시점 호출\n// (렌더 중 팩토리 호출은 react-compiler lint가 클로저의 ref 접근을 렌더 접근으로 판정한다)\nexport function chainTriggerKey(\n e: { defaultPrevented: boolean; key: string; shiftKey: boolean; preventDefault: () => void },\n open: boolean,\n onOpen: () => void,\n) {\n if (e.defaultPrevented) return;\n if (e.key === 'Enter' || e.key === ' ' || (e.key === 'Tab' && !e.shiftKey && !open)) {\n e.preventDefault();\n onOpen();\n }\n}\n\n// 가점/감점 값 보유 여부(재귀) — IF 함수는 적합/부적합 조건만, CAP·BYSCORE는 점수 조건만 허용.\n// 툴바(함수별 적용 판정)·수식(함수 변경 게이팅·IF 드롭 값 정리)이 공유(2026-07-18 컴포넌트 파일에서 이동).\nexport function hasPointsScore(node: FormulaNode): boolean {\n if (node.kind === 'group') return node.children.some(hasPointsScore);\n if (node.items?.length) return node.individual?.mode === 'points';\n return node.scoreType === 'plus' || node.scoreType === 'minus' || !!node.points;\n}\n\n// 정거장 공용 링(2026-07-16 개정) — 마우스 클릭/프레스에는 링을 내지 않는다:\n// · focus-visible = 키보드 Tab 포커스(클릭의 :focus에는 미매칭 → 트리거 hover 링과 겹쳐\n// 4px처럼 보이던 프레스 현상 제거)\n// · data-[chain-focus] = 체인의 programmatic focus(위 함수가 부여 — :focus-visible 미매칭 보완)\nexport const CHAIN_STOP_FOCUS =\n 'focus:outline-none focus-visible:ring-2 focus-visible:ring-text-field-focused-line data-[chain-focus]:ring-2 data-[chain-focus]:ring-text-field-focused-line';\n","// usePanelKeyboard — 설정 팝오버 패널의 키보드 규약(규칙 20 ⑶⑷) 공용 훅.\n// ConditionSettingMenu·ScoreSettingMenu(ScreeningConditionCard)가 공유하던 복제 코드를 추출(2026-07-18).\n//\n// 담당:\n// · 마운트 시 패널 포커스 — 패널은 위치 계산 전 visibility:hidden이라 즉시 focus()가 실패한다 → 한 틱 지연.\n// (Tab/Enter 체인이 포커스 없이는 패널에 닿지 않는다)\n// · Tab = 패널 내부 DOM(버튼·인풋·tabindex 요소) 자연 이동. disabled 요소는 수집에서 제외 —\n// 포함하면 실제 마지막 요소에서 경계 처리가 안 걸려 포커스가 패널 밖(GNB)으로 샌다.\n// · 경계 규칙: 마지막 요소에서 Tab → 완성(complete)이면 onCommit(저장+다음 진행) /\n// 미완성이면 onSkip(저장 없이 닫고 다음 정거장) / onSkip 없으면 처음으로 순환.\n// 처음 요소에서 Shift+Tab → 끝으로 순환(팝오버 밖 이탈 방지).\n//\n// 사용: const { menuRef, handleTabKey } = usePanelKeyboard({ complete, onCommit, onSkip });\n// <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>…</div>\nimport { useRef, useEffect } from 'react';\nimport type { KeyboardEvent } from 'react';\n\ninterface UsePanelKeyboardOptions {\n complete: boolean; // 저장 가능 상태 — 마지막 요소 Tab에서 onCommit 실행 조건\n onCommit: () => void; // 저장 + 다음 진행\n onSkip?: (() => void) | null; // 미완성 이탈 — 없으면 처음으로 순환\n}\n\nexport function usePanelKeyboard({ complete, onCommit, onSkip }: UsePanelKeyboardOptions) {\n const menuRef = useRef<HTMLDivElement | null>(null);\n useEffect(() => {\n const t = setTimeout(() => menuRef.current?.focus(), 0);\n return () => clearTimeout(t);\n }, []);\n\n const focusablesIn = () =>\n Array.from(\n menuRef.current?.querySelectorAll<HTMLElement>(\n \"button:not([disabled]), input:not([disabled]), [tabindex='0']\",\n ) ?? [],\n ).filter((el) => el.offsetParent !== null);\n\n const handleTabKey = (e: KeyboardEvent) => {\n if (e.key !== 'Tab' || e.defaultPrevented) return;\n const els = focusablesIn();\n if (!els.length) {\n e.preventDefault();\n if (complete) onCommit();\n else onSkip?.();\n return;\n }\n const i = els.indexOf(e.target as HTMLElement);\n if (!e.shiftKey && i === els.length - 1) {\n e.preventDefault();\n if (complete) onCommit();\n else if (onSkip) onSkip();\n else els[0].focus();\n } else if (e.shiftKey && i <= 0) {\n e.preventDefault();\n els[els.length - 1].focus();\n }\n // 그 외는 브라우저 기본 이동(패널 내부 순서대로)\n };\n\n return { menuRef, handleTabKey };\n}\n","// ScreeningConditionCard — 스크리닝 조건 카드 (Figma ConditionCard, node 8243:88380)\n// ⚠️ 내부용(ScreeningBuilderTemplate 전용) 컴포넌트 — 지원자 정보를 조건화할 때 쓰는 카드.\n// 독립 데모 페이지 없이 ScreeningBuilderTemplate 데모에서 함께 확인한다.\n//\n// 구조: [grip + 카드명] + 삭제 버튼 / [조건 셀렉트(fill) + 가/감점 셀렉트(100px)]\n// - 조건 셀렉트 → tab radio list 팝오버(SegmentedTabs + List radio + 취소/저장).\n// 탭 없이 쓰려면 conditionTabs를 생략하고 conditionOptions(평면 배열)를 준다.\n// 탭에 multiSelect(지정 탭)가 있으면 라디오 대신 다중 선택 Select(검색+확인 푸터)가\n// 나오고, 저장 시 { tab, items:[…] } — 가/감점 셀렉트 자리가 '개별설정' 버튼으로\n// 바뀌어 항목별 가/감점(또는 적합/부적합)+적용 함수를 모달로 설정한다(Figma 8532:4188~).\n// - 가/감점 셀렉트 → radio list 팝오버(가점/감점=입력+점, 적합/부적합). 선택된 라디오의 입력만 활성.\n// - 두 팝오버 모두 저장 시에만 값이 반영된다(취소/외부 클릭은 폐기).\n// 상태: default / hover(CSS) / dragging(drop — 파란 아웃라인·텍스트, DnD 중 유지)\n// 색은 condition-card-* 시멘틱 토큰만 사용. 트리거 비주얼은 Select 박스형과 동일 토큰(text-field-*).\nimport { useState, useRef } from 'react';\nimport type {\n ComponentPropsWithoutRef,\n CSSProperties,\n DragEvent as ReactDragEvent,\n HTMLAttributes,\n ReactNode,\n} from 'react';\nimport { GripVertical, Trash2, ChevronDown, Settings } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { TruncatingText } from './TruncatingText';\nimport { Select } from './Select';\nimport { ScreeningIndividualSettingModal } from './ScreeningIndividualSettingModal';\nimport { focusNextChainStop, chainTriggerKey } from './formulaFunctions';\nimport type { ConditionTab, IndividualSetting, OptionItem } from './formulaFunctions';\nimport { usePanelKeyboard } from './usePanelKeyboard';\n\n// 조건 값/저장 페이로드 — 탭 있으면 { tab, option|null }, 지정(복수)이면 { tab, option:null, items }, 탭 없으면 { option }\nexport interface ScreeningConditionValue {\n tab?: string | null;\n option?: string | null;\n items?: string[];\n}\n// 가/감점 값 — 단일(가점/감점/적합/부적합) 또는 복수 조건의 개별설정(IndividualSetting)\nexport type ScreeningScoreValue = { type: string; points?: string } | IndividualSetting;\n\n// 가/감점 라디오 항목 — 고정 4종(Figma 플로우 컷 기준)\nconst SCORE_TYPES: { value: 'plus' | 'minus' | 'fit' | 'unfit'; label: string; hasPoints: boolean }[] = [\n { value: 'plus', label: '가점', hasPoints: true },\n { value: 'minus', label: '감점', hasPoints: true },\n { value: 'fit', label: '적합', hasPoints: false },\n { value: 'unfit', label: '부적합', hasPoints: false },\n];\nconst SCORE_LABEL = Object.fromEntries(SCORE_TYPES.map((t) => [t.value, t.label]));\n\n// 셀렉트형 트리거 — Select 박스형과 같은 비주얼(text-field-* 토큰). 클릭 토글은 Popover가 담당.\nfunction FieldTrigger({\n children,\n placeholder,\n open,\n}: {\n children?: ReactNode;\n placeholder?: ReactNode;\n open?: boolean;\n}) {\n const filled = children != null && children !== '';\n return (\n <div\n // 포커스=호버(2026-07-16): 포커스는 래퍼(Popover, group/fieldstop)가 받으므로 group 변형으로\n // hover와 같은 인셋 2px 링을 켠다(키보드 focus-visible + 체인 data-chain-focus, 클릭은 표시 없음)\n className={`grid min-h-[32px] w-full cursor-pointer 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 ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 group-focus-visible/fieldstop:ring-2 group-data-[chain-focus]/fieldstop:ring-2 ${\n open ? 'ring-2 ring-text-field-focused-line' : ''\n }`}\n >\n <TruncatingText\n className={`text-14 ${filled ? 'text-text-field-filled-text' : 'text-text-field-default-text'}`}\n >\n {filled ? children : placeholder}\n </TruncatingText>\n <ChevronDown\n size={16}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 text-font-icon-5 transition-transform ${\n open ? 'rotate-180' : ''\n }`}\n />\n </div>\n );\n}\n\ninterface ScreeningConditionCardProps extends ComponentPropsWithoutRef<'div'> {\n cardName?: string;\n // ── 조건 셀렉트(tab radio list) ─────────────────────────\n // conditionTabs — 비우면 탭 없는 radio list.\n // disableOptions 탭(미보유/비대상 등)은 하단 옵션을 disabled로 두고\n // 옵션 선택 없이 저장 가능(값 = { tab, option: null })\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>; // { [tabValue]: [{ value, label }] }\n conditionOptions?: OptionItem[]; // 탭 없이 쓸 때의 평면 옵션 [{ value, label }]\n conditionValue?: ScreeningConditionValue | null; // { tab?, option } | null\n onConditionChange?: (value: ScreeningConditionValue) => void; // 저장 시에만\n conditionPlaceholder?: string;\n // ── 가/감점 셀렉트(radio list) ──────────────────────────\n scoreValue?: ScreeningScoreValue | null; // { type: 'plus'|'minus'|'fit'|'unfit', points?: string } | null\n onScoreChange?: (value: ScreeningScoreValue) => void; // 저장 시에만\n scorePlaceholder?: string;\n // ── 카드 ────────────────────────────────────────────────\n onDelete?: () => void; // 삭제 버튼 클릭\n dragging?: boolean; // 드래그 중 — drop 상태(파란 아웃라인·텍스트)\n dragHandleProps?: HTMLAttributes<HTMLSpanElement>; // grip에 전달(onMouseDown 등 — 핸들 시작 제한)\n width?: number | string; // 카드 너비: 숫자(px) | CSS 길이 | 'fill'(부모 전체 폭)\n // ...props — draggable · onDragStart · onDragOver 등 DnD 이벤트\n}\n\nexport function ScreeningConditionCard({\n cardName = '조건',\n conditionTabs = [],\n conditionOptionsByTab = {},\n conditionOptions = [],\n conditionValue = null,\n onConditionChange,\n conditionPlaceholder = '조건을 선택하세요',\n scoreValue = null,\n onScoreChange,\n scorePlaceholder = '가/감점',\n onDelete,\n dragging = false,\n dragHandleProps = {},\n width = 375,\n className = '',\n ...props\n}: ScreeningConditionCardProps) {\n const hasTabs = conditionTabs.length > 0;\n // 복수 조건(지정) — multiSelect 탭이 있는 카드. 지정 값이 저장되면 가/감점 자리가 '개별설정' 버튼이 된다.\n const multiTab = conditionTabs.find((t) => t.multiSelect);\n const multiOptions = multiTab ? (conditionOptionsByTab[multiTab.value] ?? []) : [];\n const isMultiValue = !!conditionValue?.items?.length;\n\n // 팝오버 열림 — controlled로 들고 트리거 chevron 회전·draft 초기화에 사용\n const [condOpen, setCondOpen] = useState(false);\n const [scoreOpen, setScoreOpen] = useState(false);\n // 개별설정 모달(복수 조건 전용) — 열 때마다 마운트해 draft를 카드 값으로 시드\n const [indivOpen, setIndivOpen] = useState(false);\n const indivBtnRef = useRef<HTMLSpanElement | null>(null);\n // 조건 팝오버 폭 — 기본은 셀렉트(트리거) 폭, 옵션이 길면 내용만큼만 넓어진다(열 때 트리거 폭 측정)\n const condWrapRef = useRef<HTMLDivElement | null>(null);\n const [condMinW, setCondMinW] = useState(0);\n\n const openCondition = (open: boolean) => {\n if (open) setCondMinW(condWrapRef.current?.offsetWidth ?? 0);\n setCondOpen(open);\n };\n\n // 트리거 표시 텍스트\n const tabLabel = (tab?: string | null) => conditionTabs.find((t) => t.value === tab)?.label;\n const optLabel = (tab?: string | null, opt?: string | null) =>\n (hasTabs ? (conditionOptionsByTab[tab as string] ?? []) : conditionOptions).find(\n (o) => o.value === opt,\n )?.label;\n const conditionDisplay = conditionValue\n ? isMultiValue\n ? multiOptions // 지정 — 선택 항목 라벨을 옵션 순서로 나열(Figma 8535:9887)\n .filter((o) => conditionValue.items?.includes(o.value))\n .map((o) => o.label)\n .join(', ')\n : hasTabs\n ? conditionValue.option == null\n ? tabLabel(conditionValue.tab) // disableOptions 탭 — 탭 라벨만 표시(예: \"미보유\")\n : `${tabLabel(conditionValue.tab)}, ${optLabel(conditionValue.tab, conditionValue.option)}`\n : optLabel(undefined, conditionValue.option)\n : null;\n const scoreDisplay = scoreValue\n ? scoreValue.type === 'plus' || scoreValue.type === 'minus'\n ? `${SCORE_LABEL[scoreValue.type]}, ${(scoreValue as { points?: string }).points}점`\n : SCORE_LABEL[scoreValue.type]\n : null;\n\n // 카드 상태 — default(+CSS hover) / dragging=drop 변형(Figma state3)\n // 라인은 border(안쪽) 대신 outline(바깥 얹기) — default는 gray-900 알파(배경 투영, 2026-07-16 지시)\n const cardState =\n 'outline-condition-card-default-card-line bg-condition-card-default-card-bg text-condition-card-default-card-text hover:outline-condition-card-hover-card-line hover:bg-condition-card-hover-card-bg';\n\n const widthStyle =\n width === 'fill' ? '100%' : typeof width === 'number' ? `${width}px` : width;\n\n // 드래그 고스트(마우스 추적 이미지) = drop 변형(Figma state3) — 헤더(grip+카드명)만 남긴 축약형,\n // 파란 아웃라인·텍스트·hug 폭. 목록에 남은 카드는 원래 모습을 유지하고(2026-07-16 지시),\n // dragstart에서 setDragImage로 이 오프스크린 요소를 고스트로 지정한다.\n const ghostRef = useRef<HTMLDivElement | null>(null);\n const handleDragStart = (e: ReactDragEvent<HTMLDivElement>) => {\n if (ghostRef.current && e.dataTransfer) {\n try {\n e.dataTransfer.setDragImage(ghostRef.current, 16, 20);\n } catch {\n /* setDragImage 미지원 브라우저 무시 — 기본 고스트 사용 */\n }\n }\n props.onDragStart?.(e);\n };\n\n return (\n <div\n style={{ width: widthStyle }}\n className={`group/card flex flex-col gap-spacing-5-5 rounded-round-4 outline outline-1 p-spacing-6 shadow-[0_2px_2px_0_rgba(0,0,0,0.12)] transition-colors ${cardState} ${className}`}\n {...props}\n onDragStart={handleDragStart}\n aria-grabbed={dragging || undefined}\n >\n {/* 드래그 고스트 원본 — 화면 밖 고정 배치(display:none이면 setDragImage가 안 먹음) */}\n <div\n ref={ghostRef}\n style={{ position: 'fixed', top: -9999, left: -9999 }}\n className=\"inline-flex w-fit items-center gap-spacing-4 rounded-round-4 border border-condition-card-drop-outline bg-condition-card-hover-card-bg px-spacing-6 py-spacing-4 text-condition-card-drop-text shadow-[0_2px_2px_0_rgba(0,0,0,0.12)]\"\n >\n <GripVertical size={16} strokeWidth={1.8} className=\"shrink-0\" />\n <span className=\"whitespace-nowrap text-14\">{cardName}</span>\n </div>\n {/* 헤더: grip + 카드명 / 삭제 버튼 */}\n <div className=\"flex w-full items-center justify-between\">\n <div className=\"flex min-w-0 items-center gap-spacing-4\">\n {/* 카드 hover 시 grip·카드명은 blue.400(hover-card-text) — group/card로 카드 전체 hover에 연동 */}\n <span\n {...dragHandleProps}\n className=\"shrink-0 cursor-grab text-condition-card-default-card-text transition-colors group-hover/card:text-condition-card-hover-card-text\"\n >\n <GripVertical size={16} strokeWidth={1.8} />\n </span>\n <TruncatingText className=\"min-w-0 text-14 text-condition-card-default-card-text transition-colors group-hover/card:text-condition-card-hover-card-text\">\n {cardName}\n </TruncatingText>\n </div>\n <Button variant=\"ghost\" icon={Trash2} aria-label=\"카드 삭제\" onClick={onDelete} />\n </div>\n\n {/* 셀렉트 행: 조건(fill) + 가/감점(100px) */}\n <div className=\"flex w-full items-center gap-spacing-3\">\n {/* 조건 셀렉트 → tab radio list 팝오버 — 기본 폭=트리거, 옵션이 길면 팝오버만 확장.\n 체인 정거장(data-formula-chain): Tab/Enter로 열고, 가/감점 저장 시 다음 카드로 이동 */}\n <div ref={condWrapRef} data-formula-chain className=\"min-w-0 flex-1\">\n <Popover\n className=\"group/fieldstop w-full rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => openCondition(true))}\n menuWidth={multiTab ? 351 : 'max-content'} /* 복수 조건 카드는 고정 폭(2026-07-16 지시) */\n open={condOpen}\n onOpenChange={openCondition}\n trigger={<FieldTrigger open={condOpen} placeholder={conditionPlaceholder}>{conditionDisplay}</FieldTrigger>}\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={conditionTabs}\n optionsByTab={conditionOptionsByTab}\n options={conditionOptions}\n initialTab={conditionValue?.tab}\n initialOption={conditionValue?.option}\n initialItems={conditionValue?.items}\n style={{ minWidth: condMinW }}\n onCancel={close}\n onSave={(v) => {\n onConditionChange?.(v);\n close();\n if (v.items) {\n // 지정 저장 — 가/감점 자리가 '개별설정' 버튼으로 바뀌므로 그 버튼에 포커스(체인 유지)\n setTimeout(() => indivBtnRef.current?.querySelector('button')?.focus(), 0);\n } else {\n setScoreOpen(true); // 저장(클릭·Enter·Tab 모두) → 바로 가/감점 셀렉트 열림(2026-07-14 지시)\n }\n }}\n onSkip={() => {\n close();\n focusNextChainStop(condWrapRef.current); // 값 미선택 이탈 — 다음 카드로\n }}\n />\n )}\n </Popover>\n </div>\n\n {/* 가/감점 자리 — 복수 조건(지정)이 저장되면 '개별설정' 아이콘 텍스트 버튼으로 바뀐다(Figma 8535:9887) */}\n {isMultiValue ? (\n <span ref={indivBtnRef} className=\"shrink-0\">\n <Button variant=\"line\" size=\"32\" leftIcon={Settings} onClick={() => setIndivOpen(true)}>\n 개별설정\n </Button>\n {indivOpen && (\n <ScreeningIndividualSettingModal\n items={multiOptions.filter((o) => conditionValue?.items?.includes(o.value))}\n value={scoreValue?.type === 'individual' ? (scoreValue as IndividualSetting) : null}\n onClose={() => setIndivOpen(false)}\n onConfirm={(v) => {\n onScoreChange?.(v);\n setIndivOpen(false);\n // 카드 세팅 완료 — 체인 규약대로 다음 정거장(다음 카드)으로\n focusNextChainStop(condWrapRef.current);\n }}\n />\n )}\n </span>\n ) : (\n <Popover\n className=\"group/fieldstop w-[100px] shrink-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, scoreOpen, () => setScoreOpen(true))}\n menuWidth={200}\n placement=\"auto-right\"\n open={scoreOpen}\n onOpenChange={setScoreOpen}\n trigger={<FieldTrigger open={scoreOpen} placeholder={scorePlaceholder}>{scoreDisplay}</FieldTrigger>}\n >\n {(close) => (\n <ScoreSettingMenu\n initialType={scoreValue?.type ?? null}\n initialPoints={(scoreValue as { points?: string } | null)?.points ?? ''}\n onCancel={close}\n onSave={(v) => {\n onScoreChange?.(v);\n close();\n // 카드 세팅 완료 — 체인 규약대로 다음 정거장(다음 카드 조건 셀렉트)으로\n focusNextChainStop(condWrapRef.current);\n }}\n onSkip={() => {\n close();\n focusNextChainStop(condWrapRef.current); // 미선택 이탈 — 다음 카드로\n }}\n />\n )}\n </Popover>\n )}\n </div>\n </div>\n );\n}\n\n// 조건 설정 메뉴 — tab radio list 팝오버 내용(SegmentedTabs? + 라디오 옵션 + 취소/저장).\n// ScreeningConditionCard(조건 셀렉트)와 ScreeningFormula(수식 조건 칩)가 공유한다.\n// disableOptions 탭(미보유/비대상)은 옵션 disabled + 옵션 없이 저장 가능.\n// Popover children으로 열릴 때마다 마운트되므로 draft는 mount 시 initial*로 시드된다.\n// onSave 페이로드: 탭 있으면 { tab, option|null } / 없으면 { option }\ninterface ConditionSettingMenuProps {\n tabs?: ConditionTab[];\n optionsByTab?: Record<string, OptionItem[]>;\n options?: OptionItem[];\n initialTab?: string | null;\n initialOption?: string | null;\n initialItems?: string[] | null; // multiSelect 탭의 저장된 항목 배열(지정된 자격증 등)\n onSave?: (v: ScreeningConditionValue) => void;\n onCancel?: () => void;\n onTabNext?: () => void; // Tab 키 — 저장 후 다음 편집기(값/가감점 팝오버)로 이어가기(2026-07-14 지시)\n onSkip?: () => void; // 값 미선택 상태로 Tab 이탈 — 저장 없이 닫고 다음 정거장으로(2026-07-15)\n style?: CSSProperties;\n}\n\nexport function ConditionSettingMenu({\n tabs = [],\n optionsByTab = {},\n options = [],\n initialTab = null,\n initialOption = null,\n initialItems = null,\n onSave,\n onCancel,\n onTabNext,\n onSkip,\n style,\n}: ConditionSettingMenuProps) {\n const hasTabs = tabs.length > 0;\n const [tab, setTab] = useState<string | null>(initialTab ?? tabs[0]?.value ?? null);\n const [opt, setOpt] = useState<string | null>(initialOption);\n // multiSelect 탭(지정) — 라디오 대신 다중 선택 Select. 다른 탭에서는 같은 Select를 disabled로 보여준다.\n const multiTab = tabs.find((t) => t.multiSelect);\n const onMultiTab = multiTab && tab === multiTab.value;\n const [items, setItems] = useState<string[]>(() =>\n initialTab === multiTab?.value ? (initialItems ?? []) : [],\n );\n const rows = hasTabs ? (optionsByTab[tab as string] ?? []) : options;\n const tabDisables = hasTabs && !!tabs.find((t) => t.value === tab)?.disableOptions;\n const payload = (): ScreeningConditionValue =>\n multiTab\n ? onMultiTab\n ? { tab, option: null, items }\n : { tab, option: null } // 보유/미보유 — 종류 상관없이 적용(옵션 없이 저장)\n : hasTabs\n ? { tab, option: tabDisables ? null : opt }\n : { option: opt };\n const complete = multiTab ? (onMultiTab ? items.length > 0 : true) : tabDisables ? true : !!opt;\n // 패널 포커스 + 경계 Tab 처리(규칙 20 ⑶⑷) — 공용 훅. 라디오 선택은 Space(네이티브), 확인은 Enter.\n const { menuRef, handleTabKey } = usePanelKeyboard({\n complete,\n onCommit: () => {\n onSave?.(payload());\n onTabNext?.();\n },\n onSkip, // 값 미선택 이탈 — 저장 없이 닫고 다음 정거장(다음 카드/수식)으로\n });\n\n return (\n <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>\n <PopoverMenu\n width={multiTab ? 351 : 'auto'} /* 복수 조건 메뉴는 고정 폭 — 항목 라벨 길이에 따라 출렁이지 않게 */\n style={multiTab ? undefined : style}\n footer\n footerButtonsFill\n confirmText=\"저장\"\n confirmDisabled={!complete}\n onCancel={onCancel}\n onConfirm={() => onSave?.(payload())}\n >\n {hasTabs && (\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <SegmentedTabs\n items={tabs}\n value={tab}\n onChange={(t: string) => {\n setTab(t);\n setOpt(null); // 탭이 바뀌면 이전 탭의 선택은 무효\n }}\n />\n </div>\n )}\n {multiTab ? (\n /* 복수 조건 — 항목 다중 선택 Select(검색+전체 선택/취소/확인 푸터). 지정 탭에서만 활성(Figma 8535:8591).\n PopoverMenu 루트 배경은 gray.100(블록 구분선용)이라 블록마다 흰 배경(list-group-bg)을 직접 깐다 */\n <div className=\"w-full bg-list-group-bg p-spacing-5\">\n <Select\n width=\"100%\"\n multiple\n confirm\n searchable\n options={optionsByTab[multiTab.value] ?? []}\n value={items}\n onChange={(e) => setItems(e.target.value)}\n disabled={!onMultiTab}\n placeholder={multiTab.placeholder ?? '선택하세요'}\n searchPlaceholder={multiTab.searchPlaceholder ?? '검색어를 입력하세요'}\n />\n </div>\n ) : (\n <ListGroup gap=\"3\">\n {rows.map((o) => (\n <List\n key={o.value}\n radio\n title={o.label}\n checked={opt === o.value}\n disabled={tabDisables}\n onCheckChange={() => setOpt(o.value)}\n />\n ))}\n </ListGroup>\n )}\n </PopoverMenu>\n </div>\n );\n}\n\n// 가/감점 설정 메뉴 — radio list 팝오버 내용(가점/감점=입력+점(선택된 라디오만 활성), 적합/부적합, 취소/저장).\n// ScreeningConditionCard(가/감점 셀렉트)와 ScreeningFormula(수식 점수 인풋)가 공유한다.\n// Popover children으로 열릴 때마다 마운트되므로 draft는 mount 시 initial*로 시드된다.\n// onSave 페이로드: { type: 'plus'|'minus'|'fit'|'unfit', points?: string(양수) }\ninterface ScoreSettingMenuProps {\n initialType?: string | null;\n initialPoints?: string;\n disablePoints?: boolean; // 가점/감점 라디오 비활성 — IF 함수 안 조건은 적합/부적합만(2026-07-16)\n onSave?: (v: { type: string; points?: string }) => void;\n onCancel?: () => void;\n onTabNext?: () => void;\n onSkip?: () => void;\n}\n\nexport function ScoreSettingMenu({\n initialType = null,\n initialPoints = '',\n disablePoints = false,\n onSave,\n onCancel,\n onTabNext,\n onSkip,\n}: ScoreSettingMenuProps) {\n const [type, setType] = useState<string | null>(initialType);\n const [points, setPoints] = useState<Record<'plus' | 'minus', string>>({\n plus: initialType === 'plus' ? initialPoints : '',\n minus: initialType === 'minus' ? initialPoints : '',\n });\n const needsPoints = type === 'plus' || type === 'minus';\n const pointsValue = needsPoints ? points[type as 'plus' | 'minus'] : '';\n const complete = !!type && (!needsPoints || !!pointsValue.trim());\n // 패널 포커스 + 경계 Tab 처리(규칙 20 ⑶⑷) — 공용 훅\n const { menuRef, handleTabKey } = usePanelKeyboard({\n complete,\n onCommit: () => {\n onSave?.(needsPoints ? { type, points: pointsValue.trim() } : { type: type as string });\n onTabNext?.();\n },\n onSkip, // 미선택 이탈 — 닫고 다음 정거장으로\n });\n\n // 가점/감점 라디오 선택 시 점수 입력 자동 포커스(선택 직후 바로 타이핑)\n const onTypeSelect = (value: string, hasPoints: boolean) => {\n setType(value);\n if (hasPoints) {\n setTimeout(() => menuRef.current?.querySelector<HTMLInputElement>('input[inputmode]:not([disabled])')?.focus(), 0);\n }\n };\n\n // 가점 입력칸+단위 — 해당 라디오가 선택됐을 때만 활성(List rightSlot, 행 클릭과 분리)\n const pointsSlot = (key: 'plus' | 'minus') => (\n <span className=\"flex shrink-0 items-center gap-spacing-5\" onClick={(e) => e.stopPropagation()}>\n <Input\n width={56}\n value={points[key]}\n disabled={type !== key}\n // 점수는 숫자만 입력 허용(2026-07-16 지시)\n onChange={(e) => setPoints((p) => ({ ...p, [key]: e.target.value.replace(/[^0-9]/g, '') }))}\n inputProps={{ inputMode: 'numeric' }}\n placeholder=\"0\"\n />\n <span className={`text-14 ${type === key ? 'text-list-default-text' : 'text-list-disabled-text'}`}>\n 점\n </span>\n </span>\n );\n\n return (\n <div ref={menuRef} tabIndex={-1} className=\"outline-none\" onKeyDown={handleTabKey}>\n <PopoverMenu\n width=\"100%\"\n footer\n footerButtonsFill\n confirmText=\"저장\"\n confirmDisabled={!complete}\n onCancel={onCancel}\n onConfirm={() =>\n onSave?.(needsPoints ? { type, points: pointsValue.trim() } : { type: type as string })\n }\n >\n <ListGroup gap=\"3\">\n {SCORE_TYPES.map((t) => {\n const rowDisabled = disablePoints && t.hasPoints; // IF 안 조건 — 가점/감점 선택 불가\n return (\n <List\n key={t.value}\n radio\n title={t.label}\n checked={type === t.value}\n disabled={rowDisabled}\n onCheckChange={rowDisabled ? undefined : () => onTypeSelect(t.value, t.hasPoints)}\n rightSlot={t.hasPoints ? pointsSlot(t.value as 'plus' | 'minus') : undefined}\n />\n );\n })}\n </ListGroup>\n </PopoverMenu>\n </div>\n );\n}\n","// ScreeningFormula — 스크리닝 수식 (Figma Formula 8383:112161 · 그룹 8384:114161/114251 · 함수 색상표 8384:114813)\n// ⚠️ 내부용(ScreeningBuilderTemplate 전용) 컴포넌트 — 조건 카드를 수식 영역에 드롭하면 이 수식으로 치환된다.\n// 독립 데모 페이지 없이 ScreeningBuilderTemplate 데모에서 함께 확인한다.\n//\n// 노드 모델(재귀 트리):\n// - leaf : { id, kind:'leaf', criteria, criteriaOptions, value, valueOptions, points, display?('full'|'compact') }\n// → [조건칩] = [값칩] → +N점 / compact = \"기준, 값, 점수\" 요약칩 — 클릭 시 펼침(2026-07-15 표기 개정)\n// - 묶음 leaf(복수 조건, Figma 8535:11956): { …leaf, items:[항목…], individual:{mode,fn,items}|null }\n// → [조건칩] = [지정보유, N개 조건 묶음] → [개별설정] — 항목이 100개여도 한 줄 유지.\n// 묶음 칩 클릭=지정 항목 편집(다중 선택), 개별설정 클릭=항목별 점수 모달.\n// 단수 수식과 같은 골격으로 단독 등록된다(함수 그룹 자동 감싸기 없음 — 2026-07-16 개정,\n// 그룹핑은 일반 수식과 동일하게 체크+툴바로).\n// - group: { id, kind:'group', fn:'SUM'…, children:[node…], trueScore?, falseScore? }\n// → FN( 자식수식 , 자식수식 [, 조건 추가 존] + ) — + 클릭 시에만 '조건 추가' 드롭 존이 나타나고\n// 카드를 드롭하면 그 그룹에 수식이 추가된다. 그룹은 다른 수식·그룹과 다시 그룹핑될 수 있다\n// - IF 그룹(Figma 8537:2175): IF( 자식수식 , [참점수] , [거짓점수] ) — 조건이 참이면 참점수(기본 1),\n// 아니면 거짓점수(기본 0). 조건을 더 못 받으므로 +(조건 추가)·드롭 존이 없다\n// - CAPMAX/CAPMIN 그룹(Figma 8544:36274): FN( 자식수식 , [capScore] ) — 합산 점수의 최대/최소를\n// 제한. 가/감점(점수) 있는 수식에만 적용(적합/부적합·미설정 불가), + 버튼 없음\n// - FITBYSCORE/UNFITBYSCORE 그룹(Figma 8544:36274): FN( 자식수식 , [compareOp] , [compareScore] ) —\n// 점수가 계산식(비교 연산자)·비교값을 만족하면 적합/부적합 처리. 점수 있는 수식 전용, + 버튼 없음\n// 함수 계열별 고유 텍스트 컬러(formula-* 토큰): AND·OR=green / IF=blue / SUM·MAX·MIN·COUNTIF=violet /\n// CAPMAX·CAPMIN=pink / FITBYSCORE·UNFITBYSCORE=orange. hover 아웃라인도 계열 색을 따른다.\n// 체크박스는 최상위(root)에서만 노출 — 그룹핑 대상 선택용(ScreeningBuilderTemplate이 관리).\nimport { useState, useRef, useEffect } from 'react';\nimport type { ChangeEvent, ReactNode } from 'react';\nimport { X, Plus, ChevronDown } from 'lucide-react';\nimport { Checkbox } from './Checkbox';\nimport { SelectChip } from './Select';\nimport { Input } from './Input';\nimport { Button } from './Button';\nimport { Popover } from './Popover';\nimport { PopoverMenu } from './PopoverMenu';\nimport { ListGroup } from './ListGroup';\nimport { List } from './List';\nimport { Tag } from './Tag';\nimport { Divider } from './Divider';\nimport { useHoverTooltip } from './useHoverTooltip';\nimport { ScoreSettingMenu, ConditionSettingMenu } from './ScreeningConditionCard';\nimport type { ScreeningConditionValue } from './ScreeningConditionCard';\nimport { ScreeningIndividualSettingModal } from './ScreeningIndividualSettingModal';\nimport { CHIP_COLOR_CLASS } from './chipStyles';\nimport { FORMULA_FN_FAMILY, FORMULA_GROUP_FUNCTIONS, focusNextChainStop, chainTriggerKey, hasPointsScore } from './formulaFunctions';\nimport type {\n FormulaCatalog,\n FormulaGroupNode,\n FormulaLeafNode,\n FormulaNode,\n OptionItem,\n} from './formulaFunctions';\n\n// 표기 변형 — 'formula'(수식 표기) | 'natural'(자연어 문장 표기)\ntype FormulaVariant = 'formula' | 'natural';\n\n// 계열별 정적 클래스 매핑 (Tailwind purge 안전)\n// 함수 계열 → 칩 color(2026-07-15 — 자연어 그룹 함수 셀렉트 칩 채색)\nconst FAMILY_CHIP_COLOR: Record<string, 'green' | 'blue' | 'violet' | 'pink' | 'orange'> = {\n logical: 'green',\n conditional: 'blue',\n aggregate: 'violet',\n 'score-limit': 'pink',\n evaluation: 'orange',\n};\nconst FAMILY_HOVER_BORDER: Record<string, string> = {\n logical: 'border-formula-hover-logical-outline',\n conditional: 'border-formula-hover-conditional-outline',\n aggregate: 'border-formula-hover-aggregate-outline',\n 'score-limit': 'border-formula-hover-score-limit-outline',\n evaluation: 'border-formula-hover-evaluation-outline',\n};\n\n// 수식 컨테이너 공통 셸 — 흰 bg + 1px 아웃라인(default 공통, hover=함수 계열 색).\n// hover는 CSS :hover 대신 마우스 이벤트로 추적한다 — :hover는 자식 수식 위에 있어도 부모에 걸려\n// 중첩 그룹에서 아웃라인이 겹친다(2026-07-16 지시). mouseover/out은 버블되므로 안쪽 셸이\n// stopPropagation으로 끊으면 가장 안쪽 수식 하나만 호버 상태를 가진다.\nfunction FormulaShell({\n family,\n dense = false,\n comment = null,\n className = '',\n children,\n}: {\n family: string; // 함수 계열(hover 아웃라인 색)\n dense?: boolean;\n comment?: ReactNode;\n className?: string;\n children?: ReactNode;\n}) {\n const [hovered, setHovered] = useState(false);\n const shellRef = useRef<HTMLDivElement | null>(null);\n // 호버 추적은 네이티브 리스너로 — React 합성 이벤트는 portal(팝오버 패널)에서도 컴포넌트 트리로\n // 버블돼, 패널 위 이동이 셸 hover를 갱신하고 패널이 닫히면 mouseout이 없어 hover가 고착된다\n // (조건/값 저장 후 호버 잔류 버그, 2026-07-15). 네이티브는 DOM 트리 기준이라 패널 이벤트가 안 닿는다.\n useEffect(() => {\n const el = shellRef.current;\n if (!el) return;\n // stopPropagation 금지 — 네이티브에서 전파를 끊으면 React 루트 위임까지 막혀\n // 수식 내부 툴팁(useHoverTooltip 등) mouseenter가 전부 죽는다(2026-07-15 회귀).\n // 대신 '이벤트 지점의 가장 가까운 셸이 나인가'로 판정해 가장 안쪽 셸만 hover.\n const over = (e: MouseEvent) =>\n setHovered((e.target as Element | null)?.closest('[data-formula-shell]') === el);\n const out = (e: MouseEvent) => {\n if (!el.contains(e.relatedTarget as Node | null)) setHovered(false);\n };\n el.addEventListener('mouseover', over);\n el.addEventListener('mouseout', out);\n return () => {\n el.removeEventListener('mouseover', over);\n el.removeEventListener('mouseout', out);\n };\n }, []);\n return (\n <div\n ref={shellRef}\n data-formula-shell\n className={`inline-flex max-w-full flex-col gap-spacing-4 rounded-round-4 border py-spacing-5 transition-colors ${\n dense ? 'px-spacing-5-5' : 'px-spacing-6' /* 그룹핑된 조건 수식은 좌우 10, 최상위는 12(2026-07-15) */\n } ${\n hovered\n ? `bg-formula-hover-bg ${FAMILY_HOVER_BORDER[family] ?? 'border-formula-default-outline'}`\n : 'border-formula-default-outline bg-formula-default-bg'\n } ${className}`}\n >\n <div className=\"flex max-w-full flex-wrap items-center gap-spacing-4\">{children}</div>\n {/* 설명 코멘트(Figma commet 옵션, 2026-07-15) — Divider 아래 [설명] 태그 + 요약 텍스트 */}\n {comment && (\n <>\n <Divider />\n <div className=\"flex w-full flex-wrap items-start gap-x-spacing-4 gap-y-spacing-5\">\n <Tag color=\"black\">설명</Tag>\n <p className=\"min-w-0 flex-1 text-12 leading-22 text-formula-default-text\">{comment}</p>\n </div>\n </>\n )}\n </div>\n );\n}\n\nconst Paren = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 font-semibold text-formula-parentheses\">{children}</span>\n);\nconst Comparison = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 font-semibold text-formula-comparison\">{children}</span>\n);\n// 자연어 빌더 연결어 — 레귤러 웨이트(Figma Language 8466:6316)\nconst NaturalText = ({ children }: { children?: ReactNode }) => (\n <span className=\"shrink-0 text-12 text-formula-comparison\">{children}</span>\n);\n\nconst optLabel = (options: OptionItem[] | null | undefined, v: string | null | undefined) =>\n options?.find((o) => o.value === v)?.label ?? '';\n\n// IF 안으로 들어가는 노드에서 가점/감점 값을 제거(적합/부적합·미설정은 유지)\nfunction stripPointsScore(node: FormulaNode): FormulaNode {\n if (node.kind === 'group') return { ...node, children: node.children.map(stripPointsScore) };\n if (node.items?.length)\n return node.individual?.mode === 'points' ? { ...node, individual: null } : node;\n return node.scoreType === 'plus' || node.scoreType === 'minus' || node.points\n ? { ...node, points: '', scoreType: null }\n : node;\n}\n\n\n// 수식 필드 체인 규약 — 공용 유틸은 formulaFunctions.js(focusNextChainStop/CHAIN_STOP_FOCUS,\n// 카드·수식·드롭 스트립이 공유). 여기서는 정거장 마커(data-formula-chain)만 단다.\n\n// '조건 추가' dashed 드롭 존 (Figma FormulaAdd 8384:113414 — Default/drop 2상태, + 버튼과 별개 컴포넌트).\n// 조건 카드를 이 위에 드롭하면 그 그룹 안에 수식이 추가된다. 내부 X 버튼 클릭 시 영역 삭제(onClose).\n// hover 시 사용법 안내 툴팁(\"조건 카드를 드래그 앤 드롭하세요\")을 띄운다.\nfunction FormulaAddZone({\n onDropLeaf,\n getDropLeaf,\n onClose,\n closable = true,\n}: {\n onDropLeaf: (leaf: FormulaNode) => void;\n getDropLeaf?: (() => FormulaNode | null) | null;\n onClose?: () => void;\n closable?: boolean;\n}) {\n const [over, setOver] = useState(false);\n const droppable = !!getDropLeaf;\n const zoneRef = useRef<HTMLDivElement | null>(null);\n const hoverTip = useHoverTooltip('조건 카드를 드래그 앤 드롭하세요');\n return (\n <div\n ref={zoneRef}\n onMouseEnter={hoverTip.onMouseEnter}\n onMouseLeave={hoverTip.onMouseLeave}\n onDragOver={(e) => {\n if (!droppable || !getDropLeaf?.()) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 수식 영역 드롭 존의 하이라이트/드롭과 분리\n e.dataTransfer.dropEffect = 'move';\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n if (!droppable) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 드롭 존이 최상위 수식으로 중복 추가하지 않게\n setOver(false);\n const leaf = getDropLeaf?.();\n if (leaf) onDropLeaf(leaf);\n }}\n className={`flex h-[38px] shrink-0 items-center gap-spacing-4 rounded-round-4 border border-dashed py-spacing-5 pl-spacing-7 ${\n closable ? 'pr-spacing-5-5' /* X 버튼 자체 여백 감안 */ : 'pr-spacing-7' /* X 없음 — 좌우 동일 */\n } text-12 font-semibold transition-colors ${\n over\n ? 'border-formula-add-drop-outline bg-formula-add-drop-bg text-formula-add-drop-text'\n : 'border-formula-add-outline bg-formula-add-bg text-formula-add-text'\n }`}\n >\n 조건 추가\n {/* X 버튼 위에서는 영역 툴팁을 숨겨 버튼 툴팁과 겹치지 않게 — 벗어나면 다시 표시.\n closable=false(IF 빈 상태 등 조건이 필수인 자리)면 X 없이 상시 노출 */}\n {closable && (\n <span\n className=\"inline-flex shrink-0 items-center\"\n onMouseEnter={hoverTip.onMouseLeave}\n onMouseLeave={() => {\n if (zoneRef.current) hoverTip.onMouseEnter({ currentTarget: zoneRef.current });\n }}\n >\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"영역 삭제\" onClick={onClose} />\n </span>\n )}\n {hoverTip.tooltip}\n </div>\n );\n}\n\n// FITBYSCORE/UNFITBYSCORE 비교 연산자(계산식) — Figma 8544:36274\nconst COMPARE_OPS = [\n { value: '>=', label: '>= (보다 크거나 같을 때)' },\n { value: '>', label: '> (보다 클 때)' },\n { value: '=', label: '= (같을 때)' },\n { value: '<=', label: '<= (보다 작거나 같을 때)' },\n { value: '<', label: '< (보다 작을 때)' },\n];\n// 설명 문장용 관계 표현 — \"N점 이상이면\" 식으로 잇는다('='는 \"N점이면\")\nconst COMPARE_OP_TEXT: Record<string, string> = { '>=': '이상', '>': '초과', '=': '', '<=': '이하', '<': '미만' };\n\n// 그룹 함수 점수 인풋(IF 참/거짓·CAPMAX/CAPMIN 제한값 공용) — 숫자만 입력,\n// 라벨 없는 소형 인풋이라 hover 시 용도 툴팁을 띄운다(2026-07-16 지시)\nfunction GroupScoreInput({\n tooltip,\n value,\n placeholder,\n onChange,\n}: {\n tooltip: string;\n value?: string;\n placeholder?: string;\n onChange?: (e: ChangeEvent<HTMLInputElement>) => void;\n}) {\n const tip = useHoverTooltip(tooltip);\n return (\n <span\n className=\"inline-flex shrink-0 items-center\"\n onMouseEnter={tip.onMouseEnter}\n onMouseLeave={tip.onMouseLeave}\n >\n <Input\n size=\"22\"\n width={40}\n value={value}\n placeholder={placeholder}\n onChange={onChange}\n inputProps={{ inputMode: 'numeric', 'aria-label': tooltip }}\n />\n {tip.tooltip}\n </span>\n );\n}\n\n// 자연어 그룹의 상시 드롭 스트립(컴팩트 h38, builder-area 토큰 — Figma 8466:6494 하단)\nfunction NaturalGroupDropStrip({\n getDropLeaf,\n onDropLeaf,\n}: {\n getDropLeaf?: (() => FormulaNode | null) | null;\n onDropLeaf: (leaf: FormulaNode) => void;\n}) {\n const [over, setOver] = useState(false);\n return (\n <div\n onDragOver={(e) => {\n if (!getDropLeaf || !getDropLeaf()) return;\n e.preventDefault();\n e.stopPropagation(); // 바깥 존 드롭과 분리\n e.dataTransfer.dropEffect = 'move';\n setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(e) => {\n if (!getDropLeaf) return;\n e.preventDefault();\n e.stopPropagation();\n setOver(false);\n const leaf = getDropLeaf();\n if (leaf) onDropLeaf(leaf);\n }}\n className={`flex h-[38px] w-full items-center justify-center gap-spacing-3 rounded-round-4 border border-dashed text-14 text-builder-area-add-default-text transition-colors ${\n over\n ? 'border-builder-area-add-hover-outline bg-builder-area-add-hover-bg'\n : 'border-builder-area-add-default-outline bg-builder-area-add-default-bg'\n }`}\n >\n <Plus size={16} strokeWidth={1.8} className=\"shrink-0\" />\n 다른 조건을 여기에 끌어다 놓기\n </div>\n );\n}\n\n// 재귀 수식 렌더러 props — FormulaGroup/FormulaLeaf도 노드 타입만 좁혀 공유한다.\nexport interface ScreeningFormulaProps {\n node: FormulaNode; // leaf | group 노드(위 모델)\n root?: boolean; // 최상위 여부 — 체크박스 노출(그룹핑 대상 선택)\n checked?: boolean;\n onCheckChange?: (checked: boolean) => void; // root 전용\n onChange?: (nextNode: FormulaNode) => void;\n onDelete?: () => void; // X 버튼\n getDropLeaf?: () => FormulaNode | null; // 드래그 중인 카드의 leaf(없으면 null). '조건 추가' 드롭 존이 사용\n // catalog — { criteriaOptions, valueOptionsByCriteria, conditionMetaByCriteria } — 현재 카드 영역\n // 라이브 목록(템플릿이 렌더마다 계산). 지정 시 leaf의 스냅샷보다 우선한다.\n catalog?: FormulaCatalog | null;\n variant?: FormulaVariant; // 'formula'(수식 표기) | 'natural'(자연어 문장 표기 — 같은 노드 모델·기능, 8466:6316)\n inIf?: boolean; // IF 함수 서브트리 안 여부 — 조건 값은 적합/부적합만 허용(2026-07-16)\n className?: string;\n}\n\n// 재귀 수식 렌더러 — root에서만 체크박스, onChange(nextNode)로 불변 갱신을 위로 올린다.\nexport function ScreeningFormula({\n node,\n root = false,\n checked = false,\n onCheckChange,\n onChange,\n onDelete,\n getDropLeaf,\n catalog,\n variant = 'formula',\n inIf = false,\n className = '',\n}: ScreeningFormulaProps) {\n if (node.kind === 'group') {\n return (\n <FormulaGroup\n node={node}\n root={root}\n checked={checked}\n onCheckChange={onCheckChange}\n onChange={onChange}\n onDelete={onDelete}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={inIf}\n className={className}\n />\n );\n }\n\n return (\n <FormulaLeaf\n node={node}\n root={root}\n checked={checked}\n onCheckChange={onCheckChange}\n onChange={onChange}\n onDelete={onDelete}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={inIf}\n className={className}\n />\n );\n}\n\n// 그룹 수식 — FN( 자식 , … [, 조건 추가 존] + ) X\n// '조건 추가' 드롭 존은 평소엔 없고 + 버튼을 클릭했을 때만 나타난다(2026-07-16 지시).\n// 카드를 드롭하면 그 그룹에 수식이 추가되고 존은 다시 사라진다(+ 재클릭으로 닫기도 가능).\nfunction FormulaGroup({ node, root, checked, onCheckChange, onChange, onDelete, getDropLeaf, catalog, variant = 'formula', inIf = false, className }: Omit<ScreeningFormulaProps, 'node'> & { node: FormulaGroupNode }) {\n // 조건 추가 존 목록 — +는 누를 때마다 존을 하나씩 추가(토글 아님, 2026-07-14 지시), 닫기는 각 존의 X\n const [addZoneIds, setAddZoneIds] = useState<string[]>([]);\n const addSeqRef = useRef(0);\n const appendAddZone = () => setAddZoneIds((prev) => [...prev, `add-${++addSeqRef.current}`]);\n const removeAddZone = (id: string) => setAddZoneIds((prev) => prev.filter((x) => x !== id));\n const patch = (p: Partial<FormulaGroupNode>) => onChange?.({ ...node, ...p });\n const family = FORMULA_FN_FAMILY[node.fn];\n // IF 그룹 — 참/거짓 점수 인풋(기본 1/0), 조건 추가 불가(Figma 8537:2175, 2026-07-16)\n const isIf = node.fn === 'IF';\n // CAPMAX/CAPMIN 그룹 — 제한 점수 인풋 하나, 조건 추가 불가(Figma 8544:36274, 2026-07-16)\n const isCap = node.fn === 'CAPMAX' || node.fn === 'CAPMIN';\n const capInput = (\n <GroupScoreInput\n tooltip={node.fn === 'CAPMAX' ? '최대 제한 점수' : '최소 제한 점수'}\n value={node.capScore ?? ''}\n placeholder=\"값\"\n onChange={(e) => patch({ capScore: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n // FITBYSCORE/UNFITBYSCORE 그룹 — 계산식(비교 연산자)+비교값, 조건 추가 불가(Figma 8544:36274)\n const isByScore = node.fn === 'FITBYSCORE' || node.fn === 'UNFITBYSCORE';\n const compareOpSelect = (\n <SelectChip\n options={COMPARE_OPS}\n value={node.compareOp ?? '>='}\n onChange={(e) => patch({ compareOp: e.target.value })}\n menuWidth={210}\n />\n );\n const compareScoreInput = (\n <GroupScoreInput\n tooltip=\"비교 기준 점수\"\n value={node.compareScore ?? ''}\n placeholder=\"값\"\n onChange={(e) => patch({ compareScore: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n // 함수 변경 가능 판정 — 툴바와 같은 규칙(IF=단일·비점수 조건 / CAP·BYSCORE=점수 있는 조건)을\n // 그룹 함수명 팝오버·자연어 함수 칩에도 적용해 우회 전환을 막는다\n const fnDisabled = (fn: string) => {\n if (fn === 'IF') return node.children.length !== 1 || node.children.some(hasPointsScore);\n if (fn === 'CAPMAX' || fn === 'CAPMIN' || fn === 'FITBYSCORE' || fn === 'UNFITBYSCORE')\n return !node.children.some(hasPointsScore);\n return false;\n };\n const ifScoreInput = (key: 'trueScore' | 'falseScore', fallback?: string) => (\n <GroupScoreInput\n tooltip={key === 'trueScore' ? '참일 때 부여 점수' : '거짓일 때 부여 점수'}\n value={node[key] ?? fallback}\n onChange={(e) => patch({ [key]: e.target.value.replace(/[^0-9]/g, '') })}\n />\n );\n const replaceChild = (idx: number, next: FormulaNode) => {\n const children = node.children.slice();\n children[idx] = next;\n patch({ children });\n };\n\n const removeChild = (idx: number) => patch({ children: node.children.filter((_, i) => i !== idx) });\n // 설명 코멘트 — 최상위에서만, 자식(중첩 포함) 요약을 하나로 합쳐 표시\n const groupComment = root ? nodeSummary(node, catalog) : null;\n\n // ── 자연어 표기(8466:6494/6621) — \"다음 조건들 중 [FN ▾] 반영\" + 자식 문장 스택 + 상시 드롭 스트립.\n // 함수도 셀렉트 칩으로 변경 가능(수식 빌더와 기능 동일, 표기만 다름)\n if (variant === 'natural') {\n return (\n <FormulaShell family={family} className={`w-full ${className}`}>\n <div className=\"flex w-full items-center gap-spacing-4\">\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n <NaturalText>다음 조건들 중</NaturalText>\n <SelectChip\n options={FORMULA_GROUP_FUNCTIONS.map((f) => ({ value: f, label: f, disabled: fnDisabled(f) }))}\n value={node.fn}\n onChange={(e) => patch({ fn: e.target.value })}\n menuWidth={140}\n color={FAMILY_CHIP_COLOR[family] ?? 'gray'}\n weight=\"semibold\"\n />\n <NaturalText>반영</NaturalText>\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </div>\n {node.children.map((child, i) => (\n <ScreeningFormula\n key={child.id}\n node={child}\n onChange={(next) => replaceChild(i, next)}\n onDelete={() => removeChild(i)}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={isIf || inIf}\n className=\"w-full\"\n />\n ))}\n {isIf ? (\n /* IF — 조건 추가 불가(비었을 때만 드롭 스트립 노출). 참/거짓 점수는 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [stripPointsScore(leaf)] })}\n />\n )}\n <div className=\"flex w-full items-center gap-spacing-4\">\n <NaturalText>참이면</NaturalText>\n {ifScoreInput('trueScore', '1')}\n <NaturalText>점, 아니면</NaturalText>\n {ifScoreInput('falseScore', '0')}\n <NaturalText>점 부여</NaturalText>\n </div>\n </>\n ) : isCap ? (\n /* CAPMAX/CAPMIN — 조건 추가 불가(비었을 때만 드롭 스트립). 제한 점수를 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n />\n )}\n <div className=\"flex w-full items-center gap-spacing-4\">\n <NaturalText>{node.fn === 'CAPMAX' ? '최대' : '최소'}</NaturalText>\n {capInput}\n <NaturalText>점으로 제한</NaturalText>\n </div>\n </>\n ) : isByScore ? (\n /* FITBYSCORE/UNFITBYSCORE — 조건 추가 불가. 계산식·비교값을 문장으로 편집 */\n <>\n {node.children.length === 0 && (\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n />\n )}\n <div className=\"flex w-full flex-wrap items-center gap-spacing-4\">\n <NaturalText>점수가</NaturalText>\n {compareOpSelect}\n {compareScoreInput}\n <NaturalText>{`점이면 ${node.fn === 'FITBYSCORE' ? '적합' : '부적합'} 처리`}</NaturalText>\n </div>\n </>\n ) : (\n /* 상시 드롭 스트립(컴팩트 h38) — 카드를 끌어다 놓으면 이 그룹에 조건 추가 */\n <NaturalGroupDropStrip\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [...node.children, leaf] })}\n />\n )}\n </FormulaShell>\n );\n }\n\n return (\n <FormulaShell family={family} comment={groupComment} className={className}>\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n {/* 함수명 — underline 텍스트 버튼(계열 색·semibold), 클릭하면 함수 변경 팝오버(2026-07-15) */}\n <Popover\n menuWidth={140}\n trigger={\n <Button variant=\"underline\" size=\"24\" weight=\"semibold\" color={FAMILY_CHIP_COLOR[family] ?? 'black'}>\n {node.fn}\n </Button>\n }\n >\n {(close) => (\n <PopoverMenu width=\"100%\">\n <ListGroup>\n {FORMULA_GROUP_FUNCTIONS.map((fn) => (\n <List\n key={fn}\n title={fn}\n selected={fn === node.fn}\n disabled={fnDisabled(fn)}\n onClick={\n fnDisabled(fn)\n ? undefined\n : () => {\n patch({ fn });\n close();\n }\n }\n />\n ))}\n </ListGroup>\n </PopoverMenu>\n )}\n </Popover>\n <Paren>(</Paren>\n {node.children.map((child, i) => (\n <span key={child.id} className=\"inline-flex max-w-full flex-wrap items-center gap-spacing-4\">\n {i > 0 && <Comparison>,</Comparison>}\n <ScreeningFormula\n node={child}\n onChange={(next) => replaceChild(i, next)}\n onDelete={() => removeChild(i)}\n getDropLeaf={getDropLeaf}\n catalog={catalog}\n variant={variant}\n inIf={isIf || inIf}\n />\n </span>\n ))}\n {isIf ? (\n /* IF( 조건 , 참점수 , 거짓점수 ) — 조건 추가(+) 불가(Figma 8537:2175).\n 단 조건이 삭제돼 비면 '조건 추가' 존을 상시 노출(X 없음) — IF는 조건이 필수(2026-07-16 지시) */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [stripPointsScore(leaf)] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {ifScoreInput('trueScore', '1')}\n <Comparison>,</Comparison>\n {ifScoreInput('falseScore', '0')}\n </>\n ) : isCap ? (\n /* CAPMAX/CAPMIN( 조건 , 제한점수 ) — 조건 추가(+) 불가(Figma 8544:36274), 비면 존 상시 노출 */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {capInput}\n </>\n ) : isByScore ? (\n /* FN( 조건 , 계산식 , 비교값 ) — 조건 추가(+) 불가(Figma 8544:36274), 비면 존 상시 노출 */\n <>\n {node.children.length === 0 && (\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => patch({ children: [leaf] })}\n closable={false}\n />\n )}\n <Comparison>,</Comparison>\n {compareOpSelect}\n <Comparison>,</Comparison>\n {compareScoreInput}\n </>\n ) : (\n <>\n {/* 조건 추가 존 — + 클릭마다 하나씩 추가되고, 드롭되거나 X를 누르면 그 존만 사라진다 */}\n {addZoneIds.map((zoneId, zi) => (\n <span key={zoneId} className=\"inline-flex items-center gap-spacing-4\">\n {(node.children.length > 0 || zi > 0) && <Comparison>,</Comparison>}\n <FormulaAddZone\n getDropLeaf={getDropLeaf}\n onDropLeaf={(leaf) => {\n patch({ children: [...node.children, leaf] });\n removeAddZone(zoneId);\n }}\n onClose={() => removeAddZone(zoneId)}\n />\n </span>\n ))}\n <Button variant=\"ghost\" size=\"18\" icon={Plus} aria-label=\"조건 추가\" onClick={appendAddZone} />\n </>\n )}\n <Paren>)</Paren>\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </FormulaShell>\n );\n}\n\n// 적합/부적합 라벨 — scoreType이 fit/unfit인 leaf는 점수 입력 대신 글자 표시\nconst SCORE_TEXT: Record<string, string> = { fit: '적합', unfit: '부적합' };\n\n// ── 설명 코멘트 요약(2026-07-15) — 표시는 최상위 수식에서만 하나. 그룹은 자식 요약을 합쳐 한 문장으로. ──\n// 형식: \"{조건명}가 {값}일 경우 가점 N점 부여\" (2026-07-15 문구 지정 — 조사 이/가는 받침 자동 판별)\nconst subjectParticle = (word: string) => {\n const ch = word?.charCodeAt(word.length - 1) ?? 0;\n if (ch < 0xac00 || ch > 0xd7a3) return '가'; // 한글 음절이 아니면 기본 '가'\n return (ch - 0xac00) % 28 > 0 ? '이' : '가';\n};\nfunction leafSummary(node: FormulaLeafNode, catalog?: FormulaCatalog | null) {\n const condMeta =\n catalog?.conditionMetaByCriteria?.[node.criteria ?? ''] ??\n node.conditionMetaByCriteria?.[node.criteria ?? ''] ?? { tabs: [] };\n // 묶음 leaf(복수 조건) — \"{조건명} {묶음라벨} N개 조건의 점수가 합산되어 계산됨\"(Figma 8535:11956)\n if (node.items?.length) {\n const criteriaLabel = optLabel(catalog?.criteriaOptions ?? node.criteriaOptions ?? [], node.criteria);\n if (!criteriaLabel) return null;\n const tabMeta = condMeta.tabs?.find((t) => t.value === node.valueTab);\n const bundleLabel = tabMeta?.bundleLabel ?? tabMeta?.label ?? '';\n return `${criteriaLabel} ${bundleLabel} ${node.items.length}개 조건의 점수가 합산되어 계산됨`;\n }\n const valueOpts =\n catalog?.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptions ??\n [];\n const criteriaOpts = catalog?.criteriaOptions ?? node.criteriaOptions ?? [];\n const criteriaLabel = optLabel(criteriaOpts, node.criteria);\n const tabLabel = condMeta.tabs?.find((t) => t.value === node.valueTab)?.label;\n const valueLabel = node.value != null ? optLabel(valueOpts, node.value) : tabLabel;\n const scoreText = SCORE_TEXT[node.scoreType ?? ''];\n const scoreDesc = scoreText\n ? `${scoreText} 처리`\n : node.points\n ? String(node.points).startsWith('-')\n ? `감점 ${String(node.points).slice(1)}점 부여`\n : `가점 ${node.points}점 부여`\n : null;\n // 값이 정해지면 설명 표시(점수는 정해진 경우에만 문장에 이어 붙음).\n // 탭 라벨(대상 등)은 생략해도 이해에 문제없어 값만 쓴다 — 옵션 없는 탭(미보유 등)은 탭 라벨이 곧 값.\n if (!criteriaLabel || !valueLabel) return null;\n return `${criteriaLabel}${subjectParticle(criteriaLabel)} ${valueLabel}일 경우${scoreDesc ? ` ${scoreDesc}` : ''}`;\n}\nfunction nodeSummary(node: FormulaNode, catalog?: FormulaCatalog | null): string | null {\n if (node.kind === 'group') {\n const parts = node.children.map((c) => nodeSummary(c, catalog)).filter(Boolean);\n const joined = parts.length ? parts.join(', ') : null;\n if (node.fn === 'IF') {\n // IF( 조건, 참, 거짓 ) — 조건이 참이면 참점수, 아니면 거짓점수(Figma 8537:2175)\n const ifPart = `조건이 참이면 ${node.trueScore ?? '1'}점, 아니면 ${node.falseScore ?? '0'}점 부여`;\n return joined ? `${joined} — ${ifPart}` : ifPart;\n }\n if ((node.fn === 'CAPMAX' || node.fn === 'CAPMIN') && node.capScore) {\n // CAPMAX/CAPMIN( 조건, 제한 ) — 합산 점수의 상한/하한 제한(Figma 8544:36274)\n const capPart = `점수를 ${node.fn === 'CAPMAX' ? '최대' : '최소'} ${node.capScore}점으로 제한`;\n return joined ? `${joined} — ${capPart}` : capPart;\n }\n if ((node.fn === 'FITBYSCORE' || node.fn === 'UNFITBYSCORE') && node.compareScore) {\n // FN( 조건, 계산식, 비교값 ) — 점수가 비교식을 만족하면 적합/부적합 처리(Figma 8544:36274)\n const rel = COMPARE_OP_TEXT[node.compareOp ?? '>='];\n const byPart = `점수가 ${node.compareScore}점${rel ? ` ${rel}` : ''}이면 ${\n node.fn === 'FITBYSCORE' ? '적합' : '부적합'\n } 처리`;\n return joined ? `${joined} — ${byPart}` : byPart;\n }\n return joined;\n }\n return leafSummary(node, catalog);\n}\n\n// 칩 모양 팝오버 트리거 — SelectChip 트리거와 동일한 비주얼(chip-* 토큰), 클릭 토글은 Popover가 담당\nfunction ChipTrigger({ children, open }: { children?: ReactNode; open?: boolean }) {\n return (\n <span\n // 포커스 표시=hover 디자인(2026-07-16) — 포커스는 래퍼(Popover, group/chipstop)가 받으므로\n // 키보드(focus-visible)·체인(data-chain-focus) 상태를 group 변형으로 받아 hover 색을 켠다\n className={`inline-flex min-w-0 cursor-pointer items-center gap-spacing-3 rounded-round-4 border pl-spacing-4 pr-spacing-3 py-spacing-1 text-12 font-normal transition-colors ${CHIP_COLOR_CLASS.gray} group-focus-visible/chipstop:text-chip-gray-hover-text group-focus-visible/chipstop:border-chip-gray-hover-line group-focus-visible/chipstop:bg-chip-gray-hover-bg group-data-[chain-focus]/chipstop:text-chip-gray-hover-text group-data-[chain-focus]/chipstop:border-chip-gray-hover-line group-data-[chain-focus]/chipstop:bg-chip-gray-hover-bg`}\n >\n <span className=\"truncate\">{children}</span>\n <ChevronDown\n size={12}\n strokeWidth={1.8}\n className={`pointer-events-none shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}\n />\n </span>\n );\n}\n\n// leaf 수식 — IF( 기준 == 값 , 점수 ) / compact = IF( 요약칩 )\nfunction FormulaLeaf({ node, root, checked, onCheckChange, onChange, onDelete, catalog, variant = 'formula', inIf = false, className }: Omit<ScreeningFormulaProps, 'node'> & { node: FormulaLeafNode }) {\n const patch = (p: Partial<FormulaLeafNode>) => onChange?.({ ...node, ...p });\n const startRef = useRef<HTMLSpanElement | null>(null); // 이 수식의 체인 시작점(조건 종류 칩)\n const isCompact = node.display === 'compact';\n // 묶음 leaf(복수 조건) — 항목 배열이 있으면 값·점수 대신 [묶음 칩]과 [개별설정]으로 표기\n const isBundle = !!node.items?.length;\n const [indivOpen, setIndivOpen] = useState(false);\n const [critOpen, setCritOpen] = useState(false); // 묶음 leaf 조건명 칩 팝오버(카드 조건 셀렉트와 동일 메뉴)\n // 묶음 조건 저장(조건명 칩·묶음 칩 공용) — 카드의 조건 셀렉트와 동일한 의미:\n // 지정(items)=항목 갱신 후 개별설정으로, 보유/미보유=묶음 해제(일반 leaf) 후 점수 팝오버로\n // 값 칩(일반 leaf)에서도 같은 메뉴를 쓰므로, 지정(items) 저장 시 일반 leaf가 묶음 수식으로 전환된다\n // 지정(items) 저장=묶음 leaf로, 보유/미보유 저장=일반 leaf로 — 같은 leaf 안에서만 전환된다\n // (2026-07-16 개정: 함수 그룹 자동 감싸기 취소 — 드롭 전/후 설정 모두 묶음 leaf 한 줄로 동일)\n const saveBundleCondition = (v: ScreeningConditionValue, close: () => void) => {\n if (v.items) {\n onChange?.({ ...node, valueTab: v.tab ?? null, value: null, items: v.items });\n close();\n setTimeout(() => document.querySelector<HTMLElement>(`[data-indiv-for=\"${node.id}\"] input`)?.focus(), 0);\n } else {\n onChange?.({\n ...node,\n valueTab: v.tab ?? null,\n value: v.option ?? null,\n items: undefined,\n individual: undefined,\n });\n close();\n setScoreOpen(true);\n }\n };\n const scoreText = SCORE_TEXT[node.scoreType ?? '']; // '적합'|'부적합'|undefined\n // 점수 인풋 클릭 → 카드의 가/감점 셀렉트와 동일한 설정 팝오버(ScoreSettingMenu)\n const [scoreOpen, setScoreOpen] = useState(false);\n const scoreType =\n node.scoreType ?? (node.points ? (String(node.points).startsWith('-') ? 'minus' : 'plus') : null);\n // 값 칩 클릭 → 카드의 조건 셀렉트와 동일한 설정 팝오버(ConditionSettingMenu — 탭+라디오).\n // 조건(기준) 칩은 조건 종류 선택(SelectChip). 기준이 바뀌면 그 카드의 구성을 따라간다.\n const [condOpen, setCondOpen] = useState(false);\n const condMeta = catalog?.conditionMetaByCriteria?.[node.criteria ?? ''] ??\n node.conditionMetaByCriteria?.[node.criteria ?? ''] ?? {\n tabs: [],\n optionsByTab: {},\n options: node.valueOptions ?? [],\n };\n // 카탈로그(현재 카드 영역 라이브 목록) 우선, 없으면 leaf 스냅샷 폴백 — 카드 추가/삭제가 즉시 반영\n const criteriaOpts = catalog?.criteriaOptions ?? node.criteriaOptions ?? [];\n // 값 옵션 — 기준별 맵에서 선택된 기준을 따라간다(카탈로그 → 스냅샷 → 고정 valueOptions 순)\n const valueOpts =\n catalog?.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptionsByCriteria?.[node.criteria ?? ''] ??\n node.valueOptions ??\n [];\n // 복수 조건 카드면 조건 팝오버 폭도 카드와 동일하게 고정(351 — 2026-07-16 지시)\n // ⚠️ condMeta 선언 뒤에 있어야 한다 — 앞에서 참조하면 TDZ ReferenceError(드롭 에러 원인이었음)\n const hasMultiTab = !!condMeta.tabs?.some((t) => t.multiSelect);\n const condMenuWidth = hasMultiTab ? 351 : 'max-content';\n // 설명 코멘트 — 단독(최상위) 조건일 때만 자기 요약 표시. 그룹핑되면 그룹이 합쳐진 설명 하나를 담당.\n // 자연어 표기는 문장 자체가 설명이라 코멘트 생략.\n const leafComment = variant === 'natural' ? null : root ? leafSummary(node, catalog) : null;\n return (\n <FormulaShell family=\"conditional\" dense={!root} comment={leafComment} className={className}>\n {root && onCheckChange && (\n <Checkbox checked={checked} onChange={(e) => onCheckChange(e.target.checked)} />\n )}\n {isCompact ? (\n // compact 요약칩 — 파란 텍스트·연파랑 라인. 클릭하면 full로 펼침\n <button\n type=\"button\"\n onClick={() => patch({ display: 'full' })}\n className=\"inline-flex min-w-0 items-center gap-spacing-3 rounded-round-4 border border-formula-compact-chip-line bg-formula-default-bg py-spacing-1 pl-spacing-4 pr-spacing-3 text-12 text-formula-compact-chip-text\"\n >\n <span className=\"truncate\">\n {[\n optLabel(criteriaOpts, node.criteria) || optLabel(node.criteriaOptions, node.criteria),\n isBundle ? `${node.items?.length}개 조건 묶음` : optLabel(valueOpts, node.value),\n isBundle ? null : (scoreText ?? node.points),\n ]\n .filter(Boolean)\n .join(', ')}\n </span>\n <ChevronDown size={12} strokeWidth={1.8} className=\"shrink-0\" />\n </button>\n ) : (\n <>\n {/* 조건(기준) 칩 — 조건 종류(왼쪽 카드 리스트) 선택 + 검색창. 기준이 바뀌면 값·탭 리셋.\n Tab은 하이잭하지 않는다 — 브라우저 기본 순서로 값 칩·점수 인풋에 포커스가 이동(2026-07-15) */}\n <span\n ref={startRef}\n data-formula-chain\n className=\"inline-flex min-w-0 items-center\" /* 인라인 baseline 정렬로 칩이 내려앉는 것 방지 */\n >\n {isBundle ? (\n /* 묶음 leaf의 조건명 칩 — 복수 조건 카드의 조건 셀렉트와 동일한 팝오버(2026-07-16 지시) */\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, critOpen, () => setCritOpen(true))}\n menuWidth={condMenuWidth}\n open={critOpen}\n onOpenChange={setCritOpen}\n trigger={\n <ChipTrigger open={critOpen}>\n {optLabel(criteriaOpts, node.criteria) ||\n optLabel(node.criteriaOptions, node.criteria) ||\n '조건'}\n </ChipTrigger>\n }\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current);\n }}\n />\n )}\n </Popover>\n ) : (\n <SelectChip\n options={criteriaOpts}\n value={node.criteria}\n onChange={(e) =>\n patch({\n criteria: e.target.value,\n value: null,\n valueTab: null,\n items: undefined, // 조건 종류가 바뀌면 묶음(복수 조건) 정보도 리셋\n individual: undefined,\n })\n }\n placeholder=\"조건\"\n searchable\n searchPlaceholder=\"조건 카드 검색\"\n menuWidth={160}\n tabOpens\n />\n )}\n </span>\n {variant === 'natural' ? <NaturalText>이(가)</NaturalText> : <Comparison>=</Comparison>}\n {isBundle ? (\n (() => {\n const tabMeta = condMeta.tabs?.find((t) => t.value === node.valueTab);\n const bundleText = `${tabMeta?.bundleLabel ?? tabMeta?.label ?? ''}, ${node.items?.length}개 조건 묶음`;\n const modalItems = (node.items ?? []).map((v) => ({\n value: v,\n label: optLabel(valueOpts, v) || String(v),\n }));\n return (\n <>\n {/* 묶음 칩 — 클릭하면 카드와 동일한 복수 조건 메뉴(탭+다중 선택)로 지정 항목 편집 */}\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => setCondOpen(true))}\n menuWidth={condMenuWidth}\n open={condOpen}\n onOpenChange={setCondOpen}\n trigger={<ChipTrigger open={condOpen}>{bundleText}</ChipTrigger>}\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current);\n }}\n />\n )}\n </Popover>\n {variant === 'natural' ? <NaturalText>이면,</NaturalText> : <Comparison>→</Comparison>}\n {/* 개별설정 — 점수 인풋 자리의 solid 인풋형 트리거(Figma 8535:11956), 클릭=항목별 점수 모달 */}\n {/* 개별설정 트리거 — data-indiv-for: 묶음 전환(리마운트) 후 포커스 이동용 앵커 */}\n <span data-indiv-for={node.id} className=\"inline-flex shrink-0 items-center\">\n <Input\n size=\"22\"\n width={68}\n value=\"개별설정\"\n readOnly\n className=\"cursor-pointer ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 has-[:focus-visible]:ring-2\"\n inputProps={{\n style: { cursor: 'pointer' },\n onClick: () => setIndivOpen(true),\n onKeyDown: (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setIndivOpen(true);\n }\n },\n }}\n />\n </span>\n {indivOpen && (\n <ScreeningIndividualSettingModal\n items={modalItems}\n value={node.individual ?? null}\n fitnessOnly={inIf} /* IF 안 조건 — 적합/부적합만 */\n onClose={() => setIndivOpen(false)}\n onConfirm={(v) => {\n patch({ individual: v });\n setIndivOpen(false);\n focusNextChainStop(startRef.current); // 묶음 설정 완료 — 다음 수식으로\n }}\n />\n )}\n </>\n );\n })()\n ) : (\n <>\n {/* 값 칩 — 클릭하면 카드의 조건 셀렉트와 동일한 설정 팝오버(탭+라디오), 저장 시 반영 */}\n <Popover\n className=\"group/chipstop min-w-0 rounded-round-4 focus:outline-none\"\n tabIndex={0}\n onKeyDown={(e) => chainTriggerKey(e, condOpen, () => setCondOpen(true))}\n menuWidth={condMenuWidth}\n open={condOpen}\n onOpenChange={setCondOpen}\n trigger={\n <ChipTrigger open={condOpen}>\n {(() => {\n // 표시 형식은 카드의 조건 셀렉트와 동일 — 탭이 있으면 \"대상, 심한 장애인\"처럼 탭+옵션\n const tabLabel = condMeta.tabs.find((t) => t.value === node.valueTab)?.label;\n if (node.value != null) {\n const opt = optLabel(valueOpts, node.value);\n return (tabLabel ? `${tabLabel}, ${opt}` : opt) || '조건';\n }\n return tabLabel || '조건'; // disableOptions 탭(미보유 등)은 탭 라벨만\n })()}\n </ChipTrigger>\n }\n >\n {(close) => (\n <ConditionSettingMenu\n tabs={condMeta.tabs}\n optionsByTab={condMeta.optionsByTab}\n options={condMeta.options}\n initialTab={node.valueTab}\n initialOption={node.value}\n initialItems={node.items}\n onCancel={close}\n onSave={(v) => saveBundleCondition(v, close)} /* 카드와 동일 메뉴 — 지정 저장=묶음 전환(2026-07-16) */\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current); // 값 미선택 이탈 — 다음 수식으로\n }}\n />\n )}\n </Popover>\n {variant === 'natural' ? <NaturalText>이면,</NaturalText> : <Comparison>→</Comparison>}\n {/* 점수 란 — Input 형태 유지(적합/부적합은 글자 표시). 클릭하면 카드와 동일한\n 가/감점 설정 팝오버(ScoreSettingMenu)가 열리고 저장 시에만 반영된다 */}\n <Popover\n className=\"shrink-0\"\n data-score-for={node.id}\n /* tabIndex 없음 — 포커스는 안의 readOnly 인풋이 받는다(체인 keydown만 공유) */\n onKeyDown={(e) => chainTriggerKey(e, scoreOpen, () => setScoreOpen(true))}\n menuWidth={200}\n open={scoreOpen}\n onOpenChange={setScoreOpen}\n trigger={\n <Input\n size=\"22\"\n width={scoreText ? 56 : 48}\n value={\n scoreText ??\n (node.points\n ? `${String(node.points).startsWith('-') ? '' : '+'}${node.points}점`\n : '')\n }\n readOnly\n placeholder=\"값\"\n // 팝오버 트리거 — 포인터 커서 + hover 링(readOnly라 Input 기본 hover가 꺼져 있어 직접 부여)\n className=\"cursor-pointer ring-inset ring-text-field-hover-line transition-shadow hover:ring-2 has-[:focus-visible]:ring-2\"\n inputProps={{ style: { cursor: 'pointer' } }}\n />\n }\n >\n {(close) => (\n <ScoreSettingMenu\n initialType={scoreType}\n initialPoints={String(node.points ?? '').replace(/^-/, '')}\n disablePoints={inIf} /* IF 안 조건 — 적합/부적합만 */\n onCancel={close}\n onSave={({ type, points }) => {\n const raw = (points ?? '').replace(/^-/, '');\n patch({\n scoreType: type as FormulaLeafNode['scoreType'],\n points: type === 'minus' && raw ? `-${raw}` : type === 'plus' ? raw : '',\n });\n close();\n // 수식 완료 — 체인 규약대로 다음 수식의 시작 필드로 포커스 이동\n focusNextChainStop(startRef.current);\n }}\n onSkip={() => {\n close();\n focusNextChainStop(startRef.current); // 미선택 이탈 — 다음 수식으로\n }}\n />\n )}\n </Popover>\n {variant === 'natural' && <NaturalText>{scoreText ? '처리' : '부여'}</NaturalText>}\n </>\n )}\n </>\n )}\n <Button variant=\"ghost\" size=\"18\" icon={X} aria-label=\"수식 삭제\" onClick={onDelete} />\n </FormulaShell>\n );\n}\n","// ScreeningBuilderTemplate — 스크리닝 조건 빌더\n// (Figma ConditionCard 8243:88380 + 플로우 8389:117585 + Formula 8383:112161 · 그룹 8384:114161/114251)\n// 지원자 정보를 조건화하는 카드(ScreeningConditionCard)와 수식(ScreeningFormula — 둘 다 내부용)을 관리한다:\n// 좌측 카드 목록 —\n// - 카드마다 조건(tab radio list)·가/감점(radio list)을 세팅, grip 드래그 앤 드롭 순서 변경, 삭제\n// 우측 수식 영역 —\n// - 카드를 수식 영역에 드롭하면 수식이 추가된다(카드는 목록에 남아 반복 드롭 가능).\n// 일반 카드=IF leaf, 복수 조건 카드(지정 items)=묶음 leaf 한 줄(그룹 감싸기 없음 — 2026-07-16 개정)\n// - 수식 체크박스 선택(1개 이상) + 함수 선택 + '함수 적용' 클릭 → FN( 수식, … ) 그룹으로 묶인다\n// - 그룹도 체크박스를 가지므로 다른 수식·그룹과 다시 그룹핑될 수 있다(중첩)\n// 데이터 계약(JobPositionTemplate과 동일 철학):\n// - defaultCards: mount 1회 주입(리셋은 key 리마운트)\n// - onChange(cards) / onFormulaChange(formulas): 변경 시 스냅샷 반출\n// - ref: getCards() · addCard(card) · getFormulas()\nimport { useState, useRef, forwardRef, useImperativeHandle } from 'react';\nimport type { ComponentPropsWithoutRef } from 'react';\nimport { Plus, FolderInput, FolderOutput, X } from 'lucide-react';\nimport { ScrollArea } from './ScrollArea';\nimport { ToolBar, ToolBarDivider } from './ToolBar';\nimport { Accordion, AccordionItem } from './Accordion';\nimport { Popover } from './Popover';\nimport {\n PopoverMenu,\n PopoverMenuColumns,\n PopoverMenuColumn,\n PopoverMenuSection,\n} from './PopoverMenu';\nimport { List } from './List';\nimport { SegmentedTabs } from './SegmentedTabs';\nimport { ScreeningConditionCard } from './ScreeningConditionCard';\nimport { ScreeningFormula } from './ScreeningFormula';\nimport { FORMULA_GROUP_FUNCTIONS, hasPointsScore } from './formulaFunctions';\nimport type {\n ConditionTab,\n FormulaCatalog,\n FormulaLeafNode,\n FormulaNode,\n IndividualSetting,\n OptionItem,\n} from './formulaFunctions';\nimport { Select } from './Select';\nimport { Button } from './Button';\n\n// 조건 카드 데이터 — { id, name, conditionTabs?, conditionOptionsByTab?, conditionOptions?, condition?, score? }\nexport interface ScreeningCard {\n id: string;\n name: string;\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>;\n conditionOptions?: OptionItem[];\n conditionPlaceholder?: string;\n condition?: { tab?: string | null; option?: string | null; items?: string[] } | null;\n score?: { type: string; points?: string } | IndividualSetting | null;\n}\n// onChange 스냅샷 — 카드에서 id/name/condition/score만 추린 것\ntype ScreeningCardSnapshot = Pick<ScreeningCard, 'id' | 'name' | 'condition' | 'score'>;\n\n// 수식 영역(존) — 존마다 독립된 수식 트리\ninterface ScreeningZone {\n id: string;\n name: string;\n formulas: FormulaNode[];\n}\n\n// '추가' rich menu 항목/섹션 — [ [ { title, items: [...] } ] ] (컬럼 배열 > 섹션 배열 > 항목)\ninterface ScreeningCardMenuItem {\n name: string;\n conditionTabs?: ConditionTab[];\n conditionOptionsByTab?: Record<string, OptionItem[]>;\n conditionOptions?: OptionItem[];\n conditionPlaceholder?: string;\n}\ninterface ScreeningCardMenuSection {\n title: string;\n items: ScreeningCardMenuItem[];\n}\n\n// ref 핸들 — getCards() · addCard(card) · getFormulas()\nexport interface ScreeningBuilderTemplateHandle {\n getCards: () => ScreeningCardSnapshot[];\n addCard: (card: ScreeningCard) => void;\n getFormulas: () => ScreeningZone[];\n}\n\ninterface ScreeningBuilderTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n defaultCards?: ScreeningCard[]; // 초기 카드 목록(mount 1회) — { id, name, conditionTabs?, conditionOptionsByTab?, conditionOptions?, condition?, score? }\n onChange?: (cards: ScreeningCardSnapshot[]) => void; // 카드 순서/값/삭제 변경 시 스냅샷\n onFormulaChange?: (formulas: ScreeningZone[]) => void; // 수식 트리 변경 시 스냅샷\n cardWidth?: number | string; // 카드 너비: 숫자(px) | CSS 길이 | 'fill'\n formulaArea?: boolean; // 수식 영역 표시 여부 — false면 카드 목록만\n onAddCard?: () => void; // 카드 영역 '추가' 버튼 클릭 — cardMenu 미지정 시에만 사용(없으면 빈 카드 추가)\n // cardMenu — '추가' 버튼의 rich menu 데이터(컬럼 배열 > 섹션 배열 > 항목).\n // 항목 클릭 시 조건 카드가 맨 위에 등록되고,\n // 팝오버는 계속 열려 있어 연속 추가 가능(배경 클릭으로만 닫힘)\n cardMenu?: ScreeningCardMenuSection[][];\n boxHeight?: number; // 빌더 박스 고정 높이(px) — 내용이 넘치면 내부 스크롤(ScrollArea)\n}\n\n// HMR-safe id — 모듈 카운터는 HMR 리셋으로 중복되므로 시간+랜덤(JobPositionTemplate 판례)\nconst newId = (prefix: string) =>\n `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;\n\n// 카드의 조건 옵션 평면화 — 탭이 있으면 탭별 옵션을 합친다(수식 값 칩 옵션용)\nconst flatOptions = (card: ScreeningCard) =>\n card.conditionTabs?.length\n ? Object.values(card.conditionOptionsByTab ?? {}).flat()\n : (card.conditionOptions ?? []);\n\nexport const ScreeningBuilderTemplate = forwardRef<\n ScreeningBuilderTemplateHandle,\n ScreeningBuilderTemplateProps\n>(function ScreeningBuilderTemplate(\n {\n defaultCards = [],\n onChange,\n onFormulaChange,\n cardWidth = 375,\n formulaArea = true,\n onAddCard,\n cardMenu = [],\n boxHeight = 800,\n className = '',\n ...props\n },\n ref,\n) {\n // defaultCards는 mount 1회 시드(uncontrolled) — 외부에서 리셋하려면 key를 바꿔 리마운트한다.\n const [cards, setCards] = useState<ScreeningCard[]>(() =>\n defaultCards.map((c) => ({ condition: null, score: null, ...c })),\n );\n\n const snapshot = (list: ScreeningCard[]): ScreeningCardSnapshot[] =>\n list.map(({ id, name, condition, score }) => ({ id, name, condition, score }));\n const emit = (next: ScreeningCard[]) => {\n setCards(next);\n onChange?.(snapshot(next));\n };\n\n // 빌더 모드 — '수식'(드래그·그룹핑 빌더) / '자연어'(추후 자연어 생성 방식 — 현재 빈 영역)\n const [builderMode, setBuilderMode] = useState<'formula' | 'natural'>('formula');\n // 수식 영역(존) 목록 — 존마다 아코디언 리스트 하나 + 독립된 수식 트리. '수식 영역 추가'로 늘린다.\n const [zones, setZones] = useState<ScreeningZone[]>(() => [\n { id: newId('zone'), name: '수식 영역 1', formulas: [] },\n ]);\n const [checkedIds, setCheckedIds] = useState<string[]>([]); // 그룹핑 대상 체크(모든 존 공통 — id는 유일)\n const [groupFn, setGroupFn] = useState<string | null>(null);\n const emitZones = (next: ScreeningZone[]) => {\n setZones(next);\n onFormulaChange?.(next.map((z) => ({ id: z.id, name: z.name, formulas: z.formulas })));\n };\n const patchZone = (zoneId: string, fn: (formulas: FormulaNode[]) => FormulaNode[]) =>\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, formulas: fn(z.formulas) } : z)));\n const addZone = () =>\n emitZones([{ id: newId('zone'), name: `수식 영역 ${zones.length + 1}`, formulas: [] }, ...zones]); // 새 존은 맨 위에\n const renameZone = (zoneId: string, name: string) =>\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, name } : z)));\n const removeZone = (zoneId: string) => {\n const target = zones.find((z) => z.id === zoneId);\n const ids = target ? target.formulas.map((f) => f.id) : [];\n const rest = zones.filter((z) => z.id !== zoneId);\n // 마지막 존을 삭제하면(수식 세팅 상태에서만 가능) 빈 기본 존 하나로 대체 — 영역이 0개가 되지 않게\n emitZones(rest.length > 0 ? rest : [{ id: newId('zone'), name: '수식 영역 1', formulas: [] }]);\n setCheckedIds((prev) => prev.filter((x) => !ids.includes(x))); // 삭제된 존의 체크 정리\n };\n\n useImperativeHandle(ref, () => ({\n getCards: () => snapshot(cards),\n addCard: (card) => emit([{ condition: null, score: null, ...card }, ...cards]),\n getFormulas: () => zones.map((z) => ({ id: z.id, name: z.name, formulas: z.formulas })),\n }));\n\n const patchCard = (id: string, patch: Partial<ScreeningCard>) =>\n emit(cards.map((c) => (c.id === id ? { ...c, ...patch } : c)));\n const removeCard = (id: string) => emit(cards.filter((c) => c.id !== id));\n // 카드 영역 '추가' 버튼 — cardMenu가 있으면 rich menu 팝오버(아래), 없으면 onAddCard 위임/빈 카드 추가\n const handleAddCard = () => {\n if (onAddCard) return onAddCard();\n emit([{ id: newId('card'), name: '새 조건', condition: null, score: null }, ...cards]);\n };\n // rich menu 항목 → 조건 카드 등록(맨 위). 팝오버는 닫지 않아 연속 추가 가능.\n const addCardFromMenu = (item: ScreeningCardMenuItem) =>\n emit([\n {\n id: newId('card'),\n name: item.name,\n conditionTabs: item.conditionTabs,\n conditionOptionsByTab: item.conditionOptionsByTab,\n conditionOptions: item.conditionOptions,\n conditionPlaceholder: item.conditionPlaceholder,\n condition: null,\n score: null,\n },\n ...cards,\n ]);\n // rich menu 팝오버 내용 — 헤더 '추가'와 빈 상태 '조건 추가하기' 버튼이 공유\n const richMenuContent = cardMenu.length > 0 && (\n <PopoverMenu width=\"100%\">\n <PopoverMenuColumns>\n {cardMenu.map((column, ci) => (\n <PopoverMenuColumn key={ci}>\n {column.map((section, si) => (\n <PopoverMenuSection\n key={section.title}\n title={section.title}\n grow={si === column.length - 1}\n >\n {section.items.map((item) => (\n <List\n key={item.name}\n title={item.name}\n rightButton\n rightButtonIcon={Plus}\n rightButtonAriaLabel=\"추가\"\n onClick={() => addCardFromMenu(item)}\n onButtonClick={() => addCardFromMenu(item)}\n />\n ))}\n </PopoverMenuSection>\n ))}\n </PopoverMenuColumn>\n ))}\n </PopoverMenuColumns>\n </PopoverMenu>\n );\n\n // ── 카드 드래그 앤 드롭 — 수식 영역 드롭 전용. 카드끼리 순서 변경은 하지 않는다(2026-07-16 지시) ──\n const [dragId, setDragId] = useState<string | null>(null);\n const [dropHoverId, setDropHoverId] = useState<string | null>(null); // 드래그 중 하이라이트된 존 id\n const allowDragRef = useRef(false);\n\n // 카드 → IF leaf 수식 치환(드롭 시). 카드에 세팅된 조건/가점이 있으면 이어받는다.\n // 감점은 수식에서 음수로 표시(가점 10 → 10, 감점 10 → -10), 적합/부적합은 점수 란에 글자로 표시.\n // 조건 칩 옵션/구성은 드롭 시점의 왼쪽 카드 리스트 전체(추가된 카드 포함)를 싣는다.\n // 수식 카탈로그 — 조건 칩·값 칩이 참조하는 '현재 카드 영역' 라이브 목록(렌더마다 최신).\n // 카드가 추가/삭제되면 이미 만들어진 수식의 조건 칩 옵션에도 즉시 반영된다(2026-07-14 지시).\n const formulaCatalog: FormulaCatalog = {\n criteriaOptions: cards.map((c) => ({ value: c.id, label: c.name })),\n valueOptionsByCriteria: Object.fromEntries(cards.map((c) => [c.id, flatOptions(c)])),\n conditionMetaByCriteria: Object.fromEntries(\n cards.map((c) => [\n c.id,\n {\n tabs: c.conditionTabs ?? [],\n optionsByTab: c.conditionOptionsByTab ?? {},\n options: c.conditionOptions ?? [],\n },\n ]),\n ),\n };\n\n // leaf 공통 골격 — 스냅샷은 폴백용(카드가 나중에 삭제돼도 라벨·옵션 유지), 표시는 catalog(라이브) 우선\n const baseLeaf = (card: ScreeningCard): FormulaLeafNode => ({\n id: newId('formula'),\n kind: 'leaf',\n criteria: card.id,\n criteriaOptions: formulaCatalog.criteriaOptions,\n valueOptionsByCriteria: formulaCatalog.valueOptionsByCriteria,\n conditionMetaByCriteria: formulaCatalog.conditionMetaByCriteria,\n });\n const signedPoints = (type: string | null, points?: string) => {\n const raw = (points ?? '').replace(/^-/, '');\n return type === 'minus' && raw ? `-${raw}` : type === 'plus' ? raw : '';\n };\n const cardToLeaf = (card: ScreeningCard): FormulaLeafNode => {\n const type = card.score?.type ?? null;\n return {\n ...baseLeaf(card),\n value: card.condition?.option ?? null,\n valueTab: card.condition?.tab ?? null,\n points: signedPoints(type, (card.score as { points?: string } | null)?.points),\n scoreType: type as FormulaLeafNode['scoreType'], // 'fit'|'unfit'이면 점수 란에 적합/부적합 텍스트 표시\n };\n };\n // 복수 조건(지정) 카드 → '묶음 leaf'로 치환(2026-07-16 개정 — 함수 그룹 감싸기 취소, 단수 수식과 동일 골격):\n // [조건] = [지정보유, N개 조건 묶음] → [개별설정] 한 줄(조건이 100개여도 한 줄).\n // 항목별 점수·적용 함수는 leaf의 individual(개별설정 값)에 담는다. 그룹핑은 일반 수식처럼 툴바로.\n const cardToNode = (card: ScreeningCard): FormulaNode => {\n const items = card.condition?.items;\n if (items?.length) {\n return {\n ...baseLeaf(card),\n valueTab: card.condition?.tab ?? null,\n items: [...items], // 묶음 leaf 마커 — 지정된 항목 배열\n individual: card.score?.type === 'individual' ? (card.score as IndividualSetting) : null,\n };\n }\n return cardToLeaf(card);\n };\n // 카드는 드롭해도 목록에 남는다 — 같은 조건을 여러 번 드롭해 수식을 계속 추가할 수 있다(2026-07-16 지시)\n const dropCardToZone = (zoneId: string) => {\n const card = cards.find((c) => c.id === dragId);\n if (!card) return;\n patchZone(zoneId, (fs) => [...fs, cardToNode(card)]);\n };\n // 그룹 '조건 추가' 드롭 존용 — 드래그 중인 카드의 노드(없으면 null).\n // 복수 조건 카드는 여기서도 함수 그룹으로 들어간다(그룹 안 그룹 — 모델이 중첩 지원)\n const getDropLeaf = () => {\n const card = cards.find((c) => c.id === dragId);\n return card ? cardToNode(card) : null;\n };\n\n // ── 툴바 액션 — 전부 '그 존'의 체크만 보고 동작한다(다른 존 체크에 영향 주고받지 않음, 2026-07-14 지시) ──\n const zoneCheckedIds = (zone: ScreeningZone) =>\n zone.formulas.filter((f) => checkedIds.includes(f.id)).map((f) => f.id);\n // IF 적용 가능 판정 — 조건 1개 + 적합/부적합(또는 미설정) 값일 때만(가점/감점·복수 체크는 불가, 2026-07-16)\n const ifAllowedInZone = (zone: ScreeningZone) => {\n const picked = zone.formulas.filter((f) => checkedIds.includes(f.id));\n return picked.length === 1 && !hasPointsScore(picked[0]);\n };\n // CAPMAX/CAPMIN·FITBYSCORE/UNFITBYSCORE 적용 가능 판정(2026-07-16) — 점수(가/감점)가 있어야 한다.\n // 가/감점이면 단독 조건 수식 하나에도 적용 가능(2026-07-16 지시), 여러 개면 전부 점수 값일 때.\n const capAllowedInZone = (zone: ScreeningZone) => {\n const picked = zone.formulas.filter((f) => checkedIds.includes(f.id));\n return picked.length >= 1 && picked.every(hasPointsScore);\n };\n // 함수별 선택 가능 여부 — 툴바 함수 셀렉트·함수 적용 버튼이 공유.\n // FITBYSCORE/UNFITBYSCORE도 점수가 반환되는 수식 전용이라 CAP과 같은 판정(2026-07-16)\n const fnAllowedInZone = (fn: string, zone: ScreeningZone) => {\n if (fn === 'IF') return ifAllowedInZone(zone);\n if (fn === 'CAPMAX' || fn === 'CAPMIN' || fn === 'FITBYSCORE' || fn === 'UNFITBYSCORE')\n return capAllowedInZone(zone);\n return true;\n };\n\n // 함수 적용 — 이 존에서 체크된 최상위 수식(1개 이상)을 FN(…)으로 묶는다\n // (2026-07-16 지시: 하나만 체크해도 적용 가능 — 단일 수식에 함수를 씌우는 용도)\n const groupCheckedInZone = (zoneId: string) => {\n const zone = zones.find((z) => z.id === zoneId);\n if (!zone || groupFn == null) return;\n if (!fnAllowedInZone(groupFn, zone)) return; // IF·CAPMAX·CAPMIN — 함수별 적용 조건 확인\n const pickedIds = zoneCheckedIds(zone);\n if (pickedIds.length < 1) return;\n const picked = zone.formulas.filter((f) => pickedIds.includes(f.id));\n const rest = zone.formulas.filter((f) => !pickedIds.includes(f.id));\n const insertAt = zone.formulas.findIndex((f) => pickedIds.includes(f.id)); // 첫 체크 위치에 삽입\n const next = rest.slice();\n next.splice(Math.min(insertAt, next.length), 0, {\n id: newId('group'),\n kind: 'group',\n fn: groupFn,\n children: picked,\n });\n emitZones(zones.map((z) => (z.id === zoneId ? { ...z, formulas: next } : z)));\n setCheckedIds((prev) => prev.filter((x) => !pickedIds.includes(x))); // 이 존의 체크만 해제\n setGroupFn(null); // 그룹핑 완료 — 함수 셀렉트를 플레이스홀더로 리셋(이전 선택 잔류 방지)\n };\n\n // 함수 그룹 해제 — 이 존에서 체크된 그룹을 풀어 자식을 그 자리에 펼친다(한 겹만, 중첩 자식 유지)\n const ungroupCheckedInZone = (zoneId: string) => {\n const zone = zones.find((z) => z.id === zoneId);\n if (!zone) return;\n const pickedIds = zoneCheckedIds(zone);\n emitZones(\n zones.map((z) =>\n z.id === zoneId\n ? {\n ...z,\n formulas: z.formulas.flatMap((f) =>\n pickedIds.includes(f.id) && f.kind === 'group' ? f.children : [f],\n ),\n }\n : z,\n ),\n );\n setCheckedIds((prev) => prev.filter((x) => !pickedIds.includes(x)));\n };\n\n return (\n // 빌더 전체(카드+수식 영역)를 감싸는 라인 박스 — 흰 bg·gray.100 라인, 고정 높이+내부 스크롤(규칙 9)\n <div\n style={{ height: boxHeight }}\n className={`relative overflow-hidden rounded-round-4 border border-builder-area-box-line bg-builder-area-box-bg ${className}`}\n {...props}\n >\n {/* 카드 영역(gray.25 bg) | 1px gray.100 구분선 | 빌더 영역 — 스크롤은 영역별 분리(각자 ScrollArea) */}\n <div className=\"flex h-full items-stretch\">\n {/* ── 카드 목록 — 독립 스크롤 ── */}\n <div\n className=\"relative shrink-0 border-r border-builder-area-box-line bg-builder-area-card-area-bg\"\n // 카드 사이 여백에서 놓아도 드롭 수락 — 스냅백 방지(카드 쪽 onDragOver와 동일)\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n >\n {/* 고정 헤더 배경 — 수식 영역 헤더와 동일 구조(60px: 위 42px 불투명 + 아래 18px 페이드).\n 카드 영역 배경(gray.25)에 맞춰 card-area-bg → 투명으로 페이드 */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-[60px] flex-col\">\n <div className=\"h-[42px] bg-builder-area-card-area-bg\" />\n <div className=\"h-[18px] bg-gradient-to-b from-builder-area-card-area-bg to-builder-area-header-fade-bottom\" />\n </div>\n {/* 카드 영역 고정 헤더 — 조건 리스트 타이틀 + 추가 라인 버튼(onAddCard 미지정 시 빈 카드 맨 위 추가) */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10\">\n <div className=\"pointer-events-auto flex items-center justify-between px-spacing-7 pb-spacing-6 pt-spacing-7\">\n <span className=\"text-15 font-semibold text-font-icon-5\">조건 리스트</span>\n {cardMenu.length > 0 ? (\n /* rich menu — 항목/+ 클릭 시 카드 등록(팝오버 유지), 배경 클릭으로만 닫힘 */\n <Popover\n menuWidth={804}\n trigger={\n <Button variant=\"line\" size=\"32\" leftIcon={Plus}>\n 추가\n </Button>\n }\n >\n {richMenuContent}\n </Popover>\n ) : (\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={handleAddCard}>\n 추가\n </Button>\n )}\n </div>\n </div>\n <ScrollArea maxHeight={boxHeight - 2 /* 상하 보더 제외 */}>\n {/* 스페이서 — 콘텐츠가 헤더 아래로 지나가며 페이드, thumb 트랙도 헤더 아래에서 시작 */}\n <div data-scroll-sticky-top className=\"h-[60px]\" />\n <div className=\"flex flex-col gap-spacing-6 px-spacing-7 pb-spacing-7\">\n {/* 빈 상태 — 등록된 조건이 없을 때 카피+조건 추가하기 버튼을 영역 가운데 정렬(간격 8) */}\n {cards.length === 0 && (\n <div\n style={{\n width: typeof cardWidth === 'number' ? cardWidth : undefined,\n minHeight: boxHeight - 2 - 60 - 16 /* 보더·헤더·하단 패딩 제외한 가시 영역에 수직 중앙 */,\n }}\n className=\"flex flex-col items-center justify-center gap-spacing-5\"\n >\n <p className=\"text-14 text-font-icon-3\">등록된 조건이 없습니다</p>\n {cardMenu.length > 0 ? (\n <Popover\n menuWidth={804}\n trigger={\n <Button variant=\"line\" size=\"32\" leftIcon={Plus}>\n 조건 추가하기\n </Button>\n }\n >\n {richMenuContent}\n </Popover>\n ) : (\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={handleAddCard}>\n 조건 추가하기\n </Button>\n )}\n </div>\n )}\n {cards.map((card) => (\n <ScreeningConditionCard\n key={card.id}\n cardName={card.name}\n conditionTabs={card.conditionTabs ?? []}\n conditionOptionsByTab={card.conditionOptionsByTab ?? {}}\n conditionOptions={card.conditionOptions ?? []}\n conditionPlaceholder={card.conditionPlaceholder}\n conditionValue={card.condition}\n onConditionChange={(v) => {\n // 단일(가/감점)↔복수(개별설정) 조건 전환 시 기존 점수 설정은 무효 — 함께 리셋\n const wasMulti = card.score?.type === 'individual';\n const isMulti = !!v?.items?.length;\n patchCard(card.id, { condition: v, ...(wasMulti !== isMulti ? { score: null } : {}) });\n }}\n scoreValue={card.score}\n onScoreChange={(v) => patchCard(card.id, { score: v })}\n onDelete={() => removeCard(card.id)}\n width={cardWidth}\n dragging={dragId === card.id}\n dragHandleProps={{\n onMouseDown: () => {\n allowDragRef.current = true;\n },\n onMouseUp: () => {\n allowDragRef.current = false;\n },\n }}\n draggable\n onDragStart={(e) => {\n // grip 밖에서 시작한 드래그(텍스트 등)는 차단 — 핸들에서만 이동\n if (!allowDragRef.current) {\n e.preventDefault();\n return;\n }\n setDragId(card.id);\n e.dataTransfer.effectAllowed = 'move';\n try {\n e.dataTransfer.setData('text/plain', String(card.id));\n } catch {\n /* 일부 브라우저 setData 미지원 무시 */\n }\n }}\n onDragEnd={() => {\n setDragId(null);\n setDropHoverId(null);\n allowDragRef.current = false;\n }}\n onDragOver={(e) => {\n // 드래그 중엔 자기 자신 위 포함 항상 드롭 허용 — 미허용 상태로 놓으면 크롬 스냅백 재생\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n }}\n onDrop={(e) => e.preventDefault()}\n />\n ))}\n </div>\n </ScrollArea>\n </div>\n\n {/* ── 수식 영역 — 독립 스크롤. 존마다 드롭 영역, '수식 영역 추가'로 존을 늘린다 ── */}\n {formulaArea && (\n <div className=\"relative min-w-0 flex-1\">\n {/* 고정 헤더 — 세그먼트 탭·수식 영역 추가는 아래 스크롤과 무관하게 상단 고정.\n 배경(높이 60px): 위 70%는 흰색 불투명, 아래 30%만 흰→투명 페이드(2026-07-14 비율 지정).\n 스크롤 콘텐츠가 아래로 지나가며 사라진다. 오버레이는 클릭 통과(컨트롤만 수신).\n 그룹핑 액션은 체크 시 뜨는 플로팅 ToolBar로 일원화(2026-07-14) */}\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10 flex h-[60px] flex-col\">\n <div className=\"h-[42px] bg-builder-area-header-fade-top\" />\n <div className=\"h-[18px] bg-gradient-to-b from-builder-area-header-fade-top to-builder-area-header-fade-bottom\" />\n </div>\n <div className=\"pointer-events-none absolute inset-x-0 top-0 z-10\">\n {/* 컨트롤 행 높이 = 16 + 32 + 12 = 60px (배경·스크롤 스페이서와 동일) */}\n <div className=\"pointer-events-auto flex items-center px-spacing-7 pb-spacing-6 pt-spacing-7\">\n <div className=\"w-[180px]\">\n <SegmentedTabs\n items={[\n { value: 'formula', label: '수식 빌더' },\n { value: 'natural', label: '자연어 빌더' },\n ]}\n value={builderMode}\n onChange={setBuilderMode}\n />\n </div>\n <Button variant=\"line\" size=\"32\" leftIcon={Plus} onClick={addZone} className=\"ml-auto\">\n 수식 영역 추가\n </Button>\n </div>\n </div>\n {/* 스크롤 영역은 박스 전체 — 콘텐츠가 헤더(60px) 아래로 지나가며 그라디언트에 페이드.\n 스페이서에 data-scroll-sticky-top — 세로 thumb 트랙이 헤더(페이드 영역) 아래에서 시작 */}\n <ScrollArea maxHeight={boxHeight - 2}>\n <div data-scroll-sticky-top className=\"h-[60px]\" />\n <div className=\"flex flex-col gap-spacing-5 px-spacing-7 pb-spacing-7\">\n {/* 존 = 아코디언 리스트(2026-07-14 전환) — 이름 편집·삭제는 AccordionItem 옵션,\n 바디에 수식 UI + 드롭 스트립. 수식/자연어 빌더는 같은 존 데이터를 공유하고\n 표기만 variant로 갈린다(2026-07-15) */}\n <Accordion>\n {zones.map((zone) => (\n <AccordionItem\n key={zone.id}\n title={zone.name}\n defaultOpen\n nameEditable\n onTitleChange={(t) => renameZone(zone.id, t)}\n deletable\n // 마지막 존은 비어 있을 때만 삭제 불가 — 수식이 세팅돼 있으면 삭제(=빈 기본 존으로 리셋) 가능\n deleteDisabled={zones.length <= 1 && zone.formulas.length === 0}\n onDelete={() => removeZone(zone.id)}\n >\n <div className=\"flex flex-col gap-spacing-5\">\n {zone.formulas.map((f, i) => (\n <ScreeningFormula\n key={f.id}\n node={f}\n root\n checked={checkedIds.includes(f.id)}\n onCheckChange={(on) =>\n setCheckedIds((prev) => (on ? [...prev, f.id] : prev.filter((x) => x !== f.id)))\n }\n onChange={(next) =>\n patchZone(zone.id, (fs) => fs.map((x, xi) => (xi === i ? next : x)))\n }\n onDelete={() => {\n patchZone(zone.id, (fs) => fs.filter((_, xi) => xi !== i));\n setCheckedIds((prev) => prev.filter((x) => x !== f.id));\n }}\n getDropLeaf={getDropLeaf}\n catalog={formulaCatalog}\n variant={builderMode === 'natural' ? 'natural' : 'formula'}\n />\n ))}\n {/* 드롭 스트립 — 카드를 끌어다 놓는 대상. 드래그 오버 시 hover 상태(bg·라인 진해짐).\n 이 존에 체크된 수식이 있으면 스트립 중앙에 플로팅 툴바(공통 ToolBar)가 뜬다. */}\n <div className=\"relative w-full\">\n <div\n onDragOver={(e) => {\n if (dragId == null) return;\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n setDropHoverId(zone.id);\n }}\n onDragLeave={(e) => {\n if (e.currentTarget.contains(e.relatedTarget as Node | null)) return;\n setDropHoverId((prev) => (prev === zone.id ? null : prev));\n }}\n onDrop={(e) => {\n e.preventDefault();\n setDropHoverId(null);\n dropCardToZone(zone.id);\n }}\n className={`flex h-[78px] w-full items-center justify-center gap-spacing-3 rounded-round-4 border border-dashed p-spacing-6 text-14 text-builder-area-add-default-text transition-colors ${\n dropHoverId === zone.id ||\n zone.formulas.some((f) => checkedIds.includes(f.id)) // 툴바 표시 중에도 드롭(hover) 상태 디자인\n ? 'border-builder-area-add-hover-outline bg-builder-area-add-hover-bg'\n : 'border-builder-area-add-default-outline bg-builder-area-add-default-bg'\n }`}\n >\n <Plus size={16} strokeWidth={1.8} className=\"shrink-0\" />\n 다른 조건을 여기에 끌어다 놓기\n </div>\n {/* 플로팅 툴바 — 이 존의 체크에만 반응(표시·활성·그룹핑·해제·선택 해제 전부 존 단위) */}\n {zoneCheckedIds(zone).length > 0 && (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center\">\n <ToolBar className=\"pointer-events-auto\">\n <Select\n width={200}\n options={FORMULA_GROUP_FUNCTIONS.map((f) => ({\n value: f,\n label: f,\n // IF=단일·적합/부적합 조건만 / CAPMAX·CAPMIN=점수 있는 묶음·그룹·복수 조건만\n disabled: !fnAllowedInZone(f, zone),\n }))}\n value={groupFn ?? ''}\n onChange={(e) => setGroupFn(e.target.value)}\n placeholder=\"함수 선택\"\n disabled={zoneCheckedIds(zone).length < 1}\n />\n <Button\n variant=\"ghost\"\n size=\"32\"\n leftIcon={FolderInput}\n disabled={\n groupFn == null ||\n zoneCheckedIds(zone).length < 1 ||\n !fnAllowedInZone(groupFn, zone) /* 선택 후 조건이 어긋나면 실행도 차단 */\n }\n onClick={() => groupCheckedInZone(zone.id)}\n >\n 함수 적용\n </Button>\n <ToolBarDivider />\n <Button\n variant=\"ghost\"\n size=\"32\"\n leftIcon={FolderOutput}\n disabled={\n !zone.formulas.some((f) => checkedIds.includes(f.id) && f.kind === 'group')\n }\n onClick={() => ungroupCheckedInZone(zone.id)}\n >\n 함수 해제\n </Button>\n <ToolBarDivider />\n <Button\n variant=\"ghost\"\n icon={X}\n aria-label=\"선택 해제\"\n showTooltip={false}\n onClick={() => {\n const ids = zoneCheckedIds(zone);\n setCheckedIds((prev) => prev.filter((x) => !ids.includes(x))); // 이 존만 해제\n }}\n />\n </ToolBar>\n </div>\n )}\n </div>\n </div>\n </AccordionItem>\n ))}\n </Accordion>\n </div>\n </ScrollArea>\n </div>\n )}\n </div>\n </div>\n );\n});\n","// SelectGroup — 셀렉트들을 일정 간격으로 묶는 컨테이너 (간격 규칙은 ButtonGroup과 동일)\n// 셀렉트들을 가로/세로로 모아 일관된 간격(기본 8px = spacing-5)으로 배치한다.\n// 간격은 등록된 spacing 토큰만 사용하도록 lookup으로 제한한다(임의값 금지).\nimport { Children, cloneElement, Fragment, isValidElement } from 'react';\nimport type { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';\nimport { Select } from './Select';\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 (기본)\n '6': 'gap-spacing-6', // 12px\n '7': 'gap-spacing-7', // 16px\n};\n\n// fragment·배열·조건부(`{x && <>…</>}`) children을 재귀적으로 평탄화해\n// 실제 자식 요소(Select 등)만 추려낸다. (toArray는 fragment를 펴지 않으므로 직접 처리)\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = [];\n Children.forEach(children, (child) => {\n if (isValidElement(child) && child.type === Fragment) {\n out.push(...flattenChildren((child.props as { children?: ReactNode }).children));\n } else if (child != null && typeof child !== 'boolean') {\n out.push(child);\n }\n });\n return out;\n}\n\n// ...props는 그룹 컨테이너(div)로 전달된다.\ninterface SelectGroupProps 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 SelectGroup({\n children,\n direction = 'horizontal', // 'horizontal' | 'vertical'\n gap = '5', // 간격 토큰 키 — 기본 '5'(8px)\n width = 'hug', // 'hug'(콘텐츠 폭) | 'fill'(부모 전체 폭 — 셀렉트들 균등 분할)\n className = '',\n ...props\n}: SelectGroupProps) {\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 // 최종 자식 배열 — 모든 요소에 안정 key를 직접 부여한다.\n // fill이면 각 자식을 flex-1 래퍼로 감싸 컨테이너 폭을 균등 분할하고,\n // Select 자식은 width=\"100%\"로 복제해 래퍼를 채운다(명시된 width는 존중).\n const rendered = flattenChildren(children).map((c, i) => {\n if (!isValidElement(c)) return c;\n const key = c.key ?? `sg-${i}`;\n if (!isFill) return cloneElement(c, { key });\n // Select 자식의 width prop 주입용 좁힘(런타임 동작 동일 — 타입만 명시)\n const sc = c as ReactElement<{ width?: number | string }>;\n const el = c.type === Select ? cloneElement(sc, { width: sc.props.width ?? '100%' }) : c;\n return (\n <div key={key} className=\"min-w-0 flex-1\">\n {el}\n </div>\n );\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","// 한국 전화번호 하이픈 자동 삽입 — 숫자만 남기고 자릿수에 맞춰 구분자를 넣는다.\n// 휴대폰/지역(3자리 국번): 3-3-4(10자리) / 3-4-4(11자리), 서울(02): 2-3-4(9자리) / 2-4-4(10자리)\n// 입력 중(부분 자릿수)에도 점진적으로 포맷한다. 최대 11자리로 제한.\nexport function formatPhoneNumber(raw: string | number | null | undefined): string {\n const d = String(raw).replace(/\\D/g, '').slice(0, 11);\n if (!d) return '';\n if (d.startsWith('02')) {\n if (d.length <= 2) return d;\n if (d.length <= 6) return `${d.slice(0, 2)}-${d.slice(2)}`;\n if (d.length <= 9) return `${d.slice(0, 2)}-${d.slice(2, 5)}-${d.slice(5)}`;\n return `${d.slice(0, 2)}-${d.slice(2, 6)}-${d.slice(6, 10)}`;\n }\n if (d.length <= 3) return d;\n if (d.length <= 7) return `${d.slice(0, 3)}-${d.slice(3)}`;\n if (d.length <= 10) return `${d.slice(0, 3)}-${d.slice(3, 6)}-${d.slice(6)}`;\n return `${d.slice(0, 3)}-${d.slice(3, 7)}-${d.slice(7)}`;\n}\n","// SelectOrInput — \"등록된 값 선택 OR 직접 입력\" 상호 배타 필드 (Select + Input 조립, 규칙 4)\n// 마지막에 사용한 수단이 값을 가지며, 반대쪽은 디폴트 상태로 되돌아간다:\n// - Input에 직접 입력하면 → Select는 선택 전(placeholder) 상태로 리셋\n// - Select에서 값을 선택하면 → Input은 입력 전(빈) 상태로 리셋\n// 값 계약: value/onChange는 최종 문자열 하나(e.target.value). e.target.source('select'|'input'|null)도 함께 전달.\n// controlled일 때 표시 위치는 값이 options에 있으면 Select, 없으면 Input으로 유도된다.\n// EmailField / PhoneField 는 placeholder·inputMode 기본값만 다른 얇은 래퍼.\nimport { useMemo, useState } from 'react';\nimport type { ComponentPropsWithoutRef } from 'react';\nimport { Select } from './Select';\nimport type { SelectPlacement } from './Select';\nimport { Input } from './Input';\nimport { formatPhoneNumber } from '../utils/phone';\nimport type { OptionItem } from './formulaFunctions';\n\n// 값의 출처 — 마지막으로 사용한 수단\ntype SelectOrInputSource = 'select' | 'input' | null;\n\n// ...props는 루트 컨테이너(div)로 전달된다.\ninterface SelectOrInputProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: { target: { value: string; source: SelectOrInputSource } }) => void; // (e) => void — e.target.value(문자열) · e.target.source('select'|'input'|null)\n options?: (string | OptionItem)[]; // 등록된 값 목록: 문자열 | { value, label }\n showSelect?: boolean; // false면 Select 숨김 — 직접 입력 전용(Input이 전체 폭)\n showInput?: boolean; // false면 Input 숨김 — 등록값 선택 전용(Select가 전체 폭)\n selectPlaceholder?: string;\n inputPlaceholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string; // Input의 에러 툴팁으로 표시\n formatInput?: (raw: string) => string; // (raw) => string — 직접 입력값 변환(예: 전화번호 하이픈). 입력 시마다 적용\n width?: number | string; // 전체 너비: 숫자(px) | CSS 길이('100%' 등)\n selectWidth?: number | string; // Select 너비 — 나머지는 Input이 채움\n menuWidth?: number | string; // Select 드롭다운 너비(미지정 시 트리거와 동일)\n placement?: SelectPlacement;\n searchable?: boolean; // Select 검색 필터\n inputProps?: ComponentPropsWithoutRef<'input'>; // 내부 <input> 속성 통과(inputMode 등)\n}\n\nexport function SelectOrInput({\n value,\n defaultValue = '',\n onChange, // (e) => void — e.target.value(문자열) · e.target.source('select'|'input'|null)\n options = [], // 등록된 값 목록: 문자열 | { value, label }\n showSelect = true, // false면 Select 숨김 — 직접 입력 전용(Input이 전체 폭)\n showInput = true, // false면 Input 숨김 — 등록값 선택 전용(Select가 전체 폭)\n selectPlaceholder = '선택하세요',\n inputPlaceholder = '직접 입력하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '', // Input의 에러 툴팁으로 표시\n formatInput, // (raw) => string — 직접 입력값 변환(예: 전화번호 하이픈). 입력 시마다 적용\n width = 400, // 전체 너비: 숫자(px) | CSS 길이('100%' 등)\n selectWidth = 160, // Select 너비 — 나머지는 Input이 채움\n menuWidth, // Select 드롭다운 너비(미지정 시 트리거와 동일)\n placement = 'auto',\n searchable = false, // Select 검색 필터\n inputProps = {}, // 내부 <input> 속성 통과(inputMode 등)\n className = '',\n ...props\n}: SelectOrInputProps) {\n const opts = useMemo(\n () => options.map((o) => (typeof o === 'string' ? { value: o, label: o } : o)),\n [options],\n );\n const isControlled = value !== undefined;\n // 값만 보고 출처를 추정(초기값·외부 주입값용) — 한쪽만 쓰면 그 수단으로 고정, 아니면 옵션 포함 여부\n const deriveSource = (val: string): SelectOrInputSource => {\n if (!val) return null;\n if (!showSelect) return 'input';\n if (!showInput) return 'select';\n return opts.some((o) => o.value === val) ? 'select' : 'input';\n };\n const [internal, setInternal] = useState(defaultValue);\n // 마지막으로 사용한 수단 — controlled여도 추적한다. 입력 중 값이 우연히 등록 옵션과\n // 일치해도(예: 전화번호 자동 포맷 도중) Select로 하이재킹되지 않도록 추정보다 우선.\n const [lastSource, setLastSource] = useState<SelectOrInputSource>(() => deriveSource(defaultValue));\n\n const current = value !== undefined ? value : internal;\n const source = !current\n ? null\n : !showInput\n ? 'select'\n : !showSelect\n ? 'input'\n : (lastSource ?? deriveSource(current));\n\n const emit = (val: string, src: SelectOrInputSource) => {\n const nextSource = val ? src : null;\n setLastSource(nextSource);\n if (!isControlled) setInternal(val);\n onChange?.({ target: { value: val, source: nextSource } });\n };\n\n // 상호 배타 표시 — 반대쪽 수단은 항상 디폴트 상태\n const selectValue = source === 'select' ? current : '';\n const inputValue = source === 'input' ? current : '';\n\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n\n return (\n <div className={`flex items-start gap-spacing-5 ${className}`} style={{ width: widthStyle }} {...props}>\n {showSelect && (\n <Select\n options={opts}\n value={selectValue}\n onChange={(e) => emit(e.target.value, 'select')}\n placeholder={selectPlaceholder}\n width={showInput ? selectWidth : '100%'} // Input이 없으면 Select가 전체 폭\n menuWidth={menuWidth}\n placement={placement}\n searchable={searchable}\n disabled={disabled}\n readOnly={readOnly}\n // Select 전용 모드에선 에러 툴팁을 Select가 담당(Input이 없으므로)\n error={!showInput && error}\n errorMessage={!showInput ? errorMessage : ''}\n />\n )}\n {showInput && (\n <div className=\"min-w-0 flex-1\">\n <Input\n value={inputValue}\n onChange={(e) => emit(formatInput ? formatInput(e.target.value) : e.target.value, 'input')}\n placeholder={inputPlaceholder}\n width=\"100%\"\n disabled={disabled}\n readOnly={readOnly}\n error={error}\n errorMessage={errorMessage}\n inputProps={inputProps}\n />\n </div>\n )}\n </div>\n );\n}\n\n// EmailField — 등록된 이메일 선택 or 직접 입력\nexport function EmailField({\n selectPlaceholder = '이메일 선택',\n inputPlaceholder = '이메일을 직접 입력하세요',\n inputProps = {},\n ...props\n}: SelectOrInputProps) {\n return (\n <SelectOrInput\n selectPlaceholder={selectPlaceholder}\n inputPlaceholder={inputPlaceholder}\n inputProps={{ inputMode: 'email', ...inputProps }}\n {...props}\n />\n );\n}\n\n// PhoneField — 등록된 전화번호 선택 or 직접 입력(하이픈 자동 삽입 — utils/phone.js)\ninterface PhoneFieldProps extends Omit<SelectOrInputProps, 'formatInput'> {\n formatInput?: ((raw: string) => string) | null; // 기본: 하이픈 자동 포맷(끄려면 null)\n}\n\nexport function PhoneField({\n selectPlaceholder = '전화번호 선택',\n inputPlaceholder = '전화번호를 직접 입력하세요',\n formatInput = formatPhoneNumber, // 기본: 하이픈 자동 포맷(끄려면 null)\n inputProps = {},\n ...props\n}: PhoneFieldProps) {\n return (\n <SelectOrInput\n selectPlaceholder={selectPlaceholder}\n inputPlaceholder={inputPlaceholder}\n formatInput={formatInput ?? undefined}\n inputProps={{ inputMode: 'tel', ...inputProps }}\n {...props}\n />\n );\n}\n","// SideNavigation — 사이드 내비게이션 (Figma side navigation 8200:51519 / side navigation button 8200:51452)\n// 버튼: [아이콘] [라벨] [NewTag] … [chevron ›] 한 행(높이 32, px spacing-6·py spacing-4, 내부 gap spacing-5).\n// state: default / hover(CSS) / select / disabled — 색은 side-nav-* 시멘틱 토큰만 사용.\n// line(attached): 컨테이너 우측 구분선에 붙는 형태(왼쪽만 라운드) / false면 독립형(전체 라운드).\n// select+line은 우측 1px 라인(side-nav-select-text)을 버튼이 직접 가진다(2026-07-06 Figma 개정) —\n// 컨테이너 회색 구분선(내부 absolute 라인) 위에 그 행 높이만큼 정확히 겹쳐 선택 표시가 된다.\n// none line(독립형)에는 없음. 이 라인은 border-r이 아니라 absolute 오버레이로 그린다 —\n// border는 border-box 콘텐츠 폭을 1px 줄여 선택 시 화살표가 왼쪽으로 밀린다(2026-07-06 사용자 지적).\n// 컨테이너: 세로 스택(gap spacing-2), width 180/220/260(또는 임의 px), line=우측 1px 구분선(side-nav-right-line).\n// 구분선은 border가 아니라 내부 absolute 라인으로 그린다 — 행(w-full)이 그 위까지 깔려서\n// 선택 행의 파란 오버레이 라인이 구분선과 같은 픽셀 열에 정확히 겹친다(border-r 방식이면 1px 어긋남).\n// add menu(+)는 컨테이너 내장 기능 — showAdd로 on/off, onAdd로 메뉴 추가 동작 연결(2026-07-06 지시).\n// 메뉴 목록은 ScrollArea(규칙 9) — 상위 DOM이 높이를 제한하면(h-full·고정 px 부모 등) 내비만 독립 스크롤되고,\n// 제한이 없으면 콘텐츠 높이 그대로(스크롤 없음). 추가(+) 행은 스크롤 밖에 고정된다.\nimport { ChevronRight, Plus } from 'lucide-react';\nimport type { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';\nimport { NewTag } from './Tag';\nimport { ScrollArea } from './ScrollArea';\nimport { TruncatingText } from './TruncatingText';\n\nconst STATE_STYLE = {\n default:\n 'text-side-nav-default-text hover:bg-side-nav-hover-bg hover:text-side-nav-hover-text cursor-pointer ' +\n 'focus-visible:bg-side-nav-hover-bg focus-visible:text-side-nav-hover-text', // 포커스=호버(2026-07-16)\n select: 'bg-side-nav-select-bg text-side-nav-select-text cursor-pointer',\n disabled: 'text-side-nav-disabled-text cursor-not-allowed',\n};\n\nexport interface SideNavigationButtonProps extends ComponentPropsWithoutRef<'button'> {\n /** 좌측 아이콘(lucide 컴포넌트) — 없으면 생략 */\n icon?: ElementType; // lucide 외 커스텀 아이콘 컴포넌트도 허용\n /** select 상태(파란 텍스트·알파 배경) */\n selected?: boolean;\n /** 라벨 오른쪽 NewTag(N) 표시 */\n showNewTag?: boolean;\n /** NewTag color 패스스루 */\n newTagColor?: 'blue' | 'red' | 'black';\n /** 우측 chevron(›) */\n showArrow?: boolean;\n /** 긴 라벨 처리: 'ellipsis'(말줄임 + hover 전체 툴팁) | 'wrap'(멀티라인 줄바꿈) */\n overflow?: 'ellipsis' | 'wrap';\n /** true=컨테이너 우측 라인에 붙는 형태(왼쪽만 라운드) / false=전체 라운드 */\n line?: boolean;\n}\n\nexport function SideNavigationButton({\n children = 'side menu',\n icon: Icon,\n selected = false,\n disabled = false,\n showNewTag = false,\n newTagColor = 'blue',\n showArrow = true,\n overflow = 'ellipsis',\n line = true,\n onClick,\n className = '',\n ...props\n}: SideNavigationButtonProps) {\n const state = disabled ? 'disabled' : selected ? 'select' : 'default';\n const rounding = line ? 'rounded-l-round-4' : 'rounded-round-4';\n const selectLine = state === 'select' && line;\n return (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={onClick}\n aria-current={selected || undefined}\n className={`relative flex min-h-[32px] w-full items-center px-spacing-6 py-spacing-4 text-left transition-colors focus:outline-none ${rounding} ${STATE_STYLE[state]} ${className}`}\n {...props}\n >\n {selectLine && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 right-0 w-spacing-1 bg-side-nav-select-text\"\n />\n )}\n <span className=\"flex min-w-0 flex-1 items-center gap-spacing-5\">\n {Icon && <Icon size={16} strokeWidth={1.8} className=\"shrink-0\" />}\n {overflow === 'wrap' ? (\n <span className=\"min-w-0 whitespace-normal break-words text-14 leading-20\">{children}</span>\n ) : (\n <TruncatingText as=\"span\" className=\"truncate text-14 leading-20\">\n {children}\n </TruncatingText>\n )}\n {showNewTag && <NewTag color={newTagColor} />}\n </span>\n {showArrow && <ChevronRight size={16} strokeWidth={1.8} className=\"shrink-0\" />}\n </button>\n );\n}\n\nexport interface SideNavigationProps extends ComponentPropsWithoutRef<'nav'> {\n /** 180 | 220 | 260 (Figma variants) 또는 임의 px/CSS 길이 */\n width?: number | string;\n /** 우측 1px 구분선(side-nav-right-line) 표시 */\n line?: boolean;\n /** 'add menu'(+) 행 표시 — 메뉴 추가 기능 on/off */\n showAdd?: boolean;\n addLabel?: ReactNode;\n /** 'bottom' | 'top' — 추가 행 위치(템플릿은 top 사용) */\n addPosition?: 'bottom' | 'top';\n /** (+) 클릭 시 메뉴 추가 동작 */\n onAdd?: () => void;\n}\n\nexport function SideNavigation({\n children, // SideNavigationButton들\n width = 180,\n line = true,\n showAdd = true,\n addLabel = 'add menu',\n addPosition = 'bottom',\n onAdd,\n className = '',\n ...props\n}: SideNavigationProps) {\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n return (\n <nav\n style={{ width: widthStyle }}\n className={`relative flex flex-col gap-spacing-2 ${className}`}\n {...props}\n >\n {line && (\n <span\n aria-hidden\n className=\"pointer-events-none absolute inset-y-0 right-0 w-spacing-1 bg-side-nav-right-line\"\n />\n )}\n {showAdd && addPosition === 'top' && (\n <SideNavigationButton icon={Plus} showArrow={false} line={line} onClick={onAdd}>\n {addLabel}\n </SideNavigationButton>\n )}\n <ScrollArea className=\"min-h-0 flex-1\" contentClassName=\"max-h-full flex flex-col gap-spacing-2\">\n {children}\n </ScrollArea>\n {showAdd && addPosition === 'bottom' && (\n <SideNavigationButton icon={Plus} showArrow={false} line={line} onClick={onAdd}>\n {addLabel}\n </SideNavigationButton>\n )}\n </nav>\n );\n}\n","// SideNavigationTemplate — 좌측 사이드 내비게이션 + 우측 콘텐츠 2단 템플릿\n// (Figma side navigation Template 8202:61044 — [SideNavigation 180 | gap 16(spacing-7) | 콘텐츠 슬롯 FILL])\n// 메뉴는 데이터(menus)로 렌더하고 선택은 내부 상태 또는 controlled(selectedId/onSelect).\n// 추가 행(+)은 SideNavigation 내장 기능을 상단(top)으로 사용 — showAdd/addLabel/onAdd 패스스루.\nimport { useState, useContext } from 'react';\nimport type { ComponentPropsWithoutRef, ElementType } from 'react';\nimport { ModalBodyMaxContext } from './modalContext';\nimport { SideNavigation, SideNavigationButton } from './SideNavigation';\n\n// 메뉴 항목 — [{ id, label, count?, icon?, showNewTag?, newTagColor?, disabled? }] — count는 0이면 표시 안 함\ninterface SideNavigationMenu {\n id: string;\n label: string;\n count?: number;\n icon?: ElementType;\n showNewTag?: boolean;\n newTagColor?: 'blue' | 'red' | 'black';\n disabled?: boolean;\n}\n\ninterface SideNavigationTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange' | 'onSelect'> {\n menus?: SideNavigationMenu[];\n selectedId?: string; // controlled 선택 id\n defaultSelectedId?: string; // uncontrolled 초기 선택\n onSelect?: (id: string) => void;\n navWidth?: number; // 좌측 내비 너비 (Figma 180/220/260)\n line?: boolean; // 내비 우측 구분선\n showAdd?: boolean; // 추가(+) 행 — 메뉴 추가 기능 on/off\n addLabel?: string;\n addPosition?: 'top' | 'bottom'; // 'top'(Figma 템플릿 기본) | 'bottom' — 추가 행 위치\n onAdd?: () => void;\n overflow?: 'ellipsis' | 'wrap'; // 메뉴 라벨 긴 경우 처리 — SideNavigationButton 패스스루\n // 템플릿 높이 — 숫자/CSS 길이(고정), 또는 'fill': 모달 안이면 ModalBody 가용 높이(컨텍스트)를\n // '최대치'로 사용(max-height) — 내용이 적으면 자연 높이, 가용 높이에 닿으면 멈추고 내비가 독립 스크롤.\n // 모달 밖 'fill'은 부모 100%. 숫자/CSS는 기존대로 고정 높이(2026-07-06 fill을 height→max-height로 변경).\n height?: number | string;\n}\n\nexport function SideNavigationTemplate({\n menus = [],\n selectedId: selectedProp, // controlled 선택 id\n defaultSelectedId, // uncontrolled 초기 선택\n onSelect,\n navWidth = 180,\n line = true,\n showAdd = true,\n addLabel = 'add menu',\n addPosition = 'top',\n onAdd,\n overflow = 'ellipsis',\n height,\n children, // 우측 콘텐츠 슬롯\n className = '',\n ...props\n}: SideNavigationTemplateProps) {\n const modalBodyMax = useContext(ModalBodyMaxContext);\n // fill=최대치(max-height — 내용 적으면 자연 높이), 숫자/CSS=고정(height)\n const isFill = height === 'fill';\n const fillMax = isFill ? (modalBodyMax != null ? `${modalBodyMax}px` : '100%') : null;\n const fixedHeight = !isFill ? (typeof height === 'number' ? `${height}px` : height) : null;\n\n const controlled = selectedProp !== undefined;\n const [internal, setInternal] = useState<string | undefined>(defaultSelectedId);\n // 첫 메뉴 폴백은 렌더 파생값 — mount 시 menus가 빈 배열(비동기 로드)이어도 도착 후 첫 메뉴가 선택된다\n const selected = controlled ? selectedProp : (internal ?? menus[0]?.id ?? null);\n const select = (id: string) => {\n if (!controlled) setInternal(id);\n onSelect?.(id);\n };\n\n return (\n <div style={fixedHeight ? { height: fixedHeight } : fillMax ? { maxHeight: fillMax } : undefined} className={`flex min-h-0 items-stretch gap-spacing-7 ${className}`} {...props}>\n <SideNavigation\n width={navWidth}\n line={line}\n showAdd={showAdd}\n addLabel={addLabel}\n addPosition={addPosition}\n onAdd={onAdd}\n className=\"shrink-0\"\n >\n {menus.map((m) => (\n <SideNavigationButton\n key={m.id}\n icon={m.icon}\n selected={selected === m.id}\n disabled={m.disabled}\n showNewTag={m.showNewTag}\n newTagColor={m.newTagColor}\n overflow={overflow}\n line={line}\n onClick={() => select(m.id)}\n >\n {m.count ? `${m.label} ${m.count}` : m.label}\n </SideNavigationButton>\n ))}\n </SideNavigation>\n {/* 우측 콘텐츠 슬롯 — 세로 스택(gap 12 = Figma 슬롯 규격) */}\n <div className=\"flex min-h-0 min-w-0 flex-1 flex-col gap-spacing-6\">{children}</div>\n </div>\n );\n}\n","// TableTemplate — 목록 페이지 템플릿 (table control + Table + Pagination 조합)\n// 버튼그룹·검색바·테이블·페이지네이션을 한 덩어리로 묶은 완전 옵션형 템플릿(규칙 4).\n// 각 요소를 props로 끄고 켤 수 있다:\n// - actions : 버튼그룹 내용(ReactNode 또는 (ctx) => ReactNode). null이면 버튼그룹 자체를 숨김\n// - title : 테이블 타이틀. 있으면 헤더 좌측(버튼그룹 왼쪽)에 표시(버튼그룹/검색바와 함께 가능)\n// - searchable : 검색바 on/off (false면 숨김)\n// - pagination : 페이지네이션 on/off (false면 전체 행을 한 번에 표시)\n// - bordered : 테이블 외곽선/라운드 타입\n// - selectable : 체크박스 선택 컬럼(+ 전체선택 헤더)\n// 검색 필터 · 페이지네이션 · 선택은 내부 상태로 동작하며, 선택만 selectedIds/onSelectChange로 제어 가능.\n// 좌(셀렉트그룹·버튼그룹) · 우(셀렉트그룹·버튼그룹·검색바) 컨트롤 행은 전부 꺼지면 통째로 사라진다.\n// - rightActions : 우측 슬롯(ReactNode 또는 (ctx)=>) — 검색바 대신/함께 임의 버튼 등을 우측에 배치\n// - selects / rightSelects : 좌/우 셀렉트 그룹 슬롯(ReactNode 또는 (ctx)=>) — SelectGroup으로 감싸 배치.\n// 좌=타이틀→셀렉트그룹→버튼그룹(셀렉트가 버튼그룹 앞), 우=버튼그룹→셀렉트그룹→검색바(셀렉트가 버튼그룹과 검색바 사이)\n// 색·간격·보더는 하위 컴포넌트(Table/Pagination 등)의 토큰을 그대로 따른다.\nimport { useMemo, useState } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, ReactNode } from 'react';\nimport { Table } from './Table';\nimport type { TableColumn } from './Table';\nimport { applyColumnFilters, applySort } from './tableView';\nimport type { TableRowData, TableSort } from './tableView';\nimport { Pagination } from './Pagination';\nimport { SearchBar } from './SearchBar';\nimport { ButtonGroup } from './ButtonGroup';\nimport { SelectGroup } from './SelectGroup';\n\n// 컬럼 정의 — Table 패스스루(headerMenu·sortable·filter 등 추가 옵션 허용)\n// Table의 TableColumn과 동일 계약 + 템플릿 전용 부가 옵션 통과(인덱스 시그니처)\ninterface TableTemplateColumn extends TableColumn {\n [option: string]: unknown;\n}\n\n// actions/rightActions/selects/rightSelects 함수형 슬롯에 넘기는 컨텍스트\ninterface TableTemplateContext {\n selectedIds: (string | number)[];\n clearSelection: () => void;\n visibleRows: TableRowData[];\n query: string;\n}\ntype TableTemplateSlot = ReactNode | ((ctx: TableTemplateContext) => ReactNode);\n\ninterface TableTemplateProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {\n columns?: TableTemplateColumn[];\n rows?: TableRowData[];\n rowKey?: string;\n // 요소 on/off\n actions?: TableTemplateSlot; // 버튼그룹 내용 — null이면 버튼그룹 숨김\n rightActions?: TableTemplateSlot; // 우측 슬롯(ReactNode 또는 (ctx)=>) — 검색바 대신/함께 우측에 버튼 등 배치\n selects?: TableTemplateSlot; // 좌측 셀렉트 그룹(ReactNode 또는 (ctx)=>) — 좌측 버튼그룹 앞(왼쪽)\n rightSelects?: TableTemplateSlot; // 우측 셀렉트 그룹(ReactNode 또는 (ctx)=>) — 우측 버튼그룹과 검색바 사이\n title?: ReactNode; // 테이블 타이틀 — 있으면 헤더 좌측(버튼그룹 왼쪽)에 표시\n searchable?: boolean; // 검색바\n pagination?: boolean; // 페이지네이션\n bordered?: boolean; // 테이블 외곽선 타입\n selectable?: boolean; // 체크박스 선택\n // 검색\n searchPlaceholder?: string;\n searchKeys?: string[]; // 검색 대상 컬럼 key 배열 — 기본: render 없는(텍스트) 컬럼 전체\n searchWidth?: number | string;\n // 페이지네이션\n page?: number; // 현재 페이지(1-base) — 외부 제어 시\n onPageChange?: (page: number) => void; // 페이지 변경 콜백\n defaultPageSize?: number; // 페이지당 행 수 초기값(uncontrolled)\n pageSize?: number; // 페이지당 행 수 — 외부 제어 시\n onPageSizeChange?: (size: number) => void; // 행 수 변경 핸들러\n pageSizeOptions?: number[]; // 행 수 선택 옵션(기본 5/10/20/50) — Pagination 내장 셀렉트에 노출\n showPageSize?: boolean; // Pagination의 '페이지 행' 셀렉트 표시 여부\n showTotal?: boolean; // Pagination의 좌측 '총 N개' 표시 여부\n maxButtons?: number; // 페이지 번호 노출 개수(윈도우)\n paginationClassName?: string; // 페이지네이션 영역 전용 클래스\n // 선택(controlled 옵션)\n selectedIds?: (string | number)[];\n onSelectChange?: (ids: (string | number)[]) => void;\n // 테이블 패스스루\n wrap?: boolean;\n minWidth?: number;\n maxHeight?: number | string;\n loading?: boolean;\n emptyMessage?: ReactNode;\n onRowClick?: (row: TableRowData) => void;\n buttonGroupGap?: '3' | '4' | '5' | '6' | '7'; // 좌/우 버튼그룹 간격 토큰 키 — 기본 '5'(8px, 셀렉트 그룹과 통일)\n selectGroupGap?: '3' | '4' | '5' | '6' | '7'; // 좌/우 셀렉트 그룹 간격 토큰 키 — 기본 '5'(8px, 버튼그룹과 통일)\n}\n\nexport function TableTemplate({\n columns = [],\n rows = [],\n rowKey = 'id',\n // 요소 on/off\n actions = null,\n rightActions = null,\n selects = null,\n rightSelects = null,\n title = null,\n searchable = true,\n pagination = true,\n bordered = false,\n selectable = false,\n // 검색\n searchPlaceholder = '검색어를 입력하세요',\n searchKeys,\n searchWidth = 200,\n // 페이지네이션\n page: pageProp,\n onPageChange,\n defaultPageSize = 10,\n pageSize: pageSizeProp,\n onPageSizeChange,\n pageSizeOptions,\n showPageSize = true,\n showTotal = true,\n maxButtons,\n paginationClassName = '',\n // 선택(controlled 옵션)\n selectedIds: selectedIdsProp,\n onSelectChange,\n // 테이블 패스스루\n wrap = false,\n minWidth = 720,\n maxHeight,\n loading = false,\n emptyMessage,\n onRowClick,\n buttonGroupGap = '5',\n selectGroupGap = '5',\n className = '',\n ...props\n}: TableTemplateProps) {\n const [query, setQuery] = useState('');\n\n // 헤더 필터(filter 컬럼)·정렬(headerMenu sortable) 상태 — 템플릿이 보유.\n // 페이지네이션 전에 전체 행에 적용해야 하므로 여기서 처리하고, 같은 상태를 Table에 controlled로 넘겨 헤더 UI를 동기화한다.\n const [colFilters, setColFilters] = useState<Record<string, unknown>>({});\n const [sort, setSort] = useState<TableSort | null>(null);\n\n // 현재 페이지 — controlled(pageProp) 또는 내부 상태.\n const pageControlled = pageProp !== undefined;\n const [internalPage, setInternalPage] = useState(1);\n const page = pageControlled ? pageProp : internalPage;\n const goToPage = (next: number) => {\n if (!pageControlled) setInternalPage(next);\n onPageChange?.(next);\n };\n\n // 행 수(pageSize) — controlled(pageSizeProp) 또는 내부 상태. 변경 시 1페이지로 리셋.\n const sizeControlled = pageSizeProp !== undefined;\n const [internalSize, setInternalSize] = useState(defaultPageSize);\n const pageSize = sizeControlled ? pageSizeProp : internalSize;\n const changePageSize = (size: number) => {\n if (!sizeControlled) setInternalSize(size);\n onPageSizeChange?.(size);\n goToPage(1);\n };\n\n // 선택 상태 — controlled(selectedIdsProp) 또는 내부 상태\n const selControlled = selectedIdsProp !== undefined;\n const [internalSel, setInternalSel] = useState<(string | number)[]>([]);\n const selectedIds = selControlled ? selectedIdsProp : internalSel;\n const setSelectedIds = (next: (string | number)[]) => {\n if (!selControlled) setInternalSel(next);\n onSelectChange?.(next);\n };\n\n // 검색 대상 key — 미지정 시 render 없는(텍스트) 컬럼 전체\n const keys = useMemo(\n () => searchKeys ?? columns.filter((c) => !c.render).map((c) => c.key),\n [searchKeys, columns],\n );\n\n // 검색 필터(searchable일 때만)\n const searched = useMemo(() => {\n if (!searchable || !query) return rows;\n const q = query.toLowerCase();\n return rows.filter((r) => keys.some((k) => String(r[k] ?? '').toLowerCase().includes(q)));\n }, [rows, query, searchable, keys]);\n\n // 헤더 필터 → 정렬을 전체(검색 후) 행에 적용한 뒤에 페이지를 자른다(헤더 요소가 현재 페이지 안에서만 동작하지 않게).\n const filtered = useMemo(\n () => applySort(applyColumnFilters(searched, columns, colFilters), sort),\n [searched, columns, colFilters, sort],\n );\n\n // 헤더 필터/정렬 변경 — 상태 갱신 + 1페이지로 리셋(보이는 범위가 바뀌므로).\n const handleFilterChange = (next: Record<string, unknown>) => {\n setColFilters(next);\n goToPage(1);\n };\n const handleSortChange = (next: TableSort | null) => {\n setSort(next);\n goToPage(1);\n };\n\n // 페이지네이션이 꺼지면 전체 행을 그대로 노출. 켜지면 현재 페이지 슬라이스만.\n const totalCount = filtered.length;\n const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));\n const safePage = Math.min(page, totalPages); // 필터/삭제로 범위를 벗어나면 보정\n const visibleRows = pagination\n ? filtered.slice((safePage - 1) * pageSize, (safePage - 1) * pageSize + pageSize)\n : filtered;\n\n // actions가 함수면 현재 컨텍스트를 넘겨준다(선택 항목으로 삭제 등 구현 가능).\n const ctx: TableTemplateContext = { selectedIds, clearSelection: () => setSelectedIds([]), visibleRows, query };\n const resolvedActions = typeof actions === 'function' ? actions(ctx) : actions;\n // 우측 슬롯 — 검색바 대신/함께 임의 요소(버튼 등)를 우측에 배치. actions와 동일하게 (ctx) => 함수 지원\n const resolvedRightActions = typeof rightActions === 'function' ? rightActions(ctx) : rightActions;\n // 좌/우 셀렉트 그룹 슬롯 — actions와 동일하게 (ctx) => 함수 지원\n const resolvedSelects = typeof selects === 'function' ? selects(ctx) : selects;\n const resolvedRightSelects = typeof rightSelects === 'function' ? rightSelects(ctx) : rightSelects;\n const showHeader =\n !!resolvedActions || searchable || !!title || !!resolvedRightActions || !!resolvedSelects || !!resolvedRightSelects; // 하나라도 있으면 헤더 렌더\n\n return (\n <div className={`flex flex-col gap-spacing-6 ${className}`} {...props}>\n {showHeader && (\n <div className=\"flex items-center justify-between gap-spacing-6\">\n {/* 좌: 타이틀 → 셀렉트그룹 → 버튼그룹 (셀렉트그룹이 버튼그룹 앞). 전부 없으면 빈 자리로 우측(검색바 등)을 고정.\n 그룹 간 간격 8px(spacing-5) — 그룹 내부 갭과 통일 */}\n <div className=\"flex items-center gap-spacing-5\">\n {title && <h3 className=\"flex h-[32px] items-center text-15 font-semibold text-font-icon-5\">{title}</h3>}\n {resolvedSelects && <SelectGroup gap={selectGroupGap}>{resolvedSelects}</SelectGroup>}\n {resolvedActions && <ButtonGroup gap={buttonGroupGap}>{resolvedActions}</ButtonGroup>}\n </div>\n {/* 우: rightActions 버튼그룹 → 셀렉트그룹 → 검색바(맨 오른쪽) — 셀렉트그룹은 버튼그룹과 검색바 사이.\n 좌 actions처럼 각 그룹으로 감싸 스타일 혼합 요소를 일정 간격으로 배치 */}\n {(searchable || resolvedRightActions || resolvedRightSelects) && (\n <div className=\"flex items-center gap-spacing-5\">\n {resolvedRightActions && (\n <ButtonGroup gap={buttonGroupGap}>{resolvedRightActions}</ButtonGroup>\n )}\n {resolvedRightSelects && (\n <SelectGroup gap={selectGroupGap}>{resolvedRightSelects}</SelectGroup>\n )}\n {searchable && (\n <SearchBar\n value={query}\n placeholder={searchPlaceholder}\n width={searchWidth}\n onChange={(e: ChangeEvent<HTMLInputElement>) => {\n setQuery(e.target.value);\n goToPage(1);\n }}\n />\n )}\n </div>\n )}\n </div>\n )}\n\n <Table\n columns={columns}\n rows={visibleRows}\n rowKey={rowKey}\n bordered={bordered}\n selectable={selectable}\n selectedIds={selectable ? selectedIds : undefined}\n onSelectChange={selectable ? setSelectedIds : undefined}\n filters={colFilters}\n onFilterChange={handleFilterChange}\n sort={sort}\n onSortChange={handleSortChange}\n wrap={wrap}\n minWidth={minWidth}\n maxHeight={maxHeight}\n loading={loading}\n emptyMessage={emptyMessage}\n onRowClick={onRowClick}\n />\n\n {pagination && (\n <Pagination\n page={safePage}\n onChange={goToPage}\n totalCount={totalCount}\n pageSize={pageSize}\n pageSizeOptions={pageSizeOptions}\n showPageSize={showPageSize}\n showTotal={showTotal}\n maxButtons={maxButtons}\n onPageSizeChange={changePageSize}\n className={paginationClassName}\n />\n )}\n </div>\n );\n}\n","// TextArea — 여러 줄 텍스트 입력 (Figma text area, solid 타입)\n// Input의 멀티라인 버전 — 상태(default/hover/focused/filled)는 CSS·실제 입력값으로 자동,\n// disabled/readOnly/error는 props. 하단에 글자수 카운터(Figma countTxt) 옵션.\n// 색·간격·라운드는 Input과 동일한 text-field-* 시멘틱 토큰 사용(새 토큰 없음).\n//\n// 스크롤바(규칙 9): textarea는 ScrollArea(div용 JS 오버레이)로 감쌀 수 없고, 네이티브 스크롤바는\n// hover가 브라우저 한계로 동작 안 한다 → **네이티브 스크롤(커서 추적 유지)은 그대로 두고\n// `.hide-native-scroll`로 기본 바만 숨긴 뒤, ScrollArea와 같은 오버레이 thumb를 textarea 스크롤에\n// 동기화해 얹는다**(scroll-bar 토큰, hover/드래그 동작). 커서 추적·hover 둘 다 만족.\n//\n// 에러 표현 규칙: Input과 동일 — 테두리 대신 박스 아래 absolute 툴팁 오버레이(레이아웃 영향 0).\nimport { useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';\nimport type { ChangeEvent, ComponentPropsWithoutRef, MouseEvent as ReactMouseEvent } from 'react';\nimport { listColors } from '../tokens';\nimport { Tooltip } from './Tooltip';\n\n// 편집 가능 상태의 테두리(ring) — hover/focus 2px(border-2 토큰), 색은 시멘틱 토큰.\nconst RING =\n 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\n\n// 오버레이 thumb 메트릭 — ScrollArea와 동일 상수/색\nconst THUMB_MIN = 24; // thumb 최소 길이(px)\nconst TRACK_PAD = 6; // thumb 양끝 여백(px)\nconst THUMB = { base: listColors['scroll-bar'], active: listColors['scroll-bar-hover'] };\n\n// ...props는 바깥 필드 컨테이너(div)로, 실제 <textarea> 속성은 textareaProps로 전달한다.\ninterface TextAreaProps extends Omit<ComponentPropsWithoutRef<'div'>, 'onChange'> {\n value?: string;\n defaultValue?: string;\n onChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void;\n placeholder?: string;\n rows?: number; // 기본 높이(줄 수). autoGrow=false면 고정 높이(초과 시 스크롤), true면 최소 높이\n autoGrow?: boolean; // 입력량에 따라 높이 자동 증가\n maxRows?: number; // autoGrow일 때 최대 행(초과 시 스크롤). 미지정 + autoGrow = 무한 증가\n maxLength?: number; // 최대 글자수(입력 제한 + 카운터 분모)\n showCount?: boolean; // 글자수 카운터 표시(Figma countTxt) — maxLength 있으면 'N/max', 없으면 'N'\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n width?: number | string; // 너비: 숫자(px) 또는 CSS 길이 문자열. 미지정 시 320px\n textareaProps?: ComponentPropsWithoutRef<'textarea'>;\n}\n\nexport function TextArea({\n value,\n defaultValue,\n onChange,\n placeholder = '텍스트를 입력하세요',\n rows = 4, // 기본 높이(줄 수). autoGrow=false면 고정 높이(초과 시 스크롤), true면 최소 높이\n autoGrow = false, // 입력량에 따라 높이 자동 증가\n maxRows, // autoGrow일 때 최대 행(초과 시 스크롤). 미지정 + autoGrow = 무한 증가\n maxLength, // 최대 글자수(입력 제한 + 카운터 분모)\n showCount = true, // 글자수 카운터 표시(Figma countTxt) — maxLength 있으면 'N/max', 없으면 'N'\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n width = 320, // 너비: 숫자(px) 또는 CSS 길이 문자열. 미지정 시 320px\n className = '',\n textareaProps = {},\n ...props\n}: TextAreaProps) {\n const interactive = !disabled && !readOnly;\n const widthStyle = typeof width === 'number' ? `${width}px` : width;\n const taRef = useRef<HTMLTextAreaElement | null>(null);\n const dragRef = useRef(false);\n const [internalLen, setInternalLen] = useState((defaultValue ?? '').length);\n const len = value != null ? value.length : internalLen;\n const [thumb, setThumb] = useState({ pos: 0, size: 0, visible: false });\n const [active, setActive] = useState(false); // hover 또는 드래그 중\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면 플레이스홀더도 더 연하게(Figma disabled = 연한 회색)\n const placeholderColor = disabled\n ? 'placeholder:text-text-field-disabled-text'\n : 'placeholder:text-text-field-default-text';\n\n // 네이티브 스크롤 메트릭 → 오버레이 thumb 위치/크기 (ScrollArea 세로 thumb와 동일 계산)\n const updateThumb = useCallback(() => {\n const el = taRef.current;\n if (!el) return;\n const { scrollHeight, clientHeight, scrollTop } = el;\n if (scrollHeight <= clientHeight + 1) {\n setThumb((t) => (t.visible ? { ...t, visible: false } : t));\n return;\n }\n const trackH = clientHeight - TRACK_PAD * 2;\n const size = Math.max((clientHeight / scrollHeight) * trackH, THUMB_MIN);\n const pos = TRACK_PAD + (scrollTop / (scrollHeight - clientHeight)) * (trackH - size);\n setThumb({ pos, size, visible: true });\n }, []);\n\n // autoGrow: 내용에 맞춰 height 조정(최소 rows, autoGrow=false면 rows 고정, maxRows 초과 시 그 높이로 캡→스크롤).\n const autoSize = useCallback(() => {\n const el = taRef.current;\n if (!el) return;\n if (!autoGrow) {\n el.style.height = ''; // rows 속성이 높이를 제어\n return;\n }\n el.style.height = 'auto';\n const cs = getComputedStyle(el);\n const lh = parseFloat(cs.lineHeight) || 24;\n const pad = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);\n let h = Math.max(el.scrollHeight, rows * lh + pad); // 최소 rows\n if (maxRows != null) h = Math.min(h, maxRows * lh + pad); // 최대 maxRows\n el.style.height = `${h}px`;\n }, [autoGrow, maxRows, rows]);\n\n useLayoutEffect(() => {\n autoSize();\n updateThumb();\n const el = taRef.current;\n if (!el) return undefined;\n // RO는 읽기 전용(updateThumb)만 — autoSize가 height를 바꾸므로 RO에서 호출하면 루프\n const ro = new ResizeObserver(updateThumb);\n ro.observe(el);\n const onResize = () => {\n autoSize();\n updateThumb();\n };\n window.addEventListener('resize', onResize);\n return () => {\n ro.disconnect();\n window.removeEventListener('resize', onResize);\n };\n }, [autoSize, updateThumb, value, rows]);\n\n const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {\n if (value == null) setInternalLen(e.target.value.length);\n autoSize();\n updateThumb();\n onChange?.(e);\n };\n\n // 드래그 도중 언마운트 시 window 리스너·body userSelect 잔존 방지(ScrollArea와 동일, 2026-07-07 감사)\n const dragCleanupRef = useRef<(() => void) | null>(null);\n useEffect(() => () => dragCleanupRef.current?.(), []);\n\n // thumb 드래그 → textarea.scrollTop 동기화 (ScrollArea와 동일 방식)\n const startDrag = (e: ReactMouseEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n const el = taRef.current;\n if (!el) return;\n dragRef.current = true;\n setActive(true);\n const start = e.clientY;\n const startScroll = el.scrollTop;\n const maxScroll = el.scrollHeight - el.clientHeight;\n const trackH = el.clientHeight - TRACK_PAD * 2;\n // 트랙이 thumb보다 짧으면 분모 0/음수 → NaN 스크롤 방지(이동 무시)\n const denom = trackH - thumb.size;\n const ratio = denom > 0 ? maxScroll / denom : 0;\n const onMove = (ev: MouseEvent) => {\n el.scrollTop = startScroll + (ev.clientY - start) * 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 return (\n <div\n style={{ width: widthStyle }}\n className={`relative rounded-round-4 bg-text-field-default-bg pl-spacing-6 pr-spacing-4 transition-shadow ${\n interactive ? RING : 'cursor-not-allowed'\n } ${className}`}\n {...props}\n >\n {/* textarea가 박스 전체(상·하단 flush)를 채우는 스크롤 영역 — 위·아래 여백은 안쪽 패딩(py-spacing-4).\n 카운터는 우하단 오버레이라 입력 텍스트와 겹칠 수 있다(의도). */}\n <textarea\n ref={taRef}\n value={value}\n defaultValue={defaultValue}\n onChange={handleChange}\n onScroll={updateThumb}\n placeholder={placeholder}\n disabled={disabled}\n readOnly={readOnly}\n rows={rows}\n maxLength={maxLength}\n aria-invalid={error || undefined}\n className={`hide-native-scroll block w-full resize-none overflow-y-auto bg-transparent pt-spacing-4 pb-spacing-12 text-14 outline-none disabled:cursor-not-allowed read-only:cursor-default ${placeholderColor} ${textColor}`}\n {...textareaProps}\n />\n {/* 오버레이 thumb — 항상 렌더, opacity로 표시 제어(콘텐츠 폭을 줄이지 않음).\n 시각 6px + 클릭 영역 10px(투명 ::before 사방 2px 확장) — ScrollArea thumb와 동일(2026-07-06) */}\n <div\n onMouseDown={startDrag}\n onMouseEnter={() => setActive(true)}\n onMouseLeave={() => {\n if (!dragRef.current) setActive(false);\n }}\n className=\"absolute right-spacing-3 w-spacing-4 cursor-pointer rounded-round-00 before:absolute before:-inset-spacing-2 before:content-['']\"\n style={{\n top: `${thumb.pos}px`,\n height: `${thumb.size}px`,\n opacity: thumb.visible ? 1 : 0,\n pointerEvents: thumb.visible ? 'auto' : 'none',\n backgroundColor: active ? THUMB.active : THUMB.base,\n }}\n />\n\n {/* 글자수 카운터 — 우하단 오버레이(입력 텍스트와 겹칠 수 있음). readOnly면 숨김(Figma read only 변형) */}\n {showCount && !readOnly && (\n <p className=\"pointer-events-none absolute bottom-spacing-4 right-spacing-6 text-12 text-font-icon-3\">\n {len}\n {maxLength != null ? `/${maxLength}` : ''}\n </p>\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","// TimeField — 시간 입력 트리거 + 직접 입력 필드 (DateField의 시간 버전)\n// 시계 아이콘 + 'HH:MM' 값. 누르면 인풋 영역 없는 시/분 2depth 목록(TwoDepthList showInput=false)이 뜬다.\n// - 직접 타이핑: \"12:34\"·\"1:1\"·\"1230\"·\"011\"(→01:01) → Enter/포커스 아웃 시 'HH:MM'로 정규화.\n// - 숫자·콜론만 입력 가능. 필드 상태(default·hover·focused·filled·disabled·readOnly·error)는 tf-* 토큰.\nimport { useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Clock as ClockIcon } from 'lucide-react';\nimport { Popover } from './Popover';\nimport { TwoDepthList } from './TwoDepthList';\nimport { Tooltip } from './Tooltip';\n\nconst RING = 'ring-inset ring-text-field-hover-line hover:ring-2 focus-within:ring-2 focus-within:ring-text-field-focused-line';\nconst pad2 = (n: number) => String(n).padStart(2, '0');\nconst ALLOWED_CHARS = /[\\d:]/;\nconst HOURS = Array.from({ length: 24 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}시` }));\nconst MINUTES = Array.from({ length: 60 }, (_, i) => ({ value: pad2(i), label: `${pad2(i)}분` }));\n\n// \"12:34\"·\"1:1\"(콜론) / \"1230\"·\"011\"(무구분, 시 2자리 + 분 1~2자리) → 'HH:MM' | null\nfunction parseTime(t: string | null | undefined): string | null {\n const s = (t ?? '').trim();\n const m = s.match(/^(\\d{1,2}):(\\d{1,2})$/) || s.match(/^(\\d{2})(\\d{1,2})$/);\n if (!m) return null;\n const h = Number(m[1]);\n const mn = Number(m[2]);\n if (h > 23 || mn > 59) return null;\n return `${pad2(h)}:${pad2(mn)}`;\n}\n\n// ...props는 Popover로 전달된다(팝오버 옵션 통과).\ninterface TimeFieldProps {\n value?: string | null; // 'HH:MM' | null(값 없음)\n defaultValue?: string | null;\n onChange?: (value: string | null) => void;\n placeholder?: string;\n disabled?: boolean;\n readOnly?: boolean;\n error?: boolean;\n errorMessage?: string;\n formatErrorMessage?: string;\n // 너비: 'hug' | 'fill' | 숫자(px) | CSS 길이. 미지정 시 120. 좁으면 말줄임(…).\n width?: number | string;\n className?: string;\n}\n\nexport function TimeField({\n value,\n defaultValue = null,\n onChange,\n placeholder = '시간을 선택하세요',\n disabled = false,\n readOnly = false,\n error = false,\n errorMessage = '',\n formatErrorMessage = 'HH:MM 형식으로 입력하세요',\n // 너비: 'hug' | 'fill' | 숫자(px) | CSS 길이. 미지정 시 120. 좁으면 말줄임(…).\n width,\n className = '',\n ...props\n}: TimeFieldProps) {\n const interactive = !disabled && !readOnly;\n const [open, setOpen] = useState(false);\n const [focused, setFocused] = useState(false);\n const [text, setText] = useState('');\n const [parseError, setParseError] = useState(false);\n // Enter/ESC의 동기 blur가 onBlur commit을 중복/오발화시키지 않게 1회 건너뛰는 플래그(DateField와 동일)\n const skipBlurCommitRef = useRef(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n const [truncRect, setTruncRect] = useState<DOMRect | null>(null);\n\n // 값 (controlled/uncontrolled)\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState(defaultValue);\n const current = value !== undefined ? value : internalValue;\n const applyValue = (v: string | null) => {\n if (!isControlled) setInternalValue(v);\n onChange?.(v);\n setParseError(false);\n };\n\n const display = current || '';\n const isEmpty = !display;\n const [hh, mm] = (current || '').split(':');\n\n // 직접 입력 확정(Enter/blur)\n const commit = (raw: string) => {\n const t = raw.trim();\n if (t === '') {\n applyValue(null);\n return;\n }\n const v = parseTime(t);\n if (!v) {\n setParseError(true);\n return;\n }\n applyValue(v);\n };\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 const iconColor = disabled\n ? 'text-text-field-disabled-icon'\n : readOnly\n ? 'text-text-field-readonly-icon'\n : isEmpty\n ? 'text-text-field-default-text'\n : 'text-font-icon-5';\n\n const showErr = error || parseError;\n const errMsg = parseError ? formatErrorMessage : errorMessage;\n\n // 너비 — 미지정 시 120. hug/fill 지원.\n const effWidth = width ?? 120;\n const isHug = effWidth === 'hug';\n const isFill = effWidth === 'fill';\n const fixedWidth = isHug || isFill ? undefined : typeof effWidth === 'number' ? `${effWidth}px` : effWidth;\n const shown = focused ? text : display;\n const hugSize = Math.max((shown || placeholder).length + 1, 1);\n\n const trigger = (\n <div\n className={`relative ${isFill ? 'w-full' : isHug ? 'w-fit' : ''}`}\n style={fixedWidth ? { width: fixedWidth } : undefined}\n >\n <div\n className={`group flex min-h-[32px] ${isHug ? 'w-fit' : 'w-full'} items-center gap-spacing-3 rounded-round-4 bg-text-field-default-bg px-spacing-6 py-spacing-3 transition-shadow ${\n interactive ? `cursor-text ${RING}` : disabled ? 'cursor-not-allowed' : 'cursor-default'\n }`}\n >\n <ClockIcon size={16} strokeWidth={1.8} className={`shrink-0 ${iconColor}`} />\n <input\n ref={inputRef}\n type=\"text\"\n value={focused ? text : display}\n placeholder={placeholder}\n readOnly={!interactive}\n disabled={disabled}\n size={isHug ? hugSize : undefined}\n aria-invalid={showErr || undefined}\n onClick={(e) => e.stopPropagation()}\n onMouseEnter={(e) => {\n const el = e.currentTarget;\n if (!focused && display && el.scrollWidth > el.clientWidth + 1) {\n setTruncRect(el.getBoundingClientRect());\n }\n }}\n onMouseLeave={() => setTruncRect(null)}\n onFocus={(e) => {\n if (!interactive) return;\n setFocused(true);\n setParseError(false);\n setTruncRect(null);\n setText(display);\n setOpen(true);\n e.target.select();\n }}\n onChange={(e) => {\n const v = Array.from(e.target.value).filter((c) => ALLOWED_CHARS.test(c)).join('');\n setText(v);\n if (parseError) setParseError(false);\n }}\n onBlur={() => {\n if (!interactive) return;\n setFocused(false);\n // Enter(이미 커밋)/ESC(취소) 직후의 blur는 커밋을 건너뛴다\n if (skipBlurCommitRef.current) {\n skipBlurCommitRef.current = false;\n return;\n }\n commit(text);\n }}\n onKeyDown={(e) => {\n if (!interactive) return;\n if (e.key === 'Enter') {\n e.preventDefault();\n commit(text);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n } else if (e.key === 'Escape') {\n // 취소: 초안을 버리고 기존 표시값으로 복원(커밋 금지)\n setText(display);\n setParseError(false);\n setOpen(false);\n skipBlurCommitRef.current = true;\n e.currentTarget.blur();\n }\n }}\n className={`bg-transparent text-14 outline-none placeholder:text-text-field-default-text disabled:cursor-not-allowed read-only:cursor-pointer ${textColor} ${\n isHug ? 'w-auto' : 'min-w-0 flex-1 text-ellipsis'\n }`}\n />\n </div>\n {showErr && errMsg && !open && (\n <div className=\"absolute left-0 top-full z-10 mt-spacing-2\">\n <Tooltip variant=\"error\" beak=\"top\">\n {errMsg}\n </Tooltip>\n </div>\n )}\n {truncRect &&\n !focused &&\n createPortal(\n <div\n className=\"pointer-events-none z-[1000]\"\n style={{\n position: 'fixed',\n left: truncRect.left,\n top: truncRect.top > 40 ? truncRect.top - 6 : truncRect.bottom + 6,\n transform: truncRect.top > 40 ? 'translateY(-100%)' : undefined,\n }}\n >\n <Tooltip variant=\"normal\" beak=\"none\">\n {display}\n </Tooltip>\n </div>,\n document.body,\n )}\n </div>\n );\n\n return (\n <Popover\n open={open}\n onOpenChange={(o: boolean) => {\n setOpen(o);\n // ESC 닫힘 시 입력 편집도 해제(취소+blur) — 바로 재클릭하면 focus로 즉시 재활성(DateField와 동일)\n if (!o && inputRef.current && document.activeElement === inputRef.current) {\n setText(display);\n setParseError(false);\n skipBlurCommitRef.current = true;\n inputRef.current.blur();\n }\n }}\n disabled={!interactive}\n placement=\"auto\"\n menuWidth={140}\n trigger={trigger}\n className={`${isFill ? 'w-full' : ''} ${className}`}\n {...props}\n >\n {() => (\n // 인풋 영역 없는 시/분 2depth 목록(트리거가 입력을 가지므로 showInput=false)\n <TwoDepthList\n showInput={false}\n width={140}\n leftOptions={HOURS}\n leftValue={hh}\n onLeftChange={(h: string | number) => applyValue(`${h}:${mm || '00'}`)}\n rightOptions={MINUTES}\n rightValue={mm}\n onRightChange={(m: string | number) => applyValue(`${hh || '00'}:${m}`)}\n maxVisible={5}\n />\n )}\n </Popover>\n );\n}\n"],"mappings":"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"}