feat(sw): integrate responsive home menu into header
- Merge SW / Home Menu Stories into SW / Header (sidebar: Home Menu; others verification-tagged) - Home On: logo hover ring and header top/bottom #5F4B0F lines while the menu is open - Measured More in SWHeader; home menu narrow widths use a More (...) section with a grouped text menu instead of horizontal scroll, placed right after the last visible section - Action tiles no longer stay on after a click; 최근파일 stays Active only while its list is open - More dropdown and recent files list never open together; list top joins the accent line - Recent files preview falls back left / over the list at narrow widths (390px) - Update Storybook, browser tests, docs, AI guides and QA evidence
This commit is contained in:
1 parent
c542bdc282
commit
170a5c23ae
71 files changed
+12535
-150
No files matched your search
@@ -21,7 +21,8 @@ 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 }); console.log(ok ? 'PASS' : 'FAIL', name, ok || data === undefined ? '' : JSON.stringify(data)) }
|
||||
const goto = async id => {
|
||||
await page.goto(`${base}/iframe.html?id=sw-home-menu--${id}&viewMode=story`)
|
||||
// 2026-10-19: the Home Menu Stories moved under SW/Header (sw-home-menu--<id> -> sw-header--home-menu-<id>).
|
||||
await page.goto(`${base}/iframe.html?id=sw-header--home-menu-${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)
|
||||
@@ -36,7 +37,7 @@ const activeIsHome = () => page.evaluate(() => document.activeElement?.classList
|
||||
const index = JSON.parse(await readFile(path.join(root, 'index.json'), 'utf8'))
|
||||
const titles = [...new Set(Object.values(index.entries).map(entry => entry.title).filter(title => title.startsWith('SW/')))].sort()
|
||||
report.data.titles = titles
|
||||
check('Storybook SW titles: Header, Popup, Home Menu, Viewer (SW/Tool removed 2026-10-19); no SW/Header/Options', ['SW/Header', 'SW/Popup', 'SW/Home Menu', 'SW/Viewer'].every(t => titles.includes(t)) && !titles.includes('SW/Tool') && !titles.some(t => t.startsWith('SW/Header/')), titles)
|
||||
check('Storybook SW titles: Header, Popup, Viewer (SW/Tool removed, SW/Home Menu merged into SW/Header 2026-10-19); no SW/Header/Options; sw-header--home-menu listed', ['SW/Header', 'SW/Popup', 'SW/Viewer'].every(t => titles.includes(t)) && !titles.includes('SW/Tool') && !titles.includes('SW/Home Menu') && !titles.some(t => t.startsWith('SW/Header/')) && !!index.entries['sw-header--home-menu'], titles)
|
||||
|
||||
// 2. Figma 26:10591 geometry at 1920 through SWHeader's measured placement
|
||||
await goto('capture-default')
|
||||
@@ -73,9 +74,10 @@ await page.waitForTimeout(50)
|
||||
const hover = await tile.evaluate(e => getComputedStyle(e.querySelector('.hm-sw-home-menu__label')).color)
|
||||
check('tile Hover: label #FF5C00', hover === 'rgb(255, 92, 0)', hover)
|
||||
await tile.click()
|
||||
const pressedOn = await tile.getAttribute('aria-pressed')
|
||||
await tile.click()
|
||||
check('tile click → Active (aria-pressed true), re-click → Default', pressedOn === 'true' && await tile.getAttribute('aria-pressed') === 'false', pressedOn)
|
||||
await page.mouse.move(1, 1)
|
||||
// 2026-10-19: tiles without a panel are action buttons — the click runs (onItemSelect) and nothing stays on.
|
||||
const afterClick = await tile.evaluate(e => ({ pressed: e.getAttribute('aria-pressed'), active: e.dataset.active ?? null, ring: getComputedStyle(e, '::after').opacity }))
|
||||
check('action tile click: onItemSelect, no Active / aria-pressed / ring after the click', afterClick.pressed === null && afterClick.active === null && afterClick.ring === '0' && (await log()).includes('onItemSelect(working / save, active=false)'), afterClick)
|
||||
await home().click()
|
||||
const text = await log()
|
||||
check('logo re-click closes; onHomeClick never called while homeMenu is set', await menu().count() === 0 && !text.includes('onHomeClick') && text.includes('onHomeMenuOpenChange(false, home)'), text)
|
||||
@@ -136,7 +138,7 @@ await page.keyboard.press('Escape')
|
||||
const step5 = { menu: await menu().count(), popup: await popup.count(), focusHome: await activeIsHome() }
|
||||
check('Escape inside the menu closes only the menu (popup stays), focus → logo', step5.menu === 0 && step5.popup === 1 && step5.focusHome, step5)
|
||||
|
||||
// 7. Responsive placement (measured), internal scroll reported
|
||||
// 7. Responsive placement (measured); narrow widths use the measured More instead of a scroller (2026-10-19)
|
||||
const responsive = []
|
||||
for (const width of [1920, 1440, 1080, 768, 520, 390]) {
|
||||
await page.setViewportSize({ width, height: 900 })
|
||||
@@ -145,7 +147,7 @@ for (const width of [1920, 1440, 1080, 768, 520, 390]) {
|
||||
const r = s => document.querySelector(s)?.getBoundingClientRect()
|
||||
const header = r('.hm-sw-header'), area = r('.hm-sw-header__object-area'), win = r('.hm-sw-header__window'), m = r('.hm-sw-home-menu'), homeBox = r('.hm-sw-header__home'), doc = r('.hm-sw-header__document')
|
||||
const scroller = document.querySelector('.hm-sw-home-menu__sections')
|
||||
return { headerHeight: header.height, start: m.left - (area.left - 2), endGap: (win?.left ?? header.right) - m.right, coversHome: m.left < homeBox.right - 17, coversDocument: m.left < doc.right - 1, menuWidth: Math.round(m.width), scrolls: scroller.scrollWidth > scroller.clientWidth + 1, pageScroll: document.documentElement.scrollWidth > innerWidth }
|
||||
return { headerHeight: header.height, start: m.left - (area.left - 2), endGap: (win?.left ?? header.right) - m.right, coversHome: m.left < homeBox.right - 17, coversDocument: m.left < doc.right - 1, menuWidth: Math.round(m.width), scrolls: scroller.scrollWidth > scroller.clientWidth + 1, pageScroll: document.documentElement.scrollWidth > innerWidth, rowSections: scroller.querySelectorAll(':scope > .hm-sw-home-menu__section:not(.hm-sw-home-menu__more)').length, moved: document.querySelectorAll('.hm-sw-home-menu__more-panel > .hm-sw-home-menu__text-group').length }
|
||||
})
|
||||
responsive.push({ width, ...box })
|
||||
if (width === 390 || width === 768) await page.screenshot({ path: path.join(out, `responsive-${width}.png`), clip: { x: 0, y: 0, width, height: 90 } })
|
||||
@@ -153,6 +155,8 @@ for (const width of [1920, 1440, 1080, 768, 520, 390]) {
|
||||
report.data.responsive = responsive
|
||||
check('every width: menu starts at object area −2px, ends at the window area, covers neither logo nor file name, header 49, no page scroll',
|
||||
responsive.every(r => Math.abs(r.start) < 0.6 && r.endGap > -0.6 && !r.coversHome && !r.coversDocument && r.headerHeight === 49 && !r.pageScroll), responsive)
|
||||
check('every width: no horizontal scroll in the menu; 5 sections = row + More (1920: all in the row)',
|
||||
responsive.every(r => !r.scrolls && r.rowSections + r.moved === 5 && (r.width !== 1920 || r.moved === 0)), responsive)
|
||||
|
||||
// 8. Without homeMenu (existing Header): the logo still calls onHomeClick only, no menu, no aria-expanded
|
||||
await page.setViewportSize({ width: 1920, height: 1000 })
|
||||
|
||||
@@ -17,6 +17,10 @@ export const integratedStoryIds = ['sw-header--document-open', 'sw-header--hidde
|
||||
// 2026-10-19 (viewer worktree, user request): the SW/Tool Story menu was removed; SW/Popup (the common SWPopup
|
||||
// examples) stays. SWToolButton / SWToolGroup stay public; integrated-improvement.browser.mjs opening this id is historical evidence only.
|
||||
export const retiredToolIds = ["sw-tool--tool-buttons"]
|
||||
// 2026-10-19 (Header home menu merge, user request): the SW/Home Menu sidebar menu was removed; its Stories moved under
|
||||
// SW/Header as sw-header--home-menu (sidebar) and sw-header--home-menu-* (verification tag, reachable by URL).
|
||||
export const movedHomeMenuIds = Object.fromEntries(["figma-home-on:figma", "capture-default", "capture-active", "capture-outline", "capture-recent", "header-composition", "variants", "dynamic-data", "standalone", "recent-files-panel:recent-files", "property-bar-check", "capture-property-bar", "integration", "capture-responsive"]
|
||||
.map(entry => entry.split(":")).map(([from, to = from]) => [`sw-home-menu--${from}`, `sw-header--home-menu-${to}`]))
|
||||
export const integratedChangedFiles = [
|
||||
'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',
|
||||
|
||||
Reference in new issue
Block a user