chore(sw): organize Storybook components and documentation

First-pass cleanup only; no design, behavior, public API or Story ID change.
- Stories: correct stale Text Stroke / Colorize / "latest" texts, reuse
  parseSWHeaderColor, derive the field fixture from PROPERTY_FIXTURE
- Comments: date the "Latest request" notes in sw.css and the color menu
- Docs: fix removed text-stroke tokens in spec/usage/README, add spec §18
  (property fields), current-state index banners, Storybook 33 pointer
- Add docs/sw/sw-first-pass-cleanup-review-2026-10-19.md (Story table,
  structure, SAFE / REVIEW list for the second pass)

Checks: SW + app typecheck, SW build, Storybook build, 33 Story IDs
unchanged, property fields 77/77, detail-buttons verify 257 checks, 111 doc
links 0 broken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shkim4andClaude Opus 5.5 committed 2026-10-19 09:52:46 +09:00
1 parent 0029cef60c
commit 92830fe11d
7 files changed
+267 -39

No files matched your search

+11 -25
View File
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
SWHeader, SWHeaderColorControl, SWHeaderLayerControl, SWHeaderLineStyle, SWHeaderTextStyle, SWHeaderDimensionStyle,
SWHeaderProperties, SWHeaderWindowControls, SWTheme, SWHeaderLayerMenu, SWHeaderInput, swHeaderLinePatterns, SWHeaderPropertyPanel, SWHeaderPropertyBar,
SWHeaderProperties, SWHeaderWindowControls, SWTheme, SWHeaderLayerMenu, SWHeaderInput, parseSWHeaderColor, swHeaderLinePatterns, SWHeaderPropertyPanel, SWHeaderPropertyBar,
type SWHeaderColorMode, type SWHeaderColorOption, type SWHeaderColorValue, type SWHeaderDimensionStyleOption,
type SWHeaderDocument, type SWHeaderLayer, type SWHeaderLineStyleValue, type SWHeaderLineType, type SWHeaderLineWeight,
type SWHeaderMenuId, type SWHeaderProps, type SWHeaderTextStyleOption, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderControlConfig, type SWHeaderHideableControlId, type SWHeaderPropertyShapeContent,
@@ -852,13 +852,6 @@ const FIGMA_DIMENSION_STYLES: SWHeaderDimensionStyleOption[] = [
type StyleHostProps = Partial<SWHeaderProps> & { onLog?: (line: string) => void; showState?: boolean; initialColorMode?: SWHeaderColorMode | null }
/** `#rrggbb` → `r,g,b` (sample data helper; other CSS colors are passed through). */
function hexToRgbText(color: string) {
const match = /^#([0-9a-f]{6})$/i.exec(color)
if (!match) return color
return [0, 2, 4].map(index => parseInt(match[1].slice(index, index + 2), 16)).join(",")
}
/**
* Host-owned field data: the SW program keeps color, line / text / dimension style and the lists. The
* header only reports; this example writes each report back (an explicit color clears ByLayer / ByBlock,
@@ -891,7 +884,7 @@ function StyleHeader({ onLog, showState, initialColorMode = null, ...props }: St
colorMode={colorMode}
onColorOpen={() => log("onColorOpen")}
onColorMenuOpenChange={open => log(`onColorMenuOpenChange(${open})`)}
onColorSelect={option => { log(`onColorSelect(${option.id})`); setColor({ value: option.value ?? hexToRgbText(option.color), swatch: option.color }); setColorMode(null) }}
onColorSelect={option => { log(`onColorSelect(${option.id})`); setColor({ value: option.value ?? parseSWHeaderColor(option.color)?.value ?? option.color, swatch: option.color }); setColorMode(null) }}
onColorModeChange={mode => { log(`onColorModeChange(${mode})`); setColorMode(mode); setColor(current => ({ ...current, value: mode === "byLayer" ? "ByLayer" : "ByBlock" })) }}
onColorInput={value => { log(`onColorInput(${value.value})`); setColor({ value: value.value, swatch: value.hex }); setColorMode(null) }}
onColorPick={() => log("onColorPick")}
@@ -980,7 +973,7 @@ export const ColorDropdown: Story = {
<StorySection title="ByLayer On (Figma on 26:12671)" description="colorMode='byLayer': #62533D 배경 + 밝은 inner shadow. 텍스트 #D4CBBD + 검정 Stroke. ByBlock은 Default(rgba(98,83,61,.3) + 어두운 inner shadow).">
<MenuFrame width={1920}><StyleHeader initialColorMode="byLayer" colorMenuOpen /></MenuFrame>
</StorySection>
<StorySection title="내부 버튼 Hover (고정) — 최신 예외 정책" description="ByLayer / ByBlock 글자 #FF5C00. 선택 배경과 Inset Shadow는 유지합니다. 색상 상세는 배경 .25·radius 25·Hover 아이콘으로 변합니다. 색상 칩·추출 버튼은 기존 정책을 유지합니다.">
<StorySection title="내부 버튼 Hover (고정) — 2026-10-16 추가 수정 정책" description="ByLayer / ByBlock 글자 #FF5C00. 선택 배경과 Inset Shadow는 유지합니다. 색상 상세는 배경 .25·radius 25·Hover 아이콘으로 변합니다. 색상 칩은 Hover가 없고, 색상 추출은 btn_colorize Hover(26:11305)입니다.">
<MenuFrame width={1920}><PinHover selectors={MENU_BUTTON_HOVER}><StyleHeader colorMenuOpen /></PinHover></MenuFrame>
</StorySection>
<StorySection title="RGB 입력 Hover (고정)" description="입력은 수정 가능함을 알리는 Figma Hover를 유지합니다(header/menu/색상/input hover: 1px rgba(197,177,158,.2)). 편집 중(Focus)에는 값 영역이 검정이 되고 커서는 #E60403입니다 — 아래 실제 예제에서 클릭해 확인합니다.">
@@ -1022,7 +1015,7 @@ export const LineStyleDropdown: Story = {
<StorySection title="현재 행 Hover (고정)" description="현재 선 종류(Continuous)는 Hover 및 Mouse Leave 뒤에도 갈색 배경·주황 선·이름을 유지합니다.">
<MenuFrame width={1920}><PinHover selectors={[".hm-sw-header__menu-row[data-active]"]}><StyleHeader lineStyleMenuOpen /></PinHover></MenuFrame>
</StorySection>
<StorySection title="상세 버튼 Hover (고정)" description="최신 요청에 따라 선스타일 상세의 Figma 배경 .25·radius 25·Hover 아이콘을 적용합니다.">
<StorySection title="상세 버튼 Hover (고정)" description="2026-10-16 추가 수정 기준으로 선스타일 상세의 Figma 배경 .25·radius 25·Hover 아이콘을 적용합니다.">
<MenuFrame width={1920}><PinHover selectors={MENU_BUTTON_HOVER}><StyleHeader lineStyleMenuOpen /></PinHover></MenuFrame>
</StorySection>
<StorySection title="선 종류 · 가중치 · 스케일 선택 Callback" description="선 종류 → onLineTypeSelect(가중치·스케일 유지), 가중치 → onLineWeightSelect(선 종류·스케일 유지), User 입력 → onLineWeightValueChange(0 이상), 스케일 입력 → onLineScaleChange(0 초과). 입력은 Enter/Focus 이동으로 확정, Escape는 취소. ↓↑로 항목 이동.">
@@ -1108,7 +1101,7 @@ export const TextStroke: Story = {
<StoryPageHeader
kicker={KICKER}
title="Text Stroke"
description="Figma 26:8944와 메뉴의 텍스트 Stroke(모두 OUTSIDE)를 복원했습니다: 14 · 16px = 검정 1.3px + 자기 색 0.3px, 12px 라벨 = 검정 1px, '… 상세' = 자기 색 0.4px(검정 없음). 글꼴·크기·굵기·자간·기준선·말줄임은 그대로이며 Hover/Selected는 채움색만 바뀝니다."
description="Figma 26:8944와 메뉴의 텍스트 Stroke(모두 OUTSIDE) 중 검정 외곽선을 text-shadow로 표시합니다: 14 · 16px 값 = 검정 1.3px, 12px 라벨 = 검정 1px. 자기 색 Stroke(0.3px · '… 상세' 0.4px)는 2026-10-16 추가 수정으로 제거했습니다. 글꼴·크기·굵기·자간·기준선·말줄임은 그대로이며 Hover/Selected는 채움색만 바뀝니다."
/>
<StorySection title="구현 (1920)">
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} /></Frame>
@@ -1800,19 +1793,12 @@ const FIELD_NODES = [
{ node: "26:12590", name: "두께", states: "Default · Hover · On(편집)" },
{ node: "26:12574", name: "재료", states: "Default · Hover (Figma On 없음)" },
]
const FIELD_FIXTURE: SWHeaderProperty[] = [
{ id: "layer-mixed", name: "레이어", value: "0_Layer_Cludmark", mixed: true, type: "text", groupId: "common", picker: "layer" },
{ id: "layer", name: "레이어", value: "0_Layer_Cludmark", type: "text", groupId: "common", picker: "layer" },
// Figma sample text 255,259,0 is out of range: shown as host text only, never accepted as input.
{ id: "color", name: "색상", value: "255,259,0", swatch: "#ffe500", type: "color", groupId: "common" },
{ id: "line", name: "선스타일", value: "Continuous", type: "select", groupId: "common", unit: "(ByL)", linePreview: true, options: PROPERTY_LINE_TYPES.map(({ id, name }) => ({ id, name })) },
{ id: "weight", name: "선가중치", value: "0.25", type: "number", groupId: "common", min: 0 },
{ id: "scale", name: "선축적", value: "1.0000", type: "number", groupId: "common", min: .0001, decimals: 4, unit: "(LTscale:1)" },
{ id: "opacity", name: "투명도", value: "0.25", type: "slider", groupId: "common", min: 0, max: 1, step: .01, annotation: "(ByL)", unit: "%" },
{ id: "link", name: "하이퍼링크", value: "--", type: "text", groupId: "common" },
{ id: "thickness", name: "두께", value: "0.25", type: "number", groupId: "common", unit: "mm", min: 0 },
{ id: "material", name: "재료", value: "-", type: "computed", groupId: "common", unit: "(ByL)" },
]
// The common rows of PROPERTY_FIXTURE plus a mixed layer row. Figma sample text 255,259,0 is out of range:
// shown as host text only, never accepted as input.
const FIELD_FIXTURE: SWHeaderProperty[] = PROPERTY_FIXTURE.filter(item => item.groupId === "common").flatMap(item =>
item.id === "layer" ? [{ ...item, id: "layer-mixed", mixed: true }, item]
: item.id === "color" ? [{ ...item, value: "255,259,0", swatch: "#ffe500" }]
: [item])
const FIELD_HOVER = [".hm-sw-property__value[data-field]", ".hm-sw-property__compact"]
function FieldPanel({ title, properties, onChange, menus }: { title: string; properties: SWHeaderProperty[]; onChange?: (property: SWHeaderProperty, value: string) => void; menus?: SWHeaderPropertyMenus }) {
return <SWTheme><SWHeaderPropertyPanel selection={{ id: title, title, kind: "none" }} properties={properties} groups={[{ id: "common" }]} onPropertyChange={onChange} menus={menus} /></SWTheme>