refactor(storybook): consolidate property panel comparison layout
Keep sw-header--property-panel-compare as the single property panel state page: the four existing SWHeaderPropertyPanel states in 2 columns x 2 rows at their original size, inside a scrolling Inner. Remove the duplicate Property Panel Variants and the four single-state Stories (SW 24 -> 19). Storybook-only change; SW runtime and public API untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
|
After Width: | Height: | Size: 137 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,202 @@
|
||||
{
|
||||
"mode": "before",
|
||||
"base": "static",
|
||||
"browser": "155.0.8059.80",
|
||||
"checks": [],
|
||||
"errors": [],
|
||||
"data": {
|
||||
"inventory": {
|
||||
"total": 343,
|
||||
"sw": 24,
|
||||
"ids": [
|
||||
[
|
||||
"sw-header--composed-controls",
|
||||
"Dropdown"
|
||||
],
|
||||
[
|
||||
"sw-header--detail-buttons",
|
||||
"Detail Buttons (Shared States)"
|
||||
],
|
||||
[
|
||||
"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--layer-detail-input",
|
||||
"Layer Detail · Input"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel",
|
||||
"Property Panel"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-variants",
|
||||
"Property Panel Variants"
|
||||
],
|
||||
[
|
||||
"sw-header--k-guide",
|
||||
"K Guide"
|
||||
],
|
||||
[
|
||||
"sw-header--additional-hover",
|
||||
"Additional Hover"
|
||||
],
|
||||
[
|
||||
"sw-header--property-bar",
|
||||
"Property Bar"
|
||||
],
|
||||
[
|
||||
"sw-header--dynamic-controls",
|
||||
"Dynamic Controls"
|
||||
],
|
||||
[
|
||||
"sw-header--colorize",
|
||||
"Colorize"
|
||||
],
|
||||
[
|
||||
"sw-header--composition",
|
||||
"Composition (Visibility · Order)"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-none",
|
||||
"Property Panel · 선택 없음"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-all",
|
||||
"Property Panel · 전체 선택"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-circle",
|
||||
"Property Panel · 원 1개"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-expanded",
|
||||
"Property Panel · 모두 펼침"
|
||||
],
|
||||
[
|
||||
"sw-header--property-panel-compare",
|
||||
"Property Panel · 4개 비교"
|
||||
],
|
||||
[
|
||||
"sw-header--property-shape-slot",
|
||||
"Property Panel · 형상 Slot"
|
||||
],
|
||||
[
|
||||
"sw-header--property-fields",
|
||||
"Property Panel · 필드 13개"
|
||||
]
|
||||
]
|
||||
},
|
||||
"compare": {
|
||||
"cells": [
|
||||
{
|
||||
"id": "none",
|
||||
"cell": {
|
||||
"x": 0,
|
||||
"y": 177,
|
||||
"w": 314,
|
||||
"h": 322,
|
||||
"right": 314,
|
||||
"bottom": 499
|
||||
},
|
||||
"panel": {
|
||||
"x": 0,
|
||||
"y": 243,
|
||||
"w": 314,
|
||||
"h": 256,
|
||||
"right": 314,
|
||||
"bottom": 499
|
||||
},
|
||||
"caption": "선택된 객체 없음"
|
||||
},
|
||||
{
|
||||
"id": "all",
|
||||
"cell": {
|
||||
"x": 338,
|
||||
"y": 177,
|
||||
"w": 314,
|
||||
"h": 427,
|
||||
"right": 652,
|
||||
"bottom": 604
|
||||
},
|
||||
"panel": {
|
||||
"x": 338,
|
||||
"y": 243,
|
||||
"w": 314,
|
||||
"h": 361,
|
||||
"right": 652,
|
||||
"bottom": 604
|
||||
},
|
||||
"caption": "전체 객체 선택"
|
||||
},
|
||||
{
|
||||
"id": "circle",
|
||||
"cell": {
|
||||
"x": 676,
|
||||
"y": 177,
|
||||
"w": 314,
|
||||
"h": 724,
|
||||
"right": 990,
|
||||
"bottom": 901
|
||||
},
|
||||
"panel": {
|
||||
"x": 676,
|
||||
"y": 243,
|
||||
"w": 314,
|
||||
"h": 658,
|
||||
"right": 990,
|
||||
"bottom": 901
|
||||
},
|
||||
"caption": "원 1개 선택"
|
||||
},
|
||||
{
|
||||
"id": "expanded",
|
||||
"cell": {
|
||||
"x": 1014,
|
||||
"y": 177,
|
||||
"w": 314,
|
||||
"h": 820,
|
||||
"right": 1328,
|
||||
"bottom": 997
|
||||
},
|
||||
"panel": {
|
||||
"x": 1014,
|
||||
"y": 243,
|
||||
"w": 314,
|
||||
"h": 754,
|
||||
"right": 1328,
|
||||
"bottom": 997
|
||||
},
|
||||
"caption": "모든 항목 펼침"
|
||||
}
|
||||
],
|
||||
"scroller": null,
|
||||
"doc": {
|
||||
"scrollW": 1440,
|
||||
"clientW": 1440,
|
||||
"scrollH": 1037,
|
||||
"clientH": 900
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"total": 343,
|
||||
"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--property-panel-variants",
|
||||
"name": "Property Panel Variants",
|
||||
"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-none",
|
||||
"name": "Property Panel · 선택 없음",
|
||||
"title": "SW/Header",
|
||||
"tags": [
|
||||
"dev",
|
||||
"test",
|
||||
"manifest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sw-header--property-panel-all",
|
||||
"name": "Property Panel · 전체 선택",
|
||||
"title": "SW/Header",
|
||||
"tags": [
|
||||
"dev",
|
||||
"test",
|
||||
"manifest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sw-header--property-panel-circle",
|
||||
"name": "Property Panel · 원 1개",
|
||||
"title": "SW/Header",
|
||||
"tags": [
|
||||
"dev",
|
||||
"test",
|
||||
"manifest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "sw-header--property-panel-expanded",
|
||||
"name": "Property Panel · 모두 펼침",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
{
|
||||
"mode": "preservation",
|
||||
"browser": "155.0.8059.80",
|
||||
"checks": [
|
||||
{
|
||||
"name": "required responsive width 390",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"height": 49,
|
||||
"overflow": 0,
|
||||
"documentOverflow": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "required responsive width 768",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"height": 49,
|
||||
"overflow": 0,
|
||||
"documentOverflow": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "required responsive width 1280",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"height": 49,
|
||||
"overflow": 0,
|
||||
"documentOverflow": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "required responsive width 1440",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"height": 49,
|
||||
"overflow": 0,
|
||||
"documentOverflow": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preserve property panel none",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 360,
|
||||
"y": 282.5,
|
||||
"width": 314,
|
||||
"height": 256
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preserve property panel all",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 698,
|
||||
"y": 282.5,
|
||||
"width": 314,
|
||||
"height": 361
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preserve property panel circle",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 360,
|
||||
"y": 733.5,
|
||||
"width": 314,
|
||||
"height": 658
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preserve property panel expanded",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 698,
|
||||
"y": 733.5,
|
||||
"width": 314,
|
||||
"height": 754
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "preserve window motion 1400ms",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "window motion leave clears animations",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "preserve K guide animation",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "K guide leave closes",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "preserve Colorize size",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "preserve editable Input",
|
||||
"status": "PASS"
|
||||
},
|
||||
{
|
||||
"name": "no browser errors",
|
||||
"status": "PASS",
|
||||
"data": []
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"styles": {},
|
||||
"status": "PASS"
|
||||
}
|
||||
|
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: 237 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: 72 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,774 @@
|
||||
{
|
||||
"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--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--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-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"
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 248 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 255 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 211 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 899 B |
|
After Width: | Height: | Size: 715 B |
|
After Width: | Height: | Size: 700 B |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,81 @@
|
||||
{
|
||||
"label": "run",
|
||||
"checks": [
|
||||
{
|
||||
"name": "size 44 x 30, top-right of the panel",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"w": 44,
|
||||
"h": 30,
|
||||
"right": 1,
|
||||
"top": 1,
|
||||
"imgs": [
|
||||
[
|
||||
"hm-sw-property__close-default",
|
||||
44,
|
||||
30
|
||||
],
|
||||
[
|
||||
"hm-sw-property__close-hover",
|
||||
44,
|
||||
30
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default: no canvas-light pixels on the button edge / corner",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"bright": 0,
|
||||
"where": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover: hover asset shown, no canvas-light pixels on the edge / corner",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"vis": [
|
||||
"hidden",
|
||||
"visible"
|
||||
],
|
||||
"bright": 0,
|
||||
"where": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "click closes the panel, focus back on 속성창 trigger",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"open": false,
|
||||
"focusTrigger": true,
|
||||
"log": "legacyToggle → open(true) → open(false)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Escape closes the panel, focus back on 속성창 trigger",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"open": false,
|
||||
"focusTrigger": true,
|
||||
"log": "rue) → open(false) → legacyToggle → open(true) → open(false)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "no console / page errors",
|
||||
"status": "PASS",
|
||||
"data": []
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"pixels": {
|
||||
"def": {
|
||||
"bright": 0,
|
||||
"where": []
|
||||
},
|
||||
"hov": {
|
||||
"bright": 0,
|
||||
"where": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 405 KiB |
|
After Width: | Height: | Size: 439 KiB |
|
After Width: | Height: | Size: 437 KiB |
|
After Width: | Height: | Size: 421 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,813 @@
|
||||
{
|
||||
"label": "run",
|
||||
"checks": [
|
||||
{
|
||||
"name": "Default layer-mixed: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 371.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default layer: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 403.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default color: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 435.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default line: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 467.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default scale: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 531.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default link: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 595.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default thickness: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 627.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default material: 200 x 28, radius 2, no border / surface",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 659.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopLeftRadius": "2px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default weight: compact 28px hug field (pl 6 / pr 4 incl. border), no border",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 499.5,
|
||||
"w": 39.23,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"paddingLeft": "5px",
|
||||
"paddingRight": "3px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default opacity: compact 28px hug field (pl 6 / pr 4 incl. border), no border",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 563.5,
|
||||
"w": 39.23,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"paddingLeft": "5px",
|
||||
"paddingRight": "3px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Typography: value #E60403 14px, unit #000 12px",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"value": [
|
||||
"rgb(230, 4, 3)",
|
||||
"14px"
|
||||
],
|
||||
"unit": [
|
||||
"rgb(0, 0, 0)",
|
||||
"12px"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover layer-mixed: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 371.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
377.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
377.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave layer-mixed: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 371.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover layer: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 403.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
409.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
409.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave layer: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 403.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover color: rgba(33,29,19,.4) border, 28px kept, value not moved, divider shown",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 98,
|
||||
"y": 436.5,
|
||||
"w": 114,
|
||||
"h": 26,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
436.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
436.5
|
||||
],
|
||||
"borderRightColor": "rgba(33, 29, 19, 0.4)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave color: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 435.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover line: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 467.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
473.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
473.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave line: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 467.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover scale: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 531.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
532.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
532.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave scale: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 531.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover link: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 595.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
596.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
596.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave link: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 595.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover thickness: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 627.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
628.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
628.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave thickness: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 627.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover material: rgba(33,29,19,.4) border, 28px kept, value not moved",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 659.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"before": [
|
||||
105,
|
||||
660.5
|
||||
],
|
||||
"after": [
|
||||
105,
|
||||
660.5
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Mouse Leave material: back to Default",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 659.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"borderTopColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover / Leave weight compact",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"hover": "rgba(33, 29, 19, 0.4)",
|
||||
"leave": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Hover / Leave opacity compact",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"hover": "rgba(33, 29, 19, 0.4)",
|
||||
"leave": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "On(편집) scale: #0F0D0A, border, unit #FFF, focused input with red caret + outline",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 531.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgb(15, 13, 10)",
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"unit": "rgb(255, 255, 255)",
|
||||
"focused": true,
|
||||
"caret": "rgb(230, 4, 3)",
|
||||
"shadow": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "On(편집) thickness: #0F0D0A, border, unit #FFF, focused input with red caret + outline",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 627.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgb(15, 13, 10)",
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)",
|
||||
"unit": "rgb(255, 255, 255)",
|
||||
"focused": true,
|
||||
"caret": "rgb(230, 4, 3)",
|
||||
"shadow": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Enter: 선축적 2 → validated, 4 decimals committed",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000)"
|
||||
},
|
||||
{
|
||||
"name": "Escape: draft cancelled (2.0000 restored, no callback)",
|
||||
"status": "PASS",
|
||||
"data": "2.0000"
|
||||
},
|
||||
{
|
||||
"name": "On(편집) 선가중치 compact: #0F0D0A",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 499.5,
|
||||
"w": 43.03,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgb(15, 13, 10)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Invalid 선가중치 -1 (min 0): restored, no callback",
|
||||
"status": "PASS",
|
||||
"data": "0.25"
|
||||
},
|
||||
{
|
||||
"name": "선가중치 0.5 committed",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5)"
|
||||
},
|
||||
{
|
||||
"name": "하이퍼링크 focus: no On surface (Figma has no On), editable",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 595.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)",
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "하이퍼링크 commit via host callback",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a)"
|
||||
},
|
||||
{
|
||||
"name": "재료: read-only, no On",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 659.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Layer On: #211D13 field, 234 wide list, right edges meet, starts at field bottom",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"f": {
|
||||
"x": 97,
|
||||
"y": 403.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgb(33, 29, 19)",
|
||||
"borderTopColor": "rgba(33, 29, 19, 0.4)"
|
||||
},
|
||||
"d": {
|
||||
"x": 63,
|
||||
"y": 431.5,
|
||||
"w": 234,
|
||||
"h": 250
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Layer list rows 30px, footer 40px, height = rows*30+40",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"rows": 7,
|
||||
"row": 30,
|
||||
"footer": 40,
|
||||
"h": 250
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Layer current row marked (0_Layer_Cludmark)",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Bulb toggle: callback only, list stays open, row not selected",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a) → layer.visible(Sample-Wall, true)"
|
||||
},
|
||||
{
|
||||
"name": "Row select: property(layer, CC-D GNL), closes, field shows value",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a) → layer.visible(Sample-Wall, true) → property(layer, CC-D GNL)"
|
||||
},
|
||||
{
|
||||
"name": "Close keeps the value and focus returns to the field",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "다양함: text 다양함, no current row in list",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Escape closes the layer list; panel stays",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Host data: 다양함 → 단일 값 (0_Layer_Cludmark)",
|
||||
"status": "PASS",
|
||||
"data": "0_Layer_Cludmark"
|
||||
},
|
||||
{
|
||||
"name": "External value change shown (CC-D GNL)",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Keyboard: ArrowDown opens + focuses list, Escape closes to trigger",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Outside click closes",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Line style On: #211D13, (ByL) #D4CBBD, 3 columns, right edges meet (or shifted to the 4px screen edge when no room)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"f": {
|
||||
"x": 97,
|
||||
"y": 485.5,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgb(33, 29, 19)"
|
||||
},
|
||||
"d": {
|
||||
"x": 4,
|
||||
"y": 513.5,
|
||||
"w": 471.19,
|
||||
"h": 190
|
||||
},
|
||||
"cols": [
|
||||
187.19,
|
||||
142,
|
||||
142
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Line style drop 466 x 190 (type column grows only for wider font names)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"w": 471.19,
|
||||
"h": 190,
|
||||
"cols": [
|
||||
187.19,
|
||||
142,
|
||||
142
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Weight column: callback only, stays open",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a) → layer.visible(Sample-Wall, true) → property(layer, CC-D GNL) → lineWeight(w-byblock)"
|
||||
},
|
||||
{
|
||||
"name": "Line type select: property(line, CENTER), closes, preview = CENTER pattern (4 segments)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"segs": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Figma sample 255,259,0 shown as host text, swatch from host",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Color On: dropdown 192 wide, left edges meet, RGB area #211D13 + divider",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"f": {
|
||||
"x": 97,
|
||||
"y": 435.5,
|
||||
"w": 200,
|
||||
"h": 28
|
||||
},
|
||||
"d": {
|
||||
"x": 97,
|
||||
"y": 463.5,
|
||||
"w": 192,
|
||||
"h": 158
|
||||
},
|
||||
"left": {
|
||||
"x": 98,
|
||||
"y": 436.5,
|
||||
"w": 114,
|
||||
"h": 26,
|
||||
"backgroundColor": "rgb(33, 29, 19)",
|
||||
"borderRightColor": "rgba(33, 29, 19, 0.4)"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Color dropdown height 158 (Figma)",
|
||||
"status": "PASS",
|
||||
"data": 158
|
||||
},
|
||||
{
|
||||
"name": "ByBlock: callback, required single choice (ByBlock pressed, ByLayer not)",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Clicking the pressed mode is a no-op",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Chip: property(color, 241,4,4)",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a) → layer.visible(Sample-Wall, true) → property(layer, CC-D GNL) → lineWeight(w-byblock) → property(line, CENTER) → colorMode(byBlock) → property(color, 241,4,4)"
|
||||
},
|
||||
{
|
||||
"name": "Out-of-range 255,259,0 rejected (no callback, restored)",
|
||||
"status": "PASS",
|
||||
"data": "241,4,4"
|
||||
},
|
||||
{
|
||||
"name": "Valid RGB committed normalized (1,2,3)",
|
||||
"status": "PASS",
|
||||
"data": "property(scale, 2.0000) → property(weight, 0.5) → property(link, http://a) → layer.visible(Sample-Wall, true) → property(layer, CC-D GNL) → lineWeight(w-byblock) → property(line, CENTER) → colorMode(byBlock) → property(color, 241,4,4) → property(color, 1,2,3)"
|
||||
},
|
||||
{
|
||||
"name": "btn_colorize: host callback, dropdown closes",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Field RGB 300,0,0 rejected",
|
||||
"status": "PASS",
|
||||
"data": "1,2,3"
|
||||
},
|
||||
{
|
||||
"name": "Escape #1 cancels draft, dropdown still open",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Escape #2 closes the dropdown, panel stays",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "ReadOnly 두께: no On; ReadOnly 레이어: no dropdown",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"x": 97,
|
||||
"y": 647,
|
||||
"w": 200,
|
||||
"h": 28,
|
||||
"backgroundColor": "rgba(0, 0, 0, 0)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ReadOnly keeps Hover border",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Disabled 색상: no dropdown; Disabled 선축적: no hover border",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "External value / unit props: 선축적 2.5000, 두께 cm",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "Dynamic rows: added at the end, removed row gone, order kept",
|
||||
"status": "PASS",
|
||||
"data": [
|
||||
"layer-mixed",
|
||||
"layer",
|
||||
"color",
|
||||
"line",
|
||||
"weight",
|
||||
"scale",
|
||||
"opacity",
|
||||
"thickness",
|
||||
"material",
|
||||
"extra-10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Without menus: line types from options only (1 column), no crash",
|
||||
"status": "PASS",
|
||||
"data": 1
|
||||
},
|
||||
{
|
||||
"name": "Pinned Hover specimen uses the same border",
|
||||
"status": "PASS",
|
||||
"data": "rgba(33, 29, 19, 0.4)"
|
||||
},
|
||||
{
|
||||
"name": "4 panel states: 314 x 256 / 361 / 658 / 754",
|
||||
"status": "PASS",
|
||||
"data": [
|
||||
[
|
||||
"none",
|
||||
314,
|
||||
256
|
||||
],
|
||||
[
|
||||
"all",
|
||||
314,
|
||||
361
|
||||
],
|
||||
[
|
||||
"circle",
|
||||
314,
|
||||
658
|
||||
],
|
||||
[
|
||||
"expanded",
|
||||
314,
|
||||
754
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Header-hosted panel: layer list attached; Escape closes list only",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"hosted": {
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"panelOpen": true
|
||||
},
|
||||
"stillOpen": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "no console / page errors",
|
||||
"status": "PASS",
|
||||
"data": []
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 112 KiB |
@@ -0,0 +1,118 @@
|
||||
# SW Storybook — 속성창 4개 상태 비교 통합 (2026-10-19)
|
||||
|
||||
- 브랜치: `feature/sw-package` · 시작 HEAD `c705a1fc59fe8ecbce663bf4601638fde43553a1` · 시작 시 Working Tree Clean
|
||||
- 범위: Storybook 전용 코드(`src/sw/sw-header.stories.tsx`, `src/sw/sw-stories.css`)와 검증 스크립트. SW 패키지 런타임·공개 API·AI 템플릿·Overview·Dropdown Story는 변경하지 않음.
|
||||
- 증거: `docs/sw/property-panel-comparison-qa/` (기존 QA 폴더는 덮어쓰지 않음)
|
||||
|
||||
## 1. 수정 전 속성창 관련 Story 목록 (실제 선언 기준)
|
||||
|
||||
| Story ID | 이름 | 실제 기능·목적 | 분류 |
|
||||
| --- | --- | --- | --- |
|
||||
| `sw-header--property-panel-compare` | Property Panel · 4개 비교 | 같은 `SWHeaderPropertyPanel` 4개 상태를 한 화면에 실측 크기와 함께 표시 | **유지** |
|
||||
| `sw-header--property-panel-variants` | Property Panel Variants | `PropertyPanelDemo standalone`: 상태 전환 버튼으로 **한 번에 1개** 단독 패널. 데이터 조작 버튼은 `Property Panel`과 동일 | **삭제** (중복) |
|
||||
| `sw-header--property-panel-none` | Property Panel · 선택 없음 | 314×256 단독 패널 1개 | **삭제** (비교 화면과 중복) |
|
||||
| `sw-header--property-panel-all` | Property Panel · 전체 선택 | 314×361 단독 패널 1개 | **삭제** (중복) |
|
||||
| `sw-header--property-panel-circle` | Property Panel · 원 1개 | 314×658 단독 패널 1개 | **삭제** (중복) |
|
||||
| `sw-header--property-panel-expanded` | Property Panel · 모두 펼침 | 314×754 단독 패널 1개 | **삭제** (중복) |
|
||||
| `sw-header--property-panel` | Property Panel | Header 연결 열기/닫기, Controlled/Uncontrolled, 외부 Open, 폭 전환, 속성 추가·삭제·순서·제한 상태 | **보존** (Header 연동·API 검증) |
|
||||
| `sw-header--property-shape-slot` | Property Panel · 형상 Slot | `shapeContent` 확장 계약(기본/null/ReactNode/Render Prop) | **보존** (API 검증) |
|
||||
| `sw-header--property-fields` | Property Panel · 필드 13개 | 필드 Default/Hover 표본 + 실제 조작 | **보존** (필드 검증) |
|
||||
| `sw-header--property-bar` | Property Bar | 속성Bar 독립 기능 | **보존** |
|
||||
| `sw-header--layer-detail-input` | Layer Detail · Input | Input 독립 검증 | **보존** |
|
||||
| `sw-header--interactive-states` | Interactive States | Header 공통 Hover/Selected(속성창 토글 포함) | **보존** |
|
||||
| `sw-header--detail-buttons` | Detail Buttons (Shared States) | 공통 상세 버튼 상태 | **보존** |
|
||||
|
||||
삭제한 5개는 모두 비교 화면과 같은 `PropertyState`/`SWHeaderPropertyPanel` + 같은 fixture를 렌더링했고, 고유 기능 검증은 없었습니다(Variants의 데이터 조작 버튼은 `Property Panel`에 동일하게 남아 있음).
|
||||
|
||||
## 2. 유지한 4개 상태 비교 Story
|
||||
|
||||
- ID `sw-header--property-panel-compare`, 이름 `Property Panel · 4개 비교`, 페이지 제목 `속성창 4개 상태 비교` — 모두 유지.
|
||||
- 상태명·순서 유지(코드 `PROPERTY_STATES`): ① 선택된 객체 없음 ② 전체 객체 선택 ③ 원 1개 선택 ④ 모든 항목 펼침. 각 카드 상단 캡션(상태명 · Figma 노드 · 크기 · 측정값)은 패널 밖 `figcaption`이라 제품 디자인에 영향 없음.
|
||||
- 변경: 줄바꿈 flex → 2열 × 2행 grid(간격 24px, 패널 원래 크기). 페이지를 뷰포트 높이로 고정하고 Inner 스크롤 영역(`[data-compare-scroll]`)만 가로·세로 스크롤. 설명 문구에서 "폭이 부족하면 줄바꿈합니다" 등 불필요한 문장 제거. 삭제된 개별 Story가 보여주던 Callback 기록을 대신해 한 줄 로그(`상태: callback`)를 추가.
|
||||
- Storybook 전용 배치 예외 1건: 제품 CSS의 `.hm-sw-property { max-height: calc(100dvh - 8px) }`가 낮은 뷰포트에서 754px 패널을 줄이지 않도록 비교 grid 안에서만 `max-height: none` (Overview Stage와 같은 방식). 패키지 CSS는 미변경.
|
||||
|
||||
## 3·4. 삭제한 Popup 시연 / 삭제된 Story ID
|
||||
|
||||
`sw-header--property-panel-variants`, `sw-header--property-panel-none`, `sw-header--property-panel-all`, `sw-header--property-panel-circle`, `sw-header--property-panel-expanded`
|
||||
|
||||
함께 정리한 Story 전용 코드: `PropertyStateStory`(삭제된 4개 전용), `PropertyPanelDemo`의 `standalone` 분기(Variants 전용). 제품 컴포넌트·타입·CSS·SVG·형상 Slot은 삭제하지 않음.
|
||||
|
||||
## 5. 최종 Story 개수
|
||||
|
||||
| | 수정 전 | 수정 후 |
|
||||
| --- | --- | --- |
|
||||
| SW Story | 24 | **19** |
|
||||
| 전체 저장소 Story | 343 | **338** |
|
||||
|
||||
남은 19개의 ID·이름·title·tags는 수정 전과 동일(자동 비교 PASS). Dropdown(`sw-header--composed-controls`, 이름 `Dropdown`, title `SW/Header`) 동일.
|
||||
|
||||
## 6~8. 배치·크기·스크롤 검증 (실제 좌표, Chrome, 높이 900)
|
||||
|
||||
| 폭 | ①(x,y) | ②(x,y) | ③(x,y) | ④(x,y) | 열/열/행 간격 | Inner client→scroll (W / H) |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 390 | 16,213 | 354,213 | 16,664 | 354,664 | 24/24/24 | 358→652 / 672→1271 |
|
||||
| 768 | 23,200 | 361,200 | 23,651 | 361,651 | 24/24/24 | 722→722 / 677→1271 |
|
||||
| 1280 | 38,215 | 376,215 | 38,666 | 376,666 | 24/24/24 | 1203→1203 / 647→1271 |
|
||||
| 1440 | 120,217 | 458,217 | 120,668 | 458,668 | 24/24/24 | 1200→1200 / 644→1271 |
|
||||
|
||||
- 같은 행 y 일치, 같은 열 x 일치, 2열이 1열 오른쪽·2행이 1행 아래 → 4개 폭 모두 **2열 × 2행 PASS**.
|
||||
- 패널 크기(모든 폭): 314×256 / 314×361 / 314×658 / 314×754 **PASS**.
|
||||
- 문서 자체는 가로·세로 스크롤 없음(페이지 높이 무제한 확장 없음). Inner 끝까지 스크롤 시 754px 패널 하단·우측이 스크롤 영역 안에 완전히 보임. 390px은 Inner 가로 스크롤로 2열 유지(1열 전환·축소 없음) **PASS**.
|
||||
|
||||
## 9. Dropdown / Input / Focus / 닫기
|
||||
|
||||
- Input: 원 1개 패널 반지름 Enter 확정 → Callback 기록, 모두 펼침 패널 값 불변(상태 독립). Escape 편집 취소, Focus 패널 내부 유지, Tab 이동 정상.
|
||||
- Dropdown(레이어): 1행 패널·2행 패널 모두 열림·항목 표시·뷰포트 내부. 열린 채 Inner를 120px 스크롤해도 필드에 붙어 이동(fixed overlay가 분리되지 않음). Escape 닫힘 + 필드로 Focus 복귀. 항목 선택 시 해당 패널에만 Callback.
|
||||
- 도구 토글 `aria-pressed` 패널별 독립, 그룹 펼침 후 접으면 256px 복귀(다른 패널 361px 유지), 닫기 버튼 → 해당 패널 `onClose` Callback.
|
||||
|
||||
## 10. 기존 Header / Overview 보존
|
||||
|
||||
- Overview: Header 49px·Stage 렌더 PASS. Dropdown Story: composed controls 렌더 PASS, 기존 기능 스크립트(`dropdown-consolidation.browser.mjs`) 223개 기능 체크 PASS.
|
||||
- Window Move Motion·K Guide·Property Bar·Layer Detail · Input·형상 Slot·필드 13개 렌더 PASS. `Property Panel`(Header 연결) 열기/Escape 닫기 PASS.
|
||||
|
||||
## 11. 제품 런타임·API 미변경
|
||||
|
||||
`git diff HEAD -- packages/sw/src packages/sw/ai packages/sw/package.json packages/ui packages/org-chart packages/org-chart-react docs/sw/ai-template src/sw/sw-header-overview.tsx src/sw/sw-header-dropdown.stories.tsx .storybook` → 변경 없음.
|
||||
|
||||
## 12. 변경 파일
|
||||
|
||||
- `src/sw/sw-header.stories.tsx` — 5개 Story·전용 헬퍼 삭제, 비교 화면 grid·스크롤·로그
|
||||
- `src/sw/sw-stories.css` — 비교 페이지 레이아웃(Story 전용)
|
||||
- `packages/sw/tests/storybook-inventory.mjs` — `retiredPropertyPanelIds` 추가
|
||||
- `packages/sw/tests/followup-four.browser.mjs`, `packages/sw/tests/detail-buttons.browser.mjs` — 삭제된 ID 참조를 비교 Story의 `[data-compare]` 범위로 교체, 기대 개수 24→19
|
||||
- `packages/sw/tests/property-panel-comparison.browser.mjs` — 신규 검증 스크립트(`before` / `verify`)
|
||||
- `docs/sw/property-panel-comparison-qa/` — 수정 전후 증거
|
||||
- 과거 검토·검증 문서(`sw-first-pass-cleanup-review`, `sw-storybook-screen-review` 등)의 옛 ID는 당시 기록으로 보존.
|
||||
|
||||
## 13. Typecheck / Build
|
||||
|
||||
| 항목 | 결과 |
|
||||
| --- | --- |
|
||||
| `npm run typecheck:sw` | PASS |
|
||||
| `npm run build:sw` | PASS |
|
||||
| Storybook Typecheck `npx tsc -b` | PASS |
|
||||
| `npm run build-storybook` | PASS |
|
||||
| `git diff --check` | PASS |
|
||||
|
||||
## 14. 브라우저 검증 (Chrome headless, 새 static build)
|
||||
|
||||
| 스크립트 | 결과 | 증거 |
|
||||
| --- | --- | --- |
|
||||
| `property-panel-comparison.browser.mjs verify` | **59/59 PASS**, 콘솔 오류 0 | `verify/` |
|
||||
| `followup-four.browser.mjs` (비교 Story로 이전) | 39/39 PASS | `legacy-followup-four/` |
|
||||
| `detail-buttons.browser.mjs preservation` | 15/15 PASS | `legacy-detail-buttons-preservation/` |
|
||||
| `property-fields.browser.mjs` | 77/77 PASS | `legacy-property-fields/` |
|
||||
| `property-close.browser.mjs` | 6/6 PASS | `legacy-property-close/` |
|
||||
| `dropdown-consolidation.browser.mjs` | 223 PASS / 4 FAIL (아래) | `legacy-dropdown/` |
|
||||
|
||||
`dropdown-consolidation`의 4개 FAIL은 이전 단계 기준선에 고정된 단언입니다: 28→24 / 24 개수 2건, "삭제 외 선언 동일" 1건(이번에 선언 삭제), "`src/sw/sw-stories.css` 미변경" 1건(이번에 Story CSS 수정). 이번 작업의 의도된 변경이며 Dropdown 기능 체크는 모두 PASS입니다. 이 스크립트는 당시 단계 기록이라 수정하지 않았습니다.
|
||||
|
||||
수정 전 스크린샷: `before/before-compare-1440.png`(4개가 한 줄), `before/before-property-panel-*.png`. 수정 후: `verify/compare-<폭>-top.png` / `-end.png`, `dropdown-*.png`.
|
||||
|
||||
## 15. NOT VERIFIED
|
||||
|
||||
- Firefox·Safari 등 Chrome 외 브라우저(이번 작업에서 실행하지 않음).
|
||||
- 개발 서버(6024) 화면 — 다른 worktree의 서버일 수 있어 사용하지 않고 static build로만 검증.
|
||||
- 실제 Storybook Manager UI에서의 사이드바 표시(index.json으로 목록만 검증).
|
||||
- `overview.browser.mjs`, `overview-firefox.browser.mjs`, `storybook-cleanup-stage1.browser.mjs`, `ai-template-*` 스크립트는 재실행하지 않음(Overview는 신규 스크립트에서 렌더·Header 49px만 확인).
|
||||
- 스크린리더 실사용.
|
||||
@@ -2,7 +2,7 @@ import { createRequire } from 'node:module'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import http from 'node:http'
|
||||
import { swStoryEntries, retainedInventory } from './storybook-inventory.mjs'
|
||||
import { swStoryEntries, retainedInventory, retiredPropertyPanelIds } from './storybook-inventory.mjs'
|
||||
|
||||
// Usage: node packages/sw/tests/detail-buttons.browser.mjs baseline|verify <evidence-dir> [base-url]
|
||||
const mode = process.argv[2] ?? 'verify'
|
||||
@@ -50,9 +50,9 @@ try {
|
||||
check(`required responsive width ${width}`,layout.height===49&&layout.overflow===0&&layout.documentOverflow===0,layout)
|
||||
}
|
||||
await page.setViewportSize({width:1920,height:1000})
|
||||
await goto('property-panel-compare')
|
||||
for(const [id,height] of [['none',256],['all',361],['circle',658],['expanded',754]]){
|
||||
await goto(`property-panel-${id}`)
|
||||
const box=await page.locator('.hm-sw-property').first().boundingBox()
|
||||
const box=await page.locator(`[data-compare="${id}"] .hm-sw-property`).boundingBox()
|
||||
check(`preserve property panel ${id}`,box.width===314&&box.height===height,box)
|
||||
}
|
||||
await goto('composition')
|
||||
@@ -100,8 +100,10 @@ try {
|
||||
for(const key of Object.keys(snapshots).filter(k=>!k.startsWith('text'))) check(`unchanged header ${key} (capture a current baseline for replacement fixtures)`,JSON.stringify(before[key])===JSON.stringify(snapshots[key]))
|
||||
const oldIds=JSON.parse(await readFile(path.join(output,'baseline-story-ids.json'),'utf8'))
|
||||
const retiredIds=['default','controls','responsive','text-stroke','dynamic-data','layer-dropdown','color-dropdown','line-style-dropdown','text-dimension-style-dropdown'].map(id=>`sw-header--${id}`)
|
||||
const retainedIds=retainedInventory(oldIds.map(id=>({id}))).map(e=>e.id)
|
||||
check('retained stories remain; only approved retirements are absent',ids.length===24&&retainedIds.every(id=>ids.includes(id))&&retiredIds.every(id=>!ids.includes(id)),{before:oldIds.length,after:ids.length})
|
||||
// 2026-10-19: the five duplicate property panel demos are retired as well (24 -> 19).
|
||||
retiredIds.push(...retiredPropertyPanelIds)
|
||||
const retainedIds=retainedInventory(oldIds.map(id=>({id}))).map(e=>e.id).filter(id=>!retiredPropertyPanelIds.includes(id))
|
||||
check('retained stories remain; only approved retirements are absent',ids.length===19&&retainedIds.every(id=>ids.includes(id))&&retiredIds.every(id=>!ids.includes(id)),{before:oldIds.length,after:ids.length})
|
||||
for(const id of ids){await goto(id.replace('sw-header--',''));check(`render ${id}`,!(await page.locator('.sb-errordisplay').isVisible()))}
|
||||
await goto('composition')
|
||||
const presets=['기본 전체','구성 A','구성 B (레이어·선스타일)','구성 C (로고만)','파일 목록 제거','레이어 제거','색상 제거','스타일 그룹 제거','여러 메뉴 제거','순서 변경','숨김 + 순서 변경']
|
||||
|
||||
@@ -72,8 +72,9 @@ for (const w of [1440, 1080, 768]) {
|
||||
}
|
||||
|
||||
// ---- B. Tool icon
|
||||
await goto('property-panel-all')
|
||||
const tool = page.locator('.hm-sw-property__tool').first()
|
||||
// The four states share one Story since 2026-10-19; each check is scoped to its panel.
|
||||
await goto('property-panel-compare')
|
||||
const tool = page.locator('[data-compare="all"] .hm-sw-property__tool')
|
||||
const toolInfo = await tool.evaluate(e => { const s = getComputedStyle(e), imgs = [...e.querySelectorAll('img')].map(i => ({ cls: i.className, w: i.getBoundingClientRect().width, h: i.getBoundingClientRect().height, src: i.getAttribute('src').slice(0, 60) })); return { border: s.borderTopWidth + ' ' + s.borderTopColor, outline: s.outlineStyle, bg: s.backgroundColor, w: e.getBoundingClientRect().width, h: e.getBoundingClientRect().height, imgs } })
|
||||
report.data.tool = toolInfo
|
||||
check('B1 no CSS border/outline/background on tool button', toolInfo.border.startsWith('0px') && toolInfo.outline === 'none' && toolInfo.bg === 'rgba(0, 0, 0, 0)', toolInfo)
|
||||
@@ -147,8 +148,8 @@ for (const menu of ['layer', 'color', 'text-style', 'dimension-style', 'line-sty
|
||||
}
|
||||
|
||||
// ---- E. Gauge
|
||||
await goto('property-panel-circle')
|
||||
const range = page.locator('.hm-sw-property input[type="range"]').first()
|
||||
await goto('property-panel-compare')
|
||||
const range = page.locator('[data-compare="circle"] .hm-sw-property input[type="range"]')
|
||||
const gaugeState = () => range.evaluate(e => ({ value: e.value, fill: e.style.getPropertyValue('--hm-sw-range-fill'), track: getComputedStyle(e).getPropertyValue('--hm-sw-property-range-track').trim() }))
|
||||
const gaugePixels = async name => {
|
||||
const b = await range.boundingBox()
|
||||
@@ -166,8 +167,8 @@ await range.focus(); for (let n = 0; n < 40; n++) await page.keyboard.press('Arr
|
||||
gs = await gaugePixels('keyboard'); report.data.gaugeKeyboard = gs
|
||||
check('E2 keyboard ArrowRight x40 -> 0.65, fill follows', gs.value === '0.65' && Math.abs(Number(gs.fill) - 0.65) < 1e-9 && isRed(gs.left) && isBeige(gs.right), gs)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForFunction(() => document.querySelector('.hm-sw-property [data-property-id="opacity"] .hm-sw-header__input')?.value === '0.65', null, { timeout: 2000 }).catch(() => {})
|
||||
const committed = await page.locator('.hm-sw-property [data-property-id="opacity"] .hm-sw-header__input').first().inputValue().catch(() => null)
|
||||
await page.waitForFunction(() => document.querySelector('[data-compare="circle"] .hm-sw-property [data-property-id="opacity"] .hm-sw-header__input')?.value === '0.65', null, { timeout: 2000 }).catch(() => {})
|
||||
const committed = await page.locator('[data-compare="circle"] [data-property-id="opacity"] .hm-sw-header__input').inputValue().catch(() => null)
|
||||
report.data.gaugeCommitted = committed
|
||||
check('E3 commit updates the number input (callback path)', committed === '0.65', { committed })
|
||||
await page.keyboard.press('End'); gs = await gaugePixels('max'); report.data.gaugeMax = gs
|
||||
@@ -175,18 +176,19 @@ check('E4 max -> fill 1', gs.value === '1' && Number(gs.fill) === 1 && isRed(gs.
|
||||
await page.keyboard.press('Home'); gs = await gaugePixels('min'); report.data.gaugeMin = gs
|
||||
check('E5 min -> fill 0, beige right', gs.value === '0' && Number(gs.fill) === 0 && isBeige(gs.right), gs)
|
||||
// External value: type into the numeric input and commit
|
||||
const num = page.locator('.hm-sw-property [data-property-id="opacity"] .hm-sw-header__input').first()
|
||||
const num = page.locator('[data-compare="circle"] [data-property-id="opacity"] .hm-sw-header__input')
|
||||
await num.click(); await num.fill('0.5'); await num.press('Enter'); await page.mouse.move(1, 1)
|
||||
gs = await gaugeState(); report.data.gaugeExternal = gs
|
||||
check('E6 value changed elsewhere syncs gauge', gs.value === '0.5' && Number(gs.fill) === 0.5, gs)
|
||||
await page.locator('.hm-sw-property').first().screenshot({ path: path.join(out, 'E-panel-circle.png') })
|
||||
await page.locator('[data-compare="circle"] .hm-sw-property').screenshot({ path: path.join(out, 'E-panel-circle.png') })
|
||||
|
||||
// ---- F. Panel four states render
|
||||
await goto('property-panel-compare')
|
||||
for (const id of ['property-panel-none', 'property-panel-all', 'property-panel-circle', 'property-panel-expanded']) {
|
||||
await goto(id)
|
||||
const sz = await page.locator('.hm-sw-property').first().evaluate(e => { const r = e.getBoundingClientRect(); return [r.width, r.height] })
|
||||
const panel = page.locator(`[data-compare="${id.replace('property-panel-', '')}"] .hm-sw-property`)
|
||||
const sz = await panel.evaluate(e => { const r = e.getBoundingClientRect(); return [r.width, r.height] })
|
||||
report.data[id] = sz
|
||||
await page.locator('.hm-sw-property').first().screenshot({ path: path.join(out, `F-${id}.png`) })
|
||||
await panel.screenshot({ path: path.join(out, `F-${id}.png`) })
|
||||
check(`F ${id} renders`, sz[0] === 314 && sz[1] > 200, sz)
|
||||
}
|
||||
check('G no page/console errors', report.errors.length === 0, report.errors)
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { createRequire } from 'node:module'
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import http from 'node:http'
|
||||
import { swStoryEntries, retiredPropertyPanelIds } from './storybook-inventory.mjs'
|
||||
|
||||
// Property panel comparison consolidation (2026-10-19).
|
||||
// Usage: node packages/sw/tests/property-panel-comparison.browser.mjs before|verify <evidence-dir> [base-url|static]
|
||||
const mode = process.argv[2] ?? 'verify'
|
||||
const out = path.resolve(process.argv[3] ?? 'docs/sw/property-panel-comparison-qa/' + mode)
|
||||
let base = process.argv[4] ?? 'static'
|
||||
let server
|
||||
if (base === 'static') {
|
||||
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' }
|
||||
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)); 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')
|
||||
await mkdir(out, { recursive: true })
|
||||
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 } })
|
||||
const report = { mode, base: process.argv[4] ?? 'static', browser: browser.version(), checks: [], errors: [], data: {} }
|
||||
page.on('pageerror', e => report.errors.push(e.message))
|
||||
page.on('console', m => { if (m.type() === 'error') report.errors.push(m.text()) })
|
||||
const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); if (!ok) console.error('FAIL', name, JSON.stringify(data)) }
|
||||
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 STATES = [['none', 256], ['all', 361], ['circle', 658], ['expanded', 754]]
|
||||
const geometry = () => page.evaluate(() => {
|
||||
const r = e => { const b = e.getBoundingClientRect(); return { x: b.x, y: b.y, w: b.width, h: b.height, right: b.right, bottom: b.bottom } }
|
||||
const scroller = document.querySelector('[data-compare-scroll]')
|
||||
return {
|
||||
cells: [...document.querySelectorAll('[data-compare]')].map(c => ({ id: c.dataset.compare, cell: r(c), panel: r(c.querySelector('.hm-sw-property')), caption: c.querySelector('figcaption strong')?.textContent })),
|
||||
scroller: scroller && { ...r(scroller), scrollW: scroller.scrollWidth, clientW: scroller.clientWidth, scrollH: scroller.scrollHeight, clientH: scroller.clientHeight },
|
||||
doc: { scrollW: document.documentElement.scrollWidth, clientW: document.documentElement.clientWidth, scrollH: document.documentElement.scrollHeight, clientH: document.documentElement.clientHeight },
|
||||
}
|
||||
})
|
||||
|
||||
try {
|
||||
const index = await (await fetch(`${base}/index.json`)).json()
|
||||
const stories = Object.values(index.entries).filter(e => e.type === 'story')
|
||||
const sw = swStoryEntries(index)
|
||||
report.data.inventory = { total: stories.length, sw: sw.length, ids: sw.map(e => [e.id, e.name]) }
|
||||
|
||||
if (mode === 'before') {
|
||||
await writeFile(path.join(out, 'story-inventory-before.json'), JSON.stringify({ total: stories.length, sw: sw.map(e => ({ id: e.id, name: e.name, title: e.title, tags: e.tags })) }, null, 2) + '\n')
|
||||
await goto('property-panel-compare')
|
||||
report.data.compare = await geometry()
|
||||
await page.screenshot({ path: path.join(out, 'before-compare-1440.png'), fullPage: true })
|
||||
for (const id of retiredPropertyPanelIds) {
|
||||
await goto(id.replace('sw-header--', ''))
|
||||
await page.screenshot({ path: path.join(out, `before-${id.replace('sw-header--', '')}.png`), fullPage: true })
|
||||
}
|
||||
} else {
|
||||
// ---- Inventory
|
||||
const before = JSON.parse(await readFile(path.resolve(process.env.SW_BEFORE_INVENTORY ?? 'docs/sw/property-panel-comparison-qa/before/story-inventory-before.json'), 'utf8'))
|
||||
const expected = before.sw.filter(e => !retiredPropertyPanelIds.includes(e.id))
|
||||
const signature = list => JSON.stringify(list.map(e => [e.id, e.name, e.title, e.tags]).sort((a, b) => a[0].localeCompare(b[0])))
|
||||
check('inventory: 24 -> 19 SW Stories, 343 -> 338 total, only 5 approved retirements', before.sw.length === 24 && sw.length === 19 && before.total === 343 && stories.length === 338 && retiredPropertyPanelIds.every(id => !index.entries[id]), { before: [before.total, before.sw.length], after: [stories.length, sw.length] })
|
||||
check('inventory: retained SW Story IDs, names, titles, tags unchanged', signature(expected) === signature(sw), { retained: sw.length })
|
||||
check('inventory: compare Story keeps its ID and name', index.entries['sw-header--property-panel-compare']?.name === 'Property Panel · 4개 비교')
|
||||
check('inventory: Dropdown Story keeps ID / name / title', index.entries['sw-header--composed-controls']?.name === 'Dropdown' && index.entries['sw-header--composed-controls']?.title === 'SW/Header')
|
||||
|
||||
// ---- Layout at four widths
|
||||
for (const width of [390, 768, 1280, 1440]) {
|
||||
await page.setViewportSize({ width, height: 900 })
|
||||
await goto('property-panel-compare')
|
||||
const g = await geometry(); report.data[`layout${width}`] = g
|
||||
const [a, b, c, d] = g.cells
|
||||
const sizes = g.cells.map(cell => [cell.id, cell.panel.w, cell.panel.h])
|
||||
check(`${width}: four states in original order with captions`, JSON.stringify(g.cells.map(cell => cell.id)) === JSON.stringify(STATES.map(s => s[0])) && g.cells.every(cell => cell.caption), g.cells.map(cell => [cell.id, cell.caption]))
|
||||
check(`${width}: original panel sizes 314 × 256 / 361 / 658 / 754`, STATES.every(([id, h], i) => sizes[i][0] === id && sizes[i][1] === 314 && sizes[i][2] === h), sizes)
|
||||
const colGap = b.cell.x - a.cell.right, colGap2 = d.cell.x - c.cell.right, rowGap = c.cell.y - Math.max(a.cell.bottom, b.cell.bottom)
|
||||
report.data[`gaps${width}`] = { colGap, colGap2, rowGap }
|
||||
check(`${width}: 2 columns × 2 rows by coordinates`, Math.abs(a.cell.y - b.cell.y) < 0.5 && Math.abs(c.cell.y - d.cell.y) < 0.5 && Math.abs(a.cell.x - c.cell.x) < 0.5 && Math.abs(b.cell.x - d.cell.x) < 0.5 && b.cell.x >= a.cell.right && c.cell.y >= Math.max(a.cell.bottom, b.cell.bottom), { a: a.cell, b: b.cell, c: c.cell, d: d.cell })
|
||||
check(`${width}: consistent 24px column and row gaps`, [colGap, colGap2, rowGap].every(v => Math.abs(v - 24) < 0.5), { colGap, colGap2, rowGap })
|
||||
check(`${width}: page itself does not scroll (no document overflow)`, g.doc.scrollW <= g.doc.clientW && g.doc.scrollH <= g.doc.clientH, g.doc)
|
||||
check(`${width}: Inner scroller scrolls vertically to the 754px panel`, g.scroller.scrollH > g.scroller.clientH, g.scroller)
|
||||
await page.screenshot({ path: path.join(out, `compare-${width}-top.png`) })
|
||||
// Scroll to the very end: every panel must be reachable with its bottom inside the scroller.
|
||||
const end = await page.evaluate(() => {
|
||||
const s = document.querySelector('[data-compare-scroll]'); s.scrollTop = s.scrollHeight; s.scrollLeft = s.scrollWidth
|
||||
const sr = s.getBoundingClientRect()
|
||||
return { scrollTop: s.scrollTop, scrollLeft: s.scrollLeft, panels: [...s.querySelectorAll('.hm-sw-property')].map(p => { const r = p.getBoundingClientRect(); return { bottom: r.bottom, right: r.right } }), sBottom: sr.bottom, sRight: sr.right }
|
||||
})
|
||||
report.data[`end${width}`] = end
|
||||
const last = end.panels[3], right = end.panels[1]
|
||||
check(`${width}: bottom of the 754px panel reachable, not clipped`, last.bottom <= end.sBottom + 0.5 && last.right <= end.sRight + 0.5, end)
|
||||
if (g.scroller.scrollW > g.scroller.clientW) check(`${width}: narrow Inner scrolls horizontally, keeping 2 columns`, end.scrollLeft > 0 && right.right <= end.sRight + 0.5, { scrollW: g.scroller.scrollW, clientW: g.scroller.clientW, end })
|
||||
else check(`${width}: both columns fit without horizontal scroll`, g.scroller.scrollW <= g.scroller.clientW && b.cell.right <= g.scroller.right, g.scroller)
|
||||
await page.screenshot({ path: path.join(out, `compare-${width}-end.png`) })
|
||||
}
|
||||
|
||||
// ---- Behaviour (1440 × 900)
|
||||
await page.setViewportSize({ width: 1440, height: 900 })
|
||||
await goto('property-panel-compare')
|
||||
const cell = id => page.locator(`[data-compare="${id}"]`)
|
||||
const log = () => page.locator('[data-property-log]').textContent()
|
||||
const input = (id, prop) => cell(id).locator(`[data-property-id="${prop}"] input.hm-sw-header__input`).first()
|
||||
|
||||
// Input: Enter commits through onPropertyChange; Escape restores; other panels unaffected.
|
||||
const radius = input('circle', 'radius')
|
||||
await radius.scrollIntoViewIfNeeded(); await radius.click(); await radius.fill('5.5'); await radius.press('Enter')
|
||||
const committed = { circle: await radius.inputValue(), expanded: await input('expanded', 'radius').inputValue(), log: await log() }
|
||||
check('Input Enter commits via callback; only that panel changes', committed.circle.startsWith('5.5') && committed.expanded === '4.0526' && committed.log.includes('circle: property(radius, 5.5'), committed)
|
||||
await radius.click(); await radius.fill('9'); await radius.press('Escape')
|
||||
check('Input Escape cancels the edit', (await radius.inputValue()).startsWith('5.5'), await radius.inputValue())
|
||||
const focusOk = await radius.evaluate(e => document.activeElement === e || !!e.closest('.hm-sw-property')?.contains(document.activeElement))
|
||||
check('Focus stays inside the edited panel after Escape', focusOk)
|
||||
await page.keyboard.press('Tab')
|
||||
check('Tab moves focus within the page (no trap / loss to body)', await page.evaluate(() => document.activeElement !== document.body))
|
||||
|
||||
// Dropdown (layer picker) in a top panel and in a bottom-row panel after scrolling.
|
||||
const openPicker = async id => {
|
||||
const trigger = cell(id).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 })
|
||||
const info = await drop.evaluate(e => { const r = e.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height, bottom: r.bottom, right: r.right, vw: innerWidth, vh: innerHeight, items: e.querySelectorAll('button, [role="option"], li').length } })
|
||||
return { trigger, drop, info }
|
||||
}
|
||||
for (const id of ['all', 'expanded']) {
|
||||
const { trigger, drop, info } = await openPicker(id)
|
||||
report.data[`drop-${id}`] = info
|
||||
check(`Dropdown ${id}: opens, has items, fully inside the viewport`, info.items > 0 && info.x >= 0 && info.y >= 0 && info.right <= info.vw + 0.5 && info.bottom <= info.vh + 0.5, info)
|
||||
await page.screenshot({ path: path.join(out, `dropdown-${id}.png`) })
|
||||
if (id === 'expanded') {
|
||||
// Scroll the Inner while open: the fixed overlay must stay attached to its field (below or flipped above).
|
||||
const moved = await page.locator('[data-compare-scroll]').evaluate(s => { const t = s.scrollTop; s.scrollTop += 120; return s.scrollTop - t })
|
||||
await page.waitForTimeout(150)
|
||||
const after = await page.evaluate(() => {
|
||||
const d = document.querySelector('.hm-sw-property__drop')?.getBoundingClientRect()
|
||||
const f = document.querySelector('[data-compare="expanded"] [data-property-id="layer"] [aria-expanded="true"]')?.getBoundingClientRect()
|
||||
return d && f ? { dTop: d.top, dBottom: d.bottom, fTop: f.top, fBottom: f.bottom } : null
|
||||
})
|
||||
report.data.dropScroll = { moved, before: info, after }
|
||||
check('Dropdown follows the Inner scroller (stays attached to its field)', moved > 0 && after && (Math.abs(after.dTop - after.fBottom) < 1.5 || Math.abs(after.dBottom - after.fTop) < 1.5), report.data.dropScroll)
|
||||
}
|
||||
await page.keyboard.press('Escape')
|
||||
check(`Dropdown ${id}: Escape closes and returns focus to its field`, await page.locator('.hm-sw-property__drop').count() === 0 && await trigger.evaluate(e => document.activeElement === e || e.contains(document.activeElement)))
|
||||
}
|
||||
// Select a layer value: callback updates only that panel.
|
||||
{
|
||||
const { drop } = await openPicker('all')
|
||||
const option = drop.getByRole('button', { name: 'CC-D GNL', exact: true }).first()
|
||||
if (await option.count()) {
|
||||
await option.click()
|
||||
check('Dropdown selection commits via callback in its own panel only', (await log()).includes('all: property(layer, CC-D GNL)'), await log())
|
||||
} else check('Dropdown selection commits via callback in its own panel only', false, 'option not found')
|
||||
if (await page.locator('.hm-sw-property__drop').count()) await page.keyboard.press('Escape')
|
||||
}
|
||||
|
||||
// Tool toggle, group expand, close button — each panel keeps its own state.
|
||||
const tool = cell('none').locator('.hm-sw-property__tool')
|
||||
await tool.scrollIntoViewIfNeeded(); await tool.click()
|
||||
check('Tool toggle in one panel does not affect the others', await tool.getAttribute('aria-pressed') === 'true' && await cell('all').locator('.hm-sw-property__tool').getAttribute('aria-pressed') !== 'true')
|
||||
await tool.click()
|
||||
const expand = cell('none').locator('.hm-sw-property__expand').first()
|
||||
if (await expand.count()) {
|
||||
const h0 = (await cell('none').locator('.hm-sw-property').boundingBox()).height
|
||||
await expand.click(); const h1 = (await cell('none').locator('.hm-sw-property').boundingBox()).height
|
||||
const allH = (await cell('all').locator('.hm-sw-property').boundingBox()).height
|
||||
await expand.click(); const h2 = (await cell('none').locator('.hm-sw-property').boundingBox()).height
|
||||
check('Group expand is per panel and collapses back to 256', h1 > h0 && allH === 361 && h2 === 256, { h0, h1, h2, allH })
|
||||
}
|
||||
const close = cell('circle').getByRole('button', { name: '속성창 닫기', exact: true })
|
||||
await close.scrollIntoViewIfNeeded(); await close.click()
|
||||
check('Close button calls onClose of that panel', (await log()).includes('circle: close'), await log())
|
||||
await page.screenshot({ path: path.join(out, 'compare-after-interaction.png') })
|
||||
|
||||
// ---- Preservation of other Stories
|
||||
await page.setViewportSize({ width: 1920, height: 1000 })
|
||||
await goto('overview')
|
||||
check('Overview: Header 49px and Stage render', await page.locator('header.hm-sw-header').first().evaluate(h => h.getBoundingClientRect().height) === 49 && await page.locator('.sw-overview-stage').count() === 1)
|
||||
await goto('composed-controls')
|
||||
check('Dropdown Story renders composed controls', await page.locator('.sw-composed-controls').count() > 0)
|
||||
await goto('window-move-motion')
|
||||
check('Window Move Motion Story renders', await page.locator('[data-sw-window-move]').count() > 0)
|
||||
await goto('property-panel')
|
||||
const trigger = page.locator('.hm-sw-header [data-property-panel-trigger]')
|
||||
await trigger.click(); const opened = await page.locator('.hm-sw-property').count()
|
||||
await page.keyboard.press('Escape'); await page.keyboard.press('Escape')
|
||||
check('Property Panel (Header-connected) still opens; Escape closes', opened === 1 && await page.locator('.hm-sw-property').count() === 0, { opened })
|
||||
for (const id of ['property-shape-slot', 'property-fields', 'property-bar', 'layer-detail-input', 'k-guide']) {
|
||||
await goto(id); check(`render ${id}`, !(await page.locator('.sb-errordisplay').isVisible()))
|
||||
}
|
||||
check('no browser console or page errors', report.errors.length === 0, report.errors)
|
||||
}
|
||||
} finally {
|
||||
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)
|
||||
@@ -1,6 +1,8 @@
|
||||
// Current SW inventory after the approved stage-1 and Dropdown consolidations.
|
||||
export const retiredStage1Ids = ['responsive', 'text-stroke', 'dynamic-data', 'default', 'controls'].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.
|
||||
export const retiredPropertyPanelIds = ['property-panel-variants', 'property-panel-none', 'property-panel-all', 'property-panel-circle', 'property-panel-expanded'].map(id => `sw-header--${id}`)
|
||||
// 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 retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
|
||||
|
||||
@@ -1038,7 +1038,7 @@ function usePropertyMenus(record: (line: string) => void, setProperties: (update
|
||||
}
|
||||
}
|
||||
|
||||
function PropertyPanelDemo({ standalone = false }: { standalone?: boolean }) {
|
||||
function PropertyPanelDemo() {
|
||||
const [example, setExample] = useState<PropertyExample>("none")
|
||||
const [selection, setSelection] = useState(propertySelection("none"))
|
||||
const [properties, setProperties] = useState(propertyFixture("none"))
|
||||
@@ -1087,12 +1087,11 @@ function PropertyPanelDemo({ standalone = false }: { standalone?: boolean }) {
|
||||
</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>
|
||||
{standalone ? <SWTheme><SWHeaderPropertyPanel {...panel} onClose={() => record("close")} /></SWTheme>
|
||||
: <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 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>
|
||||
)
|
||||
}
|
||||
@@ -1102,11 +1101,6 @@ export const PropertyPanel: Story = {
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="속성창" description="Figma 4개 상태를 외부 데이터·그룹 펼침으로 구성합니다. 속성창 버튼(좁은 화면: 더보기)으로 열고 닫으세요. Enter/Blur 확정, Escape 취소, 다음 Escape 닫기." /><PropertyPanelDemo /></StoryPage>,
|
||||
}
|
||||
export const PropertyPanelVariants: Story = {
|
||||
name: "Property Panel Variants",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="속성창 4개 상태" description="동일 컴포넌트와 호스트 데이터입니다. 측정용 독립 패널." /><PropertyPanelDemo standalone /></StoryPage>,
|
||||
}
|
||||
export const KGuide: Story = {
|
||||
name: "K Guide",
|
||||
parameters: { controls: { disable: true } },
|
||||
@@ -1293,22 +1287,11 @@ function PropertyState({ example, shapeContent, onLog }: { example: PropertyExam
|
||||
onPropertyChange={(property, value) => { onLog?.(`property(${property.id}, ${value})`); setProperties(items => items.map(item => item.id === property.id ? { ...item, value, mixed: false, ...(item.id === "line" ? { linePattern: linePatternOf(value) } : {}) } : item)) }}
|
||||
onClose={() => onLog?.("close")} /></SWTheme>
|
||||
}
|
||||
function PropertyStateStory({ example }: { example: PropertyExample }) {
|
||||
const state = PROPERTY_STATES.find(item => item.example === example)!
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
return <StoryPage><StoryPageHeader kicker={KICKER} title={`속성창 · ${state.title}`} description={`Figma ${state.node} (${state.size}px). 기존 SWHeaderPropertyPanel 하나에 호스트 데이터만 바꿔 표시합니다. Enter/Blur 확정, Escape 취소.`} />
|
||||
<p className="sw-story-log" data-property-log="">{log.join(" → ") || "Callback 없음"}</p>
|
||||
<div className="sw-story-property-state" data-property-state={example}><PropertyState example={example} onLog={line => setLog(lines => [...lines.slice(-9), line])} /></div>
|
||||
</StoryPage>
|
||||
}
|
||||
export const PropertyPanelNone: Story = { name: "Property Panel · 선택 없음", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="none" /> }
|
||||
export const PropertyPanelAll: Story = { name: "Property Panel · 전체 선택", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="all" /> }
|
||||
export const PropertyPanelCircle: Story = { name: "Property Panel · 원 1개", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="circle" /> }
|
||||
export const PropertyPanelExpanded: Story = { name: "Property Panel · 모두 펼침", parameters: { controls: { disable: true } }, render: () => <PropertyStateStory example="expanded" /> }
|
||||
|
||||
/** Design check only: a real header shows one panel for the current selection. */
|
||||
/** Design check only: a real header shows one panel for the current selection.
|
||||
2 columns × 2 rows at the panels' own size; the Inner scrolls instead of shrinking or wrapping them. */
|
||||
function PropertyCompare() {
|
||||
const [sizes, setSizes] = useState<Record<string, string>>({})
|
||||
const [log, setLog] = useState<string[]>([])
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
useEffect(() => {
|
||||
const measure = () => setSizes(Object.fromEntries(Array.from(ref.current?.querySelectorAll<HTMLElement>("[data-compare]") ?? []).map(cell => {
|
||||
@@ -1320,17 +1303,22 @@ function PropertyCompare() {
|
||||
ref.current?.querySelectorAll(".hm-sw-property").forEach(panel => observer.observe(panel))
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
return <div ref={ref} className="sw-story-compare">
|
||||
{PROPERTY_STATES.map(state => <figure key={state.example} className="sw-story-compare__cell" data-compare={state.example}>
|
||||
<figcaption><strong>{state.title}</strong><span>Figma {state.node} · {state.size}</span><span data-measured="">측정 {sizes[state.example] ?? "-"}</span></figcaption>
|
||||
<PropertyState example={state.example} />
|
||||
</figure>)}
|
||||
</div>
|
||||
return <>
|
||||
<p className="sw-story-log" data-property-log="" aria-live="polite">{log.join(" → ") || "Callback 없음"}</p>
|
||||
<div className="sw-story-compare-scroll" data-compare-scroll="">
|
||||
<div ref={ref} className="sw-story-compare sw-story-compare--grid">
|
||||
{PROPERTY_STATES.map(state => <figure key={state.example} className="sw-story-compare__cell" data-compare={state.example}>
|
||||
<figcaption><strong>{state.title}</strong><span>Figma {state.node} · {state.size}</span><span data-measured="">측정 {sizes[state.example] ?? "-"}</span></figcaption>
|
||||
<PropertyState example={state.example} onLog={line => setLog(lines => [...lines.slice(-7), `${state.example}: ${line}`])} />
|
||||
</figure>)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
export const PropertyPanelCompare: Story = {
|
||||
name: "Property Panel · 4개 비교",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => <StoryPage className="sw-story-followup"><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. */
|
||||
|
||||
@@ -45,6 +45,17 @@
|
||||
.sw-story-compare { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 24px; }
|
||||
.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; }
|
||||
/* Property panel 4-state comparison (2026-10-19): 2 columns × 2 rows at the panels' own size inside a
|
||||
viewport-high page. Only the Inner scroller scrolls (both axes); panels are never shrunk or wrapped. */
|
||||
.sw-story-compare-page.sb-page { height: 100dvh; min-height: 0; }
|
||||
.sw-story-compare-page > .sb-page-inner { height: 100%; display: flex; flex-direction: column; }
|
||||
.sw-story-compare-page .sb-page-header { flex: 0 0 auto; }
|
||||
.sw-story-compare-page .sw-story-log { flex: 0 0 auto; margin-top: 12px; }
|
||||
.sw-story-compare-scroll { flex: 1 1 0; min-height: 0; overflow: auto; overscroll-behavior: contain; }
|
||||
.sw-story-compare.sw-story-compare--grid { display: grid; grid-template-columns: repeat(2, max-content); align-items: start; gap: 24px; width: max-content; }
|
||||
/* Placement-only exception, same as the Overview Stage: the product clamps a panel to the viewport
|
||||
height; here the scroller provides the room so the 754px panel keeps its size. */
|
||||
.sw-story-compare--grid .hm-sw-property { max-height: none; }
|
||||
.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 > div { display: flex; align-items: center; gap: 8px; }
|
||||
|
||||