refactor(storybook): remove unused SW header demo stories
Remove nine SW/Header demo pages (property-fields, property-shape-slot, composition, colorize, dynamic-controls, property-bar, additional-hover, property-panel, layer-detail-input) and the demo code and Storybook-only CSS used only by them. SW Stories 19 -> 10, total 338 -> 329. The package runtime, public API, AI template and the retained Stories (Overview, Dropdown, Property Panel 4-state comparison, Window Move Motion and others) are unchanged. Add a current regression script and mark the older scripts that open the retired IDs as historical evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
{"v":5,"entries":{"foundation-color--all":{"type":"story","subtype":"story","id":"foundation-color--all","name":"Color","title":"Foundation/Color","importPath":"./src/foundation/color.stories.tsx","tags":["dev","test","manifest"],"exportName":"All"},"foundation-typography--all":{"type":"story","subtype":"story","id":"foundation-typography--all","name":"Typography","title":"Foundation/Typography","importPath":"./src/foundation/typography.stories.tsx","tags":["dev","test","manifest"],"exportName":"All"},"foundation-layout--all":{"type":"story","subtype":"story","id":"foundation-layout--all","name":"Layout","title":"Foundation/Layout","importPath":"./src/foundation/layout.stories.tsx","tags":["dev","test","manifest"],"exportName":"All"},"components-form-button--all":{"type":"story","subtype":"story","id":"components-form-button--all","name":"Button","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest"],"exportName":"All"},"components-form-button--roles":{"type":"story","subtype":"story","id":"components-form-button--roles","name":"Roles","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"Roles"},"components-form-button--sizes":{"type":"story","subtype":"story","id":"components-form-button--sizes","name":"Sizes","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"Sizes"},"components-form-button--states":{"type":"story","subtype":"story","id":"components-form-button--states","name":"Brand States","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"States"},"components-form-button--standard-states":{"type":"story","subtype":"story","id":"components-form-button--standard-states","name":"Standard States","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"StandardStates"},"components-form-button--long-label":{"type":"story","subtype":"story","id":"components-form-button--long-label","name":"Long Label","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"LongLabel"},"components-form-button--korean-labels":{"type":"story","subtype":"story","id":"components-form-button--korean-labels","name":"Korean Labels","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"KoreanLabels"},"components-form-button--icon-text-narrow":{"type":"story","subtype":"story","id":"components-form-button--icon-text-narrow","name":"Icon Text Narrow","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"IconTextNarrow"},"components-form-button--destructive":{"type":"story","subtype":"story","id":"components-form-button--destructive","name":"Destructive","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"Destructive"},"components-form-button--playground":{"type":"story","subtype":"story","id":"components-form-button--playground","name":"Playground","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"Playground"},"components-form-button--compatibility":{"type":"story","subtype":"story","id":"components-form-button--compatibility","name":"Compatibility","title":"Components/Form/Button","importPath":"./src/components/button.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","verification"],"exportName":"Compatibility"},"components-form-input--all":{"type":"story","subtype":"story","id":"components-form-input--all","name":"Input","title":"Components/Form/Input","importPath":"./src/components/input.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":["dev","test","manifest","play-fn"],"exportName":"All"},"components-form-input--default":{"type":"story","subtype":"story","id":"components-form-input--default","name":"Default","title":"Components/Form/Input","importPath":"./src/components/input.stories.tsx","componentPath":"./packages/ui/dist/index.js","tags":Line truncated
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"head": "c8e5fbeebc26fba92998fc5ddcd28c6c110e452d",
|
||||||
|
"total": 338,
|
||||||
|
"sw": [
|
||||||
|
{
|
||||||
|
"id": "sw-header--composed-controls",
|
||||||
|
"name": "Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--detail-buttons",
|
||||||
|
"name": "Detail Buttons (Shared States)",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest",
|
||||||
|
"verification"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--overview",
|
||||||
|
"name": "Overview",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--interactive-states",
|
||||||
|
"name": "Interactive States",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--window-move-motion",
|
||||||
|
"name": "Window Move Motion",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--logo-slot",
|
||||||
|
"name": "Logo Slot",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--document-dropdown",
|
||||||
|
"name": "Document Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--responsive-more",
|
||||||
|
"name": "Responsive More",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--layer-detail-input",
|
||||||
|
"name": "Layer Detail · Input",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-panel",
|
||||||
|
"name": "Property Panel",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--k-guide",
|
||||||
|
"name": "K Guide",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--additional-hover",
|
||||||
|
"name": "Additional Hover",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-bar",
|
||||||
|
"name": "Property Bar",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--dynamic-controls",
|
||||||
|
"name": "Dynamic Controls",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--colorize",
|
||||||
|
"name": "Colorize",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--composition",
|
||||||
|
"name": "Composition (Visibility · Order)",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-panel-compare",
|
||||||
|
"name": "Property Panel · 4개 비교",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-shape-slot",
|
||||||
|
"name": "Property Panel · 형상 Slot",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-fields",
|
||||||
|
"name": "Property Panel · 필드 13개",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,639 @@
|
|||||||
|
{
|
||||||
|
"before": [
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"after": [
|
||||||
|
{
|
||||||
|
"type": "story",
|
||||||
|
"subtype": "story",
|
||||||
|
"id": "sw-header--composed-controls",
|
||||||
|
"name": "Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"importPath": "./src/sw/sw-header-dropdown.stories.tsx",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
],
|
||||||
|
"exportName": "ComposedControls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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--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--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--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--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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"removed": [
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "story",
|
||||||
|
"subtype": "story",
|
||||||
|
"id": "sw-header--composed-controls",
|
||||||
|
"name": "Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"importPath": "./src/sw/sw-header-dropdown.stories.tsx",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
],
|
||||||
|
"exportName": "ComposedControls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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--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--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--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--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"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -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,656 @@
|
|||||||
|
{
|
||||||
|
"baseCommit": "50bf5bda8e956a4973b923d64131f2f2285bd38c",
|
||||||
|
"checks": [
|
||||||
|
{
|
||||||
|
"name": "24 retained Story IDs, names and tags; only approved Dropdown retirements",
|
||||||
|
"status": "FAIL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "retained non-Overview declarations byte-identical; relocated composition separately verified",
|
||||||
|
"status": "FAIL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "retired Dropdown IDs absent",
|
||||||
|
"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--composed-controls",
|
||||||
|
"sw-header--overview",
|
||||||
|
"sw-header--interactive-states",
|
||||||
|
"sw-header--window-move-motion",
|
||||||
|
"sw-header--logo-slot",
|
||||||
|
"sw-header--document-dropdown",
|
||||||
|
"sw-header--responsive-more",
|
||||||
|
"sw-header--k-guide",
|
||||||
|
"sw-header--property-panel-compare"
|
||||||
|
],
|
||||||
|
"status": "FAIL"
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
"sw-header--composed-controls",
|
||||||
|
"sw-header--overview",
|
||||||
|
"sw-header--interactive-states",
|
||||||
|
"sw-header--window-move-motion",
|
||||||
|
"sw-header--logo-slot",
|
||||||
|
"sw-header--document-dropdown",
|
||||||
|
"sw-header--responsive-more",
|
||||||
|
"sw-header--k-guide",
|
||||||
|
"sw-header--property-panel-compare"
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 223 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
"sw-header--composed-controls",
|
||||||
|
"Dropdown"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--overview",
|
||||||
|
"Overview"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--interactive-states",
|
||||||
|
"Interactive States"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--window-move-motion",
|
||||||
|
"Window Move Motion"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--logo-slot",
|
||||||
|
"Logo Slot"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-dropdown",
|
||||||
|
"Document Dropdown"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--responsive-more",
|
||||||
|
"Responsive More"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--k-guide",
|
||||||
|
"K Guide"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--property-panel-compare",
|
||||||
|
"Property Panel · 4개 비교"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"total": 329,
|
||||||
|
"sw": [
|
||||||
|
{
|
||||||
|
"id": "sw-header--composed-controls",
|
||||||
|
"name": "Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--detail-buttons",
|
||||||
|
"name": "Detail Buttons (Shared States)",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest",
|
||||||
|
"verification"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--overview",
|
||||||
|
"name": "Overview",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--interactive-states",
|
||||||
|
"name": "Interactive States",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--window-move-motion",
|
||||||
|
"name": "Window Move Motion",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--logo-slot",
|
||||||
|
"name": "Logo Slot",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--document-dropdown",
|
||||||
|
"name": "Document Dropdown",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--responsive-more",
|
||||||
|
"name": "Responsive More",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--k-guide",
|
||||||
|
"name": "K Guide",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--property-panel-compare",
|
||||||
|
"name": "Property Panel · 4개 비교",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# SW Storybook 불필요 9개 메뉴 삭제 (2026-10-19)
|
||||||
|
|
||||||
|
> 범위: Storybook 시연 페이지 정리만 합니다. `@hm-design/sw` 런타임·공개 API·디자인·AI 템플릿은 바꾸지 않았습니다.
|
||||||
|
> 기준 HEAD `c8e5fbeebc26fba92998fc5ddcd28c6c110e452d` (`feature/sw-package`, 시작 시 Working Tree Clean).
|
||||||
|
> 증거: [storybook-unused-story-removal-qa/](storybook-unused-story-removal-qa/). 이전 단계 증거는 수정하지 않았습니다.
|
||||||
|
|
||||||
|
## 1. 작업 전 Story 목록 (SW 19개 / 전체 338개)
|
||||||
|
|
||||||
|
`storybook-static/index.json`(HEAD 빌드) 기준: [before/story-inventory-before.json](storybook-unused-story-removal-qa/before/story-inventory-before.json), 전체 인덱스 [before/full-index-before.json](storybook-unused-story-removal-qa/before/full-index-before.json).
|
||||||
|
|
||||||
|
`composed-controls`, `detail-buttons`, `overview`, `interactive-states`, `window-move-motion`, `logo-slot`, `document-dropdown`, `responsive-more`, `layer-detail-input`, `property-panel`, `k-guide`, `additional-hover`, `property-bar`, `dynamic-controls`, `colorize`, `composition`, `property-panel-compare`, `property-shape-slot`, `property-fields` (모두 `sw-header--` 접두사, title `SW/Header`).
|
||||||
|
|
||||||
|
## 2. 요청 메뉴와 실제 Story 대응
|
||||||
|
|
||||||
|
요청 메뉴명은 각 Story의 페이지 제목(`StoryPageHeader title`)입니다. 9개 모두 `src/sw/sw-header.stories.tsx`의 **독립 Story**이며, 다른 Story의 내부 섹션이거나 서로 겹치는 항목은 없습니다.
|
||||||
|
|
||||||
|
| 요청 메뉴 (페이지 제목) | 실제 Story 이름 (사이드바) | Export | Story ID | 결과 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 속성창 입력·선택 필드 | Property Panel · 필드 13개 | `PropertyFields` | `sw-header--property-fields` | 삭제 |
|
||||||
|
| 형상 영역 Slot | Property Panel · 형상 Slot | `PropertyShapeSlot` | `sw-header--property-shape-slot` | 삭제 |
|
||||||
|
| Header 구성 · 메뉴 순서 | Composition (Visibility · Order) | `Composition` | `sw-header--composition` | 삭제 |
|
||||||
|
| btn_colorize | Colorize | `Colorize` | `sw-header--colorize` | 삭제 |
|
||||||
|
| 메뉴 컴포넌트 구성 | Dynamic Controls | `DynamicControls` | `sw-header--dynamic-controls` | 삭제 |
|
||||||
|
| 속성Bar · 동적 편집 | Property Bar | `PropertyBar` | `sw-header--property-bar` | 삭제 |
|
||||||
|
| 추가 Hover 및 Selected | Additional Hover | `AdditionalHover` | `sw-header--additional-hover` | 삭제 |
|
||||||
|
| 속성창 | Property Panel | `PropertyPanel` | `sw-header--property-panel` | 삭제 |
|
||||||
|
| Layer Detail Button · Editable Input | Layer Detail · Input | `LayerDetailInput` | `sw-header--layer-detail-input` | 삭제 |
|
||||||
|
|
||||||
|
`속성창`은 제목이 정확히 `속성창`인 `sw-header--property-panel`입니다. 제목이 `속성창 4개 상태 비교`인 `sw-header--property-panel-compare`는 유지했습니다.
|
||||||
|
|
||||||
|
## 3. 실제 삭제한 Story: 9개
|
||||||
|
|
||||||
|
위 표의 9개 ID입니다. 다른 Story는 삭제·통합·재분류하지 않았습니다.
|
||||||
|
|
||||||
|
## 4. 삭제 후 Story 목록 (SW 10개)
|
||||||
|
|
||||||
|
[verify/story-inventory-after.json](storybook-unused-story-removal-qa/verify/story-inventory-after.json)
|
||||||
|
|
||||||
|
| 순서 | Story ID | 이름 | 비고 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `sw-header--composed-controls` | Dropdown | 별도 CSF `sw-header-dropdown.stories.tsx` |
|
||||||
|
| 2 | `sw-header--detail-buttons` | Detail Buttons (Shared States) | `verification` 태그: 기존 정책대로 기본 사이드바에서 숨김, 직접 URL 유지 |
|
||||||
|
| 3 | `sw-header--overview` | Overview | |
|
||||||
|
| 4 | `sw-header--interactive-states` | Interactive States | |
|
||||||
|
| 5 | `sw-header--window-move-motion` | Window Move Motion | |
|
||||||
|
| 6 | `sw-header--logo-slot` | Logo Slot | |
|
||||||
|
| 7 | `sw-header--document-dropdown` | Document Dropdown | |
|
||||||
|
| 8 | `sw-header--responsive-more` | Responsive More | |
|
||||||
|
| 9 | `sw-header--k-guide` | K Guide | |
|
||||||
|
| 10 | `sw-header--property-panel-compare` | Property Panel · 4개 비교 | |
|
||||||
|
|
||||||
|
## 5–7. 개수와 유지 ID
|
||||||
|
|
||||||
|
- 최종 SW Story: **10개** (19 → 10).
|
||||||
|
- 저장소 전체 Story: **329개** (338 → 329). 삭제된 9개 외 인덱스 항목(docs 포함)은 전부 동일합니다(자동 비교 PASS).
|
||||||
|
- 유지 10개의 ID·이름·title·tags·순서는 작업 전과 같습니다(자동 비교 PASS). 유지 Export(Story 10개 + `ComposedDemo`) 선언은 byte 단위로 같습니다.
|
||||||
|
|
||||||
|
## 8. Overview 보존
|
||||||
|
|
||||||
|
`storybook-unused-removal.browser.mjs`: 390 / 768 / 1280 / 1440에서 Header 1개·높이 49px·16:9 Stage 1개·문서 가로 넘침 없음. 레이어 Dropdown 열기, Escape 닫기·Focus 복귀, 레이어 상세 Callback, `btn_colorize` 24 × 24와 `onColorPick`, 속성Bar Enter 확정·`min=0` 검증, 속성창 열기(같은 Bar 값), Escape 닫기·Trigger Focus 복귀 모두 PASS.
|
||||||
|
|
||||||
|
기존 `overview.browser.mjs`(증거 `legacy-overview/`): 기능 검사 52개 PASS. 문서 선택, 5개 Dropdown 선택·전환·바깥 클릭 닫기, 속성Bar·속성창 공유 편집과 검증, 동적 필드 추가·제거, 형상 Slot 비우기, 4개 상태 314 × 256/361/658/754, 내부 스크롤, 390px More Focus 복귀, Manager iframe 16:9, 콘솔 오류 0을 포함합니다. 남은 FAIL 2개는 과거 기준에 고정된 단언입니다(§17).
|
||||||
|
|
||||||
|
## 9. Header → Dropdown 보존
|
||||||
|
|
||||||
|
- ID `sw-header--composed-controls`, 이름 `Dropdown`, title `SW/Header`, 내부 그룹 `Composed Controls` 그대로입니다.
|
||||||
|
- 새 검사: 5개 필드 + `상세보기` 그룹(6개 컨트롤). 4개 폭에서 5개 메뉴 열기·Escape 닫기·Focus 복귀, 문서 넘침 없음, 문자스타일·선스타일 선택 반영 PASS.
|
||||||
|
- 기존 `dropdown-consolidation.browser.mjs`(증거 `legacy-dropdown/`): 214 PASS / 4 FAIL. 이전 실행의 223 PASS와의 차이 9개는 Story별 렌더 검사 수(19 → 10)입니다. 4개 FAIL은 이전 기록과 같은 과거 기준 단언입니다(§17).
|
||||||
|
|
||||||
|
## 10. 속성창 4개 상태 비교 보존
|
||||||
|
|
||||||
|
ID·이름·페이지 제목·상태명·순서는 그대로이고 소스 선언도 같습니다. 390 / 768 / 1280 / 1440에서 4개 패널 314 × 256 / 361 / 658 / 754, 좌표 기준 2열 × 2행, Inner 스크롤로 754px 패널 하단·우측 도달, 문서 스크롤 없음 PASS. Input Enter 확정(해당 패널만)·Escape 취소·caret `#E60403`, 레이어 Dropdown 선택 Callback, Escape 닫기·Focus 복귀도 PASS입니다.
|
||||||
|
|
||||||
|
## 11. Window Move Motion 보존
|
||||||
|
|
||||||
|
`단계별 진행 (3배 확대)` 섹션 1개, 단계 9개, zoom 3. Rest 단계는 정지 상태이고 시간 지정 단계는 일시정지된 애니메이션 3개를 유지합니다(PASS). 선언은 byte 단위로 같습니다.
|
||||||
|
|
||||||
|
## 12–13. SW 런타임·공개 API와 AI 템플릿
|
||||||
|
|
||||||
|
`git diff c8e5fbe -- packages/sw/src packages/sw/ai packages/sw/package.json packages/sw/README.md packages/ui packages/org-chart packages/org-chart-react packages/center-graphic src/sw/sw-header-overview.tsx src/sw/sw-header-dropdown.stories.tsx src/sw/sw-composed-controls.css src/sw/assets .storybook` 결과는 비어 있습니다(자동 검사 PASS). `ai-docs.check.mjs` 19/19 PASS.
|
||||||
|
|
||||||
|
삭제한 시연 페이지가 보여 주던 기능(`hiddenControls`·`menuOrder`, `controls`, `shapeContent`, `SWHeaderPropertyBar`, `SWHeaderInput`, 레이어 상세, `btn_colorize`, 상세 Hover·Selected)은 패키지와 AI 템플릿 가이드·예제에 그대로 있습니다.
|
||||||
|
|
||||||
|
## 14. 변경 파일
|
||||||
|
|
||||||
|
| 파일 | 내용 |
|
||||||
|
|---|---|
|
||||||
|
| `src/sw/sw-header.stories.tsx` | 9개 Story 선언과 그 Story만 쓰던 데모 컴포넌트·상태·fixture 삭제: `InputDemo`, `LayerHeader`/`LayerDemo`/`describeLayers`, `noop`, `FIGMA_LAYERS`, `PropertyPanelDemo`, `RequiredColorDemo`, `PropertyBarDemo`, `CONTROL_FIXTURE`/`DynamicControlsDemo`, `HIDEABLE_PARTS`/`MENU_IDS`/`COMPOSITION_PRESETS`/`CompositionDemo`, `SampleShapeViewer`/`SHAPE_MODES`/`ShapeSlotDemo`, `FIELD_*`/`FieldPanel`/`PropertyFieldsDemo`. `StyleHeader`의 `initialColorMode`(Additional Hover 전용 prop)와 사용하지 않는 import(`SWHeaderInput`, `SWHeaderControlConfig`, `SWHeaderHideableControlId`, Figma 비교 PNG 2개) 제거 |
|
||||||
|
| `src/sw/sw-stories.css` | 삭제한 Story만 쓰던 Storybook 전용 class 제거: `sw-story-explicit-disabled`, `-row`, `-layer-room`, `-reference`, `-figma`, `-detail`, `-controls`, `-followup`, `-property-state`, `-node-table` |
|
||||||
|
| `packages/sw/tests/storybook-inventory.mjs` | `retiredUnusedDemoIds` 추가 |
|
||||||
|
| `packages/sw/tests/storybook-unused-removal.browser.mjs` | 신규 현재 회귀 스크립트 |
|
||||||
|
| `packages/sw/tests/{detail-buttons,followup-four,property-close,property-fields,property-panel-comparison,storybook-cleanup-stage1}.browser.mjs` | 상단에 "삭제된 ID를 여는 과거 증거용 스크립트" 주석 2줄만 추가. 단언은 바꾸지 않음 |
|
||||||
|
| `docs/sw/sw-storybook-unused-story-removal-2026-10-19.md`, `docs/sw/storybook-unused-story-removal-qa/` | 이 보고서와 증거 |
|
||||||
|
|
||||||
|
`src/sw/assets/figma-reference/btn-detail-layer-26-10767.png`, `input-26-11656.png`는 이제 코드에서 참조하지 않지만, 자산 보존 원칙에 따라 그대로 두었습니다.
|
||||||
|
|
||||||
|
## 15. Typecheck / Build
|
||||||
|
|
||||||
|
| 명령 | 결과 |
|
||||||
|
|---|---|
|
||||||
|
| `npm run typecheck:sw` | PASS |
|
||||||
|
| `npm run build:sw` | PASS |
|
||||||
|
| Storybook Typecheck `npx tsc -b` | PASS |
|
||||||
|
| `npm run build-storybook` (prebuild: theme defaults, 전체 package build) | PASS. 기존 CSS 최적화·chunk 크기 경고만 있음 |
|
||||||
|
| `git diff --check` | PASS |
|
||||||
|
|
||||||
|
## 16. 브라우저 검증
|
||||||
|
|
||||||
|
Chrome headless(Playwright)로 production `storybook-static`을 로컬에서 제공해 확인했습니다.
|
||||||
|
|
||||||
|
| 스크립트 | 결과 | 증거 |
|
||||||
|
|---|---|---|
|
||||||
|
| `storybook-unused-removal.browser.mjs` (신규, 현재 회귀) | **81/81 PASS**, 콘솔·페이지 오류 0 | `verify/` |
|
||||||
|
| `overview.browser.mjs` | 52 PASS / 2 FAIL(과거 기준 단언) | `legacy-overview/chrome-result.json` |
|
||||||
|
| `dropdown-consolidation.browser.mjs` | 214 PASS / 4 FAIL(과거 기준 단언) | `legacy-dropdown/result.json` |
|
||||||
|
| `ai-docs.check.mjs` | 19/19 PASS | 콘솔 |
|
||||||
|
|
||||||
|
신규 스크립트 내용: 인벤토리(19 → 10, 338 → 329, 9개 부재, 유지 항목 동일), 소스(9개 Export 삭제, 유지 선언 동일, 제품·템플릿·다른 패키지 미변경), 실제 Manager 사이드바(유지 Story만 순서대로 표시, 삭제 메뉴 ID·이름 없음), 삭제 URL 미렌더, 유지 10개 렌더, 그리고 §8–§11의 Story별 기능. 삭제 URL 검사에서 Storybook이 내는 "story 없음" 오류는 의도한 결과라 오류 집계를 비운 뒤 회귀 검사를 시작합니다.
|
||||||
|
|
||||||
|
## 17. 테스트 갱신과 과거 기준 단언
|
||||||
|
|
||||||
|
- **현재 회귀:** `storybook-unused-removal.browser.mjs`를 추가했습니다. 삭제 Story의 고유 검증 중 유지 Story에서 확인할 수 있는 것(레이어 상세 Callback, `btn_colorize` 크기·Callback, 속성Bar 편집·검증, Input caret·확정·취소)은 Overview·4개 비교에서 검증합니다.
|
||||||
|
- **과거 증거로 구분(단언 미수정):** 삭제한 ID를 직접 여는 `detail-buttons`(preservation·verify 모드), `followup-four`, `property-close`, `property-fields`, `property-panel-comparison`(verify), `storybook-cleanup-stage1`. 기존 결과 파일은 그대로 두었습니다. 실패를 숨기려고 기능 검사를 지우지 않았습니다.
|
||||||
|
- **과거 기준 단언 FAIL (기능 실패 아님):**
|
||||||
|
- `overview.browser.mjs`: "24 retained Story IDs"(현재 10개), "retained non-Overview declarations byte-identical"(50bf5bd 기준에 있던 9개 선언이 의도적으로 삭제됨).
|
||||||
|
- `dropdown-consolidation.browser.mjs`: 28 → 24 / 24 개수 단언 2개, c165753 기준 선언 동일성 1개, `src/sw/sw-stories.css` 미변경 1개. 이전 단계에서도 같은 4개가 FAIL이었고, 이번에는 삭제한 선언과 CSS가 원인에 더해졌습니다.
|
||||||
|
|
||||||
|
## 18. NOT VERIFIED
|
||||||
|
|
||||||
|
- Firefox·Safari 등 Chrome 이외 브라우저(이번에 실행하지 않음).
|
||||||
|
- 개발 서버(포트 6024)에서의 실행. 검증은 production `storybook-static`으로만 했습니다.
|
||||||
|
- 삭제한 페이지에서만 보이던 조합의 Storybook 시연: `hiddenControls`·`menuOrder` 프리셋, `controls` allowlist, `shapeContent`의 ReactNode·Render Prop, 13개 필드 Hover 고정 표본, 상세 Hover·Selected 비교표. 패키지 코드는 바뀌지 않았지만, 이번 작업에서 해당 동작을 브라우저로 다시 실행하지는 않았습니다(과거 증거는 기존 QA 폴더에 있음).
|
||||||
|
- 과거 증거로 구분한 6개 스크립트의 재실행.
|
||||||
|
|
||||||
|
## 19–20. Commit / Push
|
||||||
|
|
||||||
|
- 로컬 Commit `refactor(storybook): remove unused SW header demo stories`. SHA는 최종 보고에 기록합니다.
|
||||||
|
- Push·PR·Publish·배포는 진행하지 않았습니다.
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (composition, colorize, layer-detail-input; preservation/verify modes). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (property-bar, additional-hover). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
// Usage: node packages/sw/tests/followup-four.browser.mjs <evidence-dir> [storybook-url] (Storybook uses packages/sw/dist: build first)
|
// Usage: node packages/sw/tests/followup-four.browser.mjs <evidence-dir> [storybook-url] (Storybook uses packages/sw/dist: build first)
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { mkdir, writeFile } from 'node:fs/promises'
|
import { mkdir, writeFile } from 'node:fs/promises'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (property-panel). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
// Property panel close button (Figma 26:12366): canvas bleed, Default / Hover, close by click and Escape.
|
// Property panel close button (Figma 26:12366): canvas bleed, Default / Hover, close by click and Escape.
|
||||||
// Usage: node packages/sw/tests/property-close.browser.mjs <evidence-dir> <label> [storybook-url]
|
// Usage: node packages/sw/tests/property-close.browser.mjs <evidence-dir> <label> [storybook-url]
|
||||||
// Storybook serves packages/sw/dist: build @hm-design/sw first.
|
// Storybook serves packages/sw/dist: build @hm-design/sw first.
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (property-fields). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
// Property fields (Figma 26:12388 ~ 26:12649): Default / real pointer Hover / Mouse Leave / On (editing, dropdown),
|
// Property fields (Figma 26:12388 ~ 26:12649): Default / real pointer Hover / Mouse Leave / On (editing, dropdown),
|
||||||
// selection callbacks, keyboard, Escape, external data, Disabled / ReadOnly, and the four panel states.
|
// selection callbacks, keyboard, Escape, external data, Disabled / ReadOnly, and the four panel states.
|
||||||
// Usage: node packages/sw/tests/property-fields.browser.mjs <evidence-dir> <label> [storybook-url]
|
// Usage: node packages/sw/tests/property-fields.browser.mjs <evidence-dir> <label> [storybook-url]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (property-panel and four others in verify). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (dynamic-controls, composition). Kept as evidence
|
||||||
|
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
|
||||||
// Scoped verification for the five approved Story removals; serves the production build locally.
|
// Scoped verification for the five approved Story removals; serves the production build locally.
|
||||||
// Usage: node packages/sw/tests/storybook-cleanup-stage1.browser.mjs [evidence-dir]
|
// Usage: node packages/sw/tests/storybook-cleanup-stage1.browser.mjs [evidence-dir]
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ export const retiredStage1Ids = ['responsive', 'text-stroke', 'dynamic-data', 'd
|
|||||||
export const retiredDropdownIds = ['layer-dropdown', 'color-dropdown', 'line-style-dropdown', 'text-dimension-style-dropdown'].map(id => `sw-header--${id}`)
|
export const retiredDropdownIds = ['layer-dropdown', 'color-dropdown', 'line-style-dropdown', 'text-dimension-style-dropdown'].map(id => `sw-header--${id}`)
|
||||||
// 2026-10-19: duplicate property panel demos; the four states live on sw-header--property-panel-compare.
|
// 2026-10-19: duplicate property panel demos; the four states live on sw-header--property-panel-compare.
|
||||||
export const retiredPropertyPanelIds = ['property-panel-variants', 'property-panel-none', 'property-panel-all', 'property-panel-circle', 'property-panel-expanded'].map(id => `sw-header--${id}`)
|
export const retiredPropertyPanelIds = ['property-panel-variants', 'property-panel-none', 'property-panel-all', 'property-panel-circle', 'property-panel-expanded'].map(id => `sw-header--${id}`)
|
||||||
|
// 2026-10-19: unused SW Header demo pages; their features stay in the package and the retained Stories.
|
||||||
|
export const retiredUnusedDemoIds = ['property-fields', 'property-shape-slot', 'composition', 'colorize', 'dynamic-controls', 'property-bar', 'additional-hover', 'property-panel', 'layer-detail-input'].map(id => `sw-header--${id}`)
|
||||||
// Include the old title when reading historical evidence; live hierarchy is asserted separately.
|
// Include the old title when reading historical evidence; live hierarchy is asserted separately.
|
||||||
export const swStoryEntries = index => Object.values(index.entries).filter(entry => entry.type === 'story' && (entry.title === 'SW/Header' || entry.title === 'SW/Header/Dropdown'))
|
export const swStoryEntries = index => Object.values(index.entries).filter(entry => entry.type === 'story' && (entry.title === 'SW/Header' || entry.title === 'SW/Header/Dropdown'))
|
||||||
export const retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
|
export const retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
// Current regression after removing nine unused SW Header demo pages (2026-10-19, 19 -> 10 SW Stories).
|
||||||
|
// Build Storybook first. Usage: node packages/sw/tests/storybook-unused-removal.browser.mjs [evidence-dir]
|
||||||
|
// Older scripts that open the retired IDs (detail-buttons preservation/verify, followup-four, property-close,
|
||||||
|
// property-fields, property-panel-comparison verify, storybook-cleanup-stage1) are historical evidence for their runs.
|
||||||
|
import { createRequire } from 'node:module'
|
||||||
|
import { mkdir, readFile, writeFile } 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'
|
||||||
|
import { swStoryEntries, retiredUnusedDemoIds } from './storybook-inventory.mjs'
|
||||||
|
|
||||||
|
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-unused-story-removal-qa/verify')
|
||||||
|
const qa = 'docs/sw/storybook-unused-story-removal-qa/before'
|
||||||
|
const baseCommit = 'c8e5fbeebc26fba92998fc5ddcd28c6c110e452d'
|
||||||
|
await mkdir(out, { recursive: true })
|
||||||
|
const report = { baseCommit, checks: [], errors: [], data: {} }
|
||||||
|
const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); if (!ok) console.error('FAIL', name, JSON.stringify(data)) }
|
||||||
|
|
||||||
|
// ---- Inventory: exactly the nine requested IDs leave; every other entry keeps ID, name, title, tags and order.
|
||||||
|
const beforeIndex = JSON.parse(await readFile(path.join(qa, 'full-index-before.json'), 'utf8'))
|
||||||
|
const afterIndex = JSON.parse(await readFile('storybook-static/index.json', 'utf8'))
|
||||||
|
const stories = index => Object.values(index.entries).filter(e => e.type === 'story')
|
||||||
|
const sig = list => JSON.stringify(list.map(e => [e.id, e.name, e.title, e.tags]))
|
||||||
|
const beforeSW = swStoryEntries(beforeIndex), afterSW = swStoryEntries(afterIndex)
|
||||||
|
const expected = beforeSW.filter(e => !retiredUnusedDemoIds.includes(e.id))
|
||||||
|
report.data.inventory = { before: [stories(beforeIndex).length, beforeSW.length], after: [stories(afterIndex).length, afterSW.length], sw: afterSW.map(e => [e.id, e.name]) }
|
||||||
|
await writeFile(path.join(out, 'story-inventory-after.json'), JSON.stringify({ total: stories(afterIndex).length, sw: afterSW.map(e => ({ id: e.id, name: e.name, title: e.title, tags: e.tags })) }, null, 2) + '\n')
|
||||||
|
check('inventory: 19 -> 10 SW Stories, 338 -> 329 total', beforeSW.length === 19 && afterSW.length === 10 && stories(beforeIndex).length === 338 && stories(afterIndex).length === 329, report.data.inventory)
|
||||||
|
check('inventory: the nine requested IDs existed before and are absent now', retiredUnusedDemoIds.length === 9 && retiredUnusedDemoIds.every(id => beforeIndex.entries[id] && !afterIndex.entries[id]))
|
||||||
|
check('inventory: retained SW IDs, names, titles, tags and order unchanged', sig(expected) === sig(afterSW), afterSW.map(e => e.id))
|
||||||
|
const others = index => Object.values(index.entries).filter(e => !retiredUnusedDemoIds.includes(e.id))
|
||||||
|
check('inventory: every non-removed index entry identical (docs and other packages included)', JSON.stringify(others(beforeIndex)) === JSON.stringify(others(afterIndex)))
|
||||||
|
check('inventory: Dropdown keeps sw-header--composed-controls / Dropdown / SW/Header', afterIndex.entries['sw-header--composed-controls']?.name === 'Dropdown' && afterIndex.entries['sw-header--composed-controls']?.title === 'SW/Header')
|
||||||
|
check('inventory: comparison keeps its ID and name', afterIndex.entries['sw-header--property-panel-compare']?.name === 'Property Panel · 4개 비교')
|
||||||
|
|
||||||
|
// ---- Source: retained Story declarations byte-identical; product, AI template and other packages untouched.
|
||||||
|
const sourcePath = 'src/sw/sw-header.stories.tsx'
|
||||||
|
const exports = text => Object.fromEntries(parse(text, { sourceType: 'module', plugins: ['typescript', 'jsx'] }).program.body
|
||||||
|
.filter(n => n.type === 'ExportNamedDeclaration' && n.declaration).map(n => [n.declaration.declarations?.[0].id.name ?? n.declaration.id.name, text.slice(n.start, n.end)]))
|
||||||
|
const oldExports = exports(execFileSync('git', ['show', `${baseCommit}:${sourcePath}`], { encoding: 'utf8' }).replaceAll('\r\n', '\n'))
|
||||||
|
const newExports = exports((await readFile(sourcePath, 'utf8')).replaceAll('\r\n', '\n'))
|
||||||
|
const removedExports = ['PropertyFields', 'PropertyShapeSlot', 'Composition', 'Colorize', 'DynamicControls', 'PropertyBar', 'AdditionalHover', 'PropertyPanel', 'LayerDetailInput']
|
||||||
|
check('source: the nine Story exports are removed', removedExports.every(name => oldExports[name] && !newExports[name]))
|
||||||
|
const changed = Object.keys(oldExports).filter(name => !removedExports.includes(name) && oldExports[name] !== newExports[name])
|
||||||
|
check('source: retained exports (Stories and ComposedDemo) byte-identical', changed.length === 0 && Object.keys(newExports).every(name => oldExports[name]), changed)
|
||||||
|
const untouched = ['packages/sw/src', 'packages/sw/ai', 'packages/sw/package.json', 'packages/sw/README.md', 'packages/ui', 'packages/org-chart', 'packages/org-chart-react', 'packages/center-graphic', 'src/sw/sw-header-overview.tsx', 'src/sw/sw-header-dropdown.stories.tsx', 'src/sw/sw-composed-controls.css', 'src/sw/assets', '.storybook']
|
||||||
|
const diff = execFileSync('git', ['diff', '--name-only', baseCommit, '--', ...untouched], { encoding: 'utf8' }).trim()
|
||||||
|
check('source: runtime, public API, AI template, other packages, Overview, Dropdown CSF, assets and Storybook config untouched', diff === '', diff)
|
||||||
|
|
||||||
|
// ---- Browser
|
||||||
|
const root = path.resolve('storybook-static'), mime = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.gif': 'image/gif', '.ttf': 'font/ttf', '.woff2': 'font/woff2' }
|
||||||
|
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')
|
||||||
|
const browser = await chromium.launch({ headless: true, executablePath: process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' })
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } })
|
||||||
|
report.browser = browser.version()
|
||||||
|
page.on('pageerror', e => report.errors.push(e.message))
|
||||||
|
page.on('console', m => { if (m.type() === 'error') report.errors.push(m.text()) })
|
||||||
|
const goto = async id => {
|
||||||
|
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.mouse.move(1, 1)
|
||||||
|
}
|
||||||
|
const noDocOverflow = () => page.evaluate(() => document.documentElement.scrollWidth <= document.documentElement.clientWidth)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Sidebar: the manager lists the ten retained Stories in order, none of the nine menus.
|
||||||
|
await page.goto(`${base}/?path=/story/sw-header--overview`)
|
||||||
|
await page.frameLocator('#storybook-preview-iframe').locator('[data-preview-stage]').waitFor()
|
||||||
|
const sidebar = await page.locator('[data-item-id^="sw-header--"]').evaluateAll(list => [...new Map(list.map(e => [e.dataset.itemId, e.textContent.replace('Skip to content', '').trim()])).entries()])
|
||||||
|
report.data.sidebar = sidebar
|
||||||
|
await writeFile(path.join(out, 'manager-story-order.json'), JSON.stringify(sidebar, null, 2) + '\n')
|
||||||
|
// .storybook/main.ts excludes `verification` Stories (Detail Buttons) from the default sidebar; direct URLs stay.
|
||||||
|
const visible = afterSW.filter(e => !e.tags.includes('verification'))
|
||||||
|
check('sidebar: retained non-verification Stories in index order with their names', JSON.stringify(sidebar) === JSON.stringify(visible.map(e => [e.id, e.name])), sidebar)
|
||||||
|
const removedNames = beforeSW.filter(e => retiredUnusedDemoIds.includes(e.id)).map(e => e.name)
|
||||||
|
check('sidebar: none of the nine removed menu IDs or names remain', sidebar.every(([id, text]) => !retiredUnusedDemoIds.includes(id) && !removedNames.includes(text)), removedNames)
|
||||||
|
await page.screenshot({ path: path.join(out, 'manager-sidebar.png') })
|
||||||
|
// A removed URL no longer resolves to a Story.
|
||||||
|
await page.goto(`${base}/iframe.html?id=sw-header--colorize&viewMode=story`)
|
||||||
|
await page.waitForTimeout(1500)
|
||||||
|
check('removed URL renders no SW Story', await page.locator('.hm-sw-header, .hm-sw-theme').count() === 0)
|
||||||
|
report.errors.length = 0 // the missing-story error above is expected; start the regression clean
|
||||||
|
|
||||||
|
for (const entry of afterSW) {
|
||||||
|
await goto(entry.id.replace('sw-header--', ''))
|
||||||
|
check(`render ${entry.id}`, !(await page.locator('.sb-errordisplay').isVisible()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overview: Header 49px, Stage, Dropdown open/close, Bar edit, panel open/Escape, callbacks.
|
||||||
|
for (const width of [390, 768, 1280, 1440]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 }); await goto('overview')
|
||||||
|
const header = page.locator('header.hm-sw-header')
|
||||||
|
const height = await header.evaluate(h => h.getBoundingClientRect().height)
|
||||||
|
check(`Overview ${width}: one Header 49px, one Stage, no page overflow`, height === 49 && await header.count() === 1 && await page.locator('[data-preview-stage]').count() === 1 && await noDocOverflow(), { height })
|
||||||
|
await page.screenshot({ path: path.join(out, `overview-${width}.png`) })
|
||||||
|
}
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 }); await goto('overview')
|
||||||
|
{
|
||||||
|
const header = page.locator('header.hm-sw-header'), callback = () => page.locator('[data-preview-callback]').textContent()
|
||||||
|
const layer = header.getByRole('button', { name: '레이어', exact: true })
|
||||||
|
await layer.click()
|
||||||
|
check('Overview: layer Dropdown opens', await header.locator('.hm-sw-header__layer-panel').count() === 1 && await layer.getAttribute('aria-expanded') === 'true')
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
check('Overview: Escape closes Dropdown and restores focus', await header.locator('.hm-sw-header__layer-panel').count() === 0 && await layer.evaluate(e => e === document.activeElement))
|
||||||
|
await layer.click(); await header.getByRole('button', { name: '레이어 상세', exact: true }).click()
|
||||||
|
check('Overview: layer Detail callback (former Layer Detail page) and close', (await callback()).includes('onLayerDetail') && await header.locator('.hm-sw-header__layer-panel').count() === 0)
|
||||||
|
await header.getByRole('button', { name: '색상', exact: true }).click()
|
||||||
|
const pick = header.getByRole('button', { name: '색상 추출', exact: true })
|
||||||
|
const pickBox = await pick.boundingBox()
|
||||||
|
await pick.click()
|
||||||
|
check('Overview: btn_colorize 24 × 24 calls onColorPick and closes (former Colorize page)', pickBox.width === 24 && pickBox.height === 24 && (await callback()).includes('onColorPick') && await header.locator('.hm-sw-header__color-menu').count() === 0, pickBox)
|
||||||
|
const bar = page.locator('.hm-sw-property-bar [data-property-id="radius"] input')
|
||||||
|
await bar.fill('6.25'); await bar.press('Enter')
|
||||||
|
check('Overview: property Bar Enter commits through callback', await bar.inputValue() === '6.25' && (await callback()).includes('onPropertyChange(radius, 6.25)'))
|
||||||
|
await bar.fill('-1'); await bar.press('Enter')
|
||||||
|
check('Overview: property Bar validation keeps min=0', await bar.inputValue() === '6.25')
|
||||||
|
const trigger = header.locator('[data-property-panel-trigger]')
|
||||||
|
await trigger.click(); await page.locator('.hm-sw-property').waitFor()
|
||||||
|
check('Overview: panel opens with the shared Bar value', await page.locator('.hm-sw-property [data-property-id="radius"] input').first().inputValue() === '6.25')
|
||||||
|
await page.keyboard.press('Escape'); await page.keyboard.press('Escape')
|
||||||
|
check('Overview: Escape closes panel and returns focus to its trigger', await page.locator('.hm-sw-property').count() === 0 && await trigger.evaluate(e => e === document.activeElement))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dropdown: six controls, real open, selection, Escape and focus return.
|
||||||
|
await goto('composed-controls')
|
||||||
|
{
|
||||||
|
const host = page.locator('[data-composed-controls]'), field = name => host.getByRole('button', { name, exact: true })
|
||||||
|
const menu = () => page.locator('[data-composed-controls] .hm-sw-header__menu-panel, [data-composed-controls] .hm-sw-header__layer-menu')
|
||||||
|
check('Dropdown: five fields plus the 상세보기 group (6 controls)', await host.locator('.hm-sw-header__field').count() === 5 && await host.getByRole('group', { name: '상세보기', exact: true }).count() === 1)
|
||||||
|
for (const width of [390, 768, 1280, 1440]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 })
|
||||||
|
for (const label of ['레이어', '색상', '선스타일', '문자스타일', '치수스타일']) {
|
||||||
|
await field(label).click()
|
||||||
|
const opened = await menu().count() === 1 && await field(label).getAttribute('aria-expanded') === 'true'
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
check(`Dropdown ${width}: ${label} opens, Escape closes, focus returns`, opened && await menu().count() === 0 && await field(label).evaluate(e => e === document.activeElement))
|
||||||
|
}
|
||||||
|
check(`Dropdown ${width}: no page overflow`, await noDocOverflow())
|
||||||
|
await page.screenshot({ path: path.join(out, `dropdown-${width}.png`), fullPage: true })
|
||||||
|
}
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 })
|
||||||
|
await field('문자스타일').click(); await page.locator('.hm-sw-header__list-menu--text-style').getByRole('button', { name: /Dotum/ }).click()
|
||||||
|
check('Dropdown: text style selection reflected in field and status', (await field('문자스타일').textContent()).includes('Dotum') && (await page.locator('[data-style-state]').textContent()).includes('Dotum'))
|
||||||
|
await field('선스타일').click()
|
||||||
|
const center = page.getByRole('button', { name: 'CENTER', exact: true })
|
||||||
|
await center.click()
|
||||||
|
check('Dropdown: line style selection reflected', (await page.locator('[data-style-state]').textContent()).includes('CENTER'))
|
||||||
|
if (await menu().count()) await page.keyboard.press('Escape')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparison: 2 × 2, original sizes, Inner scroll reaches the 754px bottom, input and dropdown work.
|
||||||
|
for (const width of [390, 768, 1280, 1440]) {
|
||||||
|
await page.setViewportSize({ width, height: 900 }); await goto('property-panel-compare')
|
||||||
|
const g = await page.evaluate(() => {
|
||||||
|
const r = e => e.getBoundingClientRect(), s = document.querySelector('[data-compare-scroll]')
|
||||||
|
const cells = [...document.querySelectorAll('[data-compare]')].map(c => ({ id: c.dataset.compare, x: r(c).x, y: r(c).y, right: r(c).right, bottom: r(c).bottom, w: r(c.querySelector('.hm-sw-property')).width, h: r(c.querySelector('.hm-sw-property')).height }))
|
||||||
|
s.scrollTop = s.scrollHeight; s.scrollLeft = s.scrollWidth
|
||||||
|
const last = r(document.querySelectorAll('[data-compare] .hm-sw-property')[3]), sr = r(s)
|
||||||
|
return { cells, reach: last.bottom <= sr.bottom + 0.5 && last.right <= sr.right + 0.5, docFits: document.documentElement.scrollHeight <= innerHeight && document.documentElement.scrollWidth <= innerWidth }
|
||||||
|
})
|
||||||
|
const [a, b, c, d] = g.cells
|
||||||
|
report.data[`compare${width}`] = g
|
||||||
|
check(`Compare ${width}: four states in order at 314 × 256 / 361 / 658 / 754`, JSON.stringify(g.cells.map(e => [e.id, e.w, e.h])) === JSON.stringify([['none', 314, 256], ['all', 314, 361], ['circle', 314, 658], ['expanded', 314, 754]]), g.cells)
|
||||||
|
check(`Compare ${width}: 2 columns × 2 rows`, Math.abs(a.y - b.y) < 0.5 && Math.abs(c.y - d.y) < 0.5 && Math.abs(a.x - c.x) < 0.5 && Math.abs(b.x - d.x) < 0.5 && b.x >= a.right && c.y >= Math.max(a.bottom, b.bottom))
|
||||||
|
check(`Compare ${width}: Inner scroll reaches the 754px panel bottom; page does not scroll`, g.reach && g.docFits, g)
|
||||||
|
await page.screenshot({ path: path.join(out, `compare-${width}-end.png`) })
|
||||||
|
}
|
||||||
|
await page.setViewportSize({ width: 1440, height: 900 }); await goto('property-panel-compare')
|
||||||
|
{
|
||||||
|
const cell = id => page.locator(`[data-compare="${id}"]`), log = () => page.locator('[data-property-log]').textContent()
|
||||||
|
const radius = cell('circle').locator('[data-property-id="radius"] input.hm-sw-header__input').first()
|
||||||
|
await radius.scrollIntoViewIfNeeded(); await radius.click()
|
||||||
|
check('Compare: editable Input caret #E60403 (former Layer Detail · Input page)', await radius.evaluate(e => getComputedStyle(e).caretColor === 'rgb(230, 4, 3)'))
|
||||||
|
await radius.fill('5.5'); await radius.press('Enter')
|
||||||
|
check('Compare: Input Enter commits only in its own panel', (await radius.inputValue()).startsWith('5.5') && (await log()).includes('circle: property(radius, 5.5'))
|
||||||
|
await radius.click(); await radius.fill('9'); await radius.press('Escape')
|
||||||
|
check('Compare: Input Escape cancels', (await radius.inputValue()).startsWith('5.5'))
|
||||||
|
const trigger = cell('all').locator('[data-property-id="layer"] button[aria-haspopup], [data-property-id="layer"] [aria-expanded]').first()
|
||||||
|
await trigger.scrollIntoViewIfNeeded(); await trigger.click()
|
||||||
|
const drop = page.locator('.hm-sw-property__drop').first(); await drop.waitFor({ timeout: 2000 })
|
||||||
|
await drop.getByRole('button', { name: 'CC-D GNL', exact: true }).first().click()
|
||||||
|
check('Compare: layer Dropdown selection commits via callback', (await log()).includes('all: property(layer, CC-D GNL)'))
|
||||||
|
if (await page.locator('.hm-sw-property__drop').count()) await page.keyboard.press('Escape')
|
||||||
|
await trigger.click(); await page.locator('.hm-sw-property__drop').first().waitFor()
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
check('Compare: Escape closes the Dropdown and returns focus', await page.locator('.hm-sw-property__drop').count() === 0 && await trigger.evaluate(e => document.activeElement === e || e.contains(document.activeElement)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window Move Motion: the single 3x step section with nine paused stages, live Hover 1400ms.
|
||||||
|
await goto('window-move-motion')
|
||||||
|
{
|
||||||
|
const stages = await page.locator('[data-motion-time]').evaluateAll(list => list.map(e => ({ time: e.dataset.motionTime, animations: e.querySelector('.hm-sw-header__motion')?.getAnimations({ subtree: true }).length ?? -1, zoom: getComputedStyle(e.querySelector('.sw-story-zoom')).zoom })))
|
||||||
|
check('Motion: one section 「단계별 진행 (3배 확대)」 with nine stages at zoom 3', await page.getByRole('heading', { level: 2 }).allTextContents().then(t => JSON.stringify(t) === JSON.stringify(['단계별 진행 (3배 확대)'])) && stages.length === 9 && stages.every(s => Number(s.zoom) === 3), stages)
|
||||||
|
check('Motion: rest stage static, timed stages hold three paused animations', stages[0].time === 'rest' && stages[0].animations === 0 && stages.slice(1).every(s => s.animations === 3), stages)
|
||||||
|
}
|
||||||
|
check('no browser console or page errors', report.errors.length === 0, report.errors)
|
||||||
|
} catch (e) { check('suite completed', false, e.stack) }
|
||||||
|
finally {
|
||||||
|
report.status = report.checks.every(c => c.status === 'PASS') ? 'PASS' : 'FAIL'
|
||||||
|
await writeFile(path.join(out, 'result.json'), JSON.stringify(report, null, 2) + '\n')
|
||||||
|
await browser.close(); server.close()
|
||||||
|
console.log('TOTAL', report.checks.length, 'FAIL', report.checks.filter(c => c.status === 'FAIL').length, report.status)
|
||||||
|
process.exitCode = report.status === 'PASS' ? 0 : 1
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@ import { useEffect, useId, useRef, useState, type ReactNode } from "react"
|
|||||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||||
import {
|
import {
|
||||||
SWHeader, SWHeaderColorMenu, SWHeaderLineStyleMenu, SWHeaderTextStyleMenu, SWHeaderDimensionStyleMenu,
|
SWHeader, SWHeaderColorMenu, SWHeaderLineStyleMenu, SWHeaderTextStyleMenu, SWHeaderDimensionStyleMenu,
|
||||||
SWHeaderProperties, SWHeaderWindowControls, SWTheme, SWHeaderLayerMenu, SWHeaderInput, parseSWHeaderColor, swHeaderLinePatterns, SWHeaderPropertyPanel, SWHeaderPropertyBar,
|
SWHeaderProperties, SWHeaderWindowControls, SWTheme, SWHeaderLayerMenu, parseSWHeaderColor, swHeaderLinePatterns, SWHeaderPropertyPanel, SWHeaderPropertyBar,
|
||||||
type SWHeaderColorMode, type SWHeaderColorOption, type SWHeaderColorValue, type SWHeaderDimensionStyleOption,
|
type SWHeaderColorMode, type SWHeaderColorOption, type SWHeaderColorValue, type SWHeaderDimensionStyleOption,
|
||||||
type SWHeaderDocument, type SWHeaderLayer, type SWHeaderLineStyleValue, type SWHeaderLineType, type SWHeaderLineWeight,
|
type SWHeaderDocument, type SWHeaderLayer, type SWHeaderLineStyleValue, type SWHeaderLineType, type SWHeaderLineWeight,
|
||||||
type SWHeaderMenuId, type SWHeaderProps, type SWHeaderTextStyleOption, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderControlConfig, type SWHeaderHideableControlId, type SWHeaderPropertyShapeContent,
|
type SWHeaderMenuId, type SWHeaderProps, type SWHeaderTextStyleOption, type SWHeaderProperty, type SWHeaderPropertyGroup, type SWHeaderPropertySelection, type SWHeaderPropertyMenus, type SWHeaderPropertyShapeContent,
|
||||||
} from "@hm-design/sw"
|
} from "@hm-design/sw"
|
||||||
import "@hm-design/sw/styles.css"
|
import "@hm-design/sw/styles.css"
|
||||||
import { SWHeaderMenuDetail } from "../../packages/sw/src/components/sw-header/components/SWHeaderDropdown"
|
import { SWHeaderMenuDetail } from "../../packages/sw/src/components/sw-header/components/SWHeaderDropdown"
|
||||||
@@ -13,8 +13,6 @@ import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-pag
|
|||||||
import "./sw-stories.css"
|
import "./sw-stories.css"
|
||||||
import { SWHeaderOverview } from "./sw-header-overview"
|
import { SWHeaderOverview } from "./sw-header-overview"
|
||||||
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
|
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"
|
|
||||||
|
|
||||||
// Owner: docs/sw/sw-header-design-spec.md (Figma rj6wpsfk24lOWvWiVRAANP, node 26:8944 "header_green").
|
// Owner: docs/sw/sw-header-design-spec.md (Figma rj6wpsfk24lOWvWiVRAANP, node 26:8944 "header_green").
|
||||||
// The package is independent of @hm-design/ui: this file imports only @hm-design/sw for the specimen.
|
// The package is independent of @hm-design/ui: this file imports only @hm-design/sw for the specimen.
|
||||||
@@ -615,7 +613,6 @@ export const DocumentDropdown: Story = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Test data only. The package never ships layer names; each SW program passes its own list. */
|
/** Test data only. The package never ships layer names; each SW program passes its own list. */
|
||||||
const FIGMA_LAYERS: SWHeaderLayer[] = Array.from({ length: 8 }, (_, index) => ({ id: `figma-${index + 1}`, name: "CC-D GNL" }))
|
|
||||||
const SAMPLE_LAYERS: SWHeaderLayer[] = [
|
const SAMPLE_LAYERS: SWHeaderLayer[] = [
|
||||||
{ id: "layer-0", name: "0" },
|
{ id: "layer-0", name: "0" },
|
||||||
{ id: "layer-1", name: "Sample-Center", color: "#ff0000" },
|
{ id: "layer-1", name: "Sample-Center", color: "#ff0000" },
|
||||||
@@ -640,56 +637,6 @@ const MANY_LAYERS: SWHeaderLayer[] = Array.from({ length: 20 }, (_, index) => ({
|
|||||||
locked: index % 6 === 3,
|
locked: index % 6 === 3,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const noop = () => {}
|
|
||||||
|
|
||||||
type LayerHostProps = Partial<SWHeaderProps> & { onLog?: (line: string) => void; showState?: boolean }
|
|
||||||
|
|
||||||
const describeLayers = (layers: SWHeaderLayer[]) => layers.map(layer =>
|
|
||||||
`${layer.id}: visible=${layer.visible !== false} frozen=${layer.frozen === true} locked=${layer.locked === true}`).join("\n")
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Host-owned layers: the SW program keeps the list, the current layer and each layer's state. The
|
|
||||||
* header only reports the next value; this example writes it back so the icons follow.
|
|
||||||
*/
|
|
||||||
function LayerHeader({ layers: initialLayers = SAMPLE_LAYERS, activeLayerId: initialActiveId, onLog, showState, ...props }: LayerHostProps) {
|
|
||||||
const [layers, setLayers] = useState(initialLayers)
|
|
||||||
const [activeId, setActiveId] = useState(initialActiveId)
|
|
||||||
const update = (id: string, patch: Partial<SWHeaderLayer>) => setLayers(list => list.map(item => (item.id === id ? { ...item, ...patch } : item)))
|
|
||||||
const header = (
|
|
||||||
<SWHeader
|
|
||||||
{...figmaArgs}
|
|
||||||
{...callbacks}
|
|
||||||
layers={layers}
|
|
||||||
activeLayerId={activeId}
|
|
||||||
onLayerSelect={layer => { console.info("[SWHeader] layerSelect", layer.id); onLog?.(`onLayerSelect(${layer.id})`); setActiveId(layer.id) }}
|
|
||||||
onLayerVisibilityChange={(layer, next) => { onLog?.(`onLayerVisibilityChange(${layer.id}, ${next})`); update(layer.id, { visible: next }) }}
|
|
||||||
onLayerFreezeChange={(layer, next) => { onLog?.(`onLayerFreezeChange(${layer.id}, ${next})`); update(layer.id, { frozen: next }) }}
|
|
||||||
onLayerLockChange={(layer, next) => { onLog?.(`onLayerLockChange(${layer.id}, ${next})`); update(layer.id, { locked: next }) }}
|
|
||||||
onLayerDetail={() => { console.info("[SWHeader] layerDetail"); onLog?.("onLayerDetail") }}
|
|
||||||
onLayerMenuOpenChange={open => onLog?.(`onLayerMenuOpenChange(${open})`)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
if (!showState) return header
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<pre className="sw-story-layer-state" data-layer-state="">{`${describeLayers(layers)}\nactiveLayerId: ${activeId ?? "없음"}`}</pre>
|
|
||||||
{header}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Interactive demo with a visible callback log, to check that selection and toggles never double-fire. */
|
|
||||||
function LayerDemo({ width, ...props }: LayerHostProps & { width: number }) {
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className="sw-story-log" aria-live="polite" data-callback-log="">Callback: {log.length ? log.join(" → ") : "없음"}</p>
|
|
||||||
<MenuFrame width={width}><LayerHeader showState {...props} onLog={line => setLog(lines => [...lines.slice(-9), line])} /></MenuFrame>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------------------------
|
/* ------------------------------------------------------------------------------------------------
|
||||||
Field dropdowns (2026-10-16): color 26:10986 / 26:11036, ByLayer 26:12666, ByBlock 26:12659,
|
Field dropdowns (2026-10-16): color 26:10986 / 26:11036, ByLayer 26:12666, ByBlock 26:12659,
|
||||||
line style 26:11104 (rows 26:10702), text style 26:11181, dimension style 26:11215.
|
line style 26:11104 (rows 26:10702), text style 26:11181, dimension style 26:11215.
|
||||||
@@ -735,16 +682,16 @@ const FIGMA_DIMENSION_STYLES: SWHeaderDimensionStyleOption[] = [
|
|||||||
{ id: "iso-25", name: "ISO - 25" },
|
{ id: "iso-25", name: "ISO - 25" },
|
||||||
]
|
]
|
||||||
|
|
||||||
type StyleHostProps = Partial<SWHeaderProps> & { onLog?: (line: string) => void; showState?: boolean; initialColorMode?: SWHeaderColorMode | null }
|
type StyleHostProps = Partial<SWHeaderProps> & { onLog?: (line: string) => void; showState?: boolean }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host-owned field data: the SW program keeps color, line / text / dimension style and the lists. The
|
* Host-owned field data: the SW program keeps color, line / text / dimension style and the lists. The
|
||||||
* header only reports; this example writes each report back (an explicit color clears ByLayer / ByBlock,
|
* header only reports; this example writes each report back (an explicit color clears ByLayer / ByBlock,
|
||||||
* which is this host's choice) so the fields follow. Each property changes alone.
|
* which is this host's choice) so the fields follow. Each property changes alone.
|
||||||
*/
|
*/
|
||||||
function StyleHeader({ onLog, showState, initialColorMode = null, ...props }: StyleHostProps) {
|
function StyleHeader({ onLog, showState, ...props }: StyleHostProps) {
|
||||||
const [color, setColor] = useState<SWHeaderColorValue>(figmaArgs.color!)
|
const [color, setColor] = useState<SWHeaderColorValue>(figmaArgs.color!)
|
||||||
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>(initialColorMode)
|
const [colorMode, setColorMode] = useState<SWHeaderColorMode | null>(null)
|
||||||
const [lineStyle, setLineStyle] = useState<SWHeaderLineStyleValue>(figmaArgs.lineStyle!)
|
const [lineStyle, setLineStyle] = useState<SWHeaderLineStyleValue>(figmaArgs.lineStyle!)
|
||||||
const [lineWeights, setLineWeights] = useState(FIGMA_LINE_WEIGHTS)
|
const [lineWeights, setLineWeights] = useState(FIGMA_LINE_WEIGHTS)
|
||||||
const [weightId, setWeightId] = useState<string | undefined>("w-default")
|
const [weightId, setWeightId] = useState<string | undefined>("w-default")
|
||||||
@@ -857,101 +804,6 @@ export const ResponsiveMore: Story = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Host-owned detail values for the editable input (the detail popup itself is not designed yet). */
|
|
||||||
function InputDemo() {
|
|
||||||
const [value, setValue] = useState("4.0526")
|
|
||||||
const [name, setName] = useState("벽체_외벽")
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const add = (line: string) => setLog(lines => [...lines.slice(-9), line])
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p className="sw-story-log" aria-live="polite" data-callback-log="">Callback: {log.length ? log.join(" → ") : "없음"}</p>
|
|
||||||
<pre className="sw-story-layer-state" data-input-state="">{`확정값: number=${value} · text=${name}`}</pre>
|
|
||||||
<div className="sw-story-controls">
|
|
||||||
<button type="button" onClick={() => { setValue("12.5"); add("host: value=12.5") }}>외부 값 변경 (12.5)</button>
|
|
||||||
<button type="button" onClick={() => { setName("레이어 이름 변경"); add("host: text 변경") }}>외부 텍스트 변경</button>
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-zoom">
|
|
||||||
<SWTheme className="sw-story-composed sw-story-detail">
|
|
||||||
<SWHeaderInput
|
|
||||||
aria-label="숫자 속성"
|
|
||||||
valueType="number"
|
|
||||||
value={value}
|
|
||||||
onValueChange={draft => add(`onValueChange(${draft})`)}
|
|
||||||
onCommit={next => { add(`onCommit(${next})`); setValue(next) }}
|
|
||||||
onCancel={() => add("onCancel")}
|
|
||||||
/>
|
|
||||||
<SWHeaderInput
|
|
||||||
aria-label="텍스트 속성"
|
|
||||||
value={name}
|
|
||||||
onCommit={next => { add(`onCommit(${next})`); setName(next) }}
|
|
||||||
onCancel={() => add("onCancel")}
|
|
||||||
/>
|
|
||||||
</SWTheme>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const LayerDetailInput: Story = {
|
|
||||||
name: "Layer Detail · Input",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => (
|
|
||||||
<StoryPage>
|
|
||||||
<StoryPageHeader
|
|
||||||
kicker={KICKER}
|
|
||||||
title="Layer Detail Button · Editable Input"
|
|
||||||
description="레이어 Dropdown 하단 '레이어 상세' 버튼(btn_상세/레이어 26:10767)과, 상세 영역에서 쓸 수정 가능한 Input(input 26:11656) · 커서(cursor 26:11664)입니다. 상세 팝업 전체는 Figma 전달 전이라 만들지 않으며, 버튼 Callback(onLayerDetail)과 SWHeaderInput 계약만 제공합니다."
|
|
||||||
/>
|
|
||||||
<StorySection title="Figma 원본 (비교용 이미지)" description="왼쪽: 레이어 상세 Default / Hover(배경 .25 · radius 25 · 아이콘 35%). 오른쪽: Input Default 58 × 20 / Hover 58 × 24 / On 58 × 24. 최신 요청에 따라 아래 실제 버튼에도 Hover를 적용합니다. 이미지는 Storybook 비교용이며 패키지에 포함하지 않습니다.">
|
|
||||||
<div className="sw-story-row sw-story-figma">
|
|
||||||
<img className="sw-story-reference" src={figmaLayerDetailReference} alt="Figma btn_상세/레이어 26:10767" width={180} height={108} />
|
|
||||||
<img className="sw-story-reference" src={figmaInputReference} alt="Figma input 26:11656" width={98} height={140} />
|
|
||||||
</div>
|
|
||||||
</StorySection>
|
|
||||||
<StorySection title="레이어 상세 — Default · Hover (고정) · Disabled (3배 확대)" description="왼쪽: Callback 없이 사용 가능(Default). 가운데: Hover 배경 .25·radius 25·Hover 아이콘. 오른쪽: 호스트가 fieldset disabled를 명시한 상태. Figma와 같은 113 × 24, 아이콘 14 · 간격 4 · padding 5 / 10 · 배경 rgba(197,177,158,.15) · radius 24 · 글자 35%. CAD 상세 실행은 호스트 Callback이 있을 때만 가능합니다.">
|
|
||||||
<div className="sw-story-row">
|
|
||||||
{[{ detail: undefined, hover: false, disabled: false }, { detail: noop, hover: true, disabled: false }, { detail: undefined, hover: false, disabled: true }].map((item, index) => {
|
|
||||||
const specimen = (
|
|
||||||
<div className="sw-story-zoom sw-story-layer-room" data-detail-case={index}>
|
|
||||||
<fieldset className="sw-story-explicit-disabled" disabled={item.disabled}><SWTheme className="sw-story-composed">
|
|
||||||
<SWHeaderLayerMenu value={{ name: "0" }} layers={FIGMA_LAYERS.slice(0, 2)} open onDetail={item.detail} />
|
|
||||||
</SWTheme></fieldset>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
return item.hover ? <PinHover key={index} selectors={[".hm-sw-header__layer-detail"]}>{specimen}</PinHover> : <div key={index}>{specimen}</div>
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</StorySection>
|
|
||||||
<StorySection title="레이어 상세 Click Callback" description="필드를 열고 '레이어 상세'를 누르면 onLayerDetail 1회 후 목록이 닫힙니다(기존 동작). 레이어 선택·전구·눈꽃·자물쇠 매핑은 변경하지 않았습니다.">
|
|
||||||
<LayerDemo width={1920} layers={FIGMA_LAYERS} />
|
|
||||||
</StorySection>
|
|
||||||
<StorySection title="Input — Default · Hover (고정) · ReadOnly · Disabled (3배 확대)" description="Default: 배경·테두리 없음, #E60403 14px + 검정 1.3px Stroke. Hover: 1px rgba(212,203,189,.25), 24px(글자 위치 그대로). ReadOnly(onCommit 없음 또는 readOnly): Hover·편집 상태 없음. Disabled: 40%.">
|
|
||||||
<PinHover selectors={[".sw-story-pin-hover"]}>
|
|
||||||
<div className="sw-story-zoom">
|
|
||||||
<SWTheme className="sw-story-composed sw-story-detail">
|
|
||||||
<SWHeaderInput aria-label="Default" value="4.0526" onCommit={noop} />
|
|
||||||
<SWHeaderInput aria-label="Hover" className="sw-story-pin-hover" value="4.0526" onCommit={noop} />
|
|
||||||
<SWHeaderInput aria-label="ReadOnly" value="4.0526" onCommit={noop} readOnly />
|
|
||||||
<SWHeaderInput aria-label="Disabled" value="4.0526" onCommit={noop} disabled />
|
|
||||||
</SWTheme>
|
|
||||||
</div>
|
|
||||||
</PinHover>
|
|
||||||
</StorySection>
|
|
||||||
<StorySection title="Input — Editing (Focus)" description="편집 중(26:11661): 배경 #0F0D0A, 1px rgba(212,203,189,.3), 글자 #E60403 유지, 네이티브 커서 #E60403(caret-color, 깜박임 = Figma cursor Default / Variant2). 이 예제는 입력에 자동 Focus합니다.">
|
|
||||||
<div className="sw-story-zoom">
|
|
||||||
<SWTheme className="sw-story-composed sw-story-detail">
|
|
||||||
<SWHeaderInput aria-label="Editing" value="4.0526" onCommit={noop} autoFocus />
|
|
||||||
</SWTheme>
|
|
||||||
</div>
|
|
||||||
</StorySection>
|
|
||||||
<StorySection title="Input — 입력 · Enter 확정 · Escape 취소 · 외부 값 갱신" description="숫자 입력(valueType='number'): 유효한 소수만 onCommit, 잘못된 값은 복원. 텍스트 입력(한글 IME 포함): 조합 중 Enter는 확정하지 않습니다. Escape는 편집 취소(onCancel) — 확정값으로 복원. Focus 이동(Tab / 바깥 클릭)도 확정합니다. 위 버튼으로 호스트 값을 바꾸면 최신 값이 표시됩니다.">
|
|
||||||
<InputDemo />
|
|
||||||
</StorySection>
|
|
||||||
</StoryPage>
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Additional review 2026-10-16: fixture values are host data, never package defaults. */
|
/* Additional review 2026-10-16: fixture values are host data, never package defaults. */
|
||||||
const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [
|
const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [
|
||||||
{ id: "common", collapsedCount: 6 },
|
{ id: "common", collapsedCount: 6 },
|
||||||
@@ -1038,239 +890,17 @@ function usePropertyMenus(record: (line: string) => void, setProperties: (update
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function PropertyPanelDemo() {
|
|
||||||
const [example, setExample] = useState<PropertyExample>("none")
|
|
||||||
const [selection, setSelection] = useState(propertySelection("none"))
|
|
||||||
const [properties, setProperties] = useState(propertyFixture("none"))
|
|
||||||
const [expanded, setExpanded] = useState<string[]>([])
|
|
||||||
const [controlled, setControlled] = useState(false)
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const [width, setWidth] = useState(1920)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const record = (text: string) => setLog(lines => [...lines.slice(-19), text])
|
|
||||||
const menus = usePropertyMenus(record, setProperties)
|
|
||||||
const changeExample = (next: PropertyExample) => {
|
|
||||||
setExample(next); setSelection(propertySelection(next)); setProperties(propertyFixture(next))
|
|
||||||
setExpanded(next === "expanded" ? ["common"] : [])
|
|
||||||
}
|
|
||||||
const panel = {
|
|
||||||
selection, properties, groups: PROPERTY_GROUPS, expandedGroupIds: expanded, menus,
|
|
||||||
onExpandedGroupsChange: setExpanded,
|
|
||||||
onPropertyChange: (property: SWHeaderProperty, value: string) => {
|
|
||||||
record("property(" + property.id + ", " + value + ")")
|
|
||||||
setProperties(items => items.map(item => item.id === property.id ? { ...item, value, mixed: false, ...(item.id === "line" ? { linePattern: linePatternOf(value) } : {}) } : item))
|
|
||||||
},
|
|
||||||
onToolActiveChange: (next: boolean) => record("tool(" + next + ")"),
|
|
||||||
onOverviewClick: () => changeExample("all"),
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div data-property-demo="" data-example={example}>
|
|
||||||
<div className="sw-story-controls">
|
|
||||||
{(["none", "all", "circle", "expanded"] as const).map((name, index) => <button key={name} data-example-button={name} onClick={() => changeExample(name)}>{["선택 없음", "전체 선택", "원 1개", "모두 펼침"][index]}</button>)}
|
|
||||||
{[1920, 1440, 1080, 900, 768, 640, 390].map(value => <button key={value} onClick={() => setWidth(value)}>{value}px</button>)}
|
|
||||||
<button onClick={() => { setControlled(value => !value); setOpen(false) }}>Controlled: {String(controlled)}</button>
|
|
||||||
<button onClick={() => setOpen(value => !value)}>외부 Open 변경</button>
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-controls">
|
|
||||||
<button onClick={() => setProperties(items => [...items, { id: "extra-" + items.length, groupId: "common", name: "추가 속성", type: "text", value: "호스트 값" }])}>속성 추가</button>
|
|
||||||
<button onClick={() => setProperties(items => [...items, { id: "shape-extra-" + items.length, groupId: "shape", name: "추가 형상 속성", type: "text", value: "호스트 값" }])}>형상 속성 추가</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "line" ? { ...item, options: item.options?.filter(option => option.id !== "CENTER") } : item))}>CENTER 옵션 삭제</button>
|
|
||||||
<button onClick={() => setProperties(items => items.filter(item => item.id !== "layer"))}>레이어 속성 삭제</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "layer" ? { ...item, value: "외부 변경값" } : item))}>외부 값 변경</button>
|
|
||||||
<button onClick={() => setSelection(current => ({ ...current, id: current.id + "-changed" }))}>선택 객체 교체</button>
|
|
||||||
<button onClick={() => setProperties(items => [...items].reverse())}>속성 순서 뒤집기</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "weight" ? { ...item, disabled: !item.disabled } : item))}>가중치 Disabled</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "layer" ? { ...item, readOnly: !item.readOnly } : item))}>레이어 ReadOnly</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "opacity" ? { ...item, value: "0.8" } : item))}>투명도 외부 값 변경</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "opacity" ? { ...item, disabled: !item.disabled } : item))}>투명도 Disabled</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "opacity" ? { ...item, readOnly: !item.readOnly } : item))}>투명도 ReadOnly</button>
|
|
||||||
</div>
|
|
||||||
<p className="sw-story-log" data-property-log="" aria-live="polite">{log.join(" → ") || "Callback 없음"}</p>
|
|
||||||
<pre className="sw-story-layer-state" data-property-state="">{JSON.stringify({ selection: selection.id, values: properties.map(item => [item.id, item.value]), expanded, open })}</pre>
|
|
||||||
<div className="sw-story-property-room"><Frame width={width}><SWHeader {...figmaArgs} propertyPanel={panel} propertyPanelOpen={controlled ? open : undefined}
|
|
||||||
onPropertyPanelOpenChange={next => { record("open(" + next + ")"); if (controlled) setOpen(next) }}
|
|
||||||
onPropertyPanelToggle={() => record("legacyToggle")}
|
|
||||||
layers={SAMPLE_LAYERS} activeLayerId="layer-0" colors={FIGMA_COLORS} lineTypes={FIGMA_LINE_TYPES} textStyles={FIGMA_TEXT_STYLES} dimensionStyles={FIGMA_DIMENSION_STYLES}
|
|
||||||
/></Frame></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PropertyPanel: Story = {
|
|
||||||
name: "Property Panel",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="속성창" description="Figma 4개 상태를 외부 데이터·그룹 펼침으로 구성합니다. 속성창 버튼(좁은 화면: 더보기)으로 열고 닫으세요. Enter/Blur 확정, Escape 취소, 다음 Escape 닫기." /><PropertyPanelDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
export const KGuide: Story = {
|
export const KGuide: Story = {
|
||||||
name: "K Guide",
|
name: "K Guide",
|
||||||
parameters: { controls: { disable: true } },
|
parameters: { controls: { disable: true } },
|
||||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="K 가이드 반복 애니메이션" description="기존 K 버튼 Hover/Focus → 원본 이미지 Pan·Zoom 반복. 영역을 벗어나면 정지, 재진입은 처음부터. Reduced Motion은 정지 이미지." /><div className="sw-story-property-room"><SWHeader {...figmaArgs} {...callbacks} /></div></StoryPage>,
|
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="K 가이드 반복 애니메이션" description="기존 K 버튼 Hover/Focus → 원본 이미지 Pan·Zoom 반복. 영역을 벗어나면 정지, 재진입은 처음부터. Reduced Motion은 정지 이미지." /><div className="sw-story-property-room"><SWHeader {...figmaArgs} {...callbacks} /></div></StoryPage>,
|
||||||
}
|
}
|
||||||
function RequiredColorDemo() {
|
|
||||||
const [mode, setMode] = useState<SWHeaderColorMode | null>(null)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
return <div data-mode-demo=""><div className="sw-story-controls">
|
|
||||||
<button onClick={() => setMode("byLayer")}>외부 ByLayer</button><button onClick={() => setMode("byBlock")}>외부 ByBlock</button><button onClick={() => setMode(null)}>외부 직접 색상</button>
|
|
||||||
</div><p data-mode-log="">{log.join(",")}</p><MenuFrame width={1920}><SWHeader {...figmaArgs} colors={FIGMA_COLORS} colorMode={mode} onColorModeChange={next => { setMode(next); setLog(items => [...items, next]) }} onColorSelect={() => setMode(null)} /></MenuFrame></div>
|
|
||||||
}
|
|
||||||
export const AdditionalHover: Story = {
|
|
||||||
name: "Additional Hover",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="추가 Hover 및 Selected" description="5개 상세 버튼 Hover와 목록 Selected 강조, 색상 기준 필수 선택. 실제 Hover·클릭으로 검증합니다." /><StyleDemo width={1920} onLayerDetail={() => console.info("[SWHeader] onLayerDetail")} initialColorMode="byLayer" /><RequiredColorDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const BAR_FIXTURE: SWHeaderProperty[] = [
|
const BAR_FIXTURE: SWHeaderProperty[] = [
|
||||||
{ id: "radius", name: "반지름", value: "4.0526", type: "number", groupId: "shape", min: 0 },
|
{ id: "radius", name: "반지름", value: "4.0526", type: "number", groupId: "shape", min: 0 },
|
||||||
{ id: "diameter", name: "지름", value: "8.1052", type: "number", groupId: "shape", min: 0 },
|
{ id: "diameter", name: "지름", value: "8.1052", type: "number", groupId: "shape", min: 0 },
|
||||||
{ id: "circumference", name: "원주", value: "8.1052", type: "number", groupId: "shape", min: 0 },
|
{ id: "circumference", name: "원주", value: "8.1052", type: "number", groupId: "shape", min: 0 },
|
||||||
{ id: "area", name: "면적", value: "51.5962", type: "number", groupId: "shape", min: 0 },
|
{ id: "area", name: "면적", value: "51.5962", type: "number", groupId: "shape", min: 0 },
|
||||||
]
|
]
|
||||||
function PropertyBarDemo() {
|
|
||||||
const [selection, setSelection] = useState<SWHeaderPropertySelection>({ id: "circle", title: "원", kind: "single" })
|
|
||||||
const [properties, setProperties] = useState(BAR_FIXTURE)
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const [controlled, setControlled] = useState(false)
|
|
||||||
const [readOnly, setReadOnly] = useState(false)
|
|
||||||
const [disabled, setDisabled] = useState(false)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const change = (property: SWHeaderProperty, value: string) => {
|
|
||||||
setProperties(items => items.map(item => item.id === property.id ? { ...item, value } : item))
|
|
||||||
setLog(items => [...items, `${property.id}=${value}`])
|
|
||||||
}
|
|
||||||
const data = { selection, properties, onPropertyChange: change }
|
|
||||||
return <div data-bar-demo="">
|
|
||||||
<div className="sw-story-controls">
|
|
||||||
<button onClick={() => setControlled(value => !value)}>Controlled 전환</button>
|
|
||||||
<button onClick={() => setOpen(value => !value)}>외부 열림 변경</button>
|
|
||||||
<button onClick={() => setProperties(items => [...items, { id: `extra-${items.length}`, name: "추가 속성", value: "10", type: "number", groupId: "shape", min: 0 }])}>Bar 필드 추가</button>
|
|
||||||
<button onClick={() => setProperties(items => items.filter(item => item.id !== "radius"))}>Bar 반지름 삭제</button>
|
|
||||||
<button onClick={() => setProperties(items => [...items].reverse().map(item => ({ ...item, order: undefined })))}>Bar 순서 반전</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => ({ ...item, name: `${item.name} 변경`, value: "12.5" })))}>Bar 외부 값 변경</button>
|
|
||||||
<button onClick={() => { setSelection({ id: "line", title: "직선", kind: "single" }); setProperties([{ id: "length", name: "길이", value: "25", type: "number", groupId: "shape", min: 0 }]) }}>Bar 객체 교체</button>
|
|
||||||
<button onClick={() => setReadOnly(value => !value)}>Bar ReadOnly</button>
|
|
||||||
<button onClick={() => setDisabled(value => !value)}>Bar Disabled</button>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => ({ ...item, readOnly: item.id === "diameter", disabled: item.id === "area" })))}>Bar 개별 제한</button>
|
|
||||||
</div>
|
|
||||||
<p data-bar-log="">{log.join(" → ")}</p>
|
|
||||||
<div className="sw-story-property-room">
|
|
||||||
<StyleHeader propertyBar={{ ...data, readOnly, disabled }} propertyPanel={{ ...data, groups: [{ id: "shape", name: "형상" }] }}
|
|
||||||
propertyPanelOpen={undefined} propertyBarOpen={controlled ? open : undefined} onPropertyBarOpenChange={next => { setOpen(next); setLog(items => [...items, `open=${next}`]) }} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
export const PropertyBar: Story = {
|
|
||||||
name: "Property Bar",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage className="sw-story-followup"><StoryPageHeader kicker={KICKER} title="속성Bar · 동적 편집" description="Figma 26:10492. 속성창과 독립 열림, 동일 호스트 데이터. 수치는 Figma 예시이며 CAD 계산은 수행하지 않습니다." /><PropertyBarDemo />
|
|
||||||
<StorySection title="콘텐츠 폭 기준 (부모 1222px, 높이 28)"><div className="sw-story-scroller"><SWTheme><div style={{ width: 1222 }}><SWHeaderPropertyBar selection={{ id: "reference", title: "원", kind: "single" }} properties={BAR_FIXTURE} /></div></SWTheme></div></StorySection>
|
|
||||||
</StoryPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTROL_FIXTURE: SWHeaderControlConfig[] = ["document", "drawingMode", "layer", "color", "lineStyle", "textStyle", "dimensionStyle", "details", "propertyBar", "propertyPanel", "print", "popupGuide"].map(id => ({ id: id as SWHeaderControlConfig["id"] }))
|
|
||||||
function DynamicControlsDemo() {
|
|
||||||
const [controls, setControls] = useState(CONTROL_FIXTURE)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const [title, setTitle] = useState("현재 문서.dwg")
|
|
||||||
const [documents, setDocuments] = useState<SWHeaderDocument[]>([{ id: "current", name: "현재 문서.dwg" }])
|
|
||||||
return <div data-controls-demo="">
|
|
||||||
<div className="sw-story-controls">
|
|
||||||
{CONTROL_FIXTURE.map(item => <label key={item.id}><input type="checkbox" aria-label={`구성 ${item.id}`} checked={controls.some(control => control.id === item.id && control.visible !== false)} onChange={event => setControls(items => event.target.checked ? [...items.filter(control => control.id !== item.id), item] : items.filter(control => control.id !== item.id))} />{item.id}</label>)}
|
|
||||||
<button onClick={() => setControls(CONTROL_FIXTURE)}>구성 초기화</button>
|
|
||||||
<button onClick={() => setControls([])}>모두 제거</button>
|
|
||||||
<button onClick={() => setControls([{ id: "layer" }, { id: "lineStyle" }])}>레이어·선스타일만</button>
|
|
||||||
<button onClick={() => setControls(items => items.filter(item => item.id !== "document" && item.id !== "color"))}>파일·색상 제거</button>
|
|
||||||
<button onClick={() => setControls(items => [...items].reverse())}>구성 순서 반전</button>
|
|
||||||
<button onClick={() => setControls(items => items.map(item => ({ ...item, label: `${item.id} 변경` })))}>구성 라벨 변경</button>
|
|
||||||
<button onClick={() => setControls(items => items.map(item => item.id === "color" ? { ...item, visible: false } : item))}>색상 숨김</button>
|
|
||||||
<button onClick={() => setControls(items => items.map(item => ({ ...item, visible: true })))}>숨김 복원</button>
|
|
||||||
</div>
|
|
||||||
<p data-config-log="">{log.join(" → ")}</p>
|
|
||||||
<div className="sw-story-property-room"><StyleHeader controls={controls} onLog={line => setLog(items => [...items, line])}
|
|
||||||
propertyBarOpen={undefined} propertyPanelOpen={undefined}
|
|
||||||
documentTitle={title} documents={documents} activeDocumentId="current" onDocumentSelect={document => { setTitle(document.name); setLog(items => [...items, `document=${document.id}`]) }}
|
|
||||||
onDocumentNew={() => setDocuments(items => [...items, { id: `new-${items.length}`, name: `새 문서 ${items.length}` }])}
|
|
||||||
propertyBar={{ selection: { id: "circle", title: "원", kind: "single" }, properties: BAR_FIXTURE }}
|
|
||||||
propertyPanel={{ selection: { id: "circle", title: "원", kind: "single" }, properties: BAR_FIXTURE, groups: [{ id: "shape" }] }} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
export const DynamicControls: Story = {
|
|
||||||
name: "Dynamic Controls",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage className="sw-story-followup"><StoryPageHeader kicker={KICKER} title="메뉴 컴포넌트 구성" description="Dropdown 내부 옵션과 별개인 controls allowlist. 항목 제거·재추가·숨김·순서·라벨, More 대표 슬롯과 값/Callback 보존." /><DynamicControlsDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
export const Colorize: Story = {
|
|
||||||
name: "Colorize",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="btn_colorize" description="Figma 26:11302 / 26:11305의 원본 SVG, 24px 버튼과 18px 아이콘. 클릭은 기존 onColorPick 계약입니다. 색상 돋보기 Overlay 26:11338은 호스트 연결 대상입니다." /><StyleDemo width={1920} /></StoryPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2026-10-16 composition review: every part except the logo can be removed (hiddenControls), the five
|
|
||||||
similar menus can be reordered (menuOrder). Untouched props keep the approved default header. */
|
|
||||||
const HIDEABLE_PARTS: { id: SWHeaderHideableControlId; name: string }[] = [
|
|
||||||
{ id: "home", name: "홈" }, { id: "document", name: "파일 목록" }, { id: "drawingMode", name: "그리기모드" },
|
|
||||||
{ id: "layer", name: "레이어" }, { id: "color", name: "색상" }, { id: "lineStyle", name: "선스타일" },
|
|
||||||
{ id: "textStyle", name: "문자스타일" }, { id: "dimensionStyle", name: "치수스타일" }, { id: "details", name: "상세보기" },
|
|
||||||
{ id: "propertyBar", name: "속성Bar" }, { id: "propertyPanel", name: "속성창" }, { id: "print", name: "인쇄" },
|
|
||||||
{ id: "popupGuide", name: "K 가이드" }, { id: "windowMove", name: "창 이동" }, { id: "minimize", name: "최소화" }, { id: "close", name: "종료" },
|
|
||||||
]
|
|
||||||
const MENU_IDS: SWHeaderMenuId[] = ["layer", "color", "lineStyle", "textStyle", "dimensionStyle"]
|
|
||||||
const COMPOSITION_PRESETS: { name: string; hidden: SWHeaderHideableControlId[]; order?: SWHeaderMenuId[] }[] = [
|
|
||||||
{ name: "기본 전체", hidden: [] },
|
|
||||||
{ name: "구성 A", hidden: ["drawingMode", "textStyle", "dimensionStyle", "details", "propertyBar", "propertyPanel", "print", "popupGuide", "windowMove", "minimize", "close"] },
|
|
||||||
{ name: "구성 B (레이어·선스타일)", hidden: HIDEABLE_PARTS.map(part => part.id).filter(id => id !== "layer" && id !== "lineStyle") },
|
|
||||||
{ name: "구성 C (로고만)", hidden: HIDEABLE_PARTS.map(part => part.id) },
|
|
||||||
{ name: "파일 목록 제거", hidden: ["document"] },
|
|
||||||
{ name: "레이어 제거", hidden: ["layer"] },
|
|
||||||
{ name: "색상 제거", hidden: ["color"] },
|
|
||||||
{ name: "스타일 그룹 제거", hidden: ["lineStyle", "textStyle", "dimensionStyle"] },
|
|
||||||
{ name: "여러 메뉴 제거", hidden: ["document", "color", "textStyle", "print", "minimize"] },
|
|
||||||
{ name: "순서 변경", hidden: [], order: ["color", "layer", "dimensionStyle", "lineStyle", "textStyle"] },
|
|
||||||
{ name: "숨김 + 순서 변경", hidden: ["layer", "textStyle"], order: ["dimensionStyle", "textStyle", "color", "layer"] },
|
|
||||||
]
|
|
||||||
|
|
||||||
function CompositionDemo() {
|
|
||||||
const [hidden, setHidden] = useState<SWHeaderHideableControlId[]>([])
|
|
||||||
const [order, setOrder] = useState<SWHeaderMenuId[] | undefined>(undefined)
|
|
||||||
const [width, setWidth] = useState<number | null>(null)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const record = (line: string) => setLog(lines => [...lines.slice(-11), line])
|
|
||||||
const toggle = (id: SWHeaderHideableControlId) => setHidden(list => list.includes(id) ? list.filter(item => item !== id) : [...list, id])
|
|
||||||
const header = <StyleHeader showState onLog={record} hiddenControls={hidden} menuOrder={order}
|
|
||||||
onHomeClick={() => record("onHomeClick")} onMinimize={() => record("onMinimize")} onClose={() => record("onClose")}
|
|
||||||
onPrint={() => record("onPrint")} onPopupGuide={() => record("onPopupGuide")}
|
|
||||||
onPropertyBarOpenChange={next => record(`onPropertyBarOpenChange(${next})`)} onPropertyPanelOpenChange={next => record(`onPropertyPanelOpenChange(${next})`)}
|
|
||||||
propertyBarOpen={undefined} propertyPanelOpen={undefined}
|
|
||||||
propertyBar={{ selection: { id: "circle", title: "원", kind: "single" }, properties: BAR_FIXTURE }}
|
|
||||||
propertyPanel={{ selection: { id: "circle", title: "원", kind: "single" }, properties: BAR_FIXTURE, groups: [{ id: "shape" }] }} />
|
|
||||||
return <div data-composition-demo="" data-hidden={hidden.join(",")} data-order={order?.join(",") ?? "default"}>
|
|
||||||
<div className="sw-story-controls"><strong>프리셋</strong>
|
|
||||||
{COMPOSITION_PRESETS.map(preset => <button key={preset.name} onClick={() => { setHidden(preset.hidden); setOrder(preset.order) }}>{preset.name}</button>)}
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-controls"><strong>표시 (로고 고정)</strong>
|
|
||||||
{HIDEABLE_PARTS.map(part => <label key={part.id}><input type="checkbox" aria-label={`표시 ${part.id}`} checked={!hidden.includes(part.id)} onChange={() => toggle(part.id)} />{part.name}</label>)}
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-controls"><strong>메뉴 순서</strong>
|
|
||||||
<button onClick={() => setOrder(undefined)}>기본 순서</button>
|
|
||||||
<button onClick={() => setOrder([...(order ?? MENU_IDS)].reverse())}>순서 반전</button>
|
|
||||||
<button onClick={() => setOrder(list => { const items = [...(list ?? MENU_IDS)]; items.push(items.shift()!); return items })}>첫 메뉴를 끝으로</button>
|
|
||||||
<button onClick={() => setOrder(["color", "color", "unknown" as SWHeaderMenuId, "layer"])}>중복·알 수 없는 ID</button>
|
|
||||||
<code data-order-state="">{(order ?? ["(default)"]).join(" → ")}</code>
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-controls"><strong>너비</strong>
|
|
||||||
<button onClick={() => setWidth(null)}>실제 화면</button>
|
|
||||||
{[1920, 1440, 1080, 900, 768, 640, 390].map(value => <button key={value} onClick={() => setWidth(value)}>{value}px</button>)}
|
|
||||||
</div>
|
|
||||||
<p className="sw-story-log" aria-live="polite" data-callback-log="">Callback: {log.length ? log.join(" → ") : "없음"}</p>
|
|
||||||
<div className="sw-story-property-room">{width ? <Frame width={width}>{header}</Frame> : header}</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
export const Composition: Story = {
|
|
||||||
name: "Composition (Visibility · Order)",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage className="sw-story-followup"><StoryPageHeader kicker={KICKER} title="Header 구성 · 메뉴 순서" description="hiddenControls로 로고 외 모든 구성요소를 개별 제거·재표시하고, menuOrder로 레이어·색상·선/문자/치수스타일 순서를 바꿉니다. 아무것도 지정하지 않으면 승인된 기본 디자인입니다. 값은 호스트 상태라 재표시·순서 변경 후에도 유지되며, 순서 변경은 Callback을 호출하지 않습니다." /><CompositionDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Property panel: the four Figma states with the same component and host data. */
|
/* Property panel: the four Figma states with the same component and host data. */
|
||||||
const PROPERTY_STATES: { example: PropertyExample; title: string; node: string; size: string }[] = [
|
const PROPERTY_STATES: { example: PropertyExample; title: string; node: string; size: string }[] = [
|
||||||
{ example: "none", title: "선택된 객체 없음", node: "26:11886", size: "314 × 256" },
|
{ example: "none", title: "선택된 객체 없음", node: "26:11886", size: "314 × 256" },
|
||||||
@@ -1320,109 +950,3 @@ export const PropertyPanelCompare: Story = {
|
|||||||
parameters: { controls: { disable: true } },
|
parameters: { controls: { disable: true } },
|
||||||
render: () => <StoryPage className="sw-story-compare-page"><StoryPageHeader kicker={KICKER} title="속성창 4개 상태 비교" description="네 패널은 같은 SWHeaderPropertyPanel입니다. 실제 Header에서는 현재 선택 객체의 패널 하나만 표시합니다." /><PropertyCompare /></StoryPage>,
|
render: () => <StoryPage className="sw-story-compare-page"><StoryPageHeader kicker={KICKER} title="속성창 4개 상태 비교" description="네 패널은 같은 SWHeaderPropertyPanel입니다. 실제 Header에서는 현재 선택 객체의 패널 하나만 표시합니다." /><PropertyCompare /></StoryPage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Shape slot: replaceable area; the values stay in the property rows. */
|
|
||||||
function SampleShapeViewer({ label, kind }: { label: string; kind: "circle" | "rect" }) {
|
|
||||||
return <svg width="208" height="208" viewBox="0 0 208 208" role="img" aria-label={label} data-sample-shape={kind}>
|
|
||||||
{kind === "circle" ? <circle cx="104" cy="104" r="84" fill="none" stroke="#1d5a8c" strokeWidth="2" strokeDasharray="6 4" />
|
|
||||||
: <rect x="34" y="54" width="140" height="100" fill="none" stroke="#1d5a8c" strokeWidth="2" />}
|
|
||||||
<text x="104" y="200" textAnchor="middle" fontSize="11" fill="#1d5a8c">{label}</text>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
const SHAPE_MODES = ["default", "empty", "custom", "render"] as const
|
|
||||||
const SHAPE_MODE_LABELS = { default: "기본 Figma 형상", empty: "비움 (null)", custom: "외부 컴포넌트", render: "Render Prop" }
|
|
||||||
function ShapeSlotDemo() {
|
|
||||||
const [mode, setMode] = useState<(typeof SHAPE_MODES)[number]>("default")
|
|
||||||
const [kind, setKind] = useState<"circle" | "rect">("circle")
|
|
||||||
const [properties, setProperties] = useState(() => propertyFixture("circle"))
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const shapeContent: SWHeaderPropertyShapeContent = mode === "default" ? undefined : mode === "empty" ? null
|
|
||||||
: mode === "custom" ? <SampleShapeViewer label="외부 형상 컴포넌트" kind={kind} />
|
|
||||||
: context => <SampleShapeViewer label={`${context.selection.title} · ${context.properties.length}개 값`} kind={kind} />
|
|
||||||
return <div data-shape-demo="" data-mode={mode} data-kind={kind}>
|
|
||||||
<div className="sw-story-controls"><strong>형상 Slot</strong>
|
|
||||||
{SHAPE_MODES.map(name => <button key={name} data-shape-mode={name} onClick={() => setMode(name)}>{SHAPE_MODE_LABELS[name]}</button>)}
|
|
||||||
<button onClick={() => setKind(value => value === "circle" ? "rect" : "circle")}>형상 교체 (원 ↔ 사각형)</button>
|
|
||||||
</div>
|
|
||||||
<div className="sw-story-controls"><strong>속성값 (형상과 독립)</strong>
|
|
||||||
<button onClick={() => setProperties(items => items.map(item => item.id === "diameter" ? { ...item, value: "10.0000" } : item.id === "radius" ? { ...item, value: "5.0000" } : item))}>지름·반지름 외부 변경</button>
|
|
||||||
<button onClick={() => setProperties(items => [...items, { id: `shape-extra-${items.length}`, groupId: "shape", name: "추가 형상값", type: "text", value: "호스트 값" }])}>형상 그룹 항목 추가</button>
|
|
||||||
<button onClick={() => setProperties(items => items.filter(item => item.id !== "area"))}>면적 삭제</button>
|
|
||||||
</div>
|
|
||||||
<p className="sw-story-log" data-property-log="">{log.join(" → ") || "Callback 없음"}</p>
|
|
||||||
<div className="sw-story-property-state"><SWTheme><SWHeaderPropertyPanel selection={propertySelection("circle")} properties={properties} groups={PROPERTY_GROUPS} shapeContent={shapeContent}
|
|
||||||
onPropertyChange={(property, value) => { setLog(lines => [...lines.slice(-9), `property(${property.id}, ${value})`]); setProperties(items => items.map(item => item.id === property.id ? { ...item, value } : item)) }} /></SWTheme></div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
export const PropertyShapeSlot: Story = {
|
|
||||||
name: "Property Panel · 형상 Slot",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="형상 영역 Slot" description="shapeContent: 미지정은 Figma 원형 그림, null은 208 × 208 빈 영역(치수 라벨·패널 높이 유지), ReactNode·Render Prop은 외부 형상 컴포넌트입니다. 예시 SVG는 Storybook 전용 샘플이며 패키지에 포함되지 않습니다. 원주·지름·반지름·면적·위치 값은 properties로만 표시됩니다." /><ShapeSlotDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Property fields (2026-10-19): the 13 Figma field nodes in the real SWHeaderPropertyPanel rows.
|
|
||||||
Default / pinned Hover columns are specimens; the live panel is the one to click (On / dropdown). */
|
|
||||||
const FIELD_NODES = [
|
|
||||||
{ node: "26:12388 / 26:12404", name: "선스타일", states: "Default · Hover / On(Dropdown)" },
|
|
||||||
{ node: "26:12250 / 26:12265", name: "레이어 — 다양함", states: "Default · Hover / On(Dropdown)" },
|
|
||||||
{ node: "26:12260 / 26:12291", name: "레이어 — 단일 값", states: "Default · Hover / On(Dropdown)" },
|
|
||||||
{ node: "26:12314", name: "선축적", states: "Default · Hover · On(편집)" },
|
|
||||||
{ node: "26:12649", name: "선가중치 · 투명도", states: "Default · Hover · On(편집)" },
|
|
||||||
{ node: "26:12170 / 26:12184", name: "색상", states: "Default · Hover / On(Dropdown)" },
|
|
||||||
{ node: "26:12583", name: "하이퍼링크", states: "Default · Hover (Figma On 없음)" },
|
|
||||||
{ node: "26:12590", name: "두께", states: "Default · Hover · On(편집)" },
|
|
||||||
{ node: "26:12574", name: "재료", states: "Default · Hover (Figma On 없음)" },
|
|
||||||
]
|
|
||||||
// The common rows of PROPERTY_FIXTURE plus a mixed layer row. Figma sample text 255,259,0 is out of range:
|
|
||||||
// shown as host text only, never accepted as input.
|
|
||||||
const FIELD_FIXTURE: SWHeaderProperty[] = PROPERTY_FIXTURE.filter(item => item.groupId === "common").flatMap(item =>
|
|
||||||
item.id === "layer" ? [{ ...item, id: "layer-mixed", mixed: true }, item]
|
|
||||||
: item.id === "color" ? [{ ...item, value: "255,259,0", swatch: "#ffe500" }]
|
|
||||||
: [item])
|
|
||||||
const FIELD_HOVER = [".hm-sw-property__value[data-field]", ".hm-sw-property__compact"]
|
|
||||||
function FieldPanel({ title, properties, onChange, menus }: { title: string; properties: SWHeaderProperty[]; onChange?: (property: SWHeaderProperty, value: string) => void; menus?: SWHeaderPropertyMenus }) {
|
|
||||||
return <SWTheme><SWHeaderPropertyPanel selection={{ id: title, title, kind: "none" }} properties={properties} groups={[{ id: "common" }]} onPropertyChange={onChange} menus={menus} /></SWTheme>
|
|
||||||
}
|
|
||||||
function PropertyFieldsDemo() {
|
|
||||||
const [properties, setProperties] = useState(FIELD_FIXTURE)
|
|
||||||
const [log, setLog] = useState<string[]>([])
|
|
||||||
const record = (line: string) => setLog(lines => [...lines.slice(-11), line])
|
|
||||||
const menus = usePropertyMenus(record, setProperties as (update: (items: SWHeaderProperty[]) => SWHeaderProperty[]) => void)
|
|
||||||
const [noMenus, setNoMenus] = useState(false)
|
|
||||||
const patch = (id: string, change: Partial<SWHeaderProperty>) => setProperties(items => items.map(item => item.id === id ? { ...item, ...change } : item))
|
|
||||||
const change = (property: SWHeaderProperty, value: string) => {
|
|
||||||
record(`property(${property.id}, ${value})`)
|
|
||||||
patch(property.id, { value, mixed: false, ...(property.id === "line" ? { linePattern: linePatternOf(value) } : {}), ...(property.id === "color" ? { swatch: undefined } : {}) })
|
|
||||||
}
|
|
||||||
return <div data-fields-demo="">
|
|
||||||
<div className="sw-story-controls"><strong>외부 데이터</strong>
|
|
||||||
<button data-action="layer-mixed" onClick={() => patch("layer-mixed", { mixed: !properties.find(item => item.id === "layer-mixed")?.mixed })}>레이어 다양함 ↔ 단일</button>
|
|
||||||
<button data-action="layer-external" onClick={() => patch("layer", { value: "CC-D GNL" })}>레이어 외부 값 변경</button>
|
|
||||||
<button data-action="scale-external" onClick={() => patch("scale", { value: "2.5000" })}>선축적 외부 값 변경</button>
|
|
||||||
<button data-action="thickness-unit" onClick={() => patch("thickness", { unit: properties.find(item => item.id === "thickness")?.unit === "mm" ? "cm" : "mm" })}>두께 단위 변경</button>
|
|
||||||
<button data-action="line-center" onClick={() => patch("line", { value: "CENTER", linePattern: linePatternOf("CENTER") })}>선스타일 외부 CENTER</button>
|
|
||||||
<button data-action="readonly" onClick={() => setProperties(items => items.map(item => item.id === "thickness" || item.id === "layer" ? { ...item, readOnly: !item.readOnly } : item))}>두께·레이어 ReadOnly</button>
|
|
||||||
<button data-action="disabled" onClick={() => setProperties(items => items.map(item => item.id === "scale" || item.id === "color" ? { ...item, disabled: !item.disabled } : item))}>선축적·색상 Disabled</button>
|
|
||||||
<button data-action="add-row" onClick={() => setProperties(items => [...items, { id: `extra-${items.length}`, name: "추가 속성", value: "호스트 값", type: "text", groupId: "common" }])}>속성 추가</button>
|
|
||||||
<button data-action="remove-row" onClick={() => setProperties(items => items.filter(item => item.id !== "link"))}>하이퍼링크 삭제</button>
|
|
||||||
<button data-action="no-menus" onClick={() => setNoMenus(value => !value)}>menus 없음(options 사용): {String(noMenus)}</button>
|
|
||||||
</div>
|
|
||||||
<p className="sw-story-log" data-property-log="" aria-live="polite">{log.join(" → ") || "Callback 없음"}</p>
|
|
||||||
<pre className="sw-story-layer-state" data-property-state="">{JSON.stringify(properties.map(item => [item.id, item.mixed ? "다양함" : item.value]))}</pre>
|
|
||||||
<div className="sw-story-compare">
|
|
||||||
<figure className="sw-story-compare__cell" data-fields="live"><figcaption><strong>실제 조작</strong><span>클릭 → On · Enter 확정 · Escape 취소/닫기</span></figcaption>
|
|
||||||
<FieldPanel title="실제 조작" properties={properties} onChange={change} menus={noMenus ? undefined : menus} /></figure>
|
|
||||||
<figure className="sw-story-compare__cell" data-fields="default"><figcaption><strong>Default</strong><span>Figma Default</span></figcaption>
|
|
||||||
<FieldPanel title="Default" properties={FIELD_FIXTURE} /></figure>
|
|
||||||
<figure className="sw-story-compare__cell" data-fields="hover"><figcaption><strong>Hover (고정)</strong><span>Figma Hover · 값 변경 없음</span></figcaption>
|
|
||||||
<PinHover selectors={FIELD_HOVER}><FieldPanel title="Hover" properties={FIELD_FIXTURE} /></PinHover></figure>
|
|
||||||
</div>
|
|
||||||
<table className="sw-story-node-table"><thead><tr><th>Figma</th><th>항목</th><th>상태</th></tr></thead>
|
|
||||||
<tbody>{FIELD_NODES.map(item => <tr key={item.node}><td>{item.node}</td><td>{item.name}</td><td>{item.states}</td></tr>)}</tbody></table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
export const PropertyFields: Story = {
|
|
||||||
name: "Property Panel · 필드 13개",
|
|
||||||
parameters: { controls: { disable: true } },
|
|
||||||
render: () => <StoryPage className="sw-story-followup"><StoryPageHeader kicker={KICKER} title="속성창 입력·선택 필드" description="Figma 13개 필드(26:12388 ~ 26:12649)를 기존 SWHeaderPropertyPanel 행으로 표시합니다. Default · Hover 고정 표본과 실제 조작 패널입니다. 숫자는 편집 On(#0F0D0A), 레이어·선스타일·색상은 Dropdown On(#211D13)이며 하이퍼링크·재료는 Figma에 On이 없어 Default · Hover만 있습니다. 색상 255,259,0은 Figma 표기 예시로, 입력 검증은 0~255를 유지합니다." /><PropertyFieldsDemo /></StoryPage>,
|
|
||||||
}
|
|
||||||
@@ -15,33 +15,18 @@
|
|||||||
runtime dropdown still clamps to the real viewport. Scroll the specimen to see its right edge. */
|
runtime dropdown still clamps to the real viewport. Scroll the specimen to see its right edge. */
|
||||||
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style { left: auto !important; right: 0; }
|
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style { left: auto !important; right: 0; }
|
||||||
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style[data-shifted="true"]::before { top: calc(-1 * var(--hm-sw-header-field-height) - 1px); }
|
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style[data-shifted="true"]::before { top: calc(-1 * var(--hm-sw-header-field-height) - 1px); }
|
||||||
.sw-story-explicit-disabled { min-width: 0; margin: 0; padding: 0; border: 0; }
|
|
||||||
.sw-story-log { margin: 0 0 8px; font-size: 13px; color: #3d4a45; }
|
.sw-story-log { margin: 0 0 8px; font-size: 13px; color: #3d4a45; }
|
||||||
.sw-story-stack { display: flex; flex-direction: column; gap: 8px; }
|
.sw-story-stack { display: flex; flex-direction: column; gap: 8px; }
|
||||||
/* Window Move Motion: window controls alone on the window-area surface, magnified for comparison. */
|
/* Window Move Motion: window controls alone on the window-area surface, magnified for comparison. */
|
||||||
.sw-story-window { display: flex; width: 152px; height: 48px; background: var(--hm-sw-header-document-background); }
|
.sw-story-window { display: flex; width: 152px; height: 48px; background: var(--hm-sw-header-document-background); }
|
||||||
.sw-story-zoom { zoom: 3; width: max-content; }
|
.sw-story-zoom { zoom: 3; width: max-content; }
|
||||||
.sw-story-row { display: flex; flex-wrap: wrap; gap: 24px; }
|
|
||||||
.sw-story-stages { display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 16px; }
|
.sw-story-stages { display: grid; grid-template-columns: repeat(auto-fill, minmax(480px, 1fr)); gap: 16px; }
|
||||||
.sw-story-stage { margin: 0; }
|
.sw-story-stage { margin: 0; }
|
||||||
.sw-story-stage figcaption { margin-top: 6px; font-size: 13px; color: #3d4a45; }
|
.sw-story-stage figcaption { margin-top: 6px; font-size: 13px; color: #3d4a45; }
|
||||||
/* Layer Dropdown: room below a standalone layer field for its overlay list. */
|
|
||||||
.sw-story-layer-room { padding-bottom: 300px; }
|
|
||||||
.sw-story-layer-state { margin: 0 0 8px; font-size: 12px; color: #3d4a45; font-family: ui-monospace, monospace; white-space: pre-wrap; }
|
.sw-story-layer-state { margin: 0 0 8px; font-size: 12px; color: #3d4a45; font-family: ui-monospace, monospace; white-space: pre-wrap; }
|
||||||
/* Field dropdowns / Dynamic Data: story-only controls and Figma reference images. */
|
|
||||||
.sw-story-reference { display: block; max-width: none; image-rendering: pixelated; }
|
|
||||||
.sw-story-figma { padding: 12px; background: #27241d; }
|
|
||||||
.sw-story-figma .sw-story-reference { zoom: 2; }
|
|
||||||
.sw-story-detail { background: var(--hm-sw-header-layer-menu-background); padding: 8px 12px; gap: 12px; }
|
|
||||||
.sw-story-dynamic { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
|
.sw-story-dynamic { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
|
||||||
.sw-story-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 8px; font-size: 12px; color: #3d4a45; }
|
|
||||||
.sw-story-controls > strong { min-width: 110px; }
|
|
||||||
.sw-story-controls > button { padding: 2px 8px; border: 1px solid #b6c4bf; border-radius: 4px; background: #fff; color: #1f2a26; font-size: 12px; cursor: pointer; }
|
|
||||||
.sw-story-property-room { min-height: 820px; }
|
.sw-story-property-room { min-height: 820px; }
|
||||||
/* New responsive scenarios measure the Header at the actual viewport width. */
|
|
||||||
.sw-story-followup.sb-page > .sb-page-inner { width: 100%; }
|
|
||||||
/* Property panel states (2026-10-16): standalone panels at their natural size; the comparison wraps. */
|
/* Property panel states (2026-10-16): standalone panels at their natural size; the comparison wraps. */
|
||||||
.sw-story-property-state { display: inline-block; }
|
|
||||||
.sw-story-compare { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 24px; }
|
.sw-story-compare { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 24px; }
|
||||||
.sw-story-compare__cell { margin: 0; }
|
.sw-story-compare__cell { margin: 0; }
|
||||||
.sw-story-compare__cell figcaption { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; font-size: 12px; color: #3d4a45; }
|
.sw-story-compare__cell figcaption { display: flex; flex-direction: column; gap: 2px; margin-bottom: 8px; font-size: 12px; color: #3d4a45; }
|
||||||
@@ -59,9 +44,6 @@
|
|||||||
.sw-story-detail-comparison { overflow-x: auto; padding: 16px; background: var(--hm-sw-header-layer-menu-background); }
|
.sw-story-detail-comparison { overflow-x: auto; padding: 16px; background: var(--hm-sw-header-layer-menu-background); }
|
||||||
.sw-story-detail-row { display: grid; grid-template-columns: 140px repeat(4, 180px); align-items: center; gap: 16px; min-width: 924px; padding: 12px 0; }
|
.sw-story-detail-row { display: grid; grid-template-columns: 140px repeat(4, 180px); align-items: center; gap: 16px; min-width: 924px; padding: 12px 0; }
|
||||||
.sw-story-detail-row > div { display: flex; align-items: center; gap: 8px; }
|
.sw-story-detail-row > div { display: flex; align-items: center; gap: 8px; }
|
||||||
/* 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. */
|
/* 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 { height: 100dvh; min-height: 0; padding-block: 16px; }
|
||||||
|
|||||||