feat(sw): bold 9-slice footer tabs and viewer panel bottom edge
User-approved 2026-10-22 SW changes (CSS only, public API unchanged). - Footer Model / Layout tab labels 700 in every state, letter-spacing -5%; the name-edit input keeps 400. - Slanted Layout tab and Model tab draw their SVG assets as 9-slice border-images: 97px / 73px stay the minimum, the slant and corners keep their size and only the flat middle grows with a longer label (max 12em, then ellipsis, like the rounded tabs). Hover surfaces use a union mask of the same asset (no mask-border), so Chromium and Firefox match. The default width renders pixel-identical to the previous drawing; the 8px slant / Model joint is unchanged. - Viewer panel body: the 7px bottom padding (inside the scrollport) becomes a 2px transparent bottom border, so scrolled rows stop 2px above the frame border instead of running into it. SWPopup keeps its 7px. - AI docs: DESIGN_RULES, AI_USAGE, FOOTER, VIEWER updated. - Tests: text-weight and footer-design-review expect the 700 labels and read the Model surface from the 9-slice ::before.
This commit is contained in:
1 parent
449398e3de
commit
fc65b2e836
8 files changed
+88
-36
No files matched your search
@@ -78,7 +78,8 @@ facts.addCallsBefore = addCalls
|
||||
|
||||
// 3. Model tab: selected vs not selected, Layout text rule
|
||||
const tabs = footer.locator('.hm-sw-footer-tab')
|
||||
const tabInfo = async loc => ({ tab: await style(loc), before: await style(loc, '::before'), text: await style(loc.locator('.hm-sw-footer-tab__label, .hm-sw-footer-tab__select').first()) })
|
||||
// 2026-10-22: the label (Bold 700) is the text; the 9-slice tab surface is the ::before border-image.
|
||||
const tabInfo = async loc => ({ tab: await style(loc), before: await style(loc, '::before'), surface: await loc.evaluate(e => getComputedStyle(e, '::before').borderImageSource), text: await style(loc.locator('.hm-sw-footer-tab__label').first()) })
|
||||
const model = footer.locator('.hm-sw-footer-tab--model').first()
|
||||
const modelState = await model.getAttribute('data-state')
|
||||
facts.model = { initialState: modelState, selected: null, unselected: null }
|
||||
@@ -94,8 +95,8 @@ await model.locator('.hm-sw-footer-tab__select').click(); await away(); await pa
|
||||
facts.model.selected = await tabInfo(model); await shot(model, 'model-selected', 4)
|
||||
const mu = facts.model.unselected, ms = facts.model.selected
|
||||
check('Model selected: gra-border-orange surface (::before) masked to the model shape', ms.before.bg === GRADIENT && ms.before.mask.startsWith('url('), ms.before.bg.slice(0, 60))
|
||||
check('Model not selected: its own tab image surface (tab-model.svg, beige), no orange ::before', mu.tab.bg.startsWith('url(') && !mu.before.bg.includes('rgb(218, 104, 19)'), { tab: mu.tab.bg, before: mu.before.bg })
|
||||
check('Model text #161410 12px 400 no outline — selected, unselected and hover identical', [ms, mu, facts.model.unselectedHover].every(s => s.text.color === 'rgb(22, 20, 16)' && s.text.size === '12px' && s.text.weight === '400' && s.text.shadow === 'none') && ['x', 'y', 'w', 'h'].every(k => ms.text[k] === mu.text[k] && mu.text[k] === facts.model.unselectedHover.text[k]), [ms.text, mu.text].map(t => `${t.color} ${t.size} ${t.weight} ${t.font}`))
|
||||
check('Model not selected: its own tab image surface (tab-model.svg 9-slice on ::before, beige), no orange ::before', mu.surface.startsWith('url(') && !mu.before.bg.includes('rgb(218, 104, 19)'), { surface: mu.surface.slice(0, 40), before: mu.before.bg })
|
||||
check('Model text #161410 12px 700 no outline — selected, unselected and hover identical', [ms, mu, facts.model.unselectedHover].every(s => s.text.color === 'rgb(22, 20, 16)' && s.text.size === '12px' && s.text.weight === '700' && s.text.shadow === 'none') && ['x', 'y', 'w', 'h'].every(k => ms.text[k] === mu.text[k] && mu.text[k] === facts.model.unselectedHover.text[k]), [ms.text, mu.text].map(t => `${t.color} ${t.size} ${t.weight} ${t.font}`))
|
||||
// Layout default vs hover text (non-selected layouts)
|
||||
facts.layoutHover = []
|
||||
for (let i = 1; i < layoutCount; i++) {
|
||||
@@ -105,7 +106,7 @@ for (let i = 1; i < layoutCount; i++) {
|
||||
await t.hover(); await page.waitForTimeout(150)
|
||||
const h = await tabInfo(t); await shot(t, `layout-${i}-hover`, 4); await away()
|
||||
facts.layoutHover.push({ i, d, h })
|
||||
check(`Layout ${i} default/hover text: #161410 12px 400, same box (Noto Sans KR)`, [d, h].every(s => s.text.color === 'rgb(22, 20, 16)' && s.text.size === '12px' && s.text.weight === '400' && s.text.font === '"Noto Sans KR"') && ['x', 'y', 'w', 'h'].every(k => d.text[k] === h.text[k]), `${d.text.color} → ${h.text.color}, ${d.text.w}×${d.text.h}`)
|
||||
check(`Layout ${i} default/hover text: #161410 12px 700, same box (Noto Sans KR)`, [d, h].every(s => s.text.color === 'rgb(22, 20, 16)' && s.text.size === '12px' && s.text.weight === '700' && s.text.font === '"Noto Sans KR"') && ['x', 'y', 'w', 'h'].every(k => d.text[k] === h.text[k]), `${d.text.color} → ${h.text.color}, ${d.text.w}×${d.text.h}`)
|
||||
}
|
||||
// restore the initial selection
|
||||
if (modelState !== 'on') await layoutTabs.first().locator('.hm-sw-footer-tab__select').click()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// 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
|
||||
// titles and category tabs (700, 전체보기 / commands unchanged; Model · Layout tabs 700 since 2026-10-22) · Viewer header file name, labels and
|
||||
// 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]
|
||||
@@ -99,8 +99,11 @@ const pocket = await work.locator('.hm-sw-footer-menu').evaluate(m => { const t
|
||||
return [under(t.x + 3), under(t.x + t.width / 2), under(t.right - 3)] })
|
||||
check('Footer: 수정 pocket surface stays continuous under the Bold tab', pocket.every(p => p === 'hm-sw-footer__group'), pocket)
|
||||
await tabLoc.nth(0).click(); await away()
|
||||
const keep = await styles('.sw-footer-story-workspace :is(.hm-sw-footer-tab__select, .hm-sw-footer-tool__label, .hm-sw-footer-aux__label)')
|
||||
check('Footer: Model / Layout, command and auxiliary labels keep 400', keep.length > 0 && keep.every(k => k.weight === '400'), keep.filter(k => k.weight !== '400'))
|
||||
// 2026-10-22 user request: the Model / Layout tab labels are 700; command and auxiliary labels keep 400.
|
||||
const layoutTabs = await styles('.sw-footer-story-workspace .hm-sw-footer-tab__label')
|
||||
check(`Footer: Model / Layout tab labels (${layoutTabs.length}) 12px 700`, layoutTabs.length >= 3 && layoutTabs.every(k => k.weight === '700' && k.size === '12px'), layoutTabs.filter(k => k.weight !== '700' || k.size !== '12px'))
|
||||
const keep = await styles('.sw-footer-story-workspace :is(.hm-sw-footer-tool__label, .hm-sw-footer-aux__label)')
|
||||
check('Footer: command and auxiliary labels keep 400', keep.length > 0 && keep.every(k => k.weight === '400'), keep.filter(k => k.weight !== '400'))
|
||||
const area = await work.locator('.hm-sw-footer-menu__viewport').evaluate(e => ({ w: Math.round(e.getBoundingClientRect().width * 10) / 10, h: Math.round(e.getBoundingClientRect().height), content: e.scrollWidth, steps: [...e.parentElement.querySelectorAll('.hm-sw-footer-menu__step')].filter(s => s.getClientRects().length).length }))
|
||||
report.footerCategoryArea = area
|
||||
console.log('INFO Footer category area at 1920:', JSON.stringify(area))
|
||||
|
||||
Reference in new issue
Block a user