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:
shkim4 committed 2026-10-19 22:46:28 +09:00
1 parent c542bdc282
commit 170a5c23ae
71 files changed
+12535 -150

No files matched your search

+148 -13
View File
@@ -26,6 +26,22 @@
/* Bottom accent: 26:10591 per-section strip (top → bottom) / 662:67267 2px outline (bottom → top). */
--hm-sw-home-menu-accent-strip: linear-gradient(180deg, #5f4b0f -6.4%, #ff5100 96.8%);
--hm-sw-home-menu-accent-outline: linear-gradient(0deg, #ff5100 0%, #5f4b0f 76.7%);
--hm-sw-home-menu-accent-outline-width: 2px;
--hm-sw-home-menu-accent-outline-height: 30px;
/* More dropdown (not in Figma): a text menu in the SW brown dropdown family (header/menu/레이어 26:10940, rows
header/dropdown/brown/row 26:10687): surface, border, radius, shadow, 30px rows, row hover; group title in the
최근파일 path color, groups split by the layer footer divider. */
--hm-sw-home-menu-more-background: var(--hm-sw-header-layer-menu-background);
--hm-sw-home-menu-more-border-color: var(--hm-sw-header-layer-menu-border-color);
--hm-sw-home-menu-more-radius: var(--hm-sw-header-layer-menu-radius);
--hm-sw-home-menu-more-shadow: var(--hm-sw-header-layer-menu-shadow);
--hm-sw-home-menu-more-offset: 2px;
--hm-sw-home-menu-more-min-width: 180px;
--hm-sw-home-menu-more-divider-color: var(--hm-sw-header-layer-footer-border-color);
--hm-sw-home-menu-more-title-color: var(--hm-sw-recent-files-path-color);
--hm-sw-home-menu-more-row-height: 30px;
--hm-sw-home-menu-more-row-hover-background: var(--hm-sw-header-layer-row-hover-background);
--hm-sw-home-menu-more-row-hover-color: var(--hm-sw-header-layer-row-hover-color);
/* Optional sub panel (Figma 최근파일 662:67331 / 파일출력 2뎁스 662:67471). */
--hm-sw-recent-files-width: 354px;
@@ -49,10 +65,108 @@
max-width: 100%;
/* 2px of the accent strip shows left, right and under the sections (menu 1305 x 77 in Figma). */
padding: 0 2px 2px;
/* Not in Figma (1920px only): narrower hosts scroll the menu horizontally, like the property bar. */
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
/* Not in Figma (1920px only): narrower hosts get the measured More section instead of a scroller (SWHomeMenu).
The clip only keeps the badge shadow and the strip inside the menu, as before; nothing is cut off. */
overflow: hidden;
}
/* Measurement pass (one synchronous read, removed before paint) and overflow: the menu takes all the room of its host
(clamped by max-width), so with More it still covers the whole area SWHeader gives it, as the full menu does. */
.hm-sw-home-menu:is([data-measuring], [data-overflow]) { width: 100000px; }
/* More fills the rest of the row with the section surface (the menu background stays as it is); only its badge and
⋯ tile sit right after the last shown section (2026-10-19 user correction: start, not end). */
.hm-sw-home-menu__section.hm-sw-home-menu__more { flex: 1 0 auto; align-items: flex-start; }
/* Natural tile widths (SWHomeMenu measure copy): laid out, invisible, zero-size clip (no scroll area, no hit area). */
.hm-sw-home-menu__measure {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 0;
overflow: hidden;
visibility: hidden;
pointer-events: none;
}
.hm-sw-home-menu__measure-row { display: flex; align-items: flex-start; width: max-content; }
/* More (not in Figma): the section + tile parts above with a ⋯ icon. Its dropdown sits under the ⋯ tile, left edges aligned
(left set from the DOM): a text menu of the moved sections (title + rows). Above the sub panel; laid out while
closed so it is positioned before it shows. */
.hm-sw-home-menu__more-panel {
position: absolute;
z-index: 2;
top: calc(100% + var(--hm-sw-home-menu-more-offset));
display: flex;
flex-direction: column;
min-width: var(--hm-sw-home-menu-more-min-width);
max-width: calc(100vw - 16px);
padding: 4px 0;
border: 1px solid var(--hm-sw-home-menu-more-border-color);
border-radius: var(--hm-sw-home-menu-more-radius);
background: var(--hm-sw-home-menu-more-background);
box-shadow: var(--hm-sw-home-menu-more-shadow);
color: var(--hm-sw-home-menu-item-color);
}
.hm-sw-home-menu__more-panel:not([data-open]) { visibility: hidden; pointer-events: none; }
.hm-sw-home-menu__text-group { display: flex; flex-direction: column; padding: 4px 0; }
.hm-sw-home-menu__text-group + .hm-sw-home-menu__text-group { border-top: 1px solid var(--hm-sw-home-menu-more-divider-color); }
.hm-sw-home-menu__text-title {
padding: 4px 12px;
overflow: clip;
overflow-clip-margin: var(--hm-sw-header-text-outline-room);
color: var(--hm-sw-home-menu-more-title-color);
font-size: var(--hm-sw-header-label-size);
font-weight: 700;
line-height: 16px;
letter-spacing: 0.04em;
white-space: nowrap;
text-overflow: ellipsis;
text-shadow: var(--hm-sw-header-label-outline);
}
.hm-sw-home-menu__text-list { display: flex; flex-direction: column; }
/* Row = header/dropdown/brown/row: 30px, 12px in, 14px 700 with the menu text outline. */
.hm-sw-home-menu__text-item {
display: flex;
align-items: center;
gap: 12px;
height: var(--hm-sw-home-menu-more-row-height);
padding: 0 12px;
color: var(--hm-sw-home-menu-item-color);
font-size: var(--hm-sw-header-font-size);
font-weight: 700;
line-height: 16px;
letter-spacing: 0.04em;
white-space: nowrap;
text-align: left;
}
.hm-sw-home-menu__text-label {
flex: 1 1 auto;
min-width: 0;
overflow: clip;
overflow-clip-margin: var(--hm-sw-header-text-outline-room);
text-overflow: ellipsis;
text-shadow: var(--hm-sw-header-text-outline);
}
/* Indicator column: › for a row that opens a sub panel (최근파일); ✓ only for an opt-in toggle row (toggle: true). */
.hm-sw-home-menu__text-item::after { content: ""; flex: 0 0 14px; width: 14px; text-align: center; }
.hm-sw-home-menu__text-item[aria-pressed="true"]::after { content: "✓"; }
.hm-sw-home-menu__text-item[aria-expanded]::after { content: "›"; }
/* Hover = pressed = Active (the tiles' Variant2 = Variant3, the brown rows' hover = selected): brown row, orange
text. Action rows keep nothing after the click; only the 최근파일 row stays Active while its list is open. */
.hm-sw-home-menu__text-item:is(:hover, :active, [data-active="true"]):not(:disabled) {
background: var(--hm-sw-home-menu-more-row-hover-background);
color: var(--hm-sw-home-menu-more-row-hover-color);
}
/* Accent joint (narrow widths, 2026-10-19): where the sub panel reaches past the menu, the outline's bottom band runs
on over the panel's top edge — same 2px, same gradient slice (the outline gradient sized to its box, bottom), so
the menu's bottom line and the list's side borders meet without a gap. Left / width from the measured positions. */
.hm-sw-home-menu__joint {
position: absolute;
z-index: 1;
top: calc(100% - var(--hm-sw-home-menu-accent-outline-width));
height: var(--hm-sw-home-menu-accent-outline-width);
background: var(--hm-sw-home-menu-accent-outline) left bottom / 100% var(--hm-sw-home-menu-accent-outline-height) no-repeat;
pointer-events: none;
}
/* Sub panel under the menu, at its tile (position set from the DOM). */
.hm-sw-home-menu__panel { position: absolute; z-index: 1; top: 100%; }
@@ -104,8 +218,8 @@
top: 47px;
right: 0;
left: 0;
height: 30px;
padding: 2px;
height: var(--hm-sw-home-menu-accent-outline-height);
padding: var(--hm-sw-home-menu-accent-outline-width);
border-radius: 0 0 3px 3px;
background: var(--hm-sw-home-menu-accent-outline);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
@@ -238,16 +352,17 @@
text-shadow: var(--hm-sw-header-text-outline);
}
/* Variant2 (hover) = Variant3 (active): orange stroke, #FF5C00 icon and label, art 1px up-left. */
.hm-sw-home-menu__item:is(:hover:not(:disabled), [data-active="true"])::after { opacity: 1; }
.hm-sw-home-menu__item:is(:hover:not(:disabled), [data-active="true"]) {
/* Variant2 (hover) = Variant3 (pressed while held; stays only as Active of a panel tile / More): orange stroke,
#FF5C00 icon and label, art 1px up-left. Action tiles keep nothing after the click (2026-10-19). */
.hm-sw-home-menu__item:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"])::after { opacity: 1; }
.hm-sw-home-menu__item:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"]) {
--hm-sw-home-menu-icon-color: var(--hm-sw-home-menu-item-accent-color);
color: var(--hm-sw-home-menu-item-accent-color);
}
.hm-sw-home-menu__item:is(:hover:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon { transform: translate(-1px, -1px); }
.hm-sw-home-menu__item:is(:hover:not(:disabled), [data-active="true"]) .hm-sw-home-menu__label { top: 24px; }
.hm-sw-home-menu__item[data-has-hover-icon]:is(:hover:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon:not(.hm-sw-home-menu__icon--hover) { visibility: hidden; }
.hm-sw-home-menu__item[data-has-hover-icon]:is(:hover:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon--hover { visibility: visible; }
.hm-sw-home-menu__item:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon { transform: translate(-1px, -1px); }
.hm-sw-home-menu__item:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"]) .hm-sw-home-menu__label { top: 24px; }
.hm-sw-home-menu__item[data-has-hover-icon]:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon:not(.hm-sw-home-menu__icon--hover) { visibility: hidden; }
.hm-sw-home-menu__item[data-has-hover-icon]:is(:hover:not(:disabled), :active:not(:disabled), [data-active="true"]) .hm-sw-home-menu__icon--hover { visibility: visible; }
/* SWRecentFiles — Figma 최근파일 662:67331 (354 x 750: rows 50px) + 2-depth preview 662:67471 (310 x 270). */
.hm-sw-recent-files { position: relative; width: var(--hm-sw-recent-files-width); }
@@ -321,6 +436,26 @@
filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.5));
pointer-events: none;
}
/* No room on the right (narrow widths): the same preview mirrored on the left of the list. */
.hm-sw-recent-files__preview[data-side="left"] {
right: calc(100% + 1px);
left: auto;
border-right: 0;
border-left: 1px solid var(--hm-sw-recent-files-preview-background);
border-radius: 3px 0 0 4px;
box-shadow: inset -2px 0 2.5px rgba(0, 0, 0, 0.5);
}
.hm-sw-recent-files__preview[data-side="left"] .hm-sw-recent-files__preview-body { border-radius: 0 0 0 4px; }
/* No room on either side (e.g. 390px): the same preview over the list, right under the row (above it when the
viewport has no room below). Not in Figma (2026-10-19 사용자 확정). */
.hm-sw-recent-files__preview:is([data-side="below"], [data-side="above"]) {
left: 1px;
border-left: 1px solid var(--hm-sw-recent-files-preview-background);
border-radius: 4px;
box-shadow: none;
}
.hm-sw-recent-files__preview[data-side="above"] { transform: translateY(-100%); }
.hm-sw-recent-files__preview:is([data-side="below"], [data-side="above"]) .hm-sw-recent-files__preview-body { border-radius: 0 0 4px 4px; }
.hm-sw-recent-files__preview-title {
width: 294px;
overflow: hidden;
+40
View File
@@ -29,6 +29,9 @@
/* Figma header/btn_home (26:10655) "hover" stroke: 1px, top -> bottom. */
--hm-sw-header-hover-border-gradient: linear-gradient(180deg, #a67d03 0%, #f2cc0f 16.41%, #fadd4f 37.19%, #f2cc0f 62.03%, #926e03 87.88%);
--hm-sw-header-hover-border-width: 1px;
/* Figma 662:67267 "home on" (logo menu open): header border-top / border-bottom, 2px, full width. */
--hm-sw-header-home-on-line-color: #5f4b0f;
--hm-sw-header-home-on-line-width: 2px;
/* Figma header/document_tit (26:10662) document dropdown */
--hm-sw-header-document-menu-width: 310px;
@@ -1883,7 +1886,44 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
/* ...and the file name gives way only down to the default design's smallest width (96px, see the clamps above). */
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control[data-control="document"] { min-width: 96px; }
/* Measured More (SWHeader, 2026-10-19): on top of the breakpoints, parts move into More while the object area has no
room for them and return when it has. data-measuring is set for one synchronous layout read and removed before
paint: nothing in the object area shrinks, grows or takes free space through an auto margin, so the in-flow content
shows the room it really needs. A configured file name counts with the smallest default file-name width (96px).
!important: measurement only, it must win over every tier and representative-slot rule above. */
.hm-sw-header[data-measuring] :is(.hm-sw-header__object-area, .hm-sw-header__fields, .hm-sw-header__control) > * { flex-grow: 0 !important; flex-shrink: 0 !important; }
.hm-sw-header[data-measuring] .hm-sw-header__properties,
.hm-sw-header[data-measuring] .hm-sw-header__object-area[data-squeezed~="properties"] > .hm-sw-header__more { margin-left: 0 !important; }
@container hm-sw-header (max-width: 1079.98px) {
.hm-sw-header[data-measuring] .hm-sw-header__more { margin-left: 0 !important; }
}
.hm-sw-header[data-measuring] .hm-sw-header__control[data-control="document"] { flex: 0 0 96px !important; min-width: 0 !important; }
/* Measured parts take the place the breakpoints give them: without 그리기모드 the fields start 4px in (as < 1180),
without the property block More sits at the end of the object area (as < 1080). */
.hm-sw-header__object-area[data-squeezed~="drawingMode"]:not([data-configured]) > .hm-sw-header__fields { padding-left: 4px; }
.hm-sw-header__object-area[data-squeezed~="properties"] > .hm-sw-header__more { margin-left: auto; margin-right: 4px; }
/* Logo (home) menu layer (SWHeader homeMenu, 2026-10-19). Top of the header, left / max-width measured from the
object area (−2px) to the window area. Above the property bar anchor (2), under the dropdowns (30) and the
property window (40). The logo, the file name and the window area stay visible beside it. */
.hm-sw-header__home-menu { position: absolute; top: 0; z-index: 20; }
/* Home On (Figma 662:67267 btn_home + border-top / border-bottom, 2026-10-19): only while the logo menu is open.
The logo keeps the yellow hover border (the Home On asset draws the same 1px inner gradient border) and the header
gets the 2px #5F4B0F lines across its full width. Figma layer order: border-bottom under the menu (20) over the
file name / window area, border-top over the menu, the logo over both. Lines are overlays: no size or position
changes, and closing the menu removes them. SW normal mode only (SWViewerHeader has no logo menu). */
.hm-sw-header[data-home-menu-open] .hm-sw-header__home-bg-hover { opacity: 1; }
.hm-sw-header[data-home-menu-open] > .hm-sw-header__bar > .hm-sw-header__home { z-index: 22; }
.hm-sw-header[data-home-menu-open]::before,
.hm-sw-header[data-home-menu-open]::after {
content: "";
position: absolute;
left: 0;
right: 0;
height: var(--hm-sw-header-home-on-line-width);
background: var(--hm-sw-header-home-on-line-color);
pointer-events: none;
}
.hm-sw-header[data-home-menu-open]::before { top: 0; z-index: 21; }
.hm-sw-header[data-home-menu-open]::after { top: calc(var(--hm-sw-header-bar-height) - var(--hm-sw-header-home-on-line-width)); z-index: 1; }