chore(sw): improve interactive header story preview

This commit is contained in:
shkim4 committed 2026-10-19 11:12:07 +09:00
1 parent 50bf5bda8e
commit bf16578226
58 files changed
+6028 -15

No files matched your search

@@ -0,0 +1,423 @@
[
{
"type": "story",
"subtype": "story",
"id": "sw-header--detail-buttons",
"name": "Detail Buttons (Shared States)",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest",
"verification"
],
"exportName": "DetailButtons"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--overview",
"name": "Overview",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Overview"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--interactive-states",
"name": "Interactive States",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "InteractiveStates"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--window-move-motion",
"name": "Window Move Motion",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "WindowMoveMotion"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--composed-controls",
"name": "Composed Controls",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ComposedControls"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--logo-slot",
"name": "Logo Slot",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LogoSlot"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--document-dropdown",
"name": "Document Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "DocumentDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--layer-dropdown",
"name": "Layer Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LayerDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--color-dropdown",
"name": "Color Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ColorDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--line-style-dropdown",
"name": "Line Style Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LineStyleDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--text-dimension-style-dropdown",
"name": "Text · Dimension Style Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "TextDimensionStyleDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--responsive-more",
"name": "Responsive More",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ResponsiveMore"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--layer-detail-input",
"name": "Layer Detail · Input",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LayerDetailInput"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel",
"name": "Property Panel",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanel"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-variants",
"name": "Property Panel Variants",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelVariants"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--k-guide",
"name": "K Guide",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "KGuide"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--additional-hover",
"name": "Additional Hover",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "AdditionalHover"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-bar",
"name": "Property Bar",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyBar"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--dynamic-controls",
"name": "Dynamic Controls",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "DynamicControls"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--colorize",
"name": "Colorize",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Colorize"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--composition",
"name": "Composition (Visibility · Order)",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Composition"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-none",
"name": "Property Panel · 선택 없음",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelNone"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-all",
"name": "Property Panel · 전체 선택",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelAll"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-circle",
"name": "Property Panel · 원 1개",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelCircle"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-expanded",
"name": "Property Panel · 모두 펼침",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelExpanded"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-compare",
"name": "Property Panel · 4개 비교",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelCompare"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-shape-slot",
"name": "Property Panel · 형상 Slot",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyShapeSlot"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-fields",
"name": "Property Panel · 필드 13개",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyFields"
}
]
@@ -0,0 +1,423 @@
[
{
"type": "story",
"subtype": "story",
"id": "sw-header--detail-buttons",
"name": "Detail Buttons (Shared States)",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest",
"verification"
],
"exportName": "DetailButtons"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--overview",
"name": "Overview",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Overview"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--interactive-states",
"name": "Interactive States",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "InteractiveStates"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--window-move-motion",
"name": "Window Move Motion",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "WindowMoveMotion"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--composed-controls",
"name": "Composed Controls",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ComposedControls"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--logo-slot",
"name": "Logo Slot",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LogoSlot"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--document-dropdown",
"name": "Document Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "DocumentDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--layer-dropdown",
"name": "Layer Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LayerDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--color-dropdown",
"name": "Color Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ColorDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--line-style-dropdown",
"name": "Line Style Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LineStyleDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--text-dimension-style-dropdown",
"name": "Text · Dimension Style Dropdown",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "TextDimensionStyleDropdown"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--responsive-more",
"name": "Responsive More",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "ResponsiveMore"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--layer-detail-input",
"name": "Layer Detail · Input",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "LayerDetailInput"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel",
"name": "Property Panel",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanel"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-variants",
"name": "Property Panel Variants",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelVariants"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--k-guide",
"name": "K Guide",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "KGuide"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--additional-hover",
"name": "Additional Hover",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "AdditionalHover"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-bar",
"name": "Property Bar",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyBar"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--dynamic-controls",
"name": "Dynamic Controls",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "DynamicControls"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--colorize",
"name": "Colorize",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Colorize"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--composition",
"name": "Composition (Visibility · Order)",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "Composition"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-none",
"name": "Property Panel · 선택 없음",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelNone"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-all",
"name": "Property Panel · 전체 선택",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelAll"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-circle",
"name": "Property Panel · 원 1개",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelCircle"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-expanded",
"name": "Property Panel · 모두 펼침",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelExpanded"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-panel-compare",
"name": "Property Panel · 4개 비교",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyPanelCompare"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-shape-slot",
"name": "Property Panel · 형상 Slot",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyShapeSlot"
},
{
"type": "story",
"subtype": "story",
"id": "sw-header--property-fields",
"name": "Property Panel · 필드 13개",
"title": "SW/Header",
"importPath": "./src/sw/sw-header.stories.tsx",
"componentPath": "./packages/sw/dist/index.js",
"tags": [
"dev",
"test",
"manifest"
],
"exportName": "PropertyFields"
}
]
@@ -0,0 +1,234 @@
[
{
"status": " M",
"path": "src/sw/sw-header.stories.tsx"
},
{
"status": " M",
"path": "src/sw/sw-stories.css"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/after-index.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/before-index.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/chrome-overlays-result.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/chrome-result.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/firefox-initial-infrastructure-result.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/firefox-result.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/initial-verifier-failure.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/manager-story-order.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-after-expanded.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-after-scrolled.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-after.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-before.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-firefox-1400.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-firefox-500.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-manager.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-narrow-overlay.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/overview-narrow.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/review-candidates.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screen-survey.json"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--additional-hover.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--color-dropdown.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--colorize.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--composed-controls.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--composition.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--detail-buttons.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--document-dropdown.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--dynamic-controls.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--interactive-states.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--k-guide.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--layer-detail-input.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--layer-dropdown.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--line-style-dropdown.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--logo-slot.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--overview.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-bar.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-fields.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-all.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-circle.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-compare.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-expanded.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-none.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel-variants.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-panel.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--property-shape-slot.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--responsive-more.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--text-dimension-style-dropdown.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/screens/sw-header--window-move-motion.png"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/storybook-build.txt"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/sw-build.txt"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/sw-typecheck.txt"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/validation-summary.json"
},
{
"status": "??",
"path": "docs/sw/sw-storybook-screen-review-2026-10-19.md"
},
{
"status": "??",
"path": "packages/sw/tests/overview-firefox.browser.mjs"
},
{
"status": "??",
"path": "packages/sw/tests/overview.browser.mjs"
},
{
"status": "??",
"path": "src/sw/sw-header-overview.tsx"
},
{
"status": "??",
"path": "docs/sw/storybook-overview-qa/changed-files.json"
}
]
@@ -0,0 +1,104 @@
{
"baseCommit": "50bf5bda8e956a4973b923d64131f2f2285bd38c",
"checks": [
{
"name": "all 28 Story IDs, names, order and tags retained",
"status": "PASS"
},
{
"name": "other 27 Story declarations byte-identical",
"status": "PASS"
},
{
"name": "package production source untouched",
"status": "PASS"
},
{
"name": "document first row is above the property Bar and clickable",
"status": "PASS"
},
{
"name": "existing 레이어 상세 reports Callback",
"status": "PASS"
},
{
"name": "existing 색상 상세 reports Callback",
"status": "PASS"
},
{
"name": "existing 선스타일 상세 reports Callback",
"status": "PASS"
},
{
"name": "existing 문자스타일 상세 reports Callback",
"status": "PASS"
},
{
"name": "existing 치수스타일 상세 reports Callback",
"status": "PASS"
},
{
"name": "ByBlock mode immediately appears in Header without changing numeric RGB property",
"status": "PASS"
},
{
"name": "direct color selection clears Header mode label",
"status": "PASS"
},
{
"name": "small Inner layer dropdown stays in browser width",
"status": "PASS",
"data": {
"x": 141,
"y": 345.0625,
"width": 234,
"height": 250
}
},
{
"name": "small Inner lower layer row hit-test is unobstructed",
"status": "PASS"
},
{
"name": "small Inner layer selection or Escape closes only nested dropdown",
"status": "PASS"
},
{
"name": "small Inner line dropdown stays in browser width",
"status": "PASS",
"data": {
"x": 186,
"y": 39.0625,
"width": 189.1875,
"height": 342
}
},
{
"name": "small Inner line selection or Escape closes only nested dropdown",
"status": "PASS"
},
{
"name": "small Inner color dropdown stays in browser width",
"status": "PASS",
"data": {
"x": 175,
"y": 377.0625,
"width": 192,
"height": 182
}
},
{
"name": "small Inner color selection or Escape closes only nested dropdown",
"status": "PASS"
},
{
"name": "no overlay browser errors",
"status": "PASS",
"data": []
}
],
"errors": [],
"sizes": [],
"browser": "155.0.8059.80",
"status": "PASS"
}
@@ -0,0 +1,670 @@
{
"baseCommit": "50bf5bda8e956a4973b923d64131f2f2285bd38c",
"checks": [
{
"name": "all 28 Story IDs, names, order and tags retained",
"status": "PASS"
},
{
"name": "other 27 Story declarations byte-identical",
"status": "PASS"
},
{
"name": "package production source untouched",
"status": "PASS"
},
{
"name": "16:9 fitted to both Inner dimensions at 1920x1080",
"status": "PASS",
"data": {
"stage": {
"x": 360,
"y": 264.25,
"w": 1200,
"h": 675,
"right": 1560,
"bottom": 939.25
},
"slot": {
"x": 360,
"y": 169.5,
"w": 1200,
"h": 864.5,
"right": 1560,
"bottom": 1034
},
"header": {
"x": 360,
"y": 264.25,
"w": 1200,
"h": 49,
"right": 1560,
"bottom": 313.25
},
"viewport": {
"w": 1920,
"h": 1080
},
"overflow": 0
}
},
{
"name": "16:9 fitted to both Inner dimensions at 1440x900",
"status": "PASS",
"data": {
"stage": {
"x": 120,
"y": 174.25,
"w": 1200,
"h": 675,
"right": 1320,
"bottom": 849.25
},
"slot": {
"x": 120,
"y": 169.5,
"w": 1200,
"h": 684.5,
"right": 1320,
"bottom": 854
},
"header": {
"x": 120,
"y": 174.25,
"w": 1200,
"h": 49,
"right": 1320,
"bottom": 223.25
},
"viewport": {
"w": 1440,
"h": 900
},
"overflow": 0
}
},
{
"name": "16:9 fitted to both Inner dimensions at 1280x720",
"status": "PASS",
"data": {
"stage": {
"x": 191.5625,
"y": 169.5,
"w": 896.875,
"h": 504.5,
"right": 1088.4375,
"bottom": 674
},
"slot": {
"x": 38.40625,
"y": 169.5,
"w": 1203.1875,
"h": 504.5,
"right": 1241.59375,
"bottom": 674
},
"header": {
"x": 191.5625,
"y": 169.5,
"w": 896.875,
"h": 49,
"right": 1088.4375,
"bottom": 218.5
},
"viewport": {
"w": 1280,
"h": 720
},
"overflow": 0
}
},
{
"name": "16:9 fitted to both Inner dimensions at 768x600",
"status": "PASS",
"data": {
"stage": {
"x": 42.21875,
"y": 169.5,
"w": 683.546875,
"h": 384.5,
"right": 725.765625,
"bottom": 554
},
"slot": {
"x": 23.046875,
"y": 169.5,
"w": 721.90625,
"h": 384.5,
"right": 744.953125,
"bottom": 554
},
"header": {
"x": 42.21875,
"y": 169.5,
"w": 683.546875,
"h": 49,
"right": 725.765625,
"bottom": 218.5
},
"viewport": {
"w": 768,
"h": 600
},
"overflow": 0
}
},
{
"name": "16:9 fitted to both Inner dimensions at 390x844",
"status": "PASS",
"data": {
"stage": {
"x": 16,
"y": 383.0625,
"w": 358,
"h": 201.375,
"right": 374,
"bottom": 584.4375
},
"slot": {
"x": 16,
"y": 169.5,
"w": 358,
"h": 628.5,
"right": 374,
"bottom": 798
},
"header": {
"x": 16,
"y": 383.0625,
"w": 390,
"h": 49,
"right": 406,
"bottom": 432.0625
},
"viewport": {
"w": 390,
"h": 844
},
"overflow": 0
}
},
{
"name": "16:9 fitted to both Inner dimensions at 1000x500",
"status": "PASS",
"data": {
"stage": {
"x": 247.109375,
"y": 169.5,
"w": 505.765625,
"h": 284.5,
"right": 752.875,
"bottom": 454
},
"slot": {
"x": 30,
"y": 169.5,
"w": 940,
"h": 284.5,
"right": 970,
"bottom": 454
},
"header": {
"x": 247.109375,
"y": 169.5,
"w": 505.765625,
"h": 49,
"right": 752.875,
"bottom": 218.5
},
"viewport": {
"w": 1000,
"h": 500
},
"overflow": 0
}
},
{
"name": "one Header and no duplicate fixed Figma specimen",
"status": "PASS"
},
{
"name": "document selection updates Header and Callback",
"status": "PASS",
"data": "onDocumentClick → documentOpen(true) → onDocumentSelect(doc-2) → documentOpen(false)"
},
{
"name": "layer selection updates shared data",
"status": "PASS",
"data": "onDocumentClick → documentOpen(true) → onDocumentSelect(doc-2) → documentOpen(false) → onLayerOpen → onLayerSelect(layer, CC-D GNL)"
},
{
"name": "existing layer Detail callback closes list",
"status": "PASS"
},
{
"name": "color selection invokes host callback",
"status": "PASS",
"data": "onLayerOpen → onLayerSelect(layer, CC-D GNL) → onLayerOpen → onLayerDetail → onColorOpen → onColorSelect(color, 255,255,255)"
},
{
"name": "line selection updates Header",
"status": "PASS",
"data": "onLayerOpen → onLayerDetail → onColorOpen → onColorSelect(color, 255,255,255) → onLineStyleOpen → onLineTypeSelect(line, CENTER)"
},
{
"name": "ByLayer weight mode does not turn numeric property into a mode string",
"status": "PASS"
},
{
"name": "text style selection updates Header",
"status": "PASS",
"data": "onColorSelect(color, 255,255,255) → onLineStyleOpen → onLineTypeSelect(line, CENTER) → onLineWeightSelect(w-bylayer) → onTextStyleOpen → onTextStyleSelect(dotum)"
},
{
"name": "dimension style selection updates Header",
"status": "PASS",
"data": "onLineTypeSelect(line, CENTER) → onLineWeightSelect(w-bylayer) → onTextStyleOpen → onTextStyleSelect(dotum) → onDimensionStyleOpen → onDimensionStyleSelect(iso-25)"
},
{
"name": "opening another dropdown closes the previous one",
"status": "PASS"
},
{
"name": "Escape closes Header dropdown",
"status": "PASS"
},
{
"name": "outside click closes Header dropdown",
"status": "PASS"
},
{
"name": "Bar Enter commits state",
"status": "PASS"
},
{
"name": "Bar validates min=0",
"status": "PASS"
},
{
"name": "Bar Escape cancels edit",
"status": "PASS"
},
{
"name": "Header opens panel with same Bar value",
"status": "PASS"
},
{
"name": "numeric weight property remains numeric after ByLayer mode",
"status": "PASS"
},
{
"name": "Header layer value shared with panel",
"status": "PASS"
},
{
"name": "panel edit updates the Bar",
"status": "PASS"
},
{
"name": "panel number validation rejects negative radius",
"status": "PASS"
},
{
"name": "panel Layer dropdown updates Header shared value",
"status": "PASS"
},
{
"name": "panel line dropdown updates Header shared value",
"status": "PASS"
},
{
"name": "panel color dropdown updates Header shared value",
"status": "PASS"
},
{
"name": "panel RGB validation rejects out-of-range value",
"status": "PASS"
},
{
"name": "gauge keyboard editing commits host state",
"status": "PASS"
},
{
"name": "first Escape cancels draft and retains panel",
"status": "PASS"
},
{
"name": "next Escape closes panel and restores Header focus",
"status": "PASS"
},
{
"name": "panel Close button restores focus",
"status": "PASS"
},
{
"name": "external Props update Bar",
"status": "PASS"
},
{
"name": "Bar ReadOnly",
"status": "PASS"
},
{
"name": "Bar Disabled",
"status": "PASS"
},
{
"name": "dynamic Bar field added",
"status": "PASS"
},
{
"name": "dynamic field shared with panel",
"status": "PASS"
},
{
"name": "dynamic field removed from both",
"status": "PASS"
},
{
"name": "shape Slot can be empty without changing panel structure",
"status": "PASS"
},
{
"name": "original panel size none:314x256",
"status": "PASS",
"data": {
"x": 1000,
"y": 301.25,
"width": 314,
"height": 256
}
},
{
"name": "original panel size all:314x361",
"status": "PASS",
"data": {
"x": 1000,
"y": 301.25,
"width": 314,
"height": 361
}
},
{
"name": "original panel size circle:314x658",
"status": "PASS",
"data": {
"x": 1000,
"y": 301.25,
"width": 314,
"height": 658
}
},
{
"name": "original panel size expanded:314x754",
"status": "PASS",
"data": {
"x": 1000,
"y": 301.25,
"width": 314,
"height": 754
}
},
{
"name": "expanded popup bottom accessible through internal scroll",
"status": "PASS"
},
{
"name": "small Inner reaches full popup bottom with native Header and popup dimensions",
"status": "PASS",
"data": {
"top": 630,
"left": 16,
"height": 201,
"scrollHeight": 831,
"width": 358,
"scrollWidth": 390
}
},
{
"name": "narrow popup Escape restores More focus",
"status": "PASS"
},
{
"name": "actual Storybook manager iframe fits its own Inner",
"status": "PASS",
"data": {
"width": 1071.59375,
"height": 602.765625,
"slotWidth": 1071.59375,
"slotHeight": 744.5,
"viewportWidth": 1140,
"viewportHeight": 960
}
},
{
"name": "no browser errors",
"status": "PASS",
"data": []
}
],
"errors": [],
"sizes": [
{
"stage": {
"x": 360,
"y": 264.25,
"w": 1200,
"h": 675,
"right": 1560,
"bottom": 939.25
},
"slot": {
"x": 360,
"y": 169.5,
"w": 1200,
"h": 864.5,
"right": 1560,
"bottom": 1034
},
"header": {
"x": 360,
"y": 264.25,
"w": 1200,
"h": 49,
"right": 1560,
"bottom": 313.25
},
"viewport": {
"w": 1920,
"h": 1080
},
"overflow": 0
},
{
"stage": {
"x": 120,
"y": 174.25,
"w": 1200,
"h": 675,
"right": 1320,
"bottom": 849.25
},
"slot": {
"x": 120,
"y": 169.5,
"w": 1200,
"h": 684.5,
"right": 1320,
"bottom": 854
},
"header": {
"x": 120,
"y": 174.25,
"w": 1200,
"h": 49,
"right": 1320,
"bottom": 223.25
},
"viewport": {
"w": 1440,
"h": 900
},
"overflow": 0
},
{
"stage": {
"x": 191.5625,
"y": 169.5,
"w": 896.875,
"h": 504.5,
"right": 1088.4375,
"bottom": 674
},
"slot": {
"x": 38.40625,
"y": 169.5,
"w": 1203.1875,
"h": 504.5,
"right": 1241.59375,
"bottom": 674
},
"header": {
"x": 191.5625,
"y": 169.5,
"w": 896.875,
"h": 49,
"right": 1088.4375,
"bottom": 218.5
},
"viewport": {
"w": 1280,
"h": 720
},
"overflow": 0
},
{
"stage": {
"x": 42.21875,
"y": 169.5,
"w": 683.546875,
"h": 384.5,
"right": 725.765625,
"bottom": 554
},
"slot": {
"x": 23.046875,
"y": 169.5,
"w": 721.90625,
"h": 384.5,
"right": 744.953125,
"bottom": 554
},
"header": {
"x": 42.21875,
"y": 169.5,
"w": 683.546875,
"h": 49,
"right": 725.765625,
"bottom": 218.5
},
"viewport": {
"w": 768,
"h": 600
},
"overflow": 0
},
{
"stage": {
"x": 16,
"y": 383.0625,
"w": 358,
"h": 201.375,
"right": 374,
"bottom": 584.4375
},
"slot": {
"x": 16,
"y": 169.5,
"w": 358,
"h": 628.5,
"right": 374,
"bottom": 798
},
"header": {
"x": 16,
"y": 383.0625,
"w": 390,
"h": 49,
"right": 406,
"bottom": 432.0625
},
"viewport": {
"w": 390,
"h": 844
},
"overflow": 0
},
{
"stage": {
"x": 247.109375,
"y": 169.5,
"w": 505.765625,
"h": 284.5,
"right": 752.875,
"bottom": 454
},
"slot": {
"x": 30,
"y": 169.5,
"w": 940,
"h": 284.5,
"right": 970,
"bottom": 454
},
"header": {
"x": 247.109375,
"y": 169.5,
"w": 505.765625,
"h": 49,
"right": 752.875,
"bottom": 218.5
},
"viewport": {
"w": 1000,
"h": 500
},
"overflow": 0
}
],
"browser": "155.0.8059.80",
"managerOrder": [
"sw-header--overview",
"sw-header--interactive-states",
"sw-header--window-move-motion",
"sw-header--composed-controls",
"sw-header--logo-slot",
"sw-header--document-dropdown",
"sw-header--layer-dropdown",
"sw-header--color-dropdown",
"sw-header--line-style-dropdown",
"sw-header--text-dimension-style-dropdown",
"sw-header--responsive-more",
"sw-header--layer-detail-input",
"sw-header--property-panel",
"sw-header--property-panel-variants",
"sw-header--k-guide",
"sw-header--additional-hover",
"sw-header--property-bar",
"sw-header--dynamic-controls",
"sw-header--colorize",
"sw-header--composition",
"sw-header--property-panel-none",
"sw-header--property-panel-all",
"sw-header--property-panel-circle",
"sw-header--property-panel-expanded",
"sw-header--property-panel-compare",
"sw-header--property-shape-slot",
"sw-header--property-fields"
],
"status": "PASS"
}
@@ -0,0 +1,86 @@
{
"checks": [
{
"name": "Firefox 1400: 16:9 in Inner and Header 49",
"status": "PASS",
"data": {
"width": 1200,
"height": 675,
"slotWidth": 1200,
"slotHeight": 702.5,
"headerHeight": 49,
"viewportWidth": 1400,
"viewportHeight": 915
}
},
{
"name": "Firefox 1400: full original popup 314x754",
"status": "PASS",
"data": {
"width": 314,
"height": 754,
"focus": false
}
},
{
"name": "Firefox 1400: popup bottom reachable and focusable",
"status": "PASS",
"data": {
"top": 156,
"visible": true
}
},
{
"name": "Firefox 1400: Escape closes popup and restores focus",
"status": "PASS"
},
{
"name": "Firefox 500: 16:9 in Inner and Header 49",
"status": "PASS",
"data": {
"width": 468,
"height": 263.25,
"slotWidth": 468,
"slotHeight": 502.5,
"headerHeight": 49,
"viewportWidth": 500,
"viewportHeight": 715
}
},
{
"name": "Firefox 500: full original popup 314x754",
"status": "PASS",
"data": {
"width": 314,
"height": 754,
"focus": false
}
},
{
"name": "Firefox 500: popup bottom reachable and focusable",
"status": "PASS",
"data": {
"top": 567.75,
"visible": true
}
},
{
"name": "Firefox 500: Escape closes popup and restores focus",
"status": "PASS"
},
{
"name": "Firefox browser errors absent",
"status": "FAIL",
"data": [
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:12495/sb-common-assets/nunito-sans-regular.woff2",
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:12495/sb-common-assets/nunito-sans-regular.woff2"
]
}
],
"errors": [
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:12495/sb-common-assets/nunito-sans-regular.woff2",
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:12495/sb-common-assets/nunito-sans-regular.woff2"
],
"version": "158.0",
"status": "FAIL"
}
@@ -0,0 +1,90 @@
{
"checks": [
{
"name": "Firefox 1400: 16:9 in Inner and Header 49",
"status": "PASS",
"data": {
"width": 1200,
"height": 675,
"slotWidth": 1200,
"slotHeight": 700.5,
"headerHeight": 49,
"viewportWidth": 1400,
"viewportHeight": 915
}
},
{
"name": "Firefox 1400: full original popup 314x754",
"status": "PASS",
"data": {
"width": 314,
"height": 754,
"focus": false
}
},
{
"name": "Firefox 1400: popup bottom reachable and focusable",
"status": "PASS",
"data": {
"top": 156,
"visible": true
}
},
{
"name": "Firefox 1400: Escape closes popup and restores focus",
"status": "PASS"
},
{
"name": "Firefox 500: 16:9 in Inner and Header 49",
"status": "PASS",
"data": {
"width": 468,
"height": 263.25,
"slotWidth": 468,
"slotHeight": 502.5,
"headerHeight": 49,
"viewportWidth": 500,
"viewportHeight": 715
}
},
{
"name": "Firefox 500: full original popup 314x754",
"status": "PASS",
"data": {
"width": 314,
"height": 754,
"focus": false
}
},
{
"name": "Firefox 500: popup bottom reachable and focusable",
"status": "PASS",
"data": {
"top": 567.75,
"visible": true
}
},
{
"name": "Firefox 500: Escape closes popup and restores focus",
"status": "PASS"
},
{
"name": "Firefox SW runtime errors absent; known Storybook font warnings recorded separately",
"status": "PASS",
"data": {
"errors": [],
"warnings": [
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2",
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2"
]
}
}
],
"errors": [],
"infrastructureWarnings": [
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2",
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2"
],
"version": "158.0",
"status": "PASS"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

@@ -0,0 +1,29 @@
[
"sw-header--overview",
"sw-header--interactive-states",
"sw-header--window-move-motion",
"sw-header--composed-controls",
"sw-header--logo-slot",
"sw-header--document-dropdown",
"sw-header--layer-dropdown",
"sw-header--color-dropdown",
"sw-header--line-style-dropdown",
"sw-header--text-dimension-style-dropdown",
"sw-header--responsive-more",
"sw-header--layer-detail-input",
"sw-header--property-panel",
"sw-header--property-panel-variants",
"sw-header--k-guide",
"sw-header--additional-hover",
"sw-header--property-bar",
"sw-header--dynamic-controls",
"sw-header--colorize",
"sw-header--composition",
"sw-header--property-panel-none",
"sw-header--property-panel-all",
"sw-header--property-panel-circle",
"sw-header--property-panel-expanded",
"sw-header--property-panel-compare",
"sw-header--property-shape-slot",
"sw-header--property-fields"
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,366 @@
[
{
"number": "V-01",
"name": "Overview",
"id": "sw-header--overview",
"status": "KEEP",
"audience": "통합 시연 / 사용자",
"content": "16:9 Stage에서 Header·전체 Dropdown·속성Bar·속성창·공유 속성을 조작.",
"proposal": "현재 메인으로 유지.",
"impact": "중간",
"loss": "대체 메인 없음. 이번에 실제 수정한 유일한 Story.",
"url": "http://localhost:6024/?path=/story/sw-header--overview",
"screenshot": "overview-after.png"
},
{
"number": "V-02",
"name": "Interactive States",
"id": "sw-header--interactive-states",
"status": "SIMPLIFY",
"audience": "상태 비교 / 사용자·QA",
"content": "12개 섹션: Host state, Hover/Selected/Disabled, 토글, 창 버튼 Callback, 긴 파일명.",
"proposal": "반복 Header 표본과 설명을 상태별로 축소 검토.",
"impact": "낮음",
"loss": "삭제하면 Disabled+Hover·키보드·창 버튼 Callback 비교를 잃음. 메인은 상태 비교표를 대체하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--interactive-states",
"screenshot": "screens/sw-header--interactive-states.png"
},
{
"number": "V-03",
"name": "Window Move Motion",
"id": "sw-header--window-move-motion",
"status": "KEEP",
"audience": "단계별 실컴포넌트 정지 표본 / QA",
"content": "기존 9단계의 창 이동 모션을 3배 확대.",
"proposal": "이번에 확정한 단계별 섹션 그대로 유지.",
"impact": "낮음",
"loss": "타이밍·위치 비교의 유일한 상세 화면. 메인의 실시간 Hover와 목적이 다름.",
"url": "http://localhost:6024/?path=/story/sw-header--window-move-motion",
"screenshot": "screens/sw-header--window-move-motion.png"
},
{
"number": "V-04",
"name": "Composed Controls",
"id": "sw-header--composed-controls",
"status": "KEEP",
"audience": "하위 컴포넌트 조합 / 사용자",
"content": "레이어·색상·상세보기, 선·문자·치수 공개 하위 컴포넌트와 그룹 Hover.",
"proposal": "독립 조합 API 사용 예제로 유지.",
"impact": "중간",
"loss": "전체 Header 통합 시연으로 하위 컴포넌트 단독 조합을 대체할 수 없음.",
"url": "http://localhost:6024/?path=/story/sw-header--composed-controls",
"screenshot": "screens/sw-header--composed-controls.png"
},
{
"number": "V-05",
"name": "Logo Slot",
"id": "sw-header--logo-slot",
"status": "KEEP",
"audience": "설정 예제 / 사용자",
"content": "기본 임시 로고, 가로형 logoSrc, 세로형 React logo, 미전달 4개 표본.",
"proposal": "로고 슬롯 계약 예제로 유지.",
"impact": "낮음",
"loss": "삭제하면 이미지/ReactNode/미전달의 차이를 직접 확인할 수 없음.",
"url": "http://localhost:6024/?path=/story/sw-header--logo-slot",
"screenshot": "screens/sw-header--logo-slot.png"
},
{
"number": "V-06",
"name": "Document Dropdown",
"id": "sw-header--document-dropdown",
"status": "SIMPLIFY",
"audience": "실제 조작+정적 상태 / 사용자·QA",
"content": "18개 섹션: 문서 선택·닫기·New/Open·현재 문서 위치·Hover·18개/빈 목록·긴 이름·390px.",
"proposal": "반복 현재 문서/Hover 표본을 한 비교 구역으로 줄이는 방안 검토.",
"impact": "중간",
"loss": "메인은 기본 선택·목록 변경을 대체하지만 빈 목록·긴 이름·행 위치·18개 스크롤 사례는 대체하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--document-dropdown",
"screenshot": "screens/sw-header--document-dropdown.png"
},
{
"number": "V-07",
"name": "Layer Dropdown",
"id": "sw-header--layer-dropdown",
"status": "SIMPLIFY",
"audience": "실제 조작+정적 상태 / 사용자·QA",
"content": "19개 섹션: 전구·눈꽃·잠금, 상태 혼합, 선택, 긴/빈/많은 레이어, 6개 좁은 폭.",
"proposal": "중복 Header 표본을 줄이고 아이콘 상태표·예외 사례 유지 검토.",
"impact": "중간",
"loss": "메인은 정상 선택·Toggle만 대체. On/Off/Hover 아이콘 매트릭스·20개 목록·Callback 없는 상태가 고유함.",
"url": "http://localhost:6024/?path=/story/sw-header--layer-dropdown",
"screenshot": "screens/sw-header--layer-dropdown.png"
},
{
"number": "V-08",
"name": "Color Dropdown",
"id": "sw-header--color-dropdown",
"status": "SIMPLIFY",
"audience": "실제 조작+디자인 비교 / 사용자·QA",
"content": "9개 섹션: 팔레트·사용자 색상 40개·ByLayer·RGB 입력·Colorize·상세 Callback.",
"proposal": "반복 Default/Hover Header와 설명을 축소; Colorize 통합 목적지로 검토.",
"impact": "중간",
"loss": "메인의 색상 변경은 40개 색상·정적 Hover 비교를 대체하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--color-dropdown",
"screenshot": "screens/sw-header--color-dropdown.png"
},
{
"number": "V-09",
"name": "Line Style Dropdown",
"id": "sw-header--line-style-dropdown",
"status": "SIMPLIFY",
"audience": "실제 조작+디자인 비교 / 사용자·QA",
"content": "8개 섹션: 3열 선 종류·가중치·스케일, 현재 행/상세 Hover, 390px.",
"proposal": "중복 Default/Hover를 줄이고 3열 입력·좁은 화면 사례 보존 검토.",
"impact": "중간",
"loss": "메인은 정상 선택을 대체하지만 상세 상태·열 배치 기준은 남길 필요가 있음.",
"url": "http://localhost:6024/?path=/story/sw-header--line-style-dropdown",
"screenshot": "screens/sw-header--line-style-dropdown.png"
},
{
"number": "V-10",
"name": "Text · Dimension Style Dropdown",
"id": "sw-header--text-dimension-style-dropdown",
"status": "KEEP",
"audience": "실제 조작+디자인 비교 / 사용자·QA",
"content": "문자·치수스타일 목록과 각 행·상세 Hover, 선택 Callback 등 6개 섹션.",
"proposal": "서로 다른 두 필드의 목록 계약을 함께 확인하는 현 구성 유지.",
"impact": "낮음",
"loss": "메인의 선택만으로 각 행 미리보기와 Figma Hover 비교를 대체하지 못함.",
"url": "http://localhost:6024/?path=/story/sw-header--text-dimension-style-dropdown",
"screenshot": "screens/sw-header--text-dimension-style-dropdown.png"
},
{
"number": "V-11",
"name": "Responsive More",
"id": "sw-header--responsive-more",
"status": "SIMPLIFY",
"audience": "반응형 실제 조작+폭 표본 / 사용자·QA",
"content": "1440/768/390 조작 구역 3개 + 경계 폭 16개. More 대표 필드 전환과 Callback.",
"proposal": "경계 폭 표본을 접는 방식 등 화면 길이 축소 검토. 기능은 유지 권장.",
"impact": "중간",
"loss": "삭제한 Responsive와 다른 기능. 메인은 현재 폭만 보여 주므로 모든 경계값 비교를 대체하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--responsive-more",
"screenshot": "screens/sw-header--responsive-more.png"
},
{
"number": "V-12",
"name": "Layer Detail · Input",
"id": "sw-header--layer-detail-input",
"status": "MERGE REVIEW",
"audience": "상세 버튼+독립 Input / 사용자·QA",
"content": "6개 섹션: Figma PNG, 레이어 상세 3상태, Input Default/Hover/ReadOnly/Disabled/Focus 및 입력·IME·확정/취소.",
"proposal": "상세 버튼 비교는 Detail Buttons로 옮기는 방안 검토. 독립 Input 시연은 남겨야 함.",
"impact": "중간",
"loss": "독립 Input Story는 현재 없음. 페이지 전체를 지우면 텍스트/숫자·외부 Props·취소 예제를 잃음. detail-buttons 검증이 이 ID를 참조함.",
"url": "http://localhost:6024/?path=/story/sw-header--layer-detail-input",
"screenshot": "screens/sw-header--layer-detail-input.png"
},
{
"number": "V-13",
"name": "Property Panel",
"id": "sw-header--property-panel",
"status": "KEEP",
"audience": "Header 연동과 데이터 변형 / 사용자·QA",
"content": "4가지 선택, Controlled/Uncontrolled, 외부 Open/값, 항목 추가·삭제·순서, 제한 상태·게이지.",
"proposal": "고급 데이터/API 검증 화면으로 유지.",
"impact": "중간",
"loss": "메인에 없는 Controlled 전환, 개별 제한·옵션 삭제·선택 객체 교체가 남음. Firefox·닫기·필드 테스트가 참조.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel",
"screenshot": "screens/sw-header--property-panel.png"
},
{
"number": "V-14",
"name": "Property Panel Variants",
"id": "sw-header--property-panel-variants",
"status": "MERGE REVIEW",
"audience": "독립 패널과 데이터 변형 / QA",
"content": "같은 PropertyPanelDemo를 standalone으로 렌더링. 4상태·데이터 버튼이 Property Panel과 거의 동일.",
"proposal": "단독 패널 기준이 필요하면 4개 개별/4개 비교 화면과 통합 검토.",
"impact": "중간",
"loss": "Header Overlay·Focus 복귀와 다른 단독 맥락. standalone의 닫기는 기록만 하고 패널을 유지하며 너비/Open 버튼은 Header 연동 검증을 대신하지 못함.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-variants",
"screenshot": "screens/sw-header--property-panel-variants.png"
},
{
"number": "V-15",
"name": "K Guide",
"id": "sw-header--k-guide",
"status": "KEEP",
"audience": "실시간 모션 / 사용자",
"content": "K 버튼 Hover/Focus의 원본 이미지 Pan·Zoom, 이탈 정지, Reduced Motion 안내.",
"proposal": "고유한 가이드 모션 예제로 유지.",
"impact": "낮음",
"loss": "Header 실시간 모션과 창 이동 1400ms 모션은 별개. 메인에서는 일부만 볼 수 있음.",
"url": "http://localhost:6024/?path=/story/sw-header--k-guide",
"screenshot": "screens/sw-header--k-guide.png"
},
{
"number": "V-16",
"name": "Additional Hover",
"id": "sw-header--additional-hover",
"status": "MERGE REVIEW",
"audience": "상세/Selected 회귀·외부 모드 / QA",
"content": "StyleDemo 1920과 RequiredColorDemo: 5개 상세 Hover, Selected, 외부 ByLayer/ByBlock/직접 색상.",
"proposal": "상세 상태는 Detail Buttons, 외부 색상 모드는 Color Dropdown으로 이전 검토.",
"impact": "중간",
"loss": "외부 colorMode 제어는 메인의 일반 색상 선택과 다름. followup-four가 이 ID를 참조하므로 먼저 대체 시나리오가 필요함.",
"url": "http://localhost:6024/?path=/story/sw-header--additional-hover",
"screenshot": "screens/sw-header--additional-hover.png"
},
{
"number": "V-17",
"name": "Property Bar",
"id": "sw-header--property-bar",
"status": "KEEP",
"audience": "속성Bar 전체 API / 사용자·QA",
"content": "Controlled, 외부 Open/값, 추가·삭제·순서 반전·객체 교체·개별 Disabled/ReadOnly, 1222px 부모 폭.",
"proposal": "메인의 참조 구현이자 고급 API 예제로 유지.",
"impact": "중간",
"loss": "메인은 공유 값·기본 추가/제거·제한만 제공. 개별 제한·순서·객체 교체·Controlled를 대체하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--property-bar",
"screenshot": "screens/sw-header--property-bar.png"
},
{
"number": "V-18",
"name": "Dynamic Controls",
"id": "sw-header--dynamic-controls",
"status": "KEEP",
"audience": "controls allowlist / 사용자",
"content": "12개 구성의 제거·재추가·라벨·순서·visible, 동적 문서 데이터.",
"proposal": "Composition과 다른 API임을 설명하는 방향으로 유지.",
"impact": "중간",
"loss": "controls는 전체 allowlist이며 로고 외 16개 요소를 숨기는 hiddenControls와 다름. 이름이 비슷해도 동일 기능이 아님.",
"url": "http://localhost:6024/?path=/story/sw-header--dynamic-controls",
"screenshot": "screens/sw-header--dynamic-controls.png"
},
{
"number": "V-19",
"name": "Colorize",
"id": "sw-header--colorize",
"status": "REMOVE REVIEW",
"audience": "색상 추출 버튼 접근 / QA",
"content": "별도 기본/확대 상태표 없이 StyleDemo(1920)를 다시 표시. 색상 Dropdown의 추출 버튼으로 연결.",
"proposal": "Color Dropdown에 버튼 접근·Hover·Callback 증거를 남긴 뒤 메뉴 삭제 검토 가능.",
"impact": "중간",
"loss": "실제 색상 추출 CAD 기능은 호스트 소유. 삭제 시 전용 접근 URL을 잃고 detail-buttons preservation의 colorize 참조를 이전해야 함. 지금은 삭제하지 않음.",
"url": "http://localhost:6024/?path=/story/sw-header--colorize",
"screenshot": "screens/sw-header--colorize.png"
},
{
"number": "V-20",
"name": "Composition (Visibility · Order)",
"id": "sw-header--composition",
"status": "KEEP",
"audience": "hiddenControls/menuOrder / 사용자·QA",
"content": "16개 표시 항목, 프리셋·순서·중복/알 수 없는 ID, 실제 화면/고정 폭, Callback 보존.",
"proposal": "개별 제거와 순서 계약을 보여 주는 예제로 유지.",
"impact": "중간",
"loss": "Dynamic Controls와 다른 API. 홈·창 이동·최소화·종료까지 개별 제어하고 메뉴 순서 정규화를 검증함.",
"url": "http://localhost:6024/?path=/story/sw-header--composition",
"screenshot": "screens/sw-header--composition.png"
},
{
"number": "V-21",
"name": "Property Panel · 선택 없음",
"id": "sw-header--property-panel-none",
"status": "QA",
"audience": "단독 실제 컴포넌트 / 디자인 QA",
"content": "선택 없음 314×256 고정 상태와 실제 필드.",
"proposal": "일반 사용자 목록보다는 QA 분류 검토.",
"impact": "중간",
"loss": "Variants/Compare와 겹치지만 테스트의 상태별 직접 URL·치수 기준임. 삭제/통합 시 ID 참조 이전 필요.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-none",
"screenshot": "screens/sw-header--property-panel-none.png"
},
{
"number": "V-22",
"name": "Property Panel · 전체 선택",
"id": "sw-header--property-panel-all",
"status": "QA",
"audience": "단독 실제 컴포넌트 / 디자인 QA",
"content": "전체 선택 314×361, 혼합 레이어 값과 선택 개수.",
"proposal": "개별 QA 기준으로 유지하거나 Compare 통합 검토.",
"impact": "중간",
"loss": "선택 없음/원 상태와 내용이 다름. 테스트 직접 URL·혼합 값·개수 레이아웃을 보존해야 함.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-all",
"screenshot": "screens/sw-header--property-panel-all.png"
},
{
"number": "V-23",
"name": "Property Panel · 원 1개",
"id": "sw-header--property-panel-circle",
"status": "QA",
"audience": "단독 실제 컴포넌트 / 디자인 QA",
"content": "원 1개 314×658, 기본 형상·치수·XYZ·전체 선택 복귀.",
"proposal": "개별 QA 기준으로 유지하거나 Compare 통합 검토.",
"impact": "중간",
"loss": "원형 Slot과 위치·푸터를 포함. 다른 상태와 같은 이름의 단순 중복이 아님.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-circle",
"screenshot": "screens/sw-header--property-panel-circle.png"
},
{
"number": "V-24",
"name": "Property Panel · 모두 펼침",
"id": "sw-header--property-panel-expanded",
"status": "QA",
"audience": "단독 실제 컴포넌트 / 디자인 QA",
"content": "전체 펼침 314×754, 추가 공통 속성·형상·위치·푸터.",
"proposal": "개별 QA 기준으로 유지하거나 Compare 통합 검토.",
"impact": "중간",
"loss": "754px 전체 높이와 하단 잘림 검증에 필요. 개별 ID가 기존 테스트에서 동적으로 참조됨.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-expanded",
"screenshot": "screens/sw-header--property-panel-expanded.png"
},
{
"number": "V-25",
"name": "Property Panel · 4개 비교",
"id": "sw-header--property-panel-compare",
"status": "QA",
"audience": "4개 실제 패널 병렬 비교 / 디자인 QA",
"content": "4개 선택 상태를 동시에 렌더링하고 실측 폭·높이를 표시.",
"proposal": "나란히 비교하는 QA 목적을 유지. 개별 메뉴 통합 목적지로 검토 가능.",
"impact": "중간",
"loss": "Variants는 상태 전환, Compare는 동시 비교라는 차이가 있음. property-fields의 4상태 치수 검증이 참조함.",
"url": "http://localhost:6024/?path=/story/sw-header--property-panel-compare",
"screenshot": "screens/sw-header--property-panel-compare.png"
},
{
"number": "V-26",
"name": "Property Panel · 형상 Slot",
"id": "sw-header--property-shape-slot",
"status": "KEEP",
"audience": "형상 확장 API / 사용자",
"content": "기본/null/ReactNode/Render Prop, 원·사각형 교체, 치수/그룹 추가.",
"proposal": "외부 컴포넌트 확장 계약 예제로 유지.",
"impact": "중간",
"loss": "메인은 기본/null만 제공. ReactNode와 Render Prop·형상 교체를 대체할 수 없음.",
"url": "http://localhost:6024/?path=/story/sw-header--property-shape-slot",
"screenshot": "screens/sw-header--property-shape-slot.png"
},
{
"number": "V-27",
"name": "Property Panel · 필드 13개",
"id": "sw-header--property-fields",
"status": "QA",
"audience": "필드 상태·실제 조작 / 디자인 QA",
"content": "Figma 13개 필드 Default/Hover 표본, 실제 패널, 혼합 레이어·외부 값·제한·단위.",
"proposal": "77개 필드 검증의 기준 화면으로 유지 권장.",
"impact": "중간",
"loss": "메인은 필드별 디자인 상태표를 대체하지 않음. property-fields.browser.mjs의 핵심 직접 ID.",
"url": "http://localhost:6024/?path=/story/sw-header--property-fields",
"screenshot": "screens/sw-header--property-fields.png"
},
{
"number": "V-28",
"name": "Detail Buttons (Shared States)",
"id": "sw-header--detail-buttons",
"status": "QA",
"audience": "실제 Pointer/Callback 비교 / QA",
"content": "5개 상세 버튼 × Default/Hover/Disabled/Callback 없음, 클릭 횟수. verification 태그로 기본 숨김.",
"proposal": "이미 적용된 숨김과 검증 표본 유지.",
"impact": "중간",
"loss": "메인은 실제 Dropdown 접근용이며 이 비교 매트릭스를 대체하지 못함. detail-buttons 검증의 직접 ID.",
"url": "http://localhost:6024/?path=/story/sw-header--detail-buttons",
"screenshot": "screens/sw-header--detail-buttons.png"
}
]
File diff suppressed because it is too large. Load diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

@@ -0,0 +1,306 @@
T Building storybook v10.6.1
|
o Cleaning outputDir: storybook-static
|
o Loading presets
|
o Building manager..
|
• Building open services..
|
• Building preview..
|
• Copying static files: public\fonts at storybook-static\fonts
|
• Copying static files: themes at storybook-static\themes
| Vite vite v8.3.0 building client environment for production...
transforming...
Found 1 warning while optimizing generated CSS:
│ }
│ .bg-\[var\(\.\.\.\)\] {
│ background-color: var(...);
┆ ^-- Unexpected token Delim('.')
┆
│ }
│ .bg-background {
✓ 3102 modules transformed.
|
! Vite [lightningcss minify] 'host-context' is not recognized as a valid
| pseudo-class. Did you mean '::host-context' (pseudo-element) or is
| this a typo?
| 1322 | background-color: var(--hm-orgchart-dialog-close-hover);
| 1323 | }
| 1324 | :host-context(.dark) .hm-orgchart-employee-dialog-close:hover,
| | ^
| 1325 | .dark .hm-orgchart-employee-dialog-close:hover {
| 1326 | background-color: color-mix(in oklab,
| var(--hm-orgchart-dialog-close-hover) 50%, transparent);
rendering chunks...
computing gzip size...
| Vite storybook-static/assets/input-26-11656-BIXt51ww.png
| 5.09 kB
| storybook-static/iframe.html
| 19.07 kB │ gzip: 5.42 kB
| storybook-static/assets/NotoSansKR-variable-k7NQXPol.ttf
| 10,414.58 kB
| storybook-static/assets/data-table-x0avmA1M.css
| 0.89 kB │ gzip: 0.32 kB
| storybook-static/assets/department-colors-preview-C8o8mgn-.css
| 1.12 kB │ gzip: 0.39 kB
| storybook-static/assets/org-chart-stories-CVVpQeIs.css
| 1.15 kB │ gzip: 0.39 kB
| storybook-static/assets/org-chart-expanded-BQqXfdmZ.css
| 1.34 kB │ gzip: 0.43 kB
| storybook-static/assets/story-page-B9TObVPB.css
| 1.74 kB │ gzip: 0.49 kB
| storybook-static/assets/designer-theme-Bv3QXGf0.css
| 2.88 kB │ gzip: 0.85 kB
| storybook-static/assets/reference-CElszmAj.css
| 3.13 kB │ gzip: 0.87 kB
| storybook-static/assets/center-stories-MLQreMPw.css
| 6.77 kB │ gzip: 1.49 kB
| storybook-static/assets/dashboard-layout-cwHZMCdB.css
| 6.90 kB │ gzip: 1.21 kB
| storybook-static/assets/engine-DrWcIejL.css
| 15.64 kB │ gzip: 3.10 kB
| storybook-static/assets/center-shared-CBSMPY7X.css
| 55.67 kB │ gzip: 10.48 kB
| storybook-static/assets/sw-header-CUYjY3HM.css
| 73.13 kB │ gzip: 13.58 kB
| storybook-static/assets/style-BhhUui7W.css
| 93.80 kB │ gzip: 12.41 kB
| storybook-static/assets/iframe-BW1xNYqB.css
| 237.20 kB │ gzip: 36.15 kB
| storybook-static/assets/iframe-BN7HSSg4.js
| 0.04 kB │ gzip: 0.06 kB
| storybook-static/assets/center-stories-Cr38slYE.js
| 0.09 kB │ gzip: 0.11 kB
| storybook-static/assets/org-chart-stories-BU9b3y-N.js
| 0.09 kB │ gzip: 0.11 kB
| storybook-static/assets/chart-column-39yOKDYH.js
| 0.41 kB │ gzip: 0.29 kB
| storybook-static/assets/layout-grid-6y2ii6HT.js
| 0.48 kB │ gzip: 0.28 kB
| storybook-static/assets/italic-DkeWgR9v.js
| 0.58 kB │ gzip: 0.36 kB
| storybook-static/assets/org-chart-full-composition.stories-D1pErr-l.js
| 0.59 kB │ gzip: 0.39 kB
| storybook-static/assets/org-chart-p1.stories-CfXa_6Rp.js
| 0.59 kB │ gzip: 0.40 kB
| storybook-static/assets/org-chart-department-colors.stories-CtRK7aJI.js
| 0.60 kB │ gzip: 0.39 kB
| storybook-static/assets/settings-BS9DjRDa.js
| 0.62 kB │ gzip: 0.34 kB
| storybook-static/assets/org-chart-summary.stories-B900zJzh.js
| 0.76 kB │ gzip: 0.40 kB
| storybook-static/assets/center-information.stories-QS-Xk9cm.js
| 0.94 kB │ gzip: 0.63 kB
| storybook-static/assets/sample-users-CAqvNHXG.js
| 0.94 kB │ gzip: 0.67 kB
| storybook-static/assets/center-spatial.stories-BKywiGMs.js
| 0.97 kB │ gzip: 0.64 kB
| storybook-static/assets/rolldown-runtime-DkW27tQK.js
| 1.19 kB │ gzip: 0.67 kB
| storybook-static/assets/preload-helper-VEOc_cSF.js
| 1.33 kB │ gzip: 0.75 kB
| storybook-static/assets/center-title.stories-D4g3unBO.js
| 1.36 kB │ gzip: 0.88 kB
| storybook-static/assets/org-chart-overview-internal.stories-CZjDbHDQ.js
| 1.43 kB │ gzip: 0.68 kB
| storybook-static/assets/org-chart-cards.stories-BMF7xDR7.js
| 1.60 kB │ gzip: 0.92 kB
| storybook-static/assets/color.stories-DZb1MHtw.js
| 1.80 kB │ gzip: 1.03 kB
| storybook-static/assets/center-facility.stories-SWGtrHnb.js
| 1.90 kB │ gzip: 1.08 kB
| storybook-static/assets/loading-screen.stories-Dff8ndFU.js
| 1.96 kB │ gzip: 1.04 kB
| storybook-static/assets/story-board-BEJDReki.js
| 1.99 kB │ gzip: 0.89 kB
| storybook-static/assets/skeleton.stories-kNCVEaAs.js
| 2.10 kB │ gzip: 1.01 kB
| storybook-static/assets/story-page-BdQwBtvH.js
| 2.13 kB │ gzip: 0.69 kB
| storybook-static/assets/verification-state-BvmqrSTe.js
| 2.15 kB │ gzip: 1.05 kB
| storybook-static/assets/hover-card.stories-Crbf6ekx.js
| 2.34 kB │ gzip: 1.09 kB
| storybook-static/assets/alert-dialog.stories-YCXcnGTq.js
| 2.48 kB │ gzip: 1.08 kB
| storybook-static/assets/separator.stories-DCH35yh1.js
| 2.56 kB │ gzip: 1.16 kB
| storybook-static/assets/dropdown-menu.stories-GGuA7NCs.js
| 2.58 kB │ gzip: 1.29 kB
| storybook-static/assets/badge.stories-CHBpLyWb.js
| 2.61 kB │ gzip: 1.23 kB
| storybook-static/assets/tooltip.stories-CAhvJiqg.js
| 2.65 kB │ gzip: 1.23 kB
| storybook-static/assets/publisher-regression.stories-BSdjbYdq.js
| 2.70 kB │ gzip: 1.15 kB
| storybook-static/assets/calendar.stories-CEWYrdQ5.js
| 2.79 kB │ gzip: 1.31 kB
| storybook-static/assets/switch.stories-DHCVpaSE.js
| 2.86 kB │ gzip: 1.28 kB
| storybook-static/assets/reference-yKN_SnBk.js
| 2.88 kB │ gzip: 1.24 kB
| storybook-static/assets/context-menu.stories-ZITwa1kb.js
| 2.95 kB │ gzip: 1.47 kB
| storybook-static/assets/center-shared-CSGUuM9T.js
| 2.96 kB │ gzip: 1.36 kB
| storybook-static/assets/scroll-area.stories-DEvdgplg.js
| 3.02 kB │ gzip: 1.30 kB
| storybook-static/assets/popover.stories-DvMwP766.js
| 3.05 kB │ gzip: 1.30 kB
| storybook-static/assets/aspect-ratio.stories-7mL3laNq.js
| 3.06 kB │ gzip: 1.52 kB
| storybook-static/assets/typography.stories-CKjfpWpM.js
| 3.07 kB │ gzip: 1.42 kB
| storybook-static/assets/pagination.stories-Mbhc8AU9.js
| 3.12 kB │ gzip: 1.50 kB
| storybook-static/assets/alert.stories-CpyLpNi-.js
| 3.16 kB │ gzip: 1.30 kB
| storybook-static/assets/label.stories-CPEv7gzc.js
| 3.19 kB │ gzip: 1.39 kB
| storybook-static/assets/collapsible.stories-DO9N9ksi.js
| 3.19 kB │ gzip: 1.50 kB
| storybook-static/assets/drawer.stories-C9cifdBI.js
| 3.23 kB │ gzip: 1.47 kB
| storybook-static/assets/radio-group.stories-DwqwXVDe.js
| 3.24 kB │ gzip: 1.47 kB
| storybook-static/assets/avatar.stories-B6K5na20.js
| 3.30 kB │ gzip: 1.45 kB
| storybook-static/assets/page-header.stories-HeG_6w9V.js
| 3.32 kB │ gzip: 1.30 kB
| storybook-static/assets/menubar.stories-BhTzLN_a.js
| 3.33 kB │ gzip: 1.39 kB
| storybook-static/assets/command.stories-D-XMtuKM.js
| 3.33 kB │ gzip: 1.58 kB
| storybook-static/assets/center-overview.stories-E8zT3EyS.js
| 3.36 kB │ gzip: 1.77 kB
| storybook-static/assets/filter-bar.stories-DcTIJuSP.js
| 3.43 kB │ gzip: 1.72 kB
| storybook-static/assets/sheet.stories-DdnpVhWc.js
| 3.51 kB │ gzip: 1.44 kB
| storybook-static/assets/progress.stories-BDmSy5Qj.js
| 3.52 kB │ gzip: 1.61 kB
| storybook-static/assets/breadcrumb.stories-ChR_KDt-.js
| 3.55 kB │ gzip: 1.52 kB
| storybook-static/assets/card.stories-_z1r7XI2.js
| 3.74 kB │ gzip: 1.63 kB
| storybook-static/assets/navigation-menu.stories-H4SlN4bU.js
| 3.86 kB │ gzip: 1.62 kB
| storybook-static/assets/resizable.stories-uaf5IGxv.js
| 3.96 kB │ gzip: 1.46 kB
| storybook-static/assets/specimens-Ch5aVLS1.js
| 4.13 kB │ gzip: 1.36 kB
| storybook-static/assets/sonner.stories-gVdEDxUX.js
| 4.22 kB │ gzip: 1.83 kB
| storybook-static/assets/checkbox.stories-CgA-2wok.js
| 4.39 kB │ gzip: 1.66 kB
| storybook-static/assets/date-picker.stories-DHa1DjlV.js
| 4.45 kB │ gzip: 1.69 kB
| storybook-static/assets/slider.stories-BcPpzY38.js
| 4.53 kB │ gzip: 1.86 kB
| storybook-static/assets/org-chart-page-previews-Ctz_kKme.js
| 4.70 kB │ gzip: 2.05 kB
| storybook-static/assets/carousel.stories-BLEYsPtS.js
| 4.75 kB │ gzip: 1.91 kB
| storybook-static/assets/layout.stories-BQltBMcD.js
| 4.79 kB │ gzip: 1.92 kB
| storybook-static/assets/table.stories-B82HKFvC.js
| 4.86 kB │ gzip: 2.02 kB
| storybook-static/assets/combobox.stories-49EFqEgG.js
| 5.03 kB │ gzip: 2.07 kB
| storybook-static/assets/accordion.stories-Bzzb4Ud4.js
| 5.05 kB │ gzip: 1.81 kB
| storybook-static/assets/OrgChart-DZlWXlj3-Cg7VHAIW.js
| 5.35 kB │ gzip: 1.95 kB
| storybook-static/assets/dialog.stories-CHiRjmir.js
| 5.53 kB │ gzip: 2.13 kB
| storybook-static/assets/toggle.stories-DffHj9E4.js
| 5.81 kB │ gzip: 2.13 kB
| storybook-static/assets/data-table.stories-B40WrOK1.js
| 5.92 kB │ gzip: 2.52 kB
| storybook-static/assets/textarea.stories-t-9JtP6t.js
| 5.99 kB │ gzip: 2.43 kB
| storybook-static/assets/runtime-loader.stories-Cgs8Weu2.js
| 6.18 kB │ gzip: 2.45 kB
| storybook-static/assets/toggle-group.stories-BRmj8DKL.js
| 6.57 kB │ gzip: 2.23 kB
| storybook-static/assets/react-Q1GcV6wX.js
| 7.87 kB │ gzip: 3.01 kB
| storybook-static/assets/input.stories-BXqV7hCq.js
| 8.32 kB │ gzip: 3.05 kB
| storybook-static/assets/button.stories-CG1boIti.js
| 9.65 kB │ gzip: 3.62 kB
| storybook-static/assets/select.stories-CwcBCz1l.js
| 10.52 kB │ gzip: 3.39 kB
| storybook-static/assets/tabs.stories-G8gk_Flp.js
| 11.57 kB │ gzip: 4.13 kB
| storybook-static/assets/org-chart.stories-welHTajy.js
| 12.54 kB │ gzip: 4.16 kB
| storybook-static/assets/designer-theme.stories-BzNQsLun.js
| 14.62 kB │ gzip: 5.51 kB
| storybook-static/assets/gnb.stories-CIUtlAzi.js
| 16.56 kB │ gzip: 5.84 kB
| storybook-static/assets/theme-CZw7CMzf.js
| 18.61 kB │ gzip: 3.18 kB
| storybook-static/assets/lnb.stories-BFHV7Rpz.js
| 23.06 kB │ gzip: 6.43 kB
| storybook-static/assets/dashboard-layout.stories-Okv-SwFR.js
| 23.84 kB │ gzip: 6.25 kB
| storybook-static/vite-inject-mocker-entry.js
| 27.84 kB │ gzip: 9.66 kB
| storybook-static/assets/reference-data-Dp4dBiCx.js
| 40.42 kB │ gzip: 9.43 kB
| storybook-static/assets/runtime-theme.stories-WGYRELlW.js
| 61.92 kB │ gzip: 11.60 kB
| storybook-static/assets/org-chart-expanded.stories-DYDKWoe7.js
| 79.03 kB │ gzip: 10.07 kB
| storybook-static/assets/react-18-BukvVaPF.js
| 207.62 kB │ gzip: 65.12 kB
| storybook-static/assets/core-component-preview-CbmTQndg.js
| 258.40 kB │ gzip: 146.05 kB
| storybook-static/assets/sw-header.stories-edbdMdWE.js
| 768.59 kB │ gzip: 359.85 kB
| storybook-static/assets/reference-Bz-UmAfk.js
| 1,644.48 kB │ gzip: 270.35 kB
| storybook-static/assets/iframe-NxI5EhLm.js
| 1,754.05 kB │ gzip: 503.61 kB
| storybook-static/assets/style-CNeiB0x2.js
| 2,359.94 kB │ gzip: 1,631.23 kB
|
! Vite [plugin builtin:vite-reporter]
| (!) Some chunks are larger than 500 kB after minification. Consider:
| - Using dynamic import() to code-split the application
| - Use build.rolldownOptions.output.codeSplitting to improve chunking:
| ]8;;https://rolldown.rs/reference/OutputOptions.codeSplittinghttps://rolldown.rs/reference/OutputOptions.codeSplitting]8;;
| - Adjust chunk size limit for this warning via
| build.chunkSizeWarningLimit.
| Vite ✓ built in 3.55s
|
! Vite [PLUGIN_TIMINGS] JavaScript callbacks ran for 3.0s of this 3.5s
| build (86%).
| The slowest callbacks, timed inside each callback (the wait before a
| callback starts is excluded, the time it awaits is included):
| - plugin storybook:react-docgen-plugin transform (60%, 2.1s, 3102
| calls)
| Additional callback time came from the callbacks below and from
| callbacks under 1s.
| 3 callbacks are listed without a time. Their calls overlapped, so the
| time inside one call includes work from other calls. To find their
| cost, profile the build with `node --cpu-prof`:
| - plugin vite:asset load (31 calls)
| - plugin @tailwindcss/vite:generate:build transform (19 calls)
| - plugin vite:css transform (19 calls)
| See ]8;;https://rolldown.rs/reference/InputOptions.checks#bundlertimingshttps://rolldown.rs/reference/InputOptions.checks#bundlertimings]8;;
| for more details.
|
o Output directory: D:/디자인시스템/HMDesign-SW/storybook-static
|
— Storybook build completed successfully
@@ -0,0 +1,17 @@
> hm-design-system-poc@0.1.0 build:sw
> npm run build -w @hm-design/sw
> @hm-design/sw@0.1.0 build
> node build.mjs
vite v8.3.0 building client environment for production...
transforming...
✓ 69 modules transformed.
rendering chunks...
computing gzip size...
dist/styles.css 100.39 kB │ gzip: 23.57 kB
dist/index.js 712.25 kB │ gzip: 337.98 kB
✓ built in 131ms
@@ -0,0 +1,7 @@
> hm-design-system-poc@0.1.0 typecheck:sw
> npm run typecheck -w @hm-design/sw
> @hm-design/sw@0.1.0 typecheck
> tsc -p tsconfig.json
@@ -0,0 +1,58 @@
{
"baseCommit": "50bf5bda8e956a4973b923d64131f2f2285bd38c",
"scope": "STORYBOOK_ONLY",
"storybookCount": {
"before": 28,
"after": 28
},
"chrome": {
"checks": 53,
"status": "PASS"
},
"overlays": {
"checks": 19,
"status": "PASS"
},
"firefox": {
"checks": 9,
"status": "PASS",
"version": "158.0",
"infrastructureWarnings": [
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2",
"downloadable font: download failed (font-family: \"Nunito Sans\" style:normal weight:400 width:100 src index:0): status=2147746065 source: http://127.0.0.1:8191/sb-common-assets/nunito-sans-regular.woff2"
]
},
"commands": [
{
"command": "npm.cmd run typecheck:sw",
"status": "PASS"
},
{
"command": "npm.cmd exec -- tsc -p tsconfig.app.json --noEmit",
"status": "PASS"
},
{
"command": "npm.cmd run build:sw",
"status": "PASS",
"distIndexUnchanged": true
},
{
"command": "npm.cmd exec -- storybook build -o storybook-static --disable-telemetry",
"status": "PASS"
}
],
"sourceSha256": {
"src/sw/sw-header.stories.tsx": "a2d81948871ed52821ab3fe821033c723c8e7ff38f562fa942bfd71d9a5d6e67",
"src/sw/sw-header-overview.tsx": "74a7c032fa11966147f6ab16837cc523f82012502a6de9fec59930477de721df",
"src/sw/sw-stories.css": "462e2314756a86e74e385f92fed1542e4a146f932617a9e7d860183041a28259"
},
"unchangedStoryDeclarations": 27,
"originalStoryStylesPreserved": true,
"productionPackageSourceUnchanged": true,
"notVerified": [
"Full interaction regression of the other 27 Stories",
"Full Firefox dropdown/bar/gauge integration",
"CAD, file and operating-system integrations",
"Full accessibility certification"
]
}
@@ -0,0 +1,308 @@
# SW Header 메인 개선 및 Storybook 화면별 검토 — 2026-10-19
**최종 판정: PASS — 요청한 Overview 개선·28개 화면 조사 범위.** 다른 Story의 삭제·통합·이름 변경·재분류는 적용하지 않았습니다.
- 시작: `D:\디자인시스템\HMDesign-SW`, `feature/sw-package`, HEAD `50bf5bda8e956a4973b923d64131f2f2285bd38c`, Working Tree clean, 기존 로컬 Commit 3개.
- 실제 수정 Story: **Overview — `sw-header--overview`**. 공개 `@hm-design/sw` 컴포넌트를 사용합니다.
- Story 수는 **28 → 28**이며 ID·이름·순서·태그가 그대로입니다. 다른 27개 Story 선언은 시작 Commit과 동일합니다.
- `packages/sw/src`, 공개 API, 패키지 CSS·자산, 기존 QA 기록과 디자인 명세는 수정하지 않았습니다.
- Git 정책: 현재 브랜치에 로컬 Commit만 생성. Push·PR·배포·새 브랜치 생성 없음. Commit Hash와 최종 Git 상태는 완료 메시지에 기록합니다.
## 메인 화면의 변경
이전에는 `Figma 기준 1920 × 49`와 `선택 상태`라는 고정 1920px Header 표본 두 개를 표시했습니다. 이 두 시연 영역과 긴 설명을 제거하고, 하나의 실제 Header와 편집 가능한 미리보기로 바꿨습니다. `SW Header` 페이지 제목은 한 번만 표시합니다.
- [수정 전](storybook-overview-qa/overview-before.png)
- [수정 후](storybook-overview-qa/overview-after.png)
- [Storybook 사이드바가 열린 실제 화면](storybook-overview-qa/overview-manager.png)
- [전체 펼침 팝업](storybook-overview-qa/overview-after-expanded.png)
- [팝업 하단 스크롤·편집](storybook-overview-qa/overview-after-scrolled.png)
- [작은 Inner에서 하단 접근](storybook-overview-qa/overview-narrow.png)
### 16:9 Stage와 큰 팝업 처리
기존 StoryPage의 실제 `.sb-page-inner` 안에서 제목·시연 설정·상태 줄을 제외한 남은 영역을 `ResizeObserver`로 측정합니다. `width = min(사용 가능한 폭, 사용 가능한 높이 × 16 / 9)`, `height = width × 9 / 16`으로 두 방향 모두 맞추고 중앙 정렬합니다. iframe 자체 크기를 사용하므로 Storybook 사이드바·툴바·미리보기 크기 변경에도 다시 계산됩니다.
실측 예:
- iframe 1920×1080 / 1440×900: Stage **1200×675px**. 기존 Inner 최대 폭을 따릅니다.
- iframe 1280×720: **896.875×504.5px**. 높이가 크기 제한입니다.
- iframe 768×600: **683.547×384.5px**.
- iframe 390×844: **358×201.375px**.
- iframe 1000×500: **505.766×284.5px**. 낮은 화면에서도 폭만으로 계산하지 않습니다.
- 1440×1000 브라우저에서 실제 Storybook manager 표시: 미리보기 iframe 1140×960, Stage **1071.594×602.766px**.
- 모든 조건의 Header는 **49px**, 속성Bar는 기존 **28px**입니다. Header의 문자·아이콘·버튼·Dropdown을 확대/축소하지 않습니다.
Stage는 내부 스크롤 영역이며 미리보기 내용의 최소 폭은 390px입니다. Stage가 그보다 좁으면 가로 스크롤을 제공합니다. 팝업의 **314×256 / 314×361 / 314×658 / 314×754px**는 그대로입니다. 전체 펼침 팝업이 Stage 높이보다 크면 세로 스크롤로 하단 XYZ·푸터에 접근합니다. 390px 화면에서도 실제 Z 입력·확정과 Escape 후 More Focus 복귀를 확인했습니다.
Header의 공개 `propertyPanel` 타입은 위치 `style`을 받지 않으므로 Overview에만 한정한 Story CSS로 팝업을 Header 아래의 절대 배치로 바꿨습니다. `top/left/max-height`만 배치 목적에 맞게 조정하며 자연 폭·폰트·간격·색·모양은 유지합니다. 문서 목록이 열린 경우에만 문서 wrapper의 쌓임 순서를 올려 속성Bar에 가려지지 않게 했습니다. 중첩 속성 Dropdown은 기존 fixed 위치 계산과 z-index를 사용합니다. 이 예외는 다른 Story와 제품 CSS에 적용되지 않습니다.
### 연결한 기능과 상태
- 문서 목록: 선택·닫기·New 목록 추가, Open Callback. Storybook 호스트 목록만 변경하며 실제 파일 열기/저장은 수행하지 않습니다.
- 레이어: 선택·전구·눈꽃·잠금과 상세 Callback. Header와 속성창은 같은 레이어 목록·현재 값을 사용합니다.
- 색상: 기본/사용자 팔레트·ByLayer/ByBlock·RGB 검증·Colorize·상세 Callback. Header와 속성창이 같은 RGB 상태를 사용합니다.
- 선스타일: 선 종류·가중치·User 값·스케일·상세 Callback. ByLayer/ByBlock 가중치 모드는 숫자 속성을 모드 문자열로 덮어쓰지 않습니다.
- 문자스타일·치수스타일: 각각의 선택 상태와 상세 Callback. 다른 속성 데이터에 억지로 동기화하지 않습니다.
- More: 기존 반응형 정책대로 감춰진 필드·속성Bar·속성창에 접근합니다. 모든 Dropdown을 동시에 펼치지 않습니다.
- 속성Bar: 기존 `BAR_FIXTURE`의 형상 필드와 공개 `SWHeaderPropertyBar` 편집 동작을 사용합니다. 형상 값은 속성창과 공유하며 Enter·Escape·min 검증·외부 값·Disabled/ReadOnly·필드 추가/제거를 연결했습니다. CAD 계산 관계를 새로 만들지 않았습니다.
- 속성창: Header 열기·닫기 버튼·Escape·Focus 복귀, 레이어/색상/선스타일 Dropdown, 숫자 편집·게이지·4개 선택 상태·그룹 펼침·기본/null 형상 Slot.
- 홈·인쇄·가이드·창 이동·최소화·종료는 기존 버튼과 Callback을 유지합니다. 실제 CAD/OS 동작은 호스트 소유이며 Storybook에서는 Callback을 기록합니다.
선택 상태와 외부 값·추가 필드·제한 상태·형상 Slot 설정은 **Stage 밖의 Storybook 전용 시연 설정**입니다. 기존 Storybook 조작 방식에 맞춰 native label/select/checkbox/button을 사용했으며, 제품 Header에 추가 버튼을 넣지 않았습니다. 기존 Property Bar Story와 다른 Story의 상태 관리 코드는 변경하지 않았습니다.
## 검증 결과와 재사용 근거
이번에 실행:
- `npm.cmd run typecheck:sw`: PASS.
- `npm.cmd exec -- tsc -p tsconfig.app.json --noEmit`: PASS. 새 Overview와 Story 파일 포함.
- `npm.cmd run build:sw`: PASS. 재생성 전후 `packages/sw/dist/index.js` 해시도 동일했습니다.
- `npm.cmd run build-storybook`의 패키지 사전 빌드와 Storybook Build: PASS. 이후 Storybook 코드 수정분은 `npm.cmd exec -- storybook build -o storybook-static --disable-telemetry`로 최종 빌드했습니다.
- `overview.browser.mjs`: **53/53 PASS**. Chrome 155.0.8059.80, Inner 6개 크기, 실제 manager iframe, Dropdown 선택/전환/닫기, 공유 값, 입력 검증, 게이지, Focus, 4상태 크기, 내부 스크롤.
- `overview.browser.mjs docs/sw/storybook-overview-qa overlays`: **19/19 PASS**. 상세 Callback 5종, Header 색상 모드 표시/직접 색상 복귀, 문서 첫 행 가림, 작은 Inner 중첩 Dropdown의 폭·하단 행 클릭·Escape.
- `overview-firefox.browser.mjs`: **9/9 PASS**. 설치된 Firefox 158.0, 1400/500px 창에서 Stage·Header 높이·원래 754px 팝업·하단 접근·닫기/Focus.
- 수정 전 28개 모두 실제 Chrome 초기 렌더링·브라우저 오류 0을 확인했습니다. 수정 후 나머지 27개 Story 선언 동일, CSS의 기존 구간 동일, 패키지 소스 동일을 확인했습니다.
- Git Diff의 범위·공백 오류와 Commit 직전 Staged Diff를 검토했습니다.
경고:
- Storybook Build는 생성 유틸리티의 `var(...)`, OrgChart `:host-context`, chunk 크기 경고를 포함하지만 성공했습니다. 해당 코드·설정은 변경하지 않았습니다.
- Firefox는 기존에도 보고된 `/sb-common-assets/nunito-sans-regular.woff2` 글꼴 다운로드 경고가 있습니다. 정확히 이 글꼴 경고만 infrastructureWarnings로 구분하고 SW runtime 오류는 0인지 별도로 검사했습니다. [원래 인프라 경고 포함 결과](storybook-overview-qa/firefox-initial-infrastructure-result.json)와 [최종 Firefox 결과](storybook-overview-qa/firefox-result.json)를 함께 보존합니다. 이전 [Firefox 검증 기록](sw-header-firefox-range-close-verification-2026-10-19.md)에도 같은 경고가 있습니다.
재사용한 과거 PASS — 이번 실행 결과와 구분:
- [1단계 정리](sw-storybook-cleanup-stage1-2026-10-19.md): 33→28, 범위 검증 49/49, 기존 보존 15/15. 이번에는 현재 28개 ID·이름·태그 유지와 새 메인 동작을 별도로 검증했습니다.
- [속성 필드 검증](sw-property-fields-verification-2026-10-19.md): 필드 77/77, Header 회귀 257 checks 등은 패키지·기존 Story 구현이 동일하므로 관련 근거로 재사용합니다.
- [Firefox 게이지](sw-header-firefox-range-close-verification-2026-10-19.md): 기존 29/29의 게이지 디자인·엔진별 동작은 수정하지 않았습니다. 이번 메인의 게이지 상태 연결은 Chrome에서 새로 검증했습니다.
- 과거 baseline과 1단계 전용 검증 스크립트는 당시의 고정 화면·선언을 기준으로 한 기록입니다. Overview의 의도된 변경으로 무효화되는 화면/선언 동일성 검사를 현재 메인의 합격 기준으로 재사용하지 않았고, 기존 증거를 덮어쓰지 않았습니다.
**NOT VERIFIED:** 다른 27개 Story의 모든 인터랙션에 대한 새 전체 회귀, Firefox의 메인 전체 Dropdown·속성Bar·게이지 연동, 새로운 전체 접근성 인증, CAD/파일/OS 실제 연동. 신규 업무 화면 생성이 아닌 기존 Storybook Reference 수정이므로 AI 화면 생성 Guard는 적용 대상이 아닙니다.
증거: [Chrome](storybook-overview-qa/chrome-result.json), [중첩 Dropdown](storybook-overview-qa/chrome-overlays-result.json), [Firefox](storybook-overview-qa/firefox-result.json), [원본 화면 조사](storybook-overview-qa/screen-survey.json), [수정 전 ID](storybook-overview-qa/before-index.json), [수정 후 ID](storybook-overview-qa/after-index.json).
변경 파일:
- `src/sw/sw-header.stories.tsx` — Overview 연결만 교체.
- `src/sw/sw-header-overview.tsx` — Storybook 전용 호스트 상태와 Inner 크기 계산.
- `src/sw/sw-stories.css` — Overview에만 적용되는 레이아웃·스크롤·팝업 위치.
- `packages/sw/tests/overview.browser.mjs` — 메인·중첩 Overlay 검증.
- `packages/sw/tests/overview-firefox.browser.mjs` — 설치된 Firefox 검증.
- 이 보고서 및 `docs/sw/storybook-overview-qa/`의 새 목록·조사 데이터·화면·검증 기록. [전체 변경 파일 목록](storybook-overview-qa/changed-files.json), [통합 요약과 소스 해시](storybook-overview-qa/validation-summary.json).
## 추가 정리 후보 — 이번에는 제안만
**KEEP 11 / SIMPLIFY 6 / MERGE REVIEW 3 / REMOVE REVIEW 1 / QA 7.** 사용자 승인 없이 적용한 항목은 없습니다. 삭제한 Default/Controls/Responsive/Text Stroke/Dynamic Data는 후보에서 제외했습니다.
- 우선 통합 검토: Property Panel Variants, Additional Hover, Layer Detail · Input의 상세 버튼 부분.
- 삭제 검토: Colorize. Color Dropdown의 대응 시연 및 테스트 참조를 먼저 이전해야 합니다.
- 길이 축소 검토: Interactive States, Document/Layer/Color/Line Style Dropdown, Responsive More.
- 개별 속성창 4개는 상태별 고정 치수·직접 검증 URL을 가진 QA 표본입니다. Compare/Variants와 겹치는 부분이 있어도 즉시 삭제할 근거는 아닙니다.
- Dynamic Controls와 Composition은 각각 `controls`와 `hiddenControls/menuOrder`를 보여 줍니다. API 차이가 있어 유지 권장입니다.
- 별도의 Input Story는 없습니다. Layer Detail · Input 전체를 삭제하면 독립 Input 시연도 사라집니다.
아래 V-01~V-27은 실제 manager 사이드바 표시 순서입니다. V-28 Detail Buttons는 `verification` 태그로 기본 숨김이며 직접 URL로 접근합니다(원본 index에는 첫 번째). [관측한 사이드바 순서](storybook-overview-qa/manager-story-order.json).
Story 링크는 `npm.cmd run storybook` 실행 후 기본 포트 `http://localhost:6024` 기준입니다. 이번 자동 검증은 별도 임시 로컬 포트에서 수행했습니다. 스크린샷은 메인 외에는 수정 전 1440×1000 초기 화면이며, 긴 페이지 전체 내용은 원본 DOM 섹션 조사와 소스를 함께 확인했습니다. **28개 초기 렌더링은 PASS이며, 표의 기능 설명을 모든 인터랙션의 신규 PASS로 해석하면 안 됩니다.**
| 번호 | Story 이름 | Story ID | 현재 내용 | 정리 제안 | 영향도 |
|---|---|---|---|---|---|
| V-01 | [Overview](http://localhost:6024/?path=/story/sw-header--overview) | `sw-header--overview` | 통합 시연 / 사용자: 16:9 Stage에서 Header·전체 Dropdown·속성Bar·속성창·공유 속성을 조작. | **KEEP** — 현재 메인으로 유지. | 중간 |
| V-02 | [Interactive States](http://localhost:6024/?path=/story/sw-header--interactive-states) | `sw-header--interactive-states` | 상태 비교 / 사용자·QA: 12개 섹션: Host state, Hover/Selected/Disabled, 토글, 창 버튼 Callback, 긴 파일명. | **SIMPLIFY** — 반복 Header 표본과 설명을 상태별로 축소 검토. | 낮음 |
| V-03 | [Window Move Motion](http://localhost:6024/?path=/story/sw-header--window-move-motion) | `sw-header--window-move-motion` | 단계별 실컴포넌트 정지 표본 / QA: 기존 9단계의 창 이동 모션을 3배 확대. | **KEEP** — 이번에 확정한 단계별 섹션 그대로 유지. | 낮음 |
| V-04 | [Composed Controls](http://localhost:6024/?path=/story/sw-header--composed-controls) | `sw-header--composed-controls` | 하위 컴포넌트 조합 / 사용자: 레이어·색상·상세보기, 선·문자·치수 공개 하위 컴포넌트와 그룹 Hover. | **KEEP** — 독립 조합 API 사용 예제로 유지. | 중간 |
| V-05 | [Logo Slot](http://localhost:6024/?path=/story/sw-header--logo-slot) | `sw-header--logo-slot` | 설정 예제 / 사용자: 기본 임시 로고, 가로형 logoSrc, 세로형 React logo, 미전달 4개 표본. | **KEEP** — 로고 슬롯 계약 예제로 유지. | 낮음 |
| V-06 | [Document Dropdown](http://localhost:6024/?path=/story/sw-header--document-dropdown) | `sw-header--document-dropdown` | 실제 조작+정적 상태 / 사용자·QA: 18개 섹션: 문서 선택·닫기·New/Open·현재 문서 위치·Hover·18개/빈 목록·긴 이름·390px. | **SIMPLIFY** — 반복 현재 문서/Hover 표본을 한 비교 구역으로 줄이는 방안 검토. | 중간 |
| V-07 | [Layer Dropdown](http://localhost:6024/?path=/story/sw-header--layer-dropdown) | `sw-header--layer-dropdown` | 실제 조작+정적 상태 / 사용자·QA: 19개 섹션: 전구·눈꽃·잠금, 상태 혼합, 선택, 긴/빈/많은 레이어, 6개 좁은 폭. | **SIMPLIFY** — 중복 Header 표본을 줄이고 아이콘 상태표·예외 사례 유지 검토. | 중간 |
| V-08 | [Color Dropdown](http://localhost:6024/?path=/story/sw-header--color-dropdown) | `sw-header--color-dropdown` | 실제 조작+디자인 비교 / 사용자·QA: 9개 섹션: 팔레트·사용자 색상 40개·ByLayer·RGB 입력·Colorize·상세 Callback. | **SIMPLIFY** — 반복 Default/Hover Header와 설명을 축소; Colorize 통합 목적지로 검토. | 중간 |
| V-09 | [Line Style Dropdown](http://localhost:6024/?path=/story/sw-header--line-style-dropdown) | `sw-header--line-style-dropdown` | 실제 조작+디자인 비교 / 사용자·QA: 8개 섹션: 3열 선 종류·가중치·스케일, 현재 행/상세 Hover, 390px. | **SIMPLIFY** — 중복 Default/Hover를 줄이고 3열 입력·좁은 화면 사례 보존 검토. | 중간 |
| V-10 | [Text · Dimension Style Dropdown](http://localhost:6024/?path=/story/sw-header--text-dimension-style-dropdown) | `sw-header--text-dimension-style-dropdown` | 실제 조작+디자인 비교 / 사용자·QA: 문자·치수스타일 목록과 각 행·상세 Hover, 선택 Callback 등 6개 섹션. | **KEEP** — 서로 다른 두 필드의 목록 계약을 함께 확인하는 현 구성 유지. | 낮음 |
| V-11 | [Responsive More](http://localhost:6024/?path=/story/sw-header--responsive-more) | `sw-header--responsive-more` | 반응형 실제 조작+폭 표본 / 사용자·QA: 1440/768/390 조작 구역 3개 + 경계 폭 16개. More 대표 필드 전환과 Callback. | **SIMPLIFY** — 경계 폭 표본을 접는 방식 등 화면 길이 축소 검토. 기능은 유지 권장. | 중간 |
| V-12 | [Layer Detail · Input](http://localhost:6024/?path=/story/sw-header--layer-detail-input) | `sw-header--layer-detail-input` | 상세 버튼+독립 Input / 사용자·QA: 6개 섹션: Figma PNG, 레이어 상세 3상태, Input Default/Hover/ReadOnly/Disabled/Focus 및 입력·IME·확정/취소. | **MERGE REVIEW** — 상세 버튼 비교는 Detail Buttons로 옮기는 방안 검토. 독립 Input 시연은 남겨야 함. | 중간 |
| V-13 | [Property Panel](http://localhost:6024/?path=/story/sw-header--property-panel) | `sw-header--property-panel` | Header 연동과 데이터 변형 / 사용자·QA: 4가지 선택, Controlled/Uncontrolled, 외부 Open/값, 항목 추가·삭제·순서, 제한 상태·게이지. | **KEEP** — 고급 데이터/API 검증 화면으로 유지. | 중간 |
| V-14 | [Property Panel Variants](http://localhost:6024/?path=/story/sw-header--property-panel-variants) | `sw-header--property-panel-variants` | 독립 패널과 데이터 변형 / QA: 같은 PropertyPanelDemo를 standalone으로 렌더링. 4상태·데이터 버튼이 Property Panel과 거의 동일. | **MERGE REVIEW** — 단독 패널 기준이 필요하면 4개 개별/4개 비교 화면과 통합 검토. | 중간 |
| V-15 | [K Guide](http://localhost:6024/?path=/story/sw-header--k-guide) | `sw-header--k-guide` | 실시간 모션 / 사용자: K 버튼 Hover/Focus의 원본 이미지 Pan·Zoom, 이탈 정지, Reduced Motion 안내. | **KEEP** — 고유한 가이드 모션 예제로 유지. | 낮음 |
| V-16 | [Additional Hover](http://localhost:6024/?path=/story/sw-header--additional-hover) | `sw-header--additional-hover` | 상세/Selected 회귀·외부 모드 / QA: StyleDemo 1920과 RequiredColorDemo: 5개 상세 Hover, Selected, 외부 ByLayer/ByBlock/직접 색상. | **MERGE REVIEW** — 상세 상태는 Detail Buttons, 외부 색상 모드는 Color Dropdown으로 이전 검토. | 중간 |
| V-17 | [Property Bar](http://localhost:6024/?path=/story/sw-header--property-bar) | `sw-header--property-bar` | 속성Bar 전체 API / 사용자·QA: Controlled, 외부 Open/값, 추가·삭제·순서 반전·객체 교체·개별 Disabled/ReadOnly, 1222px 부모 폭. | **KEEP** — 메인의 참조 구현이자 고급 API 예제로 유지. | 중간 |
| V-18 | [Dynamic Controls](http://localhost:6024/?path=/story/sw-header--dynamic-controls) | `sw-header--dynamic-controls` | controls allowlist / 사용자: 12개 구성의 제거·재추가·라벨·순서·visible, 동적 문서 데이터. | **KEEP** — Composition과 다른 API임을 설명하는 방향으로 유지. | 중간 |
| V-19 | [Colorize](http://localhost:6024/?path=/story/sw-header--colorize) | `sw-header--colorize` | 색상 추출 버튼 접근 / QA: 별도 기본/확대 상태표 없이 StyleDemo(1920)를 다시 표시. 색상 Dropdown의 추출 버튼으로 연결. | **REMOVE REVIEW** — Color Dropdown에 버튼 접근·Hover·Callback 증거를 남긴 뒤 메뉴 삭제 검토 가능. | 중간 |
| V-20 | [Composition (Visibility · Order)](http://localhost:6024/?path=/story/sw-header--composition) | `sw-header--composition` | hiddenControls/menuOrder / 사용자·QA: 16개 표시 항목, 프리셋·순서·중복/알 수 없는 ID, 실제 화면/고정 폭, Callback 보존. | **KEEP** — 개별 제거와 순서 계약을 보여 주는 예제로 유지. | 중간 |
| V-21 | [Property Panel · 선택 없음](http://localhost:6024/?path=/story/sw-header--property-panel-none) | `sw-header--property-panel-none` | 단독 실제 컴포넌트 / 디자인 QA: 선택 없음 314×256 고정 상태와 실제 필드. | **QA** — 일반 사용자 목록보다는 QA 분류 검토. | 중간 |
| V-22 | [Property Panel · 전체 선택](http://localhost:6024/?path=/story/sw-header--property-panel-all) | `sw-header--property-panel-all` | 단독 실제 컴포넌트 / 디자인 QA: 전체 선택 314×361, 혼합 레이어 값과 선택 개수. | **QA** — 개별 QA 기준으로 유지하거나 Compare 통합 검토. | 중간 |
| V-23 | [Property Panel · 원 1개](http://localhost:6024/?path=/story/sw-header--property-panel-circle) | `sw-header--property-panel-circle` | 단독 실제 컴포넌트 / 디자인 QA: 원 1개 314×658, 기본 형상·치수·XYZ·전체 선택 복귀. | **QA** — 개별 QA 기준으로 유지하거나 Compare 통합 검토. | 중간 |
| V-24 | [Property Panel · 모두 펼침](http://localhost:6024/?path=/story/sw-header--property-panel-expanded) | `sw-header--property-panel-expanded` | 단독 실제 컴포넌트 / 디자인 QA: 전체 펼침 314×754, 추가 공통 속성·형상·위치·푸터. | **QA** — 개별 QA 기준으로 유지하거나 Compare 통합 검토. | 중간 |
| V-25 | [Property Panel · 4개 비교](http://localhost:6024/?path=/story/sw-header--property-panel-compare) | `sw-header--property-panel-compare` | 4개 실제 패널 병렬 비교 / 디자인 QA: 4개 선택 상태를 동시에 렌더링하고 실측 폭·높이를 표시. | **QA** — 나란히 비교하는 QA 목적을 유지. 개별 메뉴 통합 목적지로 검토 가능. | 중간 |
| V-26 | [Property Panel · 형상 Slot](http://localhost:6024/?path=/story/sw-header--property-shape-slot) | `sw-header--property-shape-slot` | 형상 확장 API / 사용자: 기본/null/ReactNode/Render Prop, 원·사각형 교체, 치수/그룹 추가. | **KEEP** — 외부 컴포넌트 확장 계약 예제로 유지. | 중간 |
| V-27 | [Property Panel · 필드 13개](http://localhost:6024/?path=/story/sw-header--property-fields) | `sw-header--property-fields` | 필드 상태·실제 조작 / 디자인 QA: Figma 13개 필드 Default/Hover 표본, 실제 패널, 혼합 레이어·외부 값·제한·단위. | **QA** — 77개 필드 검증의 기준 화면으로 유지 권장. | 중간 |
| V-28 | [Detail Buttons (Shared States)](http://localhost:6024/?path=/story/sw-header--detail-buttons) | `sw-header--detail-buttons` | 실제 Pointer/Callback 비교 / QA: 5개 상세 버튼 × Default/Hover/Disabled/Callback 없음, 클릭 횟수. verification 태그로 기본 숨김. | **QA** — 이미 적용된 숨김과 검증 표본 유지. | 중간 |
## 화면별 삭제 영향과 대체 가능성
### V-01 · Overview
대체 메인 없음. 이번에 실제 수정한 유일한 Story.
[현재 화면](http://localhost:6024/?path=/story/sw-header--overview) · [대표 스크린샷](storybook-overview-qa/overview-after.png)
### V-02 · Interactive States
삭제하면 Disabled+Hover·키보드·창 버튼 Callback 비교를 잃음. 메인은 상태 비교표를 대체하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--interactive-states) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--interactive-states.png)
### V-03 · Window Move Motion
타이밍·위치 비교의 유일한 상세 화면. 메인의 실시간 Hover와 목적이 다름.
[현재 화면](http://localhost:6024/?path=/story/sw-header--window-move-motion) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--window-move-motion.png)
### V-04 · Composed Controls
전체 Header 통합 시연으로 하위 컴포넌트 단독 조합을 대체할 수 없음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--composed-controls) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--composed-controls.png)
### V-05 · Logo Slot
삭제하면 이미지/ReactNode/미전달의 차이를 직접 확인할 수 없음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--logo-slot) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--logo-slot.png)
### V-06 · Document Dropdown
메인은 기본 선택·목록 변경을 대체하지만 빈 목록·긴 이름·행 위치·18개 스크롤 사례는 대체하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--document-dropdown) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--document-dropdown.png)
### V-07 · Layer Dropdown
메인은 정상 선택·Toggle만 대체. On/Off/Hover 아이콘 매트릭스·20개 목록·Callback 없는 상태가 고유함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--layer-dropdown) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--layer-dropdown.png)
### V-08 · Color Dropdown
메인의 색상 변경은 40개 색상·정적 Hover 비교를 대체하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--color-dropdown) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--color-dropdown.png)
### V-09 · Line Style Dropdown
메인은 정상 선택을 대체하지만 상세 상태·열 배치 기준은 남길 필요가 있음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--line-style-dropdown) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--line-style-dropdown.png)
### V-10 · Text · Dimension Style Dropdown
메인의 선택만으로 각 행 미리보기와 Figma Hover 비교를 대체하지 못함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--text-dimension-style-dropdown) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--text-dimension-style-dropdown.png)
### V-11 · Responsive More
삭제한 Responsive와 다른 기능. 메인은 현재 폭만 보여 주므로 모든 경계값 비교를 대체하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--responsive-more) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--responsive-more.png)
### V-12 · Layer Detail · Input
독립 Input Story는 현재 없음. 페이지 전체를 지우면 텍스트/숫자·외부 Props·취소 예제를 잃음. detail-buttons 검증이 이 ID를 참조함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--layer-detail-input) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--layer-detail-input.png)
### V-13 · Property Panel
메인에 없는 Controlled 전환, 개별 제한·옵션 삭제·선택 객체 교체가 남음. Firefox·닫기·필드 테스트가 참조.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel.png)
### V-14 · Property Panel Variants
Header Overlay·Focus 복귀와 다른 단독 맥락. standalone의 닫기는 기록만 하고 패널을 유지하며 너비/Open 버튼은 Header 연동 검증을 대신하지 못함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-variants) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-variants.png)
### V-15 · K Guide
Header 실시간 모션과 창 이동 1400ms 모션은 별개. 메인에서는 일부만 볼 수 있음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--k-guide) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--k-guide.png)
### V-16 · Additional Hover
외부 colorMode 제어는 메인의 일반 색상 선택과 다름. followup-four가 이 ID를 참조하므로 먼저 대체 시나리오가 필요함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--additional-hover) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--additional-hover.png)
### V-17 · Property Bar
메인은 공유 값·기본 추가/제거·제한만 제공. 개별 제한·순서·객체 교체·Controlled를 대체하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-bar) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-bar.png)
### V-18 · Dynamic Controls
controls는 전체 allowlist이며 로고 외 16개 요소를 숨기는 hiddenControls와 다름. 이름이 비슷해도 동일 기능이 아님.
[현재 화면](http://localhost:6024/?path=/story/sw-header--dynamic-controls) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--dynamic-controls.png)
### V-19 · Colorize
실제 색상 추출 CAD 기능은 호스트 소유. 삭제 시 전용 접근 URL을 잃고 detail-buttons preservation의 colorize 참조를 이전해야 함. 지금은 삭제하지 않음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--colorize) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--colorize.png)
### V-20 · Composition (Visibility · Order)
Dynamic Controls와 다른 API. 홈·창 이동·최소화·종료까지 개별 제어하고 메뉴 순서 정규화를 검증함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--composition) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--composition.png)
### V-21 · Property Panel · 선택 없음
Variants/Compare와 겹치지만 테스트의 상태별 직접 URL·치수 기준임. 삭제/통합 시 ID 참조 이전 필요.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-none) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-none.png)
### V-22 · Property Panel · 전체 선택
선택 없음/원 상태와 내용이 다름. 테스트 직접 URL·혼합 값·개수 레이아웃을 보존해야 함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-all) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-all.png)
### V-23 · Property Panel · 원 1개
원형 Slot과 위치·푸터를 포함. 다른 상태와 같은 이름의 단순 중복이 아님.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-circle) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-circle.png)
### V-24 · Property Panel · 모두 펼침
754px 전체 높이와 하단 잘림 검증에 필요. 개별 ID가 기존 테스트에서 동적으로 참조됨.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-expanded) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-expanded.png)
### V-25 · Property Panel · 4개 비교
Variants는 상태 전환, Compare는 동시 비교라는 차이가 있음. property-fields의 4상태 치수 검증이 참조함.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-panel-compare) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-panel-compare.png)
### V-26 · Property Panel · 형상 Slot
메인은 기본/null만 제공. ReactNode와 Render Prop·형상 교체를 대체할 수 없음.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-shape-slot) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-shape-slot.png)
### V-27 · Property Panel · 필드 13개
메인은 필드별 디자인 상태표를 대체하지 않음. property-fields.browser.mjs의 핵심 직접 ID.
[현재 화면](http://localhost:6024/?path=/story/sw-header--property-fields) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--property-fields.png)
### V-28 · Detail Buttons (Shared States)
메인은 실제 Dropdown 접근용이며 이 비교 매트릭스를 대체하지 못함. detail-buttons 검증의 직접 ID.
[현재 화면](http://localhost:6024/?path=/story/sw-header--detail-buttons) · [대표 스크린샷](storybook-overview-qa/screens/sw-header--detail-buttons.png)
@@ -0,0 +1,68 @@
// Installed Firefox via WebDriver BiDi, matching the existing SW cross-browser workflow.
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { spawn } from 'node:child_process'
import path from 'node:path'
import http from 'node:http'
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-overview-qa')
await mkdir(out,{recursive:true})
const report = { checks:[], errors:[], infrastructureWarnings:[] }
const check = (name,ok,data) => {report.checks.push({name,status:ok?'PASS':'FAIL',data});console.log(ok?'PASS':'FAIL',name)}
const sleep = ms => new Promise(resolve=>setTimeout(resolve,ms))
const root=path.resolve('storybook-static'),mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.ttf':'font/ttf','.png':'image/png'}
const server=http.createServer(async(req,res)=>{try{const p=new URL(req.url,'http://localhost').pathname,file=path.resolve(root,'.'+decodeURIComponent(p==='/'?'/index.html':p));if(path.relative(root,file).startsWith('..'))return res.writeHead(403).end();res.setHeader('Content-Type',mime[path.extname(file)]??'application/octet-stream');res.end(await readFile(file))}catch{res.writeHead(404).end()}})
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve))
const url=`http://127.0.0.1:${server.address().port}/iframe.html?id=sw-header--overview&viewMode=story`
let processFirefox,ws
try {
for(const [width,height]of[[1400,1000],[500,800]]){
const profile=path.resolve('_local',`overview-firefox-${process.pid}-${width}`)
await mkdir(profile,{recursive:true})
const port=9500+process.pid%400
processFirefox=spawn(process.env.SW_FIREFOX_PATH??'C:/Program Files/Mozilla Firefox/firefox.exe',['--headless','--no-remote','--profile',profile,`--remote-debugging-port=${port}`],{stdio:'ignore',env:{...process.env,MOZ_HEADLESS_WIDTH:String(width),MOZ_HEADLESS_HEIGHT:String(height)}})
ws=undefined
for(let i=0;i<40&&!ws;i++){await sleep(250);try{ws=await new Promise((resolve,reject)=>{const s=new WebSocket(`ws://127.0.0.1:${port}/session`);s.onopen=()=>resolve(s);s.onerror=reject})}catch{ws=undefined}}
if(!ws)throw Error('Firefox BiDi unavailable')
let id=0;const pending=new Map()
ws.onmessage=event=>{const m=JSON.parse(event.data);if(m.id&&pending.has(m.id)){const {resolve,reject}=pending.get(m.id);pending.delete(m.id);m.type==='error'?reject(Error(m.message)):resolve(m.result)}else if(m.method==='log.entryAdded'&&m.params.level==='error'){
const text=m.params.text
if(text.startsWith('downloadable font: download failed')&&text.includes('/sb-common-assets/nunito-sans-regular.woff2'))report.infrastructureWarnings.push(text)
else report.errors.push(text)
}}
const send=(method,params={})=>new Promise((resolve,reject)=>{const next=++id;pending.set(next,{resolve,reject});ws.send(JSON.stringify({id:next,method,params}))})
const session=await send('session.new',{capabilities:{}});report.version=session.capabilities.browserVersion
await send('session.subscribe',{events:['log.entryAdded']})
const context=(await send('browsingContext.getTree')).contexts[0].context
const evaluate=async expression=>{const r=await send('script.evaluate',{expression,target:{context},awaitPromise:true});if(r.type==='exception')throw Error(r.exceptionDetails.text);return r.result.value}
const data=async expression=>JSON.parse(await evaluate(`JSON.stringify(${expression})`))
const settle=()=>evaluate('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve(true))))')
const key=async value=>send('input.performActions',{context,actions:[{type:'key',id:'keyboard',actions:[{type:'keyDown',value},{type:'keyUp',value}]}]})
await send('browsingContext.navigate',{context,url,wait:'complete'})
let ready=false
for(let i=0;i<40&&!ready;i++){ready=await evaluate('!!document.querySelector("[data-preview-stage]")');if(!ready)await sleep(100)}
if(!ready)throw Error('Overview did not render')
await settle()
const fit=await data('(()=>{const s=document.querySelector("[data-preview-stage]").getBoundingClientRect(),b=document.querySelector("[data-preview-slot]").getBoundingClientRect();return {width:s.width,height:s.height,slotWidth:b.width,slotHeight:b.height,headerHeight:document.querySelector("header.hm-sw-header").getBoundingClientRect().height,viewportWidth:innerWidth,viewportHeight:innerHeight}})()')
check(`Firefox ${width}: 16:9 in Inner and Header 49`,Math.abs(fit.width/fit.height-16/9)<.005&&fit.width<=fit.slotWidth+.1&&fit.height<=fit.slotHeight+.1&&fit.headerHeight===49,fit)
await evaluate('(()=>{const s=document.querySelector("select");s.value="expanded";s.dispatchEvent(new Event("change",{bubbles:true}));return true})()');await settle()
await evaluate('(()=>{const b=document.querySelector("[data-property-panel-trigger]");if(b.getClientRects().length)b.click();else{document.querySelector(".hm-sw-header__more-trigger").click()}return true})()');await settle()
await evaluate('(()=>{const m=[...document.querySelectorAll("[role=menuitemcheckbox]")].find(e=>e.textContent.trim()==="속성창");if(m)m.click();return true})()');await settle()
const popup=await data('(()=>{const e=document.querySelector(".hm-sw-property"),r=e.getBoundingClientRect();return {width:r.width,height:r.height,focus:document.activeElement===e}})()')
check(`Firefox ${width}: full original popup 314x754`,popup.width===314&&popup.height===754,popup)
const bottom=await data('(()=>{const z=document.querySelector(".hm-sw-property [data-property-id=z] input"),s=document.querySelector("[data-preview-stage]");z.scrollIntoView({block:"nearest",inline:"nearest"});z.focus();const r=z.getBoundingClientRect();return {top:s.scrollTop,visible:document.elementFromPoint(r.x+r.width/2,r.y+r.height/2)===z}})()')
check(`Firefox ${width}: popup bottom reachable and focusable`,bottom.top>0&&bottom.visible,bottom)
await key('\uE00C');await settle()
check(`Firefox ${width}: Escape closes popup and restores focus`,await evaluate('!document.querySelector(".hm-sw-property")&&document.activeElement.matches("[data-property-panel-trigger],.hm-sw-header__more-trigger")'))
await writeFile(path.join(out,`overview-firefox-${width}.png`),Buffer.from((await send('browsingContext.captureScreenshot',{context})).data,'base64'))
await send('session.end');ws.close();ws=undefined;processFirefox.kill();processFirefox=undefined
await sleep(250)
}
check('Firefox SW runtime errors absent; known Storybook font warnings recorded separately',report.errors.length===0,{errors:report.errors,warnings:report.infrastructureWarnings})
}catch(error){check('Firefox suite completes',false,error.stack)}
finally{
ws?.close();processFirefox?.kill();await new Promise(resolve=>server.close(resolve))
report.status=report.checks.every(c=>c.status==='PASS')?'PASS':'FAIL'
await writeFile(path.join(out,'firefox-result.json'),JSON.stringify(report,null,2)+'\n')
console.log(JSON.stringify({status:report.status,checks:report.checks.length,version:report.version}))
process.exitCode=report.status==='PASS'?0:1
}
+217
View File
@@ -0,0 +1,217 @@
// Focused Overview integration checks. Build Storybook first; historical evidence stays untouched.
// Usage: node packages/sw/tests/overview.browser.mjs [evidence-dir]
import { createRequire } from 'node:module'
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { execFileSync } from 'node:child_process'
import path from 'node:path'
import http from 'node:http'
import { parse } from '@babel/parser'
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-overview-qa')
await mkdir(out, { recursive: true })
const report = { baseCommit: '50bf5bda8e956a4973b923d64131f2f2285bd38c', checks: [], errors: [], sizes: [] }
const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); console.log(ok ? 'PASS' : 'FAIL', name) }
const before = JSON.parse(await readFile(path.join(out, 'before-index.json'), 'utf8'))
const after = Object.values(JSON.parse(await readFile('storybook-static/index.json', 'utf8')).entries).filter(e => e.type === 'story' && e.title === 'SW/Header')
await writeFile(path.join(out, 'after-index.json'), JSON.stringify(after, null, 2) + '\n')
check('all 28 Story IDs, names, order and tags retained', after.length === 28 && JSON.stringify(before.map(e => [e.id,e.name,e.tags])) === JSON.stringify(after.map(e => [e.id,e.name,e.tags])))
const source = await readFile('src/sw/sw-header.stories.tsx', 'utf8')
const baseline = execFileSync('git', ['show', `${report.baseCommit}:src/sw/sw-header.stories.tsx`], { encoding: 'utf8' })
const stories = text => Object.fromEntries(parse(text, { sourceType:'module', plugins:['typescript','jsx'] }).program.body
.filter(n => n.type === 'ExportNamedDeclaration').map(n => [n.declaration.declarations[0].id.name, text.slice(n.start,n.end).replaceAll('\r\n','\n')]))
const oldStories = stories(baseline), newStories = stories(source)
check('other 27 Story declarations byte-identical', Object.keys(oldStories).filter(k => k !== 'Overview').every(k => oldStories[k] === newStories[k]))
check('package production source untouched', !execFileSync('git', ['diff', report.baseCommit, '--', 'packages/sw/src'], { encoding:'utf8' }))
const root = path.resolve('storybook-static'), mime = { '.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.ttf':'font/ttf','.png':'image/png' }
const server = http.createServer(async(req,res) => {
try { const p = new URL(req.url,'http://localhost').pathname, file = path.resolve(root,'.'+decodeURIComponent(p === '/' ? '/index.html' : p))
if (path.relative(root,file).startsWith('..')) return res.writeHead(403).end()
res.setHeader('Content-Type',mime[path.extname(file)] ?? 'application/octet-stream'); res.end(await readFile(file))
} catch { res.writeHead(404).end() }
})
await new Promise(resolve => server.listen(0,'127.0.0.1',resolve))
const base = `http://127.0.0.1:${server.address().port}`
const { chromium } = createRequire(import.meta.url)(process.env.SW_PLAYWRIGHT_PATH ?? 'C:/Users/user/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright')
let browser, page
try {
browser = await chromium.launch({ headless:true, executablePath:process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' })
report.browser = browser.version()
page = await browser.newPage({ viewport:{width:1440,height:1000} }); page.setDefaultTimeout(6000)
page.on('pageerror', e => report.errors.push(e.message)); page.on('console', m => { if(m.type() === 'error') report.errors.push(m.text()) })
const go = async(id='overview') => {
await page.goto(`${base}/iframe.html?id=sw-header--${id}&viewMode=story`)
await page.locator('.hm-sw-header,.hm-sw-theme').first().waitFor(); await page.evaluate(() => document.fonts.ready)
await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))))
}
const geometry = () => page.evaluate(() => {
const box = e => { const r=e.getBoundingClientRect(); return {x:r.x,y:r.y,w:r.width,h:r.height,right:r.right,bottom:r.bottom} }
const stage=document.querySelector('[data-preview-stage]'),slot=document.querySelector('[data-preview-slot]')
return {stage:box(stage),slot:box(slot),header:box(document.querySelector('header.hm-sw-header')),viewport:{w:innerWidth,h:innerHeight},overflow:document.documentElement.scrollWidth-document.documentElement.clientWidth}
})
if(process.argv[3]==='overlays'){
await go()
const stage=page.locator('[data-preview-stage]'),header=page.locator('header.hm-sw-header')
const covered=locator=>locator.evaluate(e=>{const r=e.getBoundingClientRect();return e.contains(document.elementFromPoint(r.x+r.width/2,r.y+r.height/2))})
await header.locator('.hm-sw-header__document-button').click()
const firstDocument=header.locator('.hm-sw-header__document-item').first()
check('document first row is above the property Bar and clickable',await covered(firstDocument));await firstDocument.click()
for(const [id,label,detail]of[['layer','레이어','레이어 상세'],['color','색상','색상 상세'],['line-style','선스타일','선스타일 상세'],['text-style','문자스타일','문자스타일 상세'],['dimension-style','치수스타일','치수스타일 상세']]){
await stage.evaluate(e=>e.scrollTop=0)
const button=header.getByRole('button',{name:label,exact:true})
if(await button.isVisible())await button.click();else{await header.getByRole('button',{name:'더보기',exact:true}).click();await header.locator(`[role=menuitem][data-menu="${id}"]`).click()}
await header.getByRole('button',{name:detail,exact:true}).click()
check(`existing ${detail} reports Callback`,(await page.locator('[data-preview-callback]').textContent()).includes(`on${{'layer':'Layer','color':'Color','line-style':'LineStyle','text-style':'TextStyle','dimension-style':'DimensionStyle'}[id]}Detail`))
}
await header.getByRole('button',{name:'색상',exact:true}).click()
await header.getByRole('button',{name:'ByBlock',exact:true}).click()
check('ByBlock mode immediately appears in Header without changing numeric RGB property',(await header.locator('.hm-sw-header__field--color').textContent()).includes('ByBlock'))
await page.keyboard.press('Escape')
await header.getByRole('button',{name:'색상',exact:true}).click()
await header.locator('.hm-sw-header__color-option').first().click()
check('direct color selection clears Header mode label',!(await header.locator('.hm-sw-header__field--color').textContent()).includes('ByBlock'))
await page.setViewportSize({width:390,height:700});await go();await page.getByLabel('선택 상태',{exact:true}).selectOption('expanded')
await header.getByRole('button',{name:'더보기',exact:true}).click();await header.getByRole('menuitemcheckbox',{name:'속성창',exact:true}).click()
for(const id of ['layer','line','color']){
const field=page.locator(`.hm-sw-property [data-property-id="${id}"]`),trigger=field.locator(id==='color'?'input':'button')
await trigger.scrollIntoViewIfNeeded();await trigger.click()
const drop=page.locator('.hm-sw-property__drop');await drop.waitFor()
const r=await drop.boundingBox()
check(`small Inner ${id} dropdown stays in browser width`,r.x>=0&&r.x+r.width<=390.5,r)
if(id==='layer'){const last=drop.getByRole('button',{name:'Sample-Text',exact:true});check('small Inner lower layer row hit-test is unobstructed',await covered(last));await last.click()}
else await page.keyboard.press('Escape')
check(`small Inner ${id} selection or Escape closes only nested dropdown`,await drop.count()===0&&await page.locator('.hm-sw-property').count()===1)
}
await page.screenshot({path:path.join(out,'overview-narrow-overlay.png')})
check('no overlay browser errors',report.errors.length===0,report.errors)
}else{
for(const [width,height] of [[1920,1080],[1440,900],[1280,720],[768,600],[390,844],[1000,500]]) {
await page.setViewportSize({width,height}); await go()
const g=await geometry(); report.sizes.push(g)
check(`16:9 fitted to both Inner dimensions at ${width}x${height}`,g.stage.w>0&&Math.abs(g.stage.w/g.stage.h-16/9)<.005&&g.stage.w<=g.slot.w+.1&&g.stage.h<=g.slot.h+.1&&g.header.h===49&&g.overflow===0,g)
}
await page.setViewportSize({width:1440,height:1000}); await go()
const header=page.locator('header.hm-sw-header'),stage=page.locator('[data-preview-stage]')
const callback=()=>page.locator('[data-preview-callback]').textContent()
const open=async(id,label) => {
await stage.evaluate(e=>{e.scrollTop=0;e.scrollLeft=0})
const button=header.getByRole('button',{name:label,exact:true})
if(await button.isVisible()) {if(await button.getAttribute('aria-expanded')!=='true')await button.click()}
else {await header.getByRole('button',{name:'더보기',exact:true}).click();await header.locator(`[role="menuitem"][data-menu="${id}"]`).click()}
}
const panelButton=async()=>{
const b=header.locator('[data-property-panel-trigger]')
if(await b.isVisible())await b.click()
else{await header.getByRole('button',{name:'더보기',exact:true}).click();await header.getByRole('menuitemcheckbox',{name:'속성창',exact:true}).click()}
}
const barInput=id=>page.locator(`.hm-sw-property-bar [data-property-id="${id}"] input`)
const panelInput=id=>page.locator(`.hm-sw-property [data-property-id="${id}"] input`).first()
const waitValue=async(locator,value)=>page.waitForFunction(([element,value])=>element.value===value,[await locator.elementHandle(),value])
check('one Header and no duplicate fixed Figma specimen',await header.count()===1&&await page.getByText('Figma 기준 1920 × 49',{exact:true}).count()===0)
await header.locator('.hm-sw-header__document-button').click()
const document=header.locator('.hm-sw-header__document-list .hm-sw-header__document-item').nth(1)
const documentName=await document.textContent();await document.click()
check('document selection updates Header and Callback',!!documentName&&(await header.locator('.hm-sw-header__document-button').textContent()).includes(documentName.trim())&&(await callback()).includes('onDocumentSelect'),await callback())
await open('layer','레이어');await header.getByRole('button',{name:'CC-D GNL',exact:true}).click()
check('layer selection updates shared data', (await callback()).includes('onLayerSelect(layer, CC-D GNL)'),await callback())
await open('layer','레이어');await header.getByRole('button',{name:'레이어 상세',exact:true}).click()
check('existing layer Detail callback closes list',await header.locator('.hm-sw-header__layer-panel').count()===0&&(await callback()).includes('onLayerDetail'))
await open('color','색상')
const chip=header.locator('.hm-sw-header__color-menu .hm-sw-header__color-option').first()
await chip.click();check('color selection invokes host callback',(await callback()).includes('onColorSelect'),await callback())
await open('line-style','선스타일');await header.getByRole('button',{name:'CENTER',exact:true}).click()
check('line selection updates Header', (await callback()).includes('onLineTypeSelect(line, CENTER)'),await callback())
await open('line-style','선스타일');await header.locator('ul[aria-label="가중치"]').getByRole('button',{name:'ByLayer',exact:true}).click()
check('ByLayer weight mode does not turn numeric property into a mode string',(await header.locator('.hm-sw-header__field--line-style').textContent()).includes('ByLayer'))
await page.keyboard.press('Escape')
await open('text-style','문자스타일');await header.locator('.hm-sw-header__list-menu--text-style').getByRole('button',{name:/Dotum/}).click()
check('text style selection updates Header',(await callback()).includes('onTextStyleSelect(dotum)'),await callback())
await open('dimension-style','치수스타일');await header.locator('.hm-sw-header__list-menu--dimension-style').getByRole('button',{name:/ISO - 25/}).click()
check('dimension style selection updates Header',(await callback()).includes('onDimensionStyleSelect(iso-25)'),await callback())
await open('color','색상');await open('layer','레이어')
check('opening another dropdown closes the previous one',await header.locator('.hm-sw-header__color-menu').count()===0&&await header.locator('.hm-sw-header__layer-panel').count()===1)
await page.keyboard.press('Escape');check('Escape closes Header dropdown',await header.locator('.hm-sw-header__layer-panel').count()===0)
await open('layer','레이어');await page.locator('.sb-page-title').click();check('outside click closes Header dropdown',await header.locator('.hm-sw-header__layer-panel').count()===0)
await barInput('radius').fill('6.25');await barInput('radius').press('Enter')
check('Bar Enter commits state',await barInput('radius').inputValue()==='6.25'&&(await callback()).includes('onPropertyChange(radius, 6.25)'))
await barInput('radius').fill('-1');await barInput('radius').press('Enter');check('Bar validates min=0',await barInput('radius').inputValue()==='6.25')
await barInput('radius').fill('9');await barInput('radius').press('Escape');check('Bar Escape cancels edit',await barInput('radius').inputValue()==='6.25')
await panelButton();await page.locator('.hm-sw-property').waitFor()
check('Header opens panel with same Bar value',await panelInput('radius').inputValue()==='6.25')
check('numeric weight property remains numeric after ByLayer mode',await panelInput('weight').inputValue()==='0.25')
check('Header layer value shared with panel',(await page.locator('.hm-sw-property [data-property-id="layer"]').textContent()).includes('CC-D GNL'))
await panelInput('radius').fill('7.5');await panelInput('radius').press('Enter')
await waitValue(barInput('radius'),'7.5')
check('panel edit updates the Bar',await barInput('radius').inputValue()==='7.5')
await panelInput('radius').fill('-2');await panelInput('radius').press('Enter');check('panel number validation rejects negative radius',await panelInput('radius').inputValue()==='7.5')
await page.locator('.hm-sw-property [data-property-id="layer"] button').click()
const drop=page.locator('.hm-sw-property__drop')
await drop.getByRole('button',{name:'Sample-Wall',exact:true}).click()
check('panel Layer dropdown updates Header shared value',(await header.locator('.hm-sw-header__field--layer').textContent()).includes('Sample-Wall'))
await page.locator('.hm-sw-property [data-property-id="line"] button').click();await drop.getByRole('button',{name:'HIDDEN',exact:true}).click()
check('panel line dropdown updates Header shared value',(await header.locator('.hm-sw-header__field--line-style').textContent()).includes('HIDDEN'))
await panelInput('color').click()
const colorOption=drop.locator('.hm-sw-header__color-option').nth(1)
const expectedColor=await colorOption.evaluate(e=>getComputedStyle(e.querySelector('.hm-sw-header__color-chip > span')).backgroundColor.match(/\d+/g).slice(0,3).join(','))
await colorOption.click();await waitValue(panelInput('color'),expectedColor)
const selectedColor=await panelInput('color').inputValue()
check('panel color dropdown updates Header shared value',(await header.locator('.hm-sw-header__field--color').textContent()).includes(selectedColor))
await panelInput('color').fill('300,0,0');await panelInput('color').press('Enter');check('panel RGB validation rejects out-of-range value',await panelInput('color').inputValue()===selectedColor)
await page.keyboard.press('Escape')
const gauge=page.locator('.hm-sw-property input[type="range"]')
await gauge.press('End');await gauge.press('Enter');await waitValue(panelInput('opacity'),'1')
check('gauge keyboard editing commits host state',await panelInput('opacity').inputValue()==='1')
await panelInput('radius').fill('8.5');await panelInput('radius').press('Escape')
check('first Escape cancels draft and retains panel',await panelInput('radius').inputValue()==='7.5'&&await page.locator('.hm-sw-property').count()===1)
await panelInput('radius').press('Escape');check('next Escape closes panel and restores Header focus',await page.locator('.hm-sw-property').count()===0&&await header.locator('[data-property-panel-trigger]').evaluate(e=>document.activeElement===e))
await panelButton();await page.getByRole('button',{name:'속성창 닫기',exact:true}).click();check('panel Close button restores focus',await header.locator('[data-property-panel-trigger]').evaluate(e=>document.activeElement===e))
await page.getByText('시연 데이터',{exact:true}).click()
await page.getByRole('button',{name:'외부 값 12.5',exact:true}).click();await waitValue(barInput('radius'),'12.5');check('external Props update Bar',await barInput('radius').inputValue()==='12.5')
await page.getByRole('checkbox',{name:'속성 ReadOnly',exact:true}).check();check('Bar ReadOnly',await barInput('radius').evaluate(e=>e.readOnly))
await page.getByRole('checkbox',{name:'속성 ReadOnly',exact:true}).uncheck()
await page.getByRole('checkbox',{name:'속성 Disabled',exact:true}).check();check('Bar Disabled',await barInput('radius').isDisabled())
await page.getByRole('checkbox',{name:'속성 Disabled',exact:true}).uncheck()
await page.getByRole('button',{name:'필드 추가',exact:true}).click();check('dynamic Bar field added',await page.locator('.hm-sw-property-bar [data-property-id^="preview-extra-"]').count()===1)
await panelButton();check('dynamic field shared with panel',await page.locator('.hm-sw-property [data-property-id^="preview-extra-"]').count()===1)
await page.getByRole('button',{name:'추가 필드 제거',exact:true}).click();check('dynamic field removed from both',await page.locator('[data-property-id^="preview-extra-"]').count()===0)
await page.getByRole('checkbox',{name:'형상 Slot 비우기',exact:true}).check();check('shape Slot can be empty without changing panel structure',await page.locator('.hm-sw-property [data-shape-slot="empty"]').count()===1)
await page.getByRole('checkbox',{name:'형상 Slot 비우기',exact:true}).uncheck()
await page.getByText('시연 데이터',{exact:true}).click()
for(const [selection,height]of[['none',256],['all',361],['circle',658],['expanded',754]]){
await page.getByLabel('선택 상태',{exact:true}).selectOption(selection)
if(await page.locator('.hm-sw-property').count()===0)await panelButton()
const box=await page.locator('.hm-sw-property').boundingBox()
check(`original panel size ${selection}:314x${height}`,box.width===314&&box.height===height,box)
}
await page.screenshot({path:path.join(out,'overview-after-expanded.png')})
await stage.evaluate(e=>{e.scrollTop=e.scrollHeight})
await panelInput('z').scrollIntoViewIfNeeded();await panelInput('z').fill('5');await panelInput('z').press('Enter')
check('expanded popup bottom accessible through internal scroll',await panelInput('z').inputValue()==='5')
await page.screenshot({path:path.join(out,'overview-after-scrolled.png')})
await go();await page.screenshot({path:path.join(out,'overview-after.png')})
// A small Inner keeps native component dimensions; the Stage, rather than the browser document, scrolls.
await page.setViewportSize({width:390,height:700});await go();await page.getByLabel('선택 상태',{exact:true}).selectOption('expanded');await panelButton()
await panelInput('z').scrollIntoViewIfNeeded();await panelInput('z').fill('3');await panelInput('z').press('Enter')
const narrow=await stage.evaluate(e=>({top:e.scrollTop,left:e.scrollLeft,height:e.clientHeight,scrollHeight:e.scrollHeight,width:e.clientWidth,scrollWidth:e.scrollWidth}))
check('small Inner reaches full popup bottom with native Header and popup dimensions',await panelInput('z').inputValue()==='3'&&narrow.scrollHeight>narrow.height&&narrow.scrollWidth>=390,narrow)
await page.screenshot({path:path.join(out,'overview-narrow.png')})
await panelInput('z').press('Escape')
check('narrow popup Escape restores More focus',await header.getByRole('button',{name:'더보기',exact:true}).evaluate(e=>document.activeElement===e))
await page.setViewportSize({width:1440,height:1000});await page.goto(`${base}/?path=/story/sw-header--overview`)
const preview=page.frameLocator('#storybook-preview-iframe');await preview.locator('[data-preview-stage]').waitFor()
report.managerOrder=await page.locator('[data-item-id^="sw-header--"]').evaluateAll(elements=>[...new Set(elements.map(e=>e.dataset.itemId))])
await writeFile(path.join(out,'manager-story-order.json'),JSON.stringify(report.managerOrder,null,2)+'\n')
await page.screenshot({path:path.join(out,'overview-manager.png')})
const managerFit=await preview.locator('[data-preview-stage]').evaluate(e=>{const b=e.getBoundingClientRect(),slot=e.parentElement.getBoundingClientRect();return {width:b.width,height:b.height,slotWidth:slot.width,slotHeight:slot.height,viewportWidth:innerWidth,viewportHeight:innerHeight}})
check('actual Storybook manager iframe fits its own Inner',managerFit.width<=managerFit.slotWidth+.1&&managerFit.height<=managerFit.slotHeight+.1&&Math.abs(managerFit.width/managerFit.height-16/9)<.005,managerFit)
check('no browser errors',report.errors.length===0,report.errors)
}
}catch(error){check('suite completes',false,error.stack);await page?.screenshot({path:path.join(out,'failure.png')}).catch(()=>{})}
finally{
await browser?.close();await new Promise(resolve=>server.close(resolve))
report.status=report.checks.every(c=>c.status==='PASS')?'PASS':'FAIL'
await writeFile(path.join(out,process.argv[3]==='overlays'?'chrome-overlays-result.json':'chrome-result.json'),JSON.stringify(report,null,2)+'\n')
console.log(JSON.stringify({status:report.status,checks:report.checks.length,failures:report.checks.filter(c=>c.status==='FAIL')}))
process.exitCode=report.status==='PASS'?0:1
}
+199
View File
@@ -0,0 +1,199 @@
import { useId, useLayoutEffect, useRef, useState, type CSSProperties } from "react"
import {
SWHeader, parseSWHeaderColor, type SWHeaderProps, type SWHeaderProperty, type SWHeaderPropertyGroup,
type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderColorMode, type SWHeaderLayer, type SWHeaderLineWeight,
} from "@hm-design/sw"
import { StoryPage, StoryPageHeader } from "@/components/story-page"
type Selection = "none" | "all" | "circle" | "expanded"
interface OverviewProps {
initial: SWHeaderProps
fixtures: Required<Pick<SWHeaderProps, "documents" | "layers" | "colors" | "userColors" | "lineTypes" | "lineWeights" | "textStyles" | "dimensionStyles">>
propertyGroups: SWHeaderPropertyGroup[]
propertyFixture: (selection: Selection) => SWHeaderProperty[]
propertySelection: (selection: Selection) => SWHeaderPropertySelection
barFixture: SWHeaderProperty[]
}
/** Storybook host only; dropdowns, validation, editing and focus use the public SW components. */
export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFixture, propertySelection, barFixture }: OverviewProps) {
const controlId = useId()
const slotRef = useRef<HTMLDivElement>(null)
const headerRef = useRef<HTMLElement>(null)
const [size, setSize] = useState({ width: 0, height: 0 })
const [panelLeft, setPanelLeft] = useState(4)
const [example, setExample] = useState<Selection>("circle")
const getProperties = (selection: Selection) => propertyFixture(selection).map(property => ({
...property, value: barFixture.find(item => item.id === property.id)?.value ?? property.value,
}))
const [properties, setProperties] = useState(() => getProperties("circle"))
const [documents, setDocuments] = useState(fixtures.documents)
const [documentId, setDocumentId] = useState(fixtures.documents[0]?.id)
const [layers, setLayers] = useState(fixtures.layers)
const [weights, setWeights] = useState(fixtures.lineWeights)
const [weightId, setWeightId] = useState("w-default")
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>(null)
const [textStyle, setTextStyle] = useState(fixtures.textStyles[0])
const [dimensionStyle, setDimensionStyle] = useState(fixtures.dimensionStyles[0])
const [barOpen, setBarOpen] = useState(true)
const [panelOpen, setPanelOpen] = useState(false)
const [expanded, setExpanded] = useState<string[]>([])
const [readOnly, setReadOnly] = useState(false)
const [disabled, setDisabled] = useState(false)
const [emptyShape, setEmptyShape] = useState(false)
const [callbackLog, setCallbackLog] = useState<string[]>([])
const counter = useRef(0)
const record = (line: string) => { setCallbackLog(lines => [...lines.slice(-5), line]); console.info(`[SWHeader Overview] ${line}`) }
const value = (id: string) => properties.find(property => property.id === id)?.value ?? ""
const change = (id: string, next: string, event = "onPropertyChange") => {
setProperties(items => items.map(property => property.id === id ? {
...property, value: next, mixed: false, ...(id === "color" ? { swatch: parseSWHeaderColor(next)?.hex } : {}),
} : property))
if (id === "color") setColorMode(null)
record(`${event}(${id}, ${next})`)
}
const onPropertyChange = (property: SWHeaderProperty, next: string) => change(property.id, next)
const changeExample = (next: Selection) => {
setExample(next); setProperties(getProperties(next)); setExpanded(next === "expanded" ? ["common"] : [])
record(`selection(${next})`)
}
useLayoutEffect(() => {
const slot = slotRef.current
if (!slot) return
const measure = () => {
const bounds = slot.getBoundingClientRect()
const width = Math.max(0, Math.min(bounds.width, bounds.height * 16 / 9))
setSize(previous => previous.width === width ? previous : { width, height: width * 9 / 16 })
}
measure()
const observer = new ResizeObserver(measure)
observer.observe(slot)
return () => observer.disconnect()
}, [])
useLayoutEffect(() => {
const header = headerRef.current
if (!header) return
const measure = () => {
const bounds = header.getBoundingClientRect()
const trigger = header.querySelector<HTMLElement>("[data-property-panel-trigger]")
const anchor = trigger?.getClientRects().length ? trigger : header.querySelector<HTMLElement>(".hm-sw-header__more-trigger")
setPanelLeft(Math.max(4, Math.min((anchor?.getBoundingClientRect().left ?? bounds.left) - bounds.left, bounds.width - 318)))
}
measure()
const observer = new ResizeObserver(measure)
observer.observe(header)
return () => observer.disconnect()
}, [])
const toggleLayer = (key: "visible" | "frozen" | "locked") => (layer: SWHeaderLayer, next: boolean) => {
setLayers(items => items.map(item => item.id === layer.id ? { ...item, [key]: next } : item))
record(`layer.${key}(${layer.id}, ${next})`)
}
const chooseWeight = (weight: SWHeaderLineWeight) => {
setWeightId(weight.id)
// ByLayer/ByBlock select a mode, not a numeric property value (same policy as usePropertyMenus).
if (weight.value !== undefined) change("weight", weight.value, "onLineWeightSelect")
else record(`onLineWeightSelect(${weight.id})`)
}
const changeWeight = (weight: SWHeaderLineWeight, next: string) => {
setWeights(items => items.map(item => item.id === weight.id ? { ...item, value: next } : item))
if (weightId === weight.id) change("weight", next, "onLineWeightValueChange")
else record(`onLineWeightValueChange(${weight.id}, ${next})`)
}
const changeMode = (mode: SWHeaderColorMode) => { setColorMode(mode); record(`onColorModeChange(${mode})`) }
const scale = value("scale")
const menus: SWHeaderPropertyMenus = {
layers, onLayerVisibilityChange: toggleLayer("visible"), onLayerFreezeChange: toggleLayer("frozen"), onLayerLockChange: toggleLayer("locked"),
onLayerDetail: () => record("onLayerDetail"), lineTypes: fixtures.lineTypes, lineWeights: weights, activeLineWeightId: weightId, lineScale: scale,
onLineWeightSelect: chooseWeight, onLineWeightValueChange: changeWeight,
onLineScaleChange: next => change("scale", Number(next).toFixed(4), "onLineScaleChange"), onLineStyleDetail: () => record("onLineStyleDetail"),
colors: fixtures.colors, userColors: fixtures.userColors, colorMode, onColorModeChange: changeMode,
onColorPick: () => record("onColorPick"), onColorDetail: () => record("onColorDetail"),
}
const guardedProperties = properties.map(property => ({ ...property, disabled: disabled || property.disabled, readOnly: readOnly || property.readOnly }))
const barProperties = guardedProperties.flatMap(property => {
const bar = barFixture.find(item => item.id === property.id)
return bar ? [{ ...bar, value: property.value, disabled: property.disabled, readOnly: property.readOnly }]
: property.id.startsWith("preview-extra-") ? [property] : []
})
const layer = layers.find(item => item.id === value("layer"))
const line = fixtures.lineTypes.find(item => item.id === value("line"))
const color = parseSWHeaderColor(value("color"))
const weight = weights.find(item => item.id === weightId)
return <StoryPage className="sw-story-overview">
<StoryPageHeader kicker="HM DESIGN SYSTEM / SW" title="SW Header" />
<div className="sw-overview-toolbar" aria-label="Storybook 전용 미리보기 설정">
<label htmlFor={`${controlId}-selection`}>선택 상태</label>
<select id={`${controlId}-selection`} value={example} onChange={event => changeExample(event.target.value as Selection)}>
<option value="none">선택 없음</option><option value="all">전체 객체</option><option value="circle">원 선택</option><option value="expanded">전체 펼침</option>
</select>
<details><summary>시연 데이터</summary><div className="sw-overview-data">
<button onClick={() => setProperties(items => items.map(property => barFixture.some(item => item.id === property.id) ? { ...property, value: "12.5" } : property))}>외부 값 12.5</button>
<button onClick={() => {
const number = ++counter.current
setProperties(items => [...items, { id: `preview-extra-${number}`, name: `추가 속성 ${number}`, value: "10", type: "number", groupId: "shape", min: 0 }])
}}>필드 추가</button>
<button onClick={() => setProperties(items => items.filter(item => !item.id.startsWith("preview-extra-")))}>추가 필드 제거</button>
<label><input type="checkbox" checked={readOnly} onChange={event => setReadOnly(event.target.checked)} />속성 ReadOnly</label>
<label><input type="checkbox" checked={disabled} onChange={event => setDisabled(event.target.checked)} />속성 Disabled</label>
<label><input type="checkbox" checked={emptyShape} onChange={event => setEmptyShape(event.target.checked)} />형상 Slot 비우기</label>
</div></details>
</div>
<div ref={slotRef} className="sw-overview-slot" data-preview-slot="">
<div className="sw-overview-stage" data-preview-stage="" role="region" tabIndex={0} aria-label="SW Header 16:9 미리보기, 작은 화면에서는 내부 스크롤"
style={{ ...size, "--sw-preview-panel-left": `${panelLeft}px`, "--sw-preview-panel-top": barOpen ? "77px" : "49px" } as CSSProperties}>
<div className="sw-overview-world">
<SWHeader ref={headerRef} {...initial}
documents={documents} activeDocumentId={documentId} documentTitle={documents.find(document => document.id === documentId)?.name ?? "열린 문서 없음"}
onDocumentClick={() => record("onDocumentClick")} onDocumentMenuOpenChange={open => record(`documentOpen(${open})`)}
onDocumentSelect={document => { setDocumentId(document.id); record(`onDocumentSelect(${document.id})`) }}
onDocumentClose={document => {
const next = documents.filter(item => item.id !== document.id); setDocuments(next)
if (documentId === document.id) setDocumentId(next[0]?.id)
record(`onDocumentClose(${document.id})`)
}}
onDocumentNew={() => {
const id = `preview-document-${++counter.current}`
setDocuments(items => [...items, { id, name: `새 문서 ${counter.current}.dwg` }]); setDocumentId(id); record("onDocumentNew")
}} onDocumentOpen={() => record("onDocumentOpen")}
layers={layers} layer={{ name: layer?.name ?? value("layer") }} activeLayerId={layer?.id}
onLayerSelect={item => change("layer", item.id, "onLayerSelect")}
onLayerVisibilityChange={menus.onLayerVisibilityChange} onLayerFreezeChange={menus.onLayerFreezeChange} onLayerLockChange={menus.onLayerLockChange}
onLayerOpen={() => record("onLayerOpen")} onLayerDetail={menus.onLayerDetail}
colors={fixtures.colors} userColors={fixtures.userColors}
color={{ value: colorMode === "byLayer" ? "ByLayer" : colorMode === "byBlock" ? "ByBlock" : value("color"), swatch: color?.hex }} colorMode={colorMode}
onColorSelect={item => change("color", item.value ?? parseSWHeaderColor(item.color)?.value ?? item.color, "onColorSelect")}
onColorInput={next => change("color", next.value, "onColorInput")} onColorModeChange={changeMode}
onColorOpen={() => record("onColorOpen")} onColorDetail={menus.onColorDetail} onColorPick={() => record("onColorPick")}
lineTypes={fixtures.lineTypes} lineWeights={weights} activeLineTypeId={line?.id} activeLineWeightId={weightId}
lineStyle={{ name: line?.name ?? value("line"), weight: weight && weight.value === undefined ? weight.name : value("weight"), scale }}
onLineTypeSelect={item => change("line", item.id, "onLineTypeSelect")} onLineWeightSelect={chooseWeight} onLineWeightValueChange={changeWeight}
onLineScaleChange={menus.onLineScaleChange} onLineStyleOpen={() => record("onLineStyleOpen")} onLineStyleDetail={menus.onLineStyleDetail}
textStyles={fixtures.textStyles} activeTextStyleId={textStyle?.id} textStyle={textStyle}
onTextStyleSelect={item => { setTextStyle(item); record(`onTextStyleSelect(${item.id})`) }}
onTextStyleOpen={() => record("onTextStyleOpen")} onTextStyleDetail={() => record("onTextStyleDetail")}
dimensionStyles={fixtures.dimensionStyles} activeDimensionStyleId={dimensionStyle?.id} dimensionStyle={dimensionStyle}
onDimensionStyleSelect={item => { setDimensionStyle(item); record(`onDimensionStyleSelect(${item.id})`) }}
onDimensionStyleOpen={() => record("onDimensionStyleOpen")} onDimensionStyleDetail={() => record("onDimensionStyleDetail")}
propertyBarOpen={barOpen} onPropertyBarOpenChange={next => { setBarOpen(next); record(`propertyBarOpen(${next})`) }}
propertyPanelOpen={panelOpen} onPropertyPanelOpenChange={next => { setPanelOpen(next); record(`propertyPanelOpen(${next})`) }}
propertyBar={{ selection: propertySelection(example), properties: barProperties, onPropertyChange, readOnly, disabled }}
propertyPanel={{ selection: propertySelection(example), properties: guardedProperties, groups: propertyGroups,
expandedGroupIds: expanded, onExpandedGroupsChange: setExpanded, onPropertyChange, menus,
shapeContent: emptyShape ? null : undefined, onOverviewClick: () => changeExample("all"),
}}
onHomeClick={() => record("onHomeClick")} onPrint={() => record("onPrint")} onPopupGuide={() => record("onPopupGuide")}
onMinimize={() => record("onMinimize")} onClose={() => record("onClose")} onWindowMoveStart={() => record("onWindowMoveStart")}
/>
</div>
</div>
</div>
<div className="sw-overview-status">
<span data-preview-size="">{Math.round(size.width)} × {Math.round(size.height)} · 16:9 · 내부 스크롤</span>
<output data-preview-callback="" aria-live="polite" title={callbackLog.join(" → ")}>{callbackLog.join(" → ") || "Callback 없음"}</output>
</div>
</StoryPage>
}
+5 -15
View File
@@ -11,6 +11,7 @@ import "@hm-design/sw/styles.css"
import { SWHeaderMenuDetail } from "../../packages/sw/src/components/sw-header/components/SWHeaderDropdown"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import "./sw-stories.css"
import { SWHeaderOverview } from "./sw-header-overview"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
import figmaLayerDetailReference from "./assets/figma-reference/btn-detail-layer-26-10767.png"
import figmaInputReference from "./assets/figma-reference/input-26-11656.png"
@@ -181,21 +182,10 @@ function InteractiveHeader(props: Partial<SWHeaderProps>) {
export const Overview: Story = {
parameters: { controls: { disable: true } },
render: () => (
<StoryPage>
<StoryPageHeader
kicker={KICKER}
title="SW Header"
description="Figma node 26:8944(header_green, 1920 × 49)를 @hm-design/sw 독립 패키지로 구현한 헤더입니다. 파일명·객체 수정·속성 입력 면은 사용자 확정 CSS를 우선하며, @hm-design/ui 없이 동작합니다."
/>
<StorySection title="Figma 기준 1920 × 49" description="홈 / 파일명 / 객체 수정(그리기모드 · 레이어 · 색상 · 선스타일 · 문자스타일 · 치수스타일 · 상세보기) / 인쇄 · 팝업 가이드 · 창 이동 · 최소화 · 종료">
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} /></Frame>
</StorySection>
<StorySection title="선택 상태" description="선택된 텍스트와 화살표는 #FFC600으로만 바뀝니다. 선택 전용 배경·테두리·그림자는 없습니다.">
<Frame width={1920}><SWHeader {...figmaArgs} {...callbacks} activeMenu="layer" propertyPanelOpen /></Frame>
</StorySection>
</StoryPage>
),
render: () => <SWHeaderOverview initial={{ ...figmaArgs, ...callbacks }}
fixtures={{ documents: SAMPLE_DOCUMENTS, layers: PROPERTY_LAYERS, colors: FIGMA_COLORS, userColors: FIGMA_USER_COLORS,
lineTypes: PROPERTY_LINE_TYPES, lineWeights: FIGMA_LINE_WEIGHTS, textStyles: FIGMA_TEXT_STYLES, dimensionStyles: FIGMA_DIMENSION_STYLES }}
propertyGroups={PROPERTY_GROUPS} propertyFixture={propertyFixture} propertySelection={propertySelection} barFixture={BAR_FIXTURE} />,
}
export const InteractiveStates: Story = {
+23
View File
@@ -51,3 +51,26 @@
/* Property fields (2026-10-19): Figma node table under the specimens. */
.sw-story-node-table { margin-top: 16px; border-collapse: collapse; font-size: 12px; color: #3d4a45; }
.sw-story-node-table :is(th, td) { padding: 4px 10px; border-bottom: 1px solid #d5ddd9; text-align: left; }
/* Overview host layout only. Native controls below are Storybook data controls, outside the product. */
.sw-story-overview.sb-page { height: 100dvh; min-height: 0; padding-block: 16px; }
.sw-story-overview > .sb-page-inner { height: 100%; display: flex; flex-direction: column; gap: 12px; }
.sw-story-overview .sb-page-header { flex: 0 0 auto; padding-bottom: 8px; }
.sw-story-overview .sb-page-header .sb-page-kicker { margin-bottom: 4px; }
.sw-story-overview .sb-page-header .sb-page-title { margin-block: 0 4px; }
.sw-overview-toolbar { display: flex; flex: 0 0 auto; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 13px; }
.sw-overview-toolbar select, .sw-overview-data button { font: inherit; padding: 3px 8px; color: inherit; background: var(--hm-sem-bg-surface); border: 1px solid var(--hm-sem-stroke-input-default); border-radius: 4px; }
.sw-overview-toolbar details { flex-basis: 100%; }
.sw-overview-toolbar summary { width: max-content; cursor: pointer; }
.sw-overview-data { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 8px; }
.sw-overview-data label { display: inline-flex; align-items: center; gap: 4px; }
.sw-overview-slot { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; }
.sw-overview-stage { flex: 0 0 auto; overflow: auto; overscroll-behavior: contain; background: var(--hm-sem-bg-surface); outline: 1px solid var(--hm-sem-stroke-input-default); }
.sw-overview-world { min-width: 390px; min-height: 100%; position: relative; }
/* Placement-only Storybook exception: keep the original panel size/content in the Stage scroll area.
Header's public API deliberately excludes panel style; package CSS and other Stories stay untouched. */
.sw-overview-stage .hm-sw-property { position: absolute !important; left: var(--sw-preview-panel-left) !important; top: var(--sw-preview-panel-top) !important; max-height: none !important; }
/* The document wrapper otherwise stacks below the independently positioned property Bar. */
.sw-overview-stage .hm-sw-header__document:has(.hm-sw-header__document-menu) { z-index: 3; }
.sw-overview-status { flex: 0 0 auto; display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12px; min-width: 0; }
.sw-overview-status output { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }