refactor(storybook): fold SW Header Options stories into SW/Header
Remove the separate SW/Header/Options sidebar item. Its examples move to two
Stories directly under SW/Header (src/sw/sw-header-open.stories.tsx):
- Document Open: Open placement, label, 10-width responsive, composed bar,
composed (controls) responsive
- Hidden Sub Controls: document New / Open and layer freeze / lock removal
No public API or component change. Tests and inventory follow the new IDs
(integrated 97/97, existing Stories pixel-identical 28/28, unused-removal
83/83). The two remaining overview failures are pre-existing historical
assertions (also failing on 5faa1f1), not new defects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -13,6 +13,34 @@
|
|||||||
],
|
],
|
||||||
"exportName": "ComposedControls"
|
"exportName": "ComposedControls"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "story",
|
||||||
|
"subtype": "story",
|
||||||
|
"id": "sw-header--document-open",
|
||||||
|
"name": "Document Open",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"importPath": "./src/sw/sw-header-open.stories.tsx",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
],
|
||||||
|
"exportName": "DocumentOpen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "story",
|
||||||
|
"subtype": "story",
|
||||||
|
"id": "sw-header--hidden-sub-controls",
|
||||||
|
"name": "Hidden Sub Controls",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"importPath": "./src/sw/sw-header-open.stories.tsx",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
],
|
||||||
|
"exportName": "HiddenSubControls"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "story",
|
"type": "story",
|
||||||
"subtype": "story",
|
"subtype": "story",
|
||||||
|
|||||||
@@ -644,6 +644,8 @@
|
|||||||
"browser": "155.0.8059.80",
|
"browser": "155.0.8059.80",
|
||||||
"managerOrder": [
|
"managerOrder": [
|
||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
|
"sw-header--document-open",
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
"sw-header--overview",
|
"sw-header--overview",
|
||||||
"sw-header--interactive-states",
|
"sw-header--interactive-states",
|
||||||
"sw-header--window-move-motion",
|
"sw-header--window-move-motion",
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
[
|
[
|
||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
|
"sw-header--document-open",
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
"sw-header--overview",
|
"sw-header--overview",
|
||||||
"sw-header--interactive-states",
|
"sw-header--interactive-states",
|
||||||
"sw-header--window-move-motion",
|
"sw-header--window-move-motion",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 226 KiB |
@@ -3,6 +3,14 @@
|
|||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
"Dropdown"
|
"Dropdown"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-open",
|
||||||
|
"Document Open"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
|
"Hidden Sub Controls"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"sw-header--overview",
|
"sw-header--overview",
|
||||||
"Overview"
|
"Overview"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"baseCommit": "c8e5fbeebc26fba92998fc5ddcd28c6c110e452d",
|
"baseCommit": "c8e5fbeebc26fba92998fc5ddcd28c6c110e452d",
|
||||||
"checks": [
|
"checks": [
|
||||||
{
|
{
|
||||||
"name": "inventory: 19 -> 10 SW/Header Stories, 338 -> 329 total plus only the 7 integrated-improvement Stories",
|
"name": "inventory: 19 -> 10 retained SW/Header Stories, 338 -> 329 total plus only the integrated-improvement Stories",
|
||||||
"status": "PASS",
|
"status": "PASS",
|
||||||
"data": {
|
"data": {
|
||||||
"before": [
|
"before": [
|
||||||
@@ -10,14 +10,22 @@
|
|||||||
19
|
19
|
||||||
],
|
],
|
||||||
"after": [
|
"after": [
|
||||||
336,
|
334,
|
||||||
10
|
12
|
||||||
],
|
],
|
||||||
"sw": [
|
"sw": [
|
||||||
[
|
[
|
||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
"Dropdown"
|
"Dropdown"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-open",
|
||||||
|
"Document Open"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
|
"Hidden Sub Controls"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"sw-header--detail-buttons",
|
"sw-header--detail-buttons",
|
||||||
"Detail Buttons (Shared States)"
|
"Detail Buttons (Shared States)"
|
||||||
@@ -56,10 +64,8 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"added": [
|
"added": [
|
||||||
"sw-header-options--open-placement",
|
"sw-header--document-open",
|
||||||
"sw-header-options--hidden-sub-controls",
|
"sw-header--hidden-sub-controls",
|
||||||
"sw-header-options--composed-open",
|
|
||||||
"sw-header-options--composed-responsive",
|
|
||||||
"sw-popup--custom-content",
|
"sw-popup--custom-content",
|
||||||
"sw-popup--multiple-popups",
|
"sw-popup--multiple-popups",
|
||||||
"sw-tool--tool-buttons"
|
"sw-tool--tool-buttons"
|
||||||
@@ -112,7 +118,7 @@
|
|||||||
"status": "PASS",
|
"status": "PASS",
|
||||||
"data": {
|
"data": {
|
||||||
"unexpected": [],
|
"unexpected": [],
|
||||||
"integrated": 19
|
"integrated": 23
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -123,6 +129,14 @@
|
|||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
"Dropdown"
|
"Dropdown"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-open",
|
||||||
|
"Document Open"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
|
"Hidden Sub Controls"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"sw-header--overview",
|
"sw-header--overview",
|
||||||
"Overview"
|
"Overview"
|
||||||
@@ -180,6 +194,14 @@
|
|||||||
"name": "render sw-header--composed-controls",
|
"name": "render sw-header--composed-controls",
|
||||||
"status": "PASS"
|
"status": "PASS"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "render sw-header--document-open",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "render sw-header--hidden-sub-controls",
|
||||||
|
"status": "PASS"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "render sw-header--detail-buttons",
|
"name": "render sw-header--detail-buttons",
|
||||||
"status": "PASS"
|
"status": "PASS"
|
||||||
@@ -894,14 +916,22 @@
|
|||||||
19
|
19
|
||||||
],
|
],
|
||||||
"after": [
|
"after": [
|
||||||
336,
|
334,
|
||||||
10
|
12
|
||||||
],
|
],
|
||||||
"sw": [
|
"sw": [
|
||||||
[
|
[
|
||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
"Dropdown"
|
"Dropdown"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-open",
|
||||||
|
"Document Open"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
|
"Hidden Sub Controls"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"sw-header--detail-buttons",
|
"sw-header--detail-buttons",
|
||||||
"Detail Buttons (Shared States)"
|
"Detail Buttons (Shared States)"
|
||||||
@@ -945,6 +975,14 @@
|
|||||||
"sw-header--composed-controls",
|
"sw-header--composed-controls",
|
||||||
"Dropdown"
|
"Dropdown"
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
"sw-header--document-open",
|
||||||
|
"Document Open"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"sw-header--hidden-sub-controls",
|
||||||
|
"Hidden Sub Controls"
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"sw-header--overview",
|
"sw-header--overview",
|
||||||
"Overview"
|
"Overview"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"total": 336,
|
"total": 334,
|
||||||
"sw": [
|
"sw": [
|
||||||
{
|
{
|
||||||
"id": "sw-header--composed-controls",
|
"id": "sw-header--composed-controls",
|
||||||
@@ -11,6 +11,26 @@
|
|||||||
"manifest"
|
"manifest"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--document-open",
|
||||||
|
"name": "Document Open",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sw-header--hidden-sub-controls",
|
||||||
|
"name": "Hidden Sub Controls",
|
||||||
|
"title": "SW/Header",
|
||||||
|
"tags": [
|
||||||
|
"dev",
|
||||||
|
"test",
|
||||||
|
"manifest"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "sw-header--detail-buttons",
|
"id": "sw-header--detail-buttons",
|
||||||
"name": "Detail Buttons (Shared States)",
|
"name": "Detail Buttons (Shared States)",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
@@ -510,14 +510,14 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"cHome": {
|
"cHome": {
|
||||||
"x": 360,
|
"x": 360,
|
||||||
"y": 285.5,
|
"y": 6118.5,
|
||||||
"w": 80,
|
"w": 80,
|
||||||
"h": 48,
|
"h": 48,
|
||||||
"right": 440
|
"right": 440
|
||||||
},
|
},
|
||||||
"cOpen": {
|
"cOpen": {
|
||||||
"x": 425,
|
"x": 425,
|
||||||
"y": 285.5,
|
"y": 6118.5,
|
||||||
"w": 58.265625,
|
"w": 58.265625,
|
||||||
"h": 48,
|
"h": 48,
|
||||||
"right": 483.265625
|
"right": 483.265625
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 239 KiB |
@@ -1,6 +1,6 @@
|
|||||||
# SW Header 사용 가이드
|
# SW Header 사용 가이드
|
||||||
|
|
||||||
> **0.2.0 통합 개선(2026-10-19):** 로고 오른쪽 파일 열기(`documentOpenPlacement="header"`, `documentOpenLabel` 재사용, 실측 기반 More 이동), `leading` 슬롯, 하위 항목 숨김(`hiddenControls`의 `documentNew`·`documentOpen`·`layerFreeze`·`layerLock`), 도구 막대 `SWToolButton` / `SWToolGroup`, 속성창 프레임 `SWPopup`(자유 `children`)과 `SWHeaderPropertyPanel`의 `children`·`title`을 추가했습니다. API·예제는 패키지 [AI 카탈로그](../../packages/sw/ai/COMPONENT_CATALOG.md)와 [조합 패턴](../../packages/sw/ai/COMPOSITION_PATTERNS.md), Story는 `SW / Header / Options`·`SW / Tool`·`SW / Popup`, 검증은 [통합 개선 기록](./sw-integrated-improvement-2026-10-19.md)을 따릅니다.
|
> **0.2.0 통합 개선(2026-10-19):** 로고 오른쪽 파일 열기(`documentOpenPlacement="header"`, `documentOpenLabel` 재사용, 실측 기반 More 이동), `leading` 슬롯, 하위 항목 숨김(`hiddenControls`의 `documentNew`·`documentOpen`·`layerFreeze`·`layerLock`), 도구 막대 `SWToolButton` / `SWToolGroup`, 속성창 프레임 `SWPopup`(자유 `children`)과 `SWHeaderPropertyPanel`의 `children`·`title`을 추가했습니다. API·예제는 패키지 [AI 카탈로그](../../packages/sw/ai/COMPONENT_CATALOG.md)와 [조합 패턴](../../packages/sw/ai/COMPOSITION_PATTERNS.md), Story는 `SW / Header`의 Document Open·Hidden Sub Controls, `SW / Tool`, `SW / Popup`, 검증은 [통합 개선 기록](./sw-integrated-improvement-2026-10-19.md)을 따릅니다.
|
||||||
|
|
||||||
> **현재 기준 색인(2026-10-19 1차 정리):** 아래 날짜별 안내 중 최신 항목이 같은 주제의 이전 설명보다 우선합니다. 속성 필드 13개·필드 Dropdown(`picker`·`menus`·`decimals`)은 [속성창·K 가이드 추가 연결](#속성창k-가이드-추가-연결-2026-10-16)의 2026-10-19 항목, 표시·숨김(`hiddenControls`)·메뉴 순서(`menuOrder`)·속성창 4상태·형상 Slot은 [3차 절](#구성요소-표시메뉴-순서속성창-4상태형상-slot-2026-10-16-3차), 속성Bar·`controls`·Colorize는 [후속 절](#후속-5개-요구사항-연결-2026-10-16)을 봅니다. 현재 Storybook 33개 목록은 [§12](#12-storybook)와 [1차 정리 보고](./sw-first-pass-cleanup-review-2026-10-19.md)에 있습니다.
|
> **현재 기준 색인(2026-10-19 1차 정리):** 아래 날짜별 안내 중 최신 항목이 같은 주제의 이전 설명보다 우선합니다. 속성 필드 13개·필드 Dropdown(`picker`·`menus`·`decimals`)은 [속성창·K 가이드 추가 연결](#속성창k-가이드-추가-연결-2026-10-16)의 2026-10-19 항목, 표시·숨김(`hiddenControls`)·메뉴 순서(`menuOrder`)·속성창 4상태·형상 Slot은 [3차 절](#구성요소-표시메뉴-순서속성창-4상태형상-slot-2026-10-16-3차), 속성Bar·`controls`·Colorize는 [후속 절](#후속-5개-요구사항-연결-2026-10-16)을 봅니다. 현재 Storybook 33개 목록은 [§12](#12-storybook)와 [1차 정리 보고](./sw-first-pass-cleanup-review-2026-10-19.md)에 있습니다.
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
| 5 SWPopup | 속성창 프레임을 `SWPopupFrame`(내부)으로 분리해 `SWPopup`과 `SWHeaderPropertyPanel`이 같은 DOM 사용. `children`·`title`·`autoFocus`, `selection`/`properties`/`groups` 선택형. Escape 스택(안 = 그 Popup, 밖 = 최근 사용 Popup 하나, `defaultPrevented` 존중), autoFocus 시 안에서 닫히면 Focus 복귀 | PASS |
|
| 5 SWPopup | 속성창 프레임을 `SWPopupFrame`(내부)으로 분리해 `SWPopup`과 `SWHeaderPropertyPanel`이 같은 DOM 사용. `children`·`title`·`autoFocus`, `selection`/`properties`/`groups` 선택형. Escape 스택(안 = 그 Popup, 밖 = 최근 사용 Popup 하나, `defaultPrevented` 존중), autoFocus 시 안에서 닫히면 Focus 복귀 | PASS |
|
||||||
| 6 Popup 안 Dropdown | 실측: 짧은 Popup에서 레이어 목록이 본문 경계에서 잘림 확인 → 속성창 Picker의 고정 위치 계산을 `useSWFixedDropdownPosition`으로 추출해 Picker와 공개 Dropdown 5종이 공유(중복 구현 없음). `SWPopupSection` / `SWPopupRow` 추가 | PASS |
|
| 6 Popup 안 Dropdown | 실측: 짧은 Popup에서 레이어 목록이 본문 경계에서 잘림 확인 → 속성창 Picker의 고정 위치 계산을 `useSWFixedDropdownPosition`으로 추출해 Picker와 공개 Dropdown 5종이 공유(중복 구현 없음). `SWPopupSection` / `SWPopupRow` 추가 | PASS |
|
||||||
| 7 조합형 반응형 | 새 More 로직 없음. `SWHeader`의 `controls`·`documentOpenPlacement`·`leading`·`tools`로 조합형 구성이 기존 More·실측 판단을 그대로 사용. `SWTheme` 직접 배치에는 More 없음(문서화) | PARTIAL(도구 막대 자체는 More로 이동하지 않음, §6) |
|
| 7 조합형 반응형 | 새 More 로직 없음. `SWHeader`의 `controls`·`documentOpenPlacement`·`leading`·`tools`로 조합형 구성이 기존 More·실측 판단을 그대로 사용. `SWTheme` 직접 배치에는 More 없음(문서화) | PARTIAL(도구 막대 자체는 More로 이동하지 않음, §6) |
|
||||||
| 8 문서·Storybook·소비 검증 | AI 가이드 4종·예제 E/F·README·사용 가이드, Story 3개 파일(7 Story), 독립 소비 프로젝트(SW + HM UI) | PASS |
|
| 8 문서·Storybook·소비 검증 | AI 가이드 4종·예제 E/F·README·사용 가이드, Story 3개 파일(5 Story: `SW/Header`의 Document Open·Hidden Sub Controls, `SW/Tool` 1, `SW/Popup` 2. 별도 Options 메뉴 없음), 독립 소비 프로젝트(SW + HM UI) | PASS |
|
||||||
|
|
||||||
## 2. 추가·변경 공개 API (모두 선택, 기본 동작 동일)
|
## 2. 추가·변경 공개 API (모두 선택, 기본 동작 동일)
|
||||||
|
|
||||||
@@ -74,5 +74,6 @@
|
|||||||
| [ai-docs.check.mjs](../../packages/sw/tests/ai-docs.check.mjs) | 21/21 PASS(예제 E·F 포함) |
|
| [ai-docs.check.mjs](../../packages/sw/tests/ai-docs.check.mjs) | 21/21 PASS(예제 E·F 포함) |
|
||||||
| [integrated-visual.browser.mjs](../../packages/sw/tests/integrated-visual.browser.mjs) | 28/28 픽셀 동일 |
|
| [integrated-visual.browser.mjs](../../packages/sw/tests/integrated-visual.browser.mjs) | 28/28 픽셀 동일 |
|
||||||
| [build-reproducibility.mjs](../../packages/sw/tests/build-reproducibility.mjs) | PASS: LF 2경로 × 2회 dist·Tarball 동일(`e98cc5ed…`), CRLF 복사본 dist 동일, 비-LF 파일 0 |
|
| [build-reproducibility.mjs](../../packages/sw/tests/build-reproducibility.mjs) | PASS: LF 2경로 × 2회 dist·Tarball 동일(`e98cc5ed…`), CRLF 복사본 dist 동일, 비-LF 파일 0 |
|
||||||
| storybook-unused-removal / overview(기존 회귀) | 처음 실행 78/81 · 51/54: 6건 중 4건은 이 작업이 의도적으로 바꾼 범위 단언(Story 수 329 → 336, SW 소스·AI 문서), 2건은 변경 전 기준선에서도 실패하던 overview 과거 단언. 4건은 검사 의미를 유지해 범위를 갱신(SW/Header 10개·다른 항목 동일, 추가는 승인된 7개 Story만, 다른 패키지·설정·자산 무변경, SW 변경은 통합 개선 파일 목록 안에서만) → **81/81 · 52/54**. 남은 2건은 기준선과 동일 |
|
| storybook-unused-removal / overview(기존 회귀) | 처음 실행 78/81 · 51/54: 6건 중 4건은 이 작업이 의도적으로 바꾼 범위 단언(Story 수 증가, SW 소스·AI 문서), 2건은 변경 전 기준선에서도 실패하던 overview 과거 단언. 4건은 검사 의미를 유지해 범위를 갱신(SW/Header 10개·다른 항목 동일, 추가는 승인된 5개 Story만, 다른 패키지·설정·자산 무변경, SW 변경은 통합 개선 파일 목록 안에서만) → **81/81 · 52/54**. Options 메뉴를 `SW/Header`로 통합한 뒤(Story 2개 렌더 검사 추가) **83/83 · 52/54** |
|
||||||
|
| overview 남은 실패 2건 — **기존 실패, 신규 결함 아님** | "24 retained Story IDs…", "retained non-Overview declarations byte-identical…": 이미 은퇴한 Dropdown Story 시점(24개, 기준 Commit `50bf5bd`)을 고정 비교하는 과거 단언입니다. 이 작업을 시작하기 전 기준선(HEAD `5faa1f1`)에서도 같은 2건이 실패했습니다([baseline-overview.log](./integrated-improvement-qa/baseline-overview.log)). 현재 인벤토리는 storybook-unused-removal이 검증하며, overview 기능 검사 52건은 모두 PASS |
|
||||||
| font-weight 정책 테스트 | 3/3 PASS |
|
| font-weight 정책 테스트 | 3/3 PASS |
|
||||||
@@ -33,7 +33,7 @@ const log = () => page.locator('[data-callback-log]').first().textContent()
|
|||||||
const rect = locator => locator.evaluate(e => { const r = e.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height, right: r.right } })
|
const rect = locator => locator.evaluate(e => { const r = e.getBoundingClientRect(); return { x: r.x, y: r.y, w: r.width, h: r.height, right: r.right } })
|
||||||
|
|
||||||
if (sections.includes('options')) {
|
if (sections.includes('options')) {
|
||||||
await goto('sw-header-options--open-placement')
|
await goto('sw-header--document-open')
|
||||||
const frames = page.locator('.sw-story-frame')
|
const frames = page.locator('.sw-story-frame')
|
||||||
// Default placement unchanged: no header Open, dropdown footer keeps + New / Open.
|
// Default placement unchanged: no header Open, dropdown footer keeps + New / Open.
|
||||||
const plain = frames.nth(0).locator('header.hm-sw-header')
|
const plain = frames.nth(0).locator('header.hm-sw-header')
|
||||||
@@ -63,7 +63,7 @@ if (sections.includes('options')) {
|
|||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
await open.click()
|
await open.click()
|
||||||
check('header: Open calls onDocumentOpen', (await log()).includes('onDocumentOpen'))
|
check('header: Open calls onDocumentOpen', (await log()).includes('onDocumentOpen'))
|
||||||
check('documentOpenLabel is the Open text', await frames.nth(2).locator('[data-document-open]').textContent() === '파일 열기')
|
check('documentOpenLabel is the Open text', await page.locator('[data-open-label] [data-document-open]').textContent() === '파일 열기')
|
||||||
|
|
||||||
// Responsive: measured room. No overflow anywhere; Open is either shown or reachable in More.
|
// Responsive: measured room. No overflow anywhere; Open is either shown or reachable in More.
|
||||||
const widths = []
|
const widths = []
|
||||||
@@ -106,7 +106,7 @@ if (sections.includes('options')) {
|
|||||||
const flips = trace.slice(1).filter((step, index) => step[1] !== trace[index][1]).length
|
const flips = trace.slice(1).filter((step, index) => step[1] !== trace[index][1]).length
|
||||||
check('resize: collapsed Open returns once when the header grows (measured, with hysteresis)', trace[0][1] === 0 && trace.at(-1)[1] === 1 && flips === 1, trace)
|
check('resize: collapsed Open returns once when the header grows (measured, with hysteresis)', trace[0][1] === 0 && trace.at(-1)[1] === 1 && flips === 1, trace)
|
||||||
|
|
||||||
await goto('sw-header-options--hidden-sub-controls')
|
await goto('sw-header--hidden-sub-controls')
|
||||||
const onlyOpen = page.locator('[data-hidden="new"] .hm-sw-header__document-footer')
|
const onlyOpen = page.locator('[data-hidden="new"] .hm-sw-header__document-footer')
|
||||||
const footer = await rect(onlyOpen), action = await rect(onlyOpen.locator('.hm-sw-header__document-action'))
|
const footer = await rect(onlyOpen), action = await rect(onlyOpen.locator('.hm-sw-header__document-action'))
|
||||||
check('hidden documentNew: footer keeps Open on its right side', JSON.stringify(await onlyOpen.locator('.hm-sw-header__document-action').allTextContents()) === '["Open"]' && Math.abs(footer.right - 8 - action.right) < 1, { footer, action })
|
check('hidden documentNew: footer keeps Open on its right side', JSON.stringify(await onlyOpen.locator('.hm-sw-header__document-action').allTextContents()) === '["Open"]' && Math.abs(footer.right - 8 - action.right) < 1, { footer, action })
|
||||||
@@ -121,7 +121,7 @@ if (sections.includes('options')) {
|
|||||||
await page.locator('[data-hidden="layer-states"]').screenshot({ path: path.join(out, 'hidden-layer-states.png') })
|
await page.locator('[data-hidden="layer-states"]').screenshot({ path: path.join(out, 'hidden-layer-states.png') })
|
||||||
await page.locator('[data-hidden="new"]').screenshot({ path: path.join(out, 'hidden-document-new.png') })
|
await page.locator('[data-hidden="new"]').screenshot({ path: path.join(out, 'hidden-document-new.png') })
|
||||||
|
|
||||||
await goto('sw-header-options--composed-open')
|
await goto('sw-header--document-open')
|
||||||
const bar = page.locator('[data-composed-open]')
|
const bar = page.locator('[data-composed-open]')
|
||||||
const cHome = await rect(bar.locator('.hm-sw-header__home')), cOpen = await rect(bar.locator('.hm-sw-header__document[data-tool] button')), cDoc = bar.locator('.hm-sw-header__document:not([data-tool]) .hm-sw-header__document-button')
|
const cHome = await rect(bar.locator('.hm-sw-header__home')), cOpen = await rect(bar.locator('.hm-sw-header__document[data-tool] button')), cDoc = bar.locator('.hm-sw-header__document:not([data-tool]) .hm-sw-header__document-button')
|
||||||
check('composed: Open right of the logo at the same junction offset as SWHeader', Math.abs(cOpen.x - cHome.right - report.data.junction) < 0.6 && cOpen.h === 48, { cHome, cOpen })
|
check('composed: Open right of the logo at the same junction offset as SWHeader', Math.abs(cOpen.x - cHome.right - report.data.junction) < 0.6 && cOpen.h === 48, { cHome, cOpen })
|
||||||
@@ -135,7 +135,7 @@ if (sections.includes('options')) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sections.includes('options') || sections.includes('composed')) {
|
if (sections.includes('options') || sections.includes('composed')) {
|
||||||
await goto('sw-header-options--composed-responsive')
|
await goto('sw-header--document-open')
|
||||||
const rows = []
|
const rows = []
|
||||||
for (const item of await page.locator('[data-composed-responsive] > [data-width]').all()) {
|
for (const item of await page.locator('[data-composed-responsive] > [data-width]').all()) {
|
||||||
const width = Number(await item.getAttribute('data-width'))
|
const width = Number(await item.getAttribute('data-width'))
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ export const swStoryEntries = index => Object.values(index.entries).filter(entry
|
|||||||
export const retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
|
export const retainedInventory = entries => entries.filter(entry => ![...retiredStage1Ids, ...retiredDropdownIds].includes(entry.id))
|
||||||
.map(entry => entry.id === 'sw-header--composed-controls' ? { ...entry, name: 'Dropdown', title: 'SW/Header' } : entry)
|
.map(entry => entry.id === 'sw-header--composed-controls' ? { ...entry, name: 'Dropdown', title: 'SW/Header' } : entry)
|
||||||
export const inventorySignature = entries => entries.map(entry => [entry.id, entry.name, entry.tags]).sort((a, b) => a[0].localeCompare(b[0]))
|
export const inventorySignature = entries => entries.map(entry => [entry.id, entry.name, entry.tags]).sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
// 2026-10-19 SW integrated improvement (0.2.0): the only Stories added after the unused-demo removal, under their
|
// 2026-10-19 SW integrated improvement (0.2.0): the only Stories added after the unused-demo removal (two under
|
||||||
// own titles (SW/Header keeps its 10 Stories), and the only files of the package it changed.
|
// SW/Header next to its 10 retained Stories, the rest under SW/Tool and SW/Popup), and the only package files it changed.
|
||||||
export const integratedStoryIds = ['sw-header-options--open-placement', 'sw-header-options--hidden-sub-controls', 'sw-header-options--composed-open', 'sw-header-options--composed-responsive', 'sw-tool--tool-buttons', 'sw-popup--custom-content', 'sw-popup--multiple-popups']
|
// The two Header Stories sit directly under SW/Header (no separate Options menu), in src/sw/sw-header-open.stories.tsx.
|
||||||
|
export const integratedStoryIds = ['sw-header--document-open', 'sw-header--hidden-sub-controls', 'sw-tool--tool-buttons', 'sw-popup--custom-content', 'sw-popup--multiple-popups']
|
||||||
export const integratedChangedFiles = [
|
export const integratedChangedFiles = [
|
||||||
'packages/sw/README.md', 'packages/sw/package.json', 'packages/sw/build.mjs',
|
'packages/sw/README.md', 'packages/sw/package.json', 'packages/sw/build.mjs',
|
||||||
'packages/sw/ai/AI_USAGE.md', 'packages/sw/ai/COMPONENT_CATALOG.md', 'packages/sw/ai/COMPOSITION_PATTERNS.md', 'packages/sw/ai/DESIGN_RULES.md',
|
'packages/sw/ai/AI_USAGE.md', 'packages/sw/ai/COMPONENT_CATALOG.md', 'packages/sw/ai/COMPOSITION_PATTERNS.md', 'packages/sw/ai/DESIGN_RULES.md',
|
||||||
|
'packages/sw/ai/examples/ExampleE-CustomPopup.tsx', 'packages/sw/ai/examples/ExampleF-ViewerToolbar.tsx',
|
||||||
'packages/sw/src/index.ts', 'packages/sw/src/theme/sw.css',
|
'packages/sw/src/index.ts', 'packages/sw/src/theme/sw.css',
|
||||||
|
'packages/sw/src/components/sw-popup/SWPopup.tsx', 'packages/sw/src/components/sw-tool/SWTool.tsx',
|
||||||
'packages/sw/src/components/sw-header/SWHeader.tsx', 'packages/sw/src/components/sw-header/index.ts', 'packages/sw/src/components/sw-header/types.ts',
|
'packages/sw/src/components/sw-header/SWHeader.tsx', 'packages/sw/src/components/sw-header/index.ts', 'packages/sw/src/components/sw-header/types.ts',
|
||||||
...['SWHeaderDocumentTitle', 'SWHeaderDropdown', 'SWHeaderFields', 'SWHeaderLayerMenu', 'SWHeaderLineStyleMenu', 'SWHeaderMore', 'SWHeaderPropertyFields', 'SWHeaderPropertyPanel'].map(name => `packages/sw/src/components/sw-header/components/${name}.tsx`),
|
...['SWHeaderDocumentTitle', 'SWHeaderDropdown', 'SWHeaderFields', 'SWHeaderLayerMenu', 'SWHeaderLineStyleMenu', 'SWHeaderMore', 'SWHeaderPropertyFields', 'SWHeaderPropertyPanel'].map(name => `packages/sw/src/components/sw-header/components/${name}.tsx`),
|
||||||
]
|
]
|
||||||
@@ -26,12 +26,13 @@ const beforeSW = swStoryEntries(beforeIndex), afterSW = swStoryEntries(afterInde
|
|||||||
const expected = beforeSW.filter(e => !retiredUnusedDemoIds.includes(e.id))
|
const expected = beforeSW.filter(e => !retiredUnusedDemoIds.includes(e.id))
|
||||||
report.data.inventory = { before: [stories(beforeIndex).length, beforeSW.length], after: [stories(afterIndex).length, afterSW.length], sw: afterSW.map(e => [e.id, e.name]) }
|
report.data.inventory = { before: [stories(beforeIndex).length, beforeSW.length], after: [stories(afterIndex).length, afterSW.length], sw: afterSW.map(e => [e.id, e.name]) }
|
||||||
await writeFile(path.join(out, 'story-inventory-after.json'), JSON.stringify({ total: stories(afterIndex).length, sw: afterSW.map(e => ({ id: e.id, name: e.name, title: e.title, tags: e.tags })) }, null, 2) + '\n')
|
await writeFile(path.join(out, 'story-inventory-after.json'), JSON.stringify({ total: stories(afterIndex).length, sw: afterSW.map(e => ({ id: e.id, name: e.name, title: e.title, tags: e.tags })) }, null, 2) + '\n')
|
||||||
// 2026-10-19 integrated improvement: 329 + its 7 Stories under SW/Header/Options, SW/Tool and SW/Popup (SW/Header stays 10).
|
// 2026-10-19 integrated improvement: 329 + its 5 Stories (2 under SW/Header, SW/Tool, SW/Popup); the 10 retained SW/Header Stories stay.
|
||||||
const added = stories(afterIndex).filter(e => !beforeIndex.entries[e.id])
|
const added = stories(afterIndex).filter(e => !beforeIndex.entries[e.id])
|
||||||
check('inventory: 19 -> 10 SW/Header Stories, 338 -> 329 total plus only the 7 integrated-improvement Stories', beforeSW.length === 19 && afterSW.length === 10 && stories(beforeIndex).length === 338 && stories(afterIndex).length === 329 + integratedStoryIds.length && JSON.stringify(added.map(e => e.id).sort()) === JSON.stringify([...integratedStoryIds].sort()), { ...report.data.inventory, added: added.map(e => e.id) })
|
const retainedSW = afterSW.filter(e => !integratedStoryIds.includes(e.id))
|
||||||
|
check('inventory: 19 -> 10 retained SW/Header Stories, 338 -> 329 total plus only the integrated-improvement Stories', beforeSW.length === 19 && retainedSW.length === 10 && stories(beforeIndex).length === 338 && stories(afterIndex).length === 329 + integratedStoryIds.length && JSON.stringify(added.map(e => e.id).sort()) === JSON.stringify([...integratedStoryIds].sort()), { ...report.data.inventory, added: added.map(e => e.id) })
|
||||||
check('inventory: the nine requested IDs existed before and are absent now', retiredUnusedDemoIds.length === 9 && retiredUnusedDemoIds.every(id => beforeIndex.entries[id] && !afterIndex.entries[id]))
|
check('inventory: the nine requested IDs existed before and are absent now', retiredUnusedDemoIds.length === 9 && retiredUnusedDemoIds.every(id => beforeIndex.entries[id] && !afterIndex.entries[id]))
|
||||||
check('inventory: retained SW IDs, names, titles, tags and order unchanged', sig(expected) === sig(afterSW), afterSW.map(e => e.id))
|
check('inventory: retained SW IDs, names, titles, tags and order unchanged', sig(expected) === sig(retainedSW), retainedSW.map(e => e.id))
|
||||||
const others = index => Object.values(index.entries).filter(e => !retiredUnusedDemoIds.includes(e.id) && !integratedStoryIds.includes(e.id) && !/^sw-(header-options|tool|popup)--docs$/.test(e.id))
|
const others = index => Object.values(index.entries).filter(e => !retiredUnusedDemoIds.includes(e.id) && !integratedStoryIds.includes(e.id) && !/^sw-(tool|popup)--docs$/.test(e.id))
|
||||||
check('inventory: every non-removed index entry identical (docs and other packages included)', JSON.stringify(others(beforeIndex)) === JSON.stringify(others(afterIndex)))
|
check('inventory: every non-removed index entry identical (docs and other packages included)', JSON.stringify(others(beforeIndex)) === JSON.stringify(others(afterIndex)))
|
||||||
check('inventory: Dropdown keeps sw-header--composed-controls / Dropdown / SW/Header', afterIndex.entries['sw-header--composed-controls']?.name === 'Dropdown' && afterIndex.entries['sw-header--composed-controls']?.title === 'SW/Header')
|
check('inventory: Dropdown keeps sw-header--composed-controls / Dropdown / SW/Header', afterIndex.entries['sw-header--composed-controls']?.name === 'Dropdown' && afterIndex.entries['sw-header--composed-controls']?.title === 'SW/Header')
|
||||||
check('inventory: comparison keeps its ID and name', afterIndex.entries['sw-header--property-panel-compare']?.name === 'Property Panel · 4개 비교')
|
check('inventory: comparison keeps its ID and name', afterIndex.entries['sw-header--property-panel-compare']?.name === 'Property Panel · 4개 비교')
|
||||||
|
|||||||
@@ -49,21 +49,36 @@ function useLog() {
|
|||||||
return [view, (line: string) => setLog(lines => [...lines.slice(-7), line])] as const
|
return [view, (line: string) => setLog(lines => [...lines.slice(-7), line])] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Two Stories directly under SW/Header (no separate Options menu): Document Open and Hidden Sub Controls.
|
||||||
const meta = {
|
const meta = {
|
||||||
title: "SW/Header/Options",
|
title: "SW/Header",
|
||||||
|
id: "sw-header",
|
||||||
parameters: { layout: "fullscreen", controls: { disable: true } },
|
parameters: { layout: "fullscreen", controls: { disable: true } },
|
||||||
} satisfies Meta
|
} satisfies Meta
|
||||||
export default meta
|
export default meta
|
||||||
type Story = StoryObj<typeof meta>
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
function OpenPlacementPage() {
|
function DocumentOpenPage() {
|
||||||
const [log, record] = useLog()
|
const [log, record] = useLog()
|
||||||
|
const [layerId, setLayerId] = useState("wall")
|
||||||
const header = (props: Partial<SWHeaderProps>) => (
|
const header = (props: Partial<SWHeaderProps>) => (
|
||||||
<SWHeader {...base} onDocumentOpen={() => record("onDocumentOpen")} onDocumentNew={() => record("onDocumentNew")} {...props} />
|
<SWHeader {...base} onDocumentOpen={() => record("onDocumentOpen")} onDocumentNew={() => record("onDocumentNew")} {...props} />
|
||||||
)
|
)
|
||||||
|
// Composed layouts that need responsive behavior use SWHeader itself (controls + documentOpenPlacement):
|
||||||
|
// the one existing More logic serves them, no second More is created.
|
||||||
|
const viewer = (width: number) => (
|
||||||
|
<div key={width} data-width={width}>
|
||||||
|
<Frame width={width}>
|
||||||
|
<SWHeader {...base} documentOpenPlacement="header" documentOpenLabel="파일 열기"
|
||||||
|
controls={[{ id: "document" }, { id: "layer" }, { id: "color" }, { id: "popupGuide" }]}
|
||||||
|
colors={[{ id: "red", color: "#ff0000", label: "빨강" }, { id: "green", color: "#00ff00", label: "초록" }]}
|
||||||
|
onDocumentOpen={() => record("onDocumentOpen")} onColorSelect={option => record(`onColorSelect(${option.id})`)} onPopupGuide={() => record("onPopupGuide")} />
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<StoryPage>
|
<StoryPage>
|
||||||
<StoryPageHeader kicker={KICKER} title="파일 열기 위치 (documentOpenPlacement)"
|
<StoryPageHeader kicker={KICKER} title="파일 열기 (documentOpenPlacement)"
|
||||||
description="documentOpenPlacement="header"이면 Open이 로고 바로 오른쪽에 파일명과 같은 면(hm-sw-header__document-button)으로 표시되고, 문서 Dropdown의 Open은 숨겨집니다. 두 곳 모두 onDocumentOpen과 documentOpenLabel을 씁니다. 공간이 부족하면(실측) Open은 More로 이동합니다." />
|
description="documentOpenPlacement="header"이면 Open이 로고 바로 오른쪽에 파일명과 같은 면(hm-sw-header__document-button)으로 표시되고, 문서 Dropdown의 Open은 숨겨집니다. 두 곳 모두 onDocumentOpen과 documentOpenLabel을 씁니다. 공간이 부족하면(실측) Open은 More로 이동합니다." />
|
||||||
{log}
|
{log}
|
||||||
<StorySection title="기본값 (menu) — 변경 없음" description="documentOpenPlacement를 주지 않으면 기존과 같습니다(Open은 Dropdown 하단).">
|
<StorySection title="기본값 (menu) — 변경 없음" description="documentOpenPlacement를 주지 않으면 기존과 같습니다(Open은 Dropdown 하단).">
|
||||||
@@ -73,18 +88,35 @@ function OpenPlacementPage() {
|
|||||||
<div data-open-placement="header"><Frame width={1920}>{header({ documentOpenPlacement: "header" })}</Frame></div>
|
<div data-open-placement="header"><Frame width={1920}>{header({ documentOpenPlacement: "header" })}</Frame></div>
|
||||||
</StorySection>
|
</StorySection>
|
||||||
<StorySection title="header + documentOpenLabel="파일 열기"" description="버튼·More 항목 글자는 documentOpenLabel입니다(기본 Open).">
|
<StorySection title="header + documentOpenLabel="파일 열기"" description="버튼·More 항목 글자는 documentOpenLabel입니다(기본 Open).">
|
||||||
<Frame width={1920}>{header({ documentOpenPlacement: "header", documentOpenLabel: "파일 열기" })}</Frame>
|
<div data-open-label=""><Frame width={1920}>{header({ documentOpenPlacement: "header", documentOpenLabel: "파일 열기" })}</Frame></div>
|
||||||
</StorySection>
|
</StorySection>
|
||||||
<StorySection title="반응형 (10개 폭)" description="파일명이 먼저 줄고, 그래도 넘치면 Open이 More(⋯)로 이동합니다.">
|
<StorySection title="반응형 (10개 폭)" description="파일명이 먼저 줄고, 그래도 넘치면 Open이 More(⋯)로 이동합니다.">
|
||||||
<div data-responsive-open="">
|
<div data-responsive-open="">
|
||||||
{OPTION_WIDTHS.map(width => <div key={width} data-width={width}><Frame width={width}>{header({ documentOpenPlacement: "header" })}</Frame></div>)}
|
{OPTION_WIDTHS.map(width => <div key={width} data-width={width}><Frame width={width}>{header({ documentOpenPlacement: "header" })}</Frame></div>)}
|
||||||
</div>
|
</div>
|
||||||
</StorySection>
|
</StorySection>
|
||||||
|
<StorySection title="조합형 막대 — 로고 → 파일 열기 → 문서 → 레이어" description="SWHeader 없이 같은 구성: SWHeaderHome → SWToolButton appearance="document" → SWHeaderDocumentTitle showOpen={false}. 배치용 CSS만 소비 프로젝트가 작성합니다. 이 막대에는 More가 없습니다.">
|
||||||
|
<Frame width={1440}>
|
||||||
|
<SWTheme className="sw-story-composed-bar" data-composed-open="">
|
||||||
|
<SWHeaderHome logoSrc={placeholderLogo} logoAlt="임시 로고" onClick={() => record("onHomeClick")} />
|
||||||
|
<SWToolButton appearance="document" onClick={() => record("onDocumentOpen")}>Open</SWToolButton>
|
||||||
|
<SWHeaderDocumentTitle title={DOCUMENTS[0].name} documents={DOCUMENTS} activeDocumentId="doc-1" showOpen={false}
|
||||||
|
onDocumentSelect={document => record(`onDocumentSelect(${document.id})`)} onNew={() => record("onNew")} />
|
||||||
|
<div className="sw-story-composed-surface">
|
||||||
|
<SWHeaderLayerMenu value={{ name: LAYERS.find(layer => layer.id === layerId)!.name }} layers={LAYERS} activeLayerId={layerId}
|
||||||
|
onLayerSelect={layer => { setLayerId(layer.id); record(`onLayerSelect(${layer.id})`) }} />
|
||||||
|
</div>
|
||||||
|
</SWTheme>
|
||||||
|
</Frame>
|
||||||
|
</StorySection>
|
||||||
|
<StorySection title="조합형 구성의 반응형 (SWHeader controls, 10개 폭)" description="로고 → 파일 열기 → 파일 → 레이어 → 색상 → K만 쓰는 구성을 SWHeader의 controls / documentOpenPlacement로 만들면 기존 More와 가용 공간 판단이 그대로 동작합니다.">
|
||||||
|
<div data-composed-responsive="">{OPTION_WIDTHS.map(viewer)}</div>
|
||||||
|
</StorySection>
|
||||||
</StoryPage>
|
</StoryPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OpenPlacement: Story = { name: "Open Placement", render: () => <OpenPlacementPage /> }
|
export const DocumentOpen: Story = { name: "Document Open", render: () => <DocumentOpenPage /> }
|
||||||
|
|
||||||
function HiddenSubControlsPage() {
|
function HiddenSubControlsPage() {
|
||||||
const [log, record] = useLog()
|
const [log, record] = useLog()
|
||||||
@@ -106,57 +138,3 @@ function HiddenSubControlsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const HiddenSubControls: Story = { name: "Hidden Sub Controls", render: () => <HiddenSubControlsPage /> }
|
export const HiddenSubControls: Story = { name: "Hidden Sub Controls", render: () => <HiddenSubControlsPage /> }
|
||||||
|
|
||||||
function ComposedOpenPage() {
|
|
||||||
const [log, record] = useLog()
|
|
||||||
const [layerId, setLayerId] = useState("wall")
|
|
||||||
return (
|
|
||||||
<StoryPage>
|
|
||||||
<StoryPageHeader kicker={KICKER} title="조합형 Header의 파일 열기"
|
|
||||||
description="SWHeader 없이 같은 구성: SWHeaderHome → SWToolButton appearance="document" → SWHeaderDocumentTitle showOpen={false}. 배치용 CSS(.app-*)만 소비 프로젝트가 작성합니다." />
|
|
||||||
{log}
|
|
||||||
<StorySection title="로고 → 파일 열기 → 문서 → 레이어">
|
|
||||||
<Frame width={1440}>
|
|
||||||
<SWTheme className="sw-story-composed-bar" data-composed-open="">
|
|
||||||
<SWHeaderHome logoSrc={placeholderLogo} logoAlt="임시 로고" onClick={() => record("onHomeClick")} />
|
|
||||||
<SWToolButton appearance="document" onClick={() => record("onDocumentOpen")}>Open</SWToolButton>
|
|
||||||
<SWHeaderDocumentTitle title={DOCUMENTS[0].name} documents={DOCUMENTS} activeDocumentId="doc-1" showOpen={false}
|
|
||||||
onDocumentSelect={document => record(`onDocumentSelect(${document.id})`)} onNew={() => record("onNew")} />
|
|
||||||
<div className="sw-story-composed-surface">
|
|
||||||
<SWHeaderLayerMenu value={{ name: LAYERS.find(layer => layer.id === layerId)!.name }} layers={LAYERS} activeLayerId={layerId}
|
|
||||||
onLayerSelect={layer => { setLayerId(layer.id); record(`onLayerSelect(${layer.id})`) }} />
|
|
||||||
</div>
|
|
||||||
</SWTheme>
|
|
||||||
</Frame>
|
|
||||||
</StorySection>
|
|
||||||
</StoryPage>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ComposedOpen: Story = { name: "Composed Open", render: () => <ComposedOpenPage /> }
|
|
||||||
|
|
||||||
// Composed layouts that need responsive behavior use SWHeader itself (controls + leading + documentOpenPlacement):
|
|
||||||
// the one existing More logic serves them, no second More is created.
|
|
||||||
function ComposedResponsivePage() {
|
|
||||||
const [log, record] = useLog()
|
|
||||||
const viewer = (width: number) => (
|
|
||||||
<div key={width} data-width={width}>
|
|
||||||
<Frame width={width}>
|
|
||||||
<SWHeader {...base} documentOpenPlacement="header" documentOpenLabel="파일 열기"
|
|
||||||
controls={[{ id: "document" }, { id: "layer" }, { id: "color" }, { id: "popupGuide" }]}
|
|
||||||
colors={[{ id: "red", color: "#ff0000", label: "빨강" }, { id: "green", color: "#00ff00", label: "초록" }]}
|
|
||||||
onDocumentOpen={() => record("onDocumentOpen")} onColorSelect={option => record(`onColorSelect(${option.id})`)} onPopupGuide={() => record("onPopupGuide")} />
|
|
||||||
</Frame>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<StoryPage>
|
|
||||||
<StoryPageHeader kicker={KICKER} title="조합형 구성의 반응형 (SWHeader controls)"
|
|
||||||
description="뷰어처럼 로고 → 파일 열기 → 파일 → 레이어 → 색상 → K만 쓰는 구성을 SWHeader의 controls / documentOpenPlacement로 만들면 기존 More와 가용 공간 판단이 그대로 동작합니다. SWTheme로 직접 배치한 막대에는 More가 없습니다." />
|
|
||||||
{log}
|
|
||||||
<StorySection title="10개 폭"><div data-composed-responsive="">{OPTION_WIDTHS.map(viewer)}</div></StorySection>
|
|
||||||
</StoryPage>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ComposedResponsive: Story = { name: "Composed Responsive", render: () => <ComposedResponsivePage /> }
|
|
||||||