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

+51 -8
View File
@@ -1,7 +1,8 @@
// 2026-10-20 SW text weight request: Bold only on the named texts, with the black text outline where asked.
// Header file name (700) · Composed Controls field values (700) vs SWHeader field values (400) · Footer 보조기능 / 명령어
// titles and category tabs (700, 전체보기 / Model · Layout / commands unchanged) · Viewer header file name, labels and
// Model / Layout tabs (700, More kept) · recent files name / path / preview title (700 + outline). Hover keeps the
// Model / Layout tabs (700, More kept; 2026-10-21 logo menu and sample list follow the SWHeader `...` menu / file-name
// list rules) · recent files name / path / preview title (700 + outline). Hover keeps the
// text in place. Usage: build Storybook first, then node packages/sw/tests/text-weight.browser.mjs [evidence-dir]
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import path from 'node:path'
@@ -109,19 +110,61 @@ check('Footer: ◀ ▶ only when the categories overflow their area', (area.cont
await page.screenshot({ path: path.join(out, 'footer.png'), clip: { x: 0, y: 940, width: 1920, height: 160 } })
// ---------- 3. Viewer header ----------
// 2026-10-21 (approved restructure): 파일 열기 moved into the logo menu (the SWHeader `...` menu, SWHeaderMore), 샘플 into
// the file-name list (SWHeaderDocumentTitle) and 전체 보기 into More. The header keeps the Bold file name (now the
// SWHeader file-name button), 레이어 · 설정 · 속성 and Model / Layout; the two menus follow the SWHeader rules, measured
// on SWHeader itself below and compared row by row.
const typo = s => ({ weight: s.weight, size: s.size, family: s.family, outline: s.outline })
// SWHeader references: the responsive `...` menu rows and the file-name list rows.
await goto('sw-header--responsive-more')
const refTrigger = page.locator('.hm-sw-header__more-trigger').filter({ visible: true }).first()
await refTrigger.click(); await page.waitForTimeout(200)
const refMore = await styles('.hm-sw-header__more-menu .hm-sw-header__more-item > span:first-child')
check('SWHeader ... menu rows (reference): Regular 400 without outline', refMore.length > 0 && refMore.every(r => r.weight === '400' && r.outline === 'none'), refMore)
await page.keyboard.press('Escape')
await goto('sw-header--document-dropdown')
await page.locator('.hm-sw-header__document-button').first().click(); await page.waitForTimeout(200)
const refDocs = await styles('.hm-sw-header__document-menu .hm-sw-header__document-item-name')
check('SWHeader file-name list rows (reference): 14px + 1.3px outline, one weight', refDocs.length > 0 && refDocs.every(r => r.size === '14px' && r.outline === OUTLINE_14 && r.weight === refDocs[0].weight), refDocs)
// 2026-10-21: the logo menu takes the viewer dropdown type (SWViewerMenu, "상단 메뉴 + Dropdown"); its rows and label are the reference.
await goto('sw-viewer--dropdowns')
await page.getByRole('button', { name: '도면' }).click(); await page.waitForTimeout(200)
const refViewerItem = await styles('.hm-sw-viewer-menu .hm-sw-viewer-menu__text')
const refViewerLabel = await styles('.hm-sw-viewer-menu .hm-sw-viewer-menu__label')
check('SWViewerMenu rows / label (reference): 14px 400 and 12px 500, no outline', refViewerItem.length > 0 && refViewerItem.every(r => r.size === '14px' && r.weight === '400' && r.outline === 'none')
&& refViewerLabel.length > 0 && refViewerLabel.every(r => r.size === '12px' && r.weight === '500' && r.outline === 'none'), { refViewerItem: refViewerItem.slice(0, 2), refViewerLabel })
await page.keyboard.press('Escape')
report.reference = { more: refMore[0], documents: refDocs[0], viewerItem: refViewerItem[0], viewerLabel: refViewerLabel[0] }
await goto('sw-viewer--viewer-header')
// The 1440px Story frame at full width (as the viewer suites): the header has room for every label.
await page.addStyleTag({ content: '.sb-page { --sb-page-max-width: 100vw; }' }); await page.waitForTimeout(200)
const vh = page.locator('.hm-sw-viewer-header').first()
const vNames = await styles('.hm-sw-viewer-header >> nth=0 >> :is(.hm-sw-viewer-header__document-name, .hm-sw-viewer-header__label, .hm-sw-viewer-header__tab)')
const vNames = await styles('.hm-sw-viewer-header >> nth=0 >> :is(.hm-sw-header__document-name, .hm-sw-viewer-header__document-name, .hm-sw-viewer-header__label, .hm-sw-viewer-header__tab)')
report.viewer = vNames
const want = ['파일 열기', '샘플', '전체 보기', '레이어', '설정', '속성']
// 2026-10-21 measured steps: at 1440 the header has room, so 전체 보기 is a header label again (Bold like the others), and
// the file open button after the logo shows 파일 열기 on the SWHeader file-name surface (Bold + outline like the file name).
const want = ['C0060202-001-평면및종단면도.dwg', '파일 열기', '전체 보기', '레이어', '설정', '속성']
const labels = vNames.map(v => v.text)
check('Viewer: file name, 파일열기 · 샘플 · 전체보기 · 레이어 · 설정 · 속성, Model · Layout are 14px 700 + 1.3px outline', want.every(w => labels.includes(w)) && labels.some(t => t.startsWith('Model')) && labels.some(t => t.startsWith('Layout')) && vNames.every(v => v.weight === '700' && v.size === '14px' && v.outline === OUTLINE_14), vNames)
check('Viewer: file name, 파일 열기, 전체 보기 · 레이어 · 설정 · 속성, Model · Layout are 14px 700 + 1.3px outline', want.every(w => labels.includes(w)) && labels.some(t => t.startsWith('Model')) && labels.some(t => t.startsWith('Layout')) && vNames.every(v => v.weight === '700' && v.size === '14px' && v.outline === OUTLINE_14), vNames)
check('Viewer: 샘플 is no longer a header label', !labels.includes('샘플'), labels)
const counts = await styles('.hm-sw-viewer-header >> nth=0 >> .hm-sw-viewer-header__count')
check('Viewer: tab counts keep 11px 400', counts.length > 0 && counts.every(c => c.weight === '400' && c.size === '11px'), counts)
for (const t of ['파일 열기', '레이어']) {
const lab = vh.locator('.hm-sw-viewer-header__label', { hasText: t }).first()
await stableOnHover(`Viewer ${t}`, lab.locator('xpath=ancestor::button[1]'), lab)
}
// File open button (파일 열기, the logo menu is gone): hover keeps its label in place, size and weight.
const openButton = vh.locator('[data-document-open]')
await stableOnHover('Viewer 파일 열기', openButton, openButton.locator('.hm-sw-viewer-header__open-label'))
// File-name list (샘플): rows exactly as the SWHeader file-name list rows.
const docButton2 = vh.locator('.hm-sw-viewer-header__document .hm-sw-header__document-button')
await stableOnHover('Viewer file name', docButton2, docButton2.locator('.hm-sw-header__document-name'))
await docButton2.click(); await page.waitForTimeout(150)
const sampleRows = await styles('.hm-sw-viewer-header >> nth=0 >> .hm-sw-header__document-item-name')
report.viewerSamples = sampleRows
check('Viewer sample list: 3 rows = SWHeader file-name list rows (weight / size / family / outline)', sampleRows.length === 3 && sampleRows.every(r => JSON.stringify(typo(r)) === JSON.stringify(typo(refDocs[0])) && !r.cut), { sampleRows, ref: refDocs[0] })
const sampleRow = vh.locator('.hm-sw-header__document-item').first()
await stableOnHover('Viewer sample row', sampleRow, sampleRow.locator('.hm-sw-header__document-item-name'))
await page.keyboard.press('Escape'); await page.waitForTimeout(100)
const lab = vh.locator('.hm-sw-viewer-header__label', { hasText: '레이어' }).first()
await stableOnHover('Viewer 레이어', lab.locator('xpath=ancestor::button[1]'), lab)
const tab = vh.locator('.hm-sw-viewer-header__tab', { hasText: 'Layout' }).first()
await stableOnHover('Viewer Layout tab', tab, tab)
// More stays: narrow header shows the More button, it opens and lists the moved items.