feat(sw): viewer header logo menu and file list; simplify SW Storybook

SW Viewer Header (approved optional props, public export names unchanged at 170):
- SWViewerHeader: logoMenu, documents, activeDocumentId, onDocumentSelect,
  onDocumentOpen, documentOpenLabel; measured responsive toggles (full ->
  icons -> More) with the SWHeader More / file-list styling.
- SW AI guides, ExampleH, viewer/text-weight/theme-inheritance tests and the
  logo-document-menu tests with their QA record.

SW Storybook cleanup (Story IDs and test fixtures kept):
- Sidebar: Overview -> "SW Header" (first), 전체 Footer -> "SW Footer",
  Footer/Viewer responsive pages -> "Responsive More", Viewer content-variants
  -> "Settings Popup" (merges the settings popup page).
- Hidden by the verification tag (direct URL kept): Header Dropdown,
  Document Open, Interactive States, Document Dropdown, Hidden Sub Controls,
  Home Menu standalone; Footer tool groups / custom content / parts;
  SW/Theme; Viewer menu groups / item states / variable size / settings
  panel / Header + Dropdown.
- Removed demo UI: Overview size and callback output (demo data and
  selection only with &verification), Home Menu Figma node table and width
  demo, Header Responsive More reduced to 1440 / 768 / 390, Popup reopen
  button, property-panel comparison captions, Viewer context-menu demo
  (&verification only).
- Tests: storybook-inventory records the hidden and renamed Stories;
  overview / range-cross-browser / viewer open the demo controls with
  &verification; integrated-popup reloads the Story instead of the reopen
  button; dropdown-consolidation asserts Dropdown is hidden from the sidebar
  and still opens by its direct URL.
This commit is contained in:
shkim4 committed 2026-10-21 14:52:46 +09:00
1 parent 46119eacb5
commit 23d522c136
57 files changed
+2623 -333

No files matched your search

+5 -43
View File
@@ -1,4 +1,4 @@
import { useCallback, useRef, useState, type ReactNode } from "react"
import { useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
// Public API only, like a consuming project.
import {
@@ -10,14 +10,14 @@ import "@hm-design/sw/styles.css"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import "./sw-home-menu.css"
import {
figmaHomeMenuItemIds, figmaHomeMenuSections, figmaRecentFiles, figmaTileNodes, figmaTitleNodes, withRecentFiles,
figmaHomeMenuItemIds, figmaHomeMenuSections, figmaRecentFiles, withRecentFiles,
} from "./sw-home-menu-fixture"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
import figmaPropertyBar from "./assets/home-menu/figma-26-10492.png"
// Owner: docs/sw/figma-additional/analysis-report.md (Figma rj6wpsfk24lOWvWiVRAANP 26:10591 / 662:67267).
// 2026-10-19: the former SW/Home Menu Stories live under SW/Header (SW normal mode only; SW/Viewer has no logo menu).
// "Home Menu" and "Home Menu · 단독 사용 · 조합형" are the sidebar Stories; the other Stories keep their checks reachable
// "Home Menu" is the sidebar Story (단독 사용 hidden 2026-10-21); the other Stories keep their checks reachable
// by URL (verification tag). 2026-10-20: Figma screenshot comparisons that no test or document used were removed;
// the Figma originals stay in docs/sw/figma-additional/screenshots/.
const KICKER = "HM DESIGN SYSTEM / SW"
@@ -85,42 +85,6 @@ function HeaderWithMenu({ width, defaultOpen = false, defaultActiveIds = [], acc
)
}
/**
* Header width set by a range input (Storybook control only): the header measures its own width. The range ends at
* the room the page gives (no page scroll), so the browser width and the slider both drive it.
*/
function ResizableHeaderWithMenu() {
const [width, setWidth] = useState(1280)
const [room, setRoom] = useState(1920)
const observed = useRef<ResizeObserver | null>(null)
const measureRef = useCallback((element: HTMLDivElement | null) => {
observed.current?.disconnect()
if (!element) return
observed.current = new ResizeObserver(() => setRoom(Math.floor(element.clientWidth)))
observed.current.observe(element)
}, [])
const shown = Math.min(width, room)
return (
<>
<label className="sw-home-menu-story__width">
헤더 폭 <input type="range" min={Math.min(390, room)} max={room} step={1} value={shown} onChange={event => setWidth(Number(event.target.value))} aria-label="헤더 폭" />
<output>{shown}px</output>
</label>
<div ref={measureRef}><HeaderWithMenu width={shown}>{log => log}</HeaderWithMenu></div>
</>
)
}
const tileTable = (
<table className="sw-home-menu-story__table">
<thead><tr><th>섹션</th><th>제목 노드</th><th>타일 (노드)</th></tr></thead>
<tbody>{figmaHomeMenuSections.map(section => (
<tr key={section.id}><td>{section.title}</td><td>{figmaTitleNodes[section.id]}</td>
<td>{section.items.map(item => `${item.label} (${figmaTileNodes[item.id]})`).join(" · ")}</td></tr>
))}</tbody>
</table>
)
/** The sidebar Story: the logo menu of SWHeader (SW normal mode) with its Figma states, sub panel and Escape stack. */
export const HomeMenu: Story = {
name: "Home Menu",
@@ -131,16 +95,12 @@ export const HomeMenu: Story = {
<StorySection title="로고 클릭 · 열기 / 닫기 (현재 폭)" description="5개 섹션(파일 · 손상된 파일 · Display · 설정 · 궁금하면) 15개 타일. 마우스를 올리면 Hover(Variant2), 누르는 동안 Pressed(Variant3)이며 클릭 후 ON 상태는 남지 않습니다. 최근파일 타일만 하위 패널을 열고, 목록이 열린 동안 Variant3를 유지합니다.">
<HeaderWithMenu>{log => log}</HeaderWithMenu>
</StorySection>
<StorySection title="반응형 · 헤더 폭 조절 · 더보기(⋯)" description="메뉴 위치와 폭은 고정 x가 아니라 Header를 측정해 정합니다(객체 수정 영역 −2px ~ 창 영역). 폭이 부족한 Header에서는 기존 더보기(…) 규칙이 그대로 동작하고, 메뉴는 가로 스크롤 없이 들어가는 섹션만 앞에서부터 보여 주며 나머지는 끝의 더보기(⋯) 섹션으로 옮깁니다. 더보기를 누르면 옮긴 섹션이 텍스트 메뉴(섹션 제목 + 기능 목록, 그룹 사이 구분선)로 아래에 열리고(Hover·Active·클릭은 타일과 같음, 최근파일은 기존 목록을 엶), 폭이 넓어지면 원래 자리로 돌아갑니다. 브라우저 폭이나 슬라이더로 확인합니다(슬라이더 최대 = 현재 페이지 폭).">
<ResizableHeaderWithMenu />
</StorySection>
<StorySection title="열림 (현재 폭)" description="열림 동안 로고는 Home On(노란 테두리), Header 상·하단에 #5F4B0F 2px 선이 표시되고 닫으면 기본으로 돌아갑니다(Figma 26:10591 / 662:67267).">
<HeaderWithMenu defaultOpen autoFocus={false} />
</StorySection>
<StorySection title="Default / Variant2 · Hover / Variant3 · Active" description="Figma 버튼 세트 15개(26:11355 … 26:11631)는 모두 같은 SWHomeMenuItem입니다. Variant2(Hover)는 마우스를 올려 확인하며 Figma에서 Variant3와 시각 속성이 같습니다. Variant3는 activeIds로 지정합니다.">
<SWTheme className="sw-home-menu-story__stage"><SWHomeMenu defaultOpen autoFocus={false} sections={figmaHomeMenuSections} closeOnBackgroundClick={false} aria-label="Default" /></SWTheme>
<SWTheme className="sw-home-menu-story__stage"><SWHomeMenu defaultOpen autoFocus={false} sections={figmaHomeMenuSections} activeIds={figmaHomeMenuItemIds} closeOnBackgroundClick={false} aria-label="Active" /></SWTheme>
{tileTable}
</StorySection>
<StorySection title="최근파일 하위 패널 (662:67267)" description="최근파일 타일을 누르면(Variant3) 메뉴 하단 강조가 전체 2px 외곽선으로 바뀌고 그 아래에 최근파일 목록이 열립니다. 행에 마우스를 올리면 2뎁스 미리보기가 옆에 나타납니다. 다시 누르거나 Escape로 닫습니다. 최근파일이 더보기 안에 있으면 목록은 더보기 타일 아래에 열리고 더보기 타일이 Variant3가 됩니다. 목록과 미리보기 이미지는 Story 데이터입니다.">
<div className="sw-home-menu-story__tall"><HeaderWithMenu defaultOpen autoFocus={false} defaultActiveIds={["recent"]}>{log => log}</HeaderWithMenu></div>
@@ -254,6 +214,8 @@ function StandaloneDemo() {
export const HomeMenuStandalone: Story = {
name: "Home Menu · 단독 사용 · 조합형",
// 2026-10-21 SW Storybook menu cleanup: hidden from the sidebar; ID and direct URL kept for the existing checks.
tags: ["verification"],
render: () => (
<StoryPage>
<StoryPageHeader kicker={KICKER} title="SWTheme 안에서 단독 사용"