chore(sw): remove selected Storybook demos

This commit is contained in:
shkim4 committed 2026-10-19 10:42:32 +09:00
1 parent 92830fe11d
commit 50bf5bda8e
11 files changed
+2387 -265

No files matched your search

+1 -260
View File
@@ -198,14 +198,6 @@ export const Overview: Story = {
),
}
export const Default: Story = {
render: args => <Frame width={1920}><SWHeader {...args} /></Frame>,
}
export const Controls: Story = {
render: args => <SWHeader {...args} />,
}
export const InteractiveStates: Story = {
name: "Interactive States",
parameters: { controls: { disable: true } },
@@ -260,26 +252,6 @@ export const InteractiveStates: Story = {
),
}
const RESPONSIVE_WIDTHS = [1920, 1800, 1799, 1770, 1600, 1440, 1280, 1180, 1179, 1080, 1079, 900, 899, 768, 640, 639, 560, 390]
export const Responsive: Story = {
parameters: { controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={KICKER}
title="Responsive"
description="필드는 모든 폭에서 Figma 폭을 유지하고, 공간이 부족하면 기능을 더보기로 옮깁니다. 헤더 폭 1800 미만: 선·문자·치수 스타일 / 1180 미만: 그리기모드 라벨 숨김 / 1080 미만: 속성Bar·속성창 / 900 미만: 색상 / 640 미만: 인쇄·팝업 가이드. 520 미만에서만 레이어명이 말줄임됩니다. 헤더 폭 기준이므로 각 프레임에서 그대로 확인할 수 있습니다."
/>
{RESPONSIVE_WIDTHS.map(width => (
<StorySection key={width} title={`${width}px`}>
<Frame width={width}><InteractiveHeader /></Frame>
</StorySection>
))}
</StoryPage>
),
}
/** Preserve GIF phase ratios/positions, with the user-requested 1640 -> 1400ms speed change. */
const motionTime = (gifTime: number) => gifTime * 1400 / 1640
const MOTION_STAGES = [
@@ -320,58 +292,12 @@ function MotionSpecimen({ time }: { time?: number }) {
)
}
/** Live header with a callback / animation log, to check drag and clicks under the motion. */
function MotionDemo({ width }: { width: number }) {
const [log, setLog] = useState<string[]>([])
const ref = useRef<HTMLDivElement>(null)
const add = (line: string) => setLog(lines => [...lines.slice(-9), line])
useEffect(() => {
const area = ref.current?.querySelector(".hm-sw-header__motion")
if (!area) return
// One tooltip animation per play stands for the whole motion.
const listen = (type: string, line: string) => {
const handler = (event: Event) => { if ((event as AnimationEvent).animationName.endsWith("-tooltip")) add(line) }
area.addEventListener(type, handler)
return () => area.removeEventListener(type, handler)
}
const cleanups = [listen("animationstart", "motion start"), listen("animationiteration", "motion cycle"), listen("animationend", "motion end"), listen("animationcancel", "motion reset")]
return () => cleanups.forEach(cleanup => cleanup())
}, [])
return (
<>
<p className="sw-story-log" aria-live="polite" data-callback-log="">기록: {log.length ? log.join(" → ") : "없음"}</p>
<div ref={ref}>
<Frame width={width}>
<SWHeader
{...figmaArgs}
{...callbacks}
onWindowMoveStart={() => { console.info("[SWHeader] windowMoveStart"); add("onWindowMoveStart") }}
onMinimize={() => { callbacks.onMinimize(); add("onMinimize") }}
onClose={() => { callbacks.onClose(); add("onClose") }}
/>
</Frame>
</div>
</>
)
}
export const WindowMoveMotion: Story = {
name: "Window Move Motion",
parameters: { controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={KICKER}
title="Window Move Motion"
description="ani_hover_move.gif의 이동 경로·구간 비율을 유지하고 사용자 요청으로 1640ms → 1400ms로 단축했습니다: 커서 상승 → 약 444ms 문구 등장 → 오른쪽 위로 이동 → 약 888ms부터 512ms 유지 → 1400ms 반복. 이탈 시 기본 SVG로 복귀하고 누르는 동안 모션 정지·문구 숨김을 유지합니다. 88 × 48 영역·줄무늬·SVG·Tooltip 규격은 그대로입니다."
/>
<StorySection title="GIF 주요 3상태 (정적, 3배 확대)" description="왼쪽부터 기존 Default · 문구 등장 약 444ms · 이동 완료 약 888ms. 창 제어 영역(창 이동 88 + 최소화·종료 64)만 공개 하위 컴포넌트로 표시합니다. Tooltip은 창 이동 영역 밖으로 나갈 수 있으며 포인터를 가로채지 않습니다.">
<div className="sw-story-row">
<MotionSpecimen />
<MotionSpecimen time={motionTime(520)} />
<MotionSpecimen time={motionTime(1040)} />
</div>
</StorySection>
<StoryPageHeader kicker={KICKER} title="Window Move Motion" />
<StorySection title="단계별 진행 (3배 확대)" description="CSS 애니메이션을 해당 시점에 멈춘 화면입니다(Storybook 전용 data-sb-hover + Web Animations API로 시간만 고정).">
<div className="sw-story-stages">
{MOTION_STAGES.map(stage => (
@@ -382,21 +308,6 @@ export const WindowMoveMotion: Story = {
))}
</div>
</StorySection>
<StorySection title="실제 Hover · 창 이동 · 클릭 (1920)" description="Hover 동안 1.4초마다 반복(motion cycle)하고 이탈하면 복귀합니다(motion reset). 누르는 동안 모션 일시정지·Tooltip 숨김으로 Drag를 방해하지 않습니다. onWindowMoveStart는 기존대로 한 번 호출되며 최소화·종료 클릭도 그대로 전달됩니다.">
<MotionDemo width={1920} />
</StorySection>
<StorySection title="이동 완료 실제 배치 (1920, 고정)" description="현재 이동 완료 약 888ms(GIF 1040ms와 같은 위치)를 고정합니다. Tooltip은 최소화·종료 일부에 겹치지만 헤더 안에 머물러 가로 스크롤을 만들지 않습니다.">
<Frame width={1920}><MotionAt time={motionTime(1040)}><SWHeader {...figmaArgs} {...callbacks} /></MotionAt></Frame>
</StorySection>
<StorySection title="헤더 1080 미만 (1079 고정 · 768 실제 Hover)" description="창 이동 영역이 24px로 줄어드는 단계에서는 아이콘 모션 공간이 없어 Tooltip만 표시합니다(영역 오른쪽, Figma hover2와 같은 관계 x = 영역 폭 + 1, y 11). 640 미만에서는 기존대로 창 이동 영역이 숨겨집니다.">
<div className="sw-story-stack">
<Frame width={1079}><MotionAt time={500}><SWHeader {...figmaArgs} {...callbacks} /></MotionAt></Frame>
<MotionDemo width={768} />
</div>
</StorySection>
<StorySection title="Reduced Motion" description="OS의 동작 줄이기(prefers-reduced-motion: reduce)가 켜져 있으면 기존 Default SVG를 유지하고, Hover 동안 정적 Tooltip만 표시됩니다. Drag 중에는 문구를 숨깁니다.">
<p className="sw-story-log">OS 설정을 켠 뒤 위 &apos;실제 Hover&apos; 예제에 마우스를 올려 확인합니다. Storybook은 이 미디어 조건을 강제로 켤 수 없습니다.</p>
</StorySection>
</StoryPage>
),
}
@@ -1093,47 +1004,6 @@ export const ResponsiveMore: Story = {
),
}
export const TextStroke: Story = {
name: "Text Stroke",
parameters: { controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={KICKER}
title="Text Stroke"
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>
</StorySection>
<StorySection title="3배 확대 — Default · Hover (고정) · Selected" description="12px 라벨 · 14px 값(한글·영문·숫자) · 16px 글꼴 미리보기 · 상세보기 토글.">
<div className="sw-story-zoom sw-story-stack">
<SWTheme className="sw-story-composed">
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} />
<SWHeaderColorControl value={figmaArgs.color!} />
<SWHeaderTextStyle value={figmaArgs.textStyle!} />
</SWTheme>
<PinHover selectors={[".hm-sw-header__field"]}>
<SWTheme className="sw-story-composed">
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} />
<SWHeaderColorControl value={figmaArgs.color!} />
<SWHeaderTextStyle value={figmaArgs.textStyle!} />
</SWTheme>
</PinHover>
<SWTheme className="sw-story-composed">
<SWHeaderLayerControl value={{ name: "레이어_Layer_0123" }} selected />
<SWHeaderColorControl value={figmaArgs.color!} selected />
<SWHeaderProperties barOpen panelOpen />
</SWTheme>
</div>
</StorySection>
<StorySection title="긴 텍스트 말줄임" description="말줄임 텍스트는 Stroke가 잘리지 않도록 2px 여유(overflow-clip-margin)를 둡니다.">
<Frame width={1440}><SWHeader {...figmaArgs} {...callbacks} documentTitle="C0020101-006-평면및종단면도(06)-수정본-최종-검토용-2026-10-16.dwg" layer={{ name: "Sample-매우-긴-레이어-이름-Ellipsis-확인용-Layer-Name" }} /></Frame>
</StorySection>
</StoryPage>
),
}
/** Host-owned detail values for the editable input (the detail popup itself is not designed yet). */
function InputDemo() {
const [value, setValue] = useState("4.0526")
@@ -1229,135 +1099,6 @@ export const LayerDetailInput: Story = {
),
}
/* Dynamic data: every list is host data. Adding, removing, renaming, revaluing and reordering items
needs no component change; ids keep each row's state; a removed current item leaves no selection. */
type DynamicLists = {
layers: SWHeaderLayer[]
colors: SWHeaderColorOption[]
userColors: SWHeaderColorOption[]
lineTypes: SWHeaderLineType[]
lineWeights: SWHeaderLineWeight[]
textStyles: SWHeaderTextStyleOption[]
dimensionStyles: SWHeaderDimensionStyleOption[]
documents: SWHeaderDocument[]
}
type DynamicKey = keyof DynamicLists
const DYNAMIC_INITIAL: DynamicLists = {
layers: SAMPLE_LAYERS.slice(0, 4),
colors: FIGMA_COLORS,
userColors: FIGMA_USER_COLORS,
lineTypes: FIGMA_LINE_TYPES,
lineWeights: FIGMA_LINE_WEIGHTS,
textStyles: FIGMA_TEXT_STYLES,
dimensionStyles: FIGMA_DIMENSION_STYLES,
documents: CURRENT_DOCUMENT,
}
const DYNAMIC_LABELS: Record<DynamicKey, string> = {
layers: "레이어", colors: "기본 색상", userColors: "사용자 색상", lineTypes: "선 종류", lineWeights: "가중치",
textStyles: "문자스타일", dimensionStyles: "치수스타일", documents: "문서",
}
/** New sample item of each list (`n` keeps ids unique). */
const DYNAMIC_FACTORY: { [K in DynamicKey]: (n: number) => DynamicLists[K][number] } = {
layers: n => ({ id: `new-layer-${n}`, name: `New-Layer-${n}`, color: "#ffa500" }),
colors: n => ({ id: `color-${n}`, color: `hsl(${(n * 67) % 360} 80% 55%)`, label: `추가 색상 ${n}` }),
userColors: n => ({ id: `user-color-${n}`, color: `hsl(${(n * 83) % 360} 70% 60%)`, label: `사용자 색상 추가 ${n}` }),
lineTypes: n => ({ id: `line-${n}`, name: `DASH-${n}`, pattern: [12, 4, 12, 4] }),
lineWeights: n => ({ id: `weight-${n}`, name: `${(0.05 * n).toFixed(2)} mm`, value: (0.05 * n).toFixed(2) }),
textStyles: n => ({ id: `text-${n}`, font: "굴림", name: `Text-${n}` }),
dimensionStyles: n => ({ id: `dim-${n}`, name: `DIM-${n}` }),
documents: n => ({ id: `doc-${n}`, name: `Sample-New-${n}.dwg` }),
}
const renameFirst = <T extends { name?: string; label?: string }>(list: T[]) =>
list.map((item, index) => (index !== 0 ? item : "name" in item ? { ...item, name: `${item.name}*` } : { ...item, label: `${item.label ?? ""}*` }))
function DynamicDataDemo() {
const [lists, setLists] = useState(DYNAMIC_INITIAL)
const [active, setActive] = useState({ layer: "layer-0", lineType: "continuous", lineWeight: "w-default", textStyle: "standard", dimensionStyle: "standard", document: "doc-current" })
const [lineStyle, setLineStyle] = useState<SWHeaderLineStyleValue>(figmaArgs.lineStyle!)
const [log, setLog] = useState<string[]>([])
const counter = useRef(0)
const add = (line: string) => setLog(lines => [...lines.slice(-9), line])
const update = <K extends DynamicKey>(key: K, next: (list: DynamicLists[K]) => DynamicLists[K], line: string) => {
setLists(current => ({ ...current, [key]: next(current[key]) }))
add(line)
}
const keys = Object.keys(lists) as DynamicKey[]
const currentDocument = lists.documents.find(item => item.id === active.document)
return (
<>
<div className="sw-story-dynamic">
{keys.map(key => (
<div key={key} className="sw-story-controls" data-dynamic-list={key}>
<strong>{DYNAMIC_LABELS[key]} ({lists[key].length})</strong>
<button type="button" onClick={() => update(key, list => [...list, DYNAMIC_FACTORY[key](++counter.current)] as typeof list, `${key}: 추가`)}>추가</button>
<button type="button" onClick={() => update(key, list => list.slice(1) as typeof list, `${key}: 첫 항목 삭제`)}>첫 항목 삭제</button>
<button type="button" onClick={() => update(key, list => renameFirst(list as { name?: string; label?: string }[]) as typeof list, `${key}: 첫 항목 이름 변경`)}>이름 변경</button>
<button type="button" onClick={() => update(key, list => [...list].reverse() as typeof list, `${key}: 순서 뒤집기`)}>순서 뒤집기</button>
</div>
))}
<div className="sw-story-controls">
<strong>값 · 상태 변경</strong>
<button type="button" onClick={() => update("lineWeights", list => list.map(item => (item.editable ? { ...item, value: "0.50" } : item)), "lineWeights: User = 0.50")}>User 가중치 0.50</button>
<button type="button" onClick={() => update("layers", list => list.map((item, index) => (index === 1 ? { ...item, visible: item.visible === false } : item)), "layers: 2번째 표시 반전")}>2번째 레이어 표시 반전</button>
<button type="button" onClick={() => update("lineTypes", list => list.map(item => (item.id === "continuous" ? { ...item, disabled: !item.disabled } : item)), "lineTypes: Continuous disabled 반전")}>Continuous Disabled</button>
</div>
</div>
<p className="sw-story-log" aria-live="polite" data-callback-log="">기록: {log.length ? log.join(" → ") : "없음"}</p>
<pre className="sw-story-layer-state" data-dynamic-state="">{`active: ${JSON.stringify(active)}`}</pre>
<MenuFrame width={1920}>
<SWHeader
{...figmaArgs}
{...callbacks}
documentTitle={currentDocument?.name ?? "열린 문서 없음"}
documents={lists.documents}
activeDocumentId={active.document}
onDocumentSelect={document => { add(`onDocumentSelect(${document.id})`); setActive(current => ({ ...current, document: document.id })) }}
onDocumentClose={document => update("documents", list => list.filter(item => item.id !== document.id), `onDocumentClose(${document.id})`)}
layers={lists.layers}
activeLayerId={active.layer}
onLayerSelect={layer => { add(`onLayerSelect(${layer.id})`); setActive(current => ({ ...current, layer: layer.id })) }}
onLayerVisibilityChange={(layer, next) => update("layers", list => list.map(item => (item.id === layer.id ? { ...item, visible: next } : item)), `onLayerVisibilityChange(${layer.id}, ${next})`)}
colors={lists.colors}
userColors={lists.userColors}
onColorSelect={option => add(`onColorSelect(${option.id})`)}
lineStyle={lineStyle}
lineTypes={lists.lineTypes}
lineWeights={lists.lineWeights}
activeLineTypeId={active.lineType}
activeLineWeightId={active.lineWeight}
onLineTypeSelect={type => { add(`onLineTypeSelect(${type.id})`); setActive(current => ({ ...current, lineType: type.id })); setLineStyle(current => ({ ...current, name: type.name })) }}
onLineWeightSelect={weight => { add(`onLineWeightSelect(${weight.id})`); setActive(current => ({ ...current, lineWeight: weight.id })); setLineStyle(current => ({ ...current, weight: weight.value ?? weight.name })) }}
onLineWeightValueChange={(weight, value) => update("lineWeights", list => list.map(item => (item.id === weight.id ? { ...item, value } : item)), `onLineWeightValueChange(${weight.id}, ${value})`)}
onLineScaleChange={value => { add(`onLineScaleChange(${value})`); setLineStyle(current => ({ ...current, scale: value })) }}
textStyles={lists.textStyles}
activeTextStyleId={active.textStyle}
onTextStyleSelect={style => { add(`onTextStyleSelect(${style.id})`); setActive(current => ({ ...current, textStyle: style.id })) }}
dimensionStyles={lists.dimensionStyles}
activeDimensionStyleId={active.dimensionStyle}
onDimensionStyleSelect={style => { add(`onDimensionStyleSelect(${style.id})`); setActive(current => ({ ...current, dimensionStyle: style.id })) }}
/>
</MenuFrame>
</>
)
}
export const DynamicData: Story = {
name: "Dynamic Data",
parameters: { controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={KICKER}
title="Dynamic Data"
description="모든 Dropdown 항목은 호스트 데이터(id · 이름 · 값 · 순서 · disabled / editable)로 렌더링합니다. 아래 버튼으로 항목을 추가·삭제·이름 변경·값 변경·순서 변경해도 컴포넌트 소스 수정이 필요 없고, id 기준으로 해당 항목만 갱신됩니다. 현재 항목을 삭제하면 선택 표시가 없어지고(다음 항목 결정은 호스트) 필드는 값 Props를 그대로 표시합니다. 목록이 길면 Dropdown 안에서만 스크롤합니다."
/>
<StorySection title="항목 추가 · 삭제 · 이름 · 값 · 순서 변경" description="Dropdown을 연 채로 버튼을 눌러도 됩니다(버튼은 Storybook 전용 조작 UI, 헤더 스타일과 무관). 열린 목록은 버튼 클릭(바깥 클릭)으로 닫히므로 다시 열어 확인합니다.">
<DynamicDataDemo />
</StorySection>
</StoryPage>
),
}
/* Additional review 2026-10-16: fixture values are host data, never package defaults. */
const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [
{ id: "common", collapsedCount: 6 },