fix(sw): 2026-10-21 SW design review and responsive header without More from 760px

User-approved design review (2026-10-21):
- SW Header: from 760px up nothing moves into More; the five menus stay with their
  titles (titles and values ellipsize), file name shrinks to 96px. Open next to the
  logo no longer blocks field shrinking above 760px (760/768 overflow fixed, also for
  configured `controls` headers). Dropdown yellow ring inside the button, black seam.
- Property Bar values as read-only red text; property popup "=" labels, read-only
  black values, per-entry hover with pointer.
- Home Menu recent files (3 rows, path + name caption, empty preview), Footer + slot
  guide off by default, Viewer file list without icons, Storybook widths 1920-760.
- Tests: old More expectations (integrated-improvement, keyboard-dismiss,
  footer-popup-viewer-refinement, storybook-cleanup-stage1) moved to the new rule;
  functional checks kept.
This commit is contained in:
shkim4 committed 2026-10-21 17:29:25 +09:00
1 parent a25d2b31c5
commit 2fc1b3e108
37 files changed
+447 -211

No files matched your search

+134 -32
View File
@@ -240,6 +240,10 @@
.hm-sw-property-bar__field { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.hm-sw-property-bar__field label { color: var(--hm-sw-header-text-color); letter-spacing: .04em; }
.hm-sw-property-bar__field .hm-sw-header__input { flex-shrink: 0; }
/* 2026-10-21 (user decision): the bar shows its values (반지름 · 지름 · 원주 · 면적 …) as plain text (an <output>, never
focused) in the input's place, font and outline. They stay red: the property window edits them. */
.hm-sw-property-bar__value { flex-shrink: 0; padding: 2px 4px 2px 6px; color: var(--hm-sw-header-input-color); font-size: var(--hm-sw-header-font-size); font-weight: 700; line-height: 16px; letter-spacing: 0.02em; white-space: nowrap; text-shadow: var(--hm-sw-header-text-outline); }
.hm-sw-property-bar__value[data-disabled="true"] { opacity: 0.4; }
.hm-sw-property-bar:focus-visible { outline: 2px solid var(--hm-sw-header-focus-color); outline-offset: -2px; }
/* Configured controls use the same components in an ordered list; no empty removed slots.
@@ -1602,6 +1606,10 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
/* SWSelect: native select, panel field look; focus = the SW popup focus ring. */
.hm-sw-select { flex: 0 0 auto; height: 28px; padding: 0 8px; border: 1px solid var(--hm-sw-select-border-color); border-radius: 4px; background: var(--hm-sw-select-background); color: var(--hm-sw-select-text); font: inherit; font-size: 12px; }
.hm-sw-select:disabled { opacity: 0.4; cursor: default; }
/* 2026-10-21: in an SW popup the chosen value of an enabled select is editable, so it is red like every editable value;
a disabled select and the option list keep --hm-sw-select-text. The viewer panels keep their own text color (sw-viewer.css). */
.hm-sw-property .hm-sw-select:not(:disabled) { color: var(--hm-sw-header-input-color); }
.hm-sw-property .hm-sw-select option { color: var(--hm-sw-select-text); }
.hm-sw-property__unit { flex: 0 0 auto; font-size: 12px; white-space: nowrap; }
/* Color preview (26:12175): 70 x 16 black tile (1px padding, radius 2), 68 x 14 swatch with a white 1px border. */
.hm-sw-property__swatch-area { display: flex; align-items: center; flex: 0 0 auto; }
@@ -1654,6 +1662,12 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-property__expand[aria-expanded="true"] > img { transform: rotate(180deg); }
.hm-sw-property__counts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.hm-sw-property__counts > span { min-height: 28px; padding: 5px 0; border-bottom: 1px solid rgba(0,0,0,.5); font-weight: 700; }
/* 2026-10-21 design review: each object type entry (원 1, 폴리선 10 …) changes its own text color on Hover, with the
SW row accent #FF5C00 (the header list row hover color). Display only: no click, selection or filter. */
.hm-sw-property__counts > span:hover { color: var(--hm-sw-header-layer-row-hover-color); }
/* 2026-10-21 user request: Popup items whose color changes on Hover show the pointer (display-only entries too: no
click is added). Inputs and read-only values keep their own cursor. */
:is(.hm-sw-property__tool, .hm-sw-property__close, .hm-sw-property__expand, .hm-sw-property__counts > span) { cursor: pointer; }
.hm-sw-property__group--circle .hm-sw-property__group-content { position: relative; min-height: 260px; }
.hm-sw-property__group--circle .hm-sw-property__editors { position: relative; min-height: 260px; padding-top: 260px; }
/* Shape slot (Figma 26:11932 / 26:12035): 208 x 208 at (39, 26) of the 260px shape group. Default content is
@@ -1664,6 +1678,8 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-property__group--circle .hm-sw-property__editor { position: absolute; }
.hm-sw-property__group--circle .hm-sw-property__row { padding: 0; min-height: 24px; gap: 4px; }
.hm-sw-property__group--circle .hm-sw-property__row > label { flex-basis: auto; }
/* 2026-10-21 user request: 원주 / 지름 / 반지름 / 면적 = [Input] — a text "=" after the name, outside the input. */
.hm-sw-property__editor[data-slot] .hm-sw-property__row > label::after { content: " =" / ""; }
.hm-sw-property__group--circle .hm-sw-property__value { flex: 0 1 auto; }
.hm-sw-property__group--circle .hm-sw-header__input { width: calc(var(--hm-sw-input-chars) * 1ch + 8px); flex: 0 1 auto; }
.hm-sw-property__editor[data-slot="circumference"] { left: 113px; top: 0; }
@@ -1749,6 +1765,46 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
text-shadow: var(--hm-sw-header-text-outline);
}
/* Slider value box (2026-10-21, 투명도): value + annotation in one field that fills the room before the gauge; the
same Default / Hover / On (#0F0D0A, white annotation) as the number box. */
.hm-sw-property__slider-value {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
flex: 1 1 auto;
min-width: 0;
height: 28px;
padding: 0 7px;
border: 1px solid transparent;
border-radius: 2px;
}
.hm-sw-property__row:not([data-disabled]) .hm-sw-property__slider-value:is(:hover, :not([data-readonly]):focus-within) { border-color: var(--hm-sw-property-field-hover-border-color); }
.hm-sw-property__slider-value > .hm-sw-header__input,
.hm-sw-property__slider-value > .hm-sw-header__input:is(:hover, :focus) {
flex: 1 1 auto;
align-self: stretch;
width: auto;
min-width: 0;
height: auto;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
font-weight: 500;
letter-spacing: 0.02em;
text-shadow: none;
}
.hm-sw-property__slider-value > .hm-sw-header__input:not(:read-only):focus-visible { outline: none; }
.hm-sw-property__slider-value[data-on="edit"]:focus-within { background: var(--hm-sw-property-field-editing-background); }
.hm-sw-property__slider-value[data-on="edit"]:focus-within > .hm-sw-header__input { text-shadow: var(--hm-sw-header-text-outline); }
.hm-sw-property__slider-value[data-on="edit"]:focus-within > .hm-sw-property__unit { color: var(--hm-sw-property-field-editing-unit-color); }
/* Value color rule (2026-10-21): red (#E60403) marks an editable value only. A read-only, computed or disabled value
is the black label color of the light popup; a disabled row also keeps its 40%. */
:is(.hm-sw-property__value, .hm-sw-property__slider-value)[data-readonly] :is(.hm-sw-header__input, .hm-sw-property__trigger, select:not(.hm-sw-select)) { color: var(--hm-sw-property-label); }
/* Dropdown fields: layer (pl/pr 8), line style (pl 8, pr 6: value + (ByL) + pattern), color (RGB | swatch). */
.hm-sw-property__trigger {
display: flex;
@@ -1808,40 +1864,72 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
}
/* Responsive — header width (container query); the header never scrolls horizontally.
Fields keep their Figma widths at every tier (measured Noto Sans KR content widths are
231 / 168 / 278 / 142 / 163px, so shrinking would only truncate values). Tools move into
More instead, so every Prop and Callback stays reachable. Breakpoints come from the
measured widths: fixed parts (home 63 + gaps 6 + window 240) + object area + file name.
From 760px up (2026-10-21 user decision; the SW Footer story widths) nothing moves into More: the five menus 레이어 · 색상 · 선스타일 ·
문자스타일 · 치수스타일 always stay in the header with their titles, and the More button is not shown. As it narrows:
>= 1800 Figma layout. 1920 -> 1800 only the file name shrinks (310 -> 190).
< 1800 선/문자/치수 스타일 -> More (object area 1301 -> 719)
< 1180 그리기모드 label hides (719 -> 633)
< 1080 속성Bar/속성창 -> More, window move 88 -> 24 (633 -> 480, window 240 -> 176)
< 900 색상 -> More (480 -> 284)
< 640 인쇄/팝업 가이드 -> More, window move hides (window 176 -> 64)
Only below ~520px (phones) does the layer field shrink: its state icons hide and the
layer name ellipsizes.
The file name always takes the remaining width (ellipsis). cqw = header width.
Hiding targets the flex item of each field ([data-menu] / [data-tier] on the field button, or on
the wrapper of a field with a dropdown), so a dropdown field hides like a plain one. */
< 1800 the 그리기모드 text hides, the line-type / dimension pictures hide and the color chip is 24px (fields take
their content width); the file name gives way first (down to 96px, ellipsis), then the field values
shrink with an ellipsis, each by its own width.
< 1080 window move area 88 -> 24 (window 240 -> 176); layer-state icons, the color chip and the 문자스타일
font name hide, field insets 6px.
<= 900 the 선스타일 field shows 선스타일 only (가중치 / 스케일 hide).
Below 760px (phones, e.g. 390) the former tiers are unchanged: parts move into More (선/문자/치수 스타일, 그리기모드,
속성Bar/속성창, 색상, then 인쇄/팝업 가이드 below 640), and below 520 the layer-state icons hide.
Each field keeps its button, title, value, dropdown and hover / focus box. cqw = header width.
Hiding targets the flex item of each field ([data-menu] / [data-tier] on the field button, or on the wrapper of a
field with a dropdown), so a dropdown field hides like a plain one. */
.hm-sw-header__document { flex: 0 0 clamp(190px, calc(100cqw - 1610px - var(--hm-sw-header-leading-width, 0px)), 310px); min-width: 0; }
.hm-sw-header__field--layer { flex-shrink: 1; }
.hm-sw-header__field:not(.hm-sw-header__field--layer) { flex-shrink: 0; }
@container hm-sw-header (max-width: 1799.98px) {
.hm-sw-header__fields > [data-tier="aux"] { display: none; }
.hm-sw-header__more { display: block; }
.hm-sw-header__more-item[data-tier="aux"] { display: flex; }
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 1030px - var(--hm-sw-header-leading-width, 0px)), 310px); }
}
@container hm-sw-header (max-width: 1179.98px) {
@container hm-sw-header (min-width: 760px) and (max-width: 1799.98px) {
.hm-sw-header__mode { display: none; }
.hm-sw-header__fields { padding-left: 4px; }
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 944px - var(--hm-sw-header-leading-width, 0px)), 310px); }
/* Content width (no Figma box) that may shrink: the free room is taken from each value by its own width. */
.hm-sw-header__fields > :is(.hm-sw-header__layer, .hm-sw-header__menu),
.hm-sw-header__fields > .hm-sw-header__control > :is(.hm-sw-header__layer, .hm-sw-header__menu) { flex: 0 1 auto; width: auto; min-width: 0; }
.hm-sw-header__fields .hm-sw-header__field { flex: 0 1 auto; width: auto; min-width: 0; }
/* Fixed-size buttons in a configured bar (인쇄 · K) keep their box: only the fields give way. */
.hm-sw-header__fields > .hm-sw-header__control:is([data-control="print"], [data-control="popupGuide"]) { flex-shrink: 0; }
.hm-sw-header__layer > .hm-sw-header__field--layer { width: 100%; }
.hm-sw-header__field :is(.hm-sw-header__line-values, .hm-sw-header__font-name) { min-width: 0; }
/* Tighter fields: smaller insets, a 24px color chip, no line-type / dimension pictures (the value names stay). */
.hm-sw-header__fields .hm-sw-header__field { padding: 0 8px; gap: 8px; }
.hm-sw-header__fields .hm-sw-header__chip { flex-basis: 24px; width: 24px; }
.hm-sw-header__fields :is(.hm-sw-header__linetype, .hm-sw-header__dimension-icon) { display: none; }
.hm-sw-header__fields .hm-sw-header__line-values { gap: 8px; }
/* Open dropdown (2026-10-21 user request): the fields are narrower than their lists here, so the yellow ring stays
inside each button and the list gets its own ring, as a shifted list (치수스타일) already does. */
.hm-sw-header__fields :is(.hm-sw-header__layer-menu, .hm-sw-header__menu-panel:not(.hm-sw-header__line-menu))::before { top: -1px; }
.hm-sw-header__fields .hm-sw-header__field[data-expanded="true"]::after { opacity: 1; }
/* Where the list meets the button the seam is black across the button width (as the 선스타일 seam). */
.hm-sw-header__fields :is(.hm-sw-header__layer, .hm-sw-header__menu:not(.hm-sw-header__menu--line-style)):has(> .hm-sw-header__field[data-expanded="true"])::after {
content: "";
position: absolute;
z-index: 31;
top: calc(100% - 1px);
left: 0;
right: 0;
height: 2px;
background: var(--hm-sw-header-layer-menu-border-color);
pointer-events: none;
}
/* 1610 - 1301 + the tighter object area with titles, without 그리기모드 (891, measured) = 1200. */
.hm-sw-header__document { flex-basis: clamp(96px, calc(100cqw - 1200px - var(--hm-sw-header-leading-width, 0px)), 310px); }
}
@container hm-sw-header (min-width: 760px) and (max-width: 1079.98px) {
/* Values need the room (1024px, 2026-10-21 user request): the layer-state icons, the color chip and the font name
beside 문자스타일 hide, insets 6px, every value part may shrink (ellipsis). */
.hm-sw-header__fields .hm-sw-header__field { padding: 0 6px; gap: 6px; }
.hm-sw-header__fields :is(.hm-sw-header__field--layer .hm-sw-header__layer-icons, .hm-sw-header__chip, .hm-sw-header__font-name) { display: none; }
.hm-sw-header__fields .hm-sw-header__line-values { grid-template-columns: minmax(0, auto) minmax(0, 38px) minmax(0, 38px); gap: 6px; }
}
@container hm-sw-header (min-width: 760px) and (max-width: 900px) {
/* 900px and narrower (2026-10-21 user request): the 선스타일 field keeps only 선스타일 (가중치 / 스케일 hide). */
.hm-sw-header__fields .hm-sw-header__line-values { grid-template-columns: minmax(0, auto); }
.hm-sw-header__fields .hm-sw-header__line-values > :not(:first-child) { display: none; }
}
@container hm-sw-header (max-width: 1079.98px) {
.hm-sw-header__properties { display: none; }
.hm-sw-header__more { margin-left: auto; margin-right: 4px; }
.hm-sw-header__more-item[data-tier="panel"] { display: flex; }
.hm-sw-header__motion { flex-basis: 24px; background-size: 18px 37px; }
/* 24px move area: no room for the icon motion, so hover shows only the tooltip, right of the
area like Figma hover2 (x = area width + 1, y 11). */
@@ -1851,9 +1939,17 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
.hm-sw-header__motion-tooltip { left: calc(100% + 1px); top: 11px; }
.hm-sw-header__motion:hover > .hm-sw-header__motion-tooltip { animation: none; opacity: 1; }
.hm-sw-header__window { flex-basis: 176px; width: 176px; }
.hm-sw-header__document { flex-basis: clamp(120px, calc(100cqw - 725px - var(--hm-sw-header-leading-width, 0px)), 310px); }
}
@container hm-sw-header (max-width: 899.98px) {
/* Below 760px: the former tiers (선/문자/치수 스타일 < 1800, 그리기모드 < 1180, 속성Bar/속성창 < 1080, 색상 < 900). */
@container hm-sw-header (max-width: 759.98px) {
.hm-sw-header__fields > [data-tier="aux"] { display: none; }
.hm-sw-header__more { display: block; }
.hm-sw-header__more-item[data-tier="aux"] { display: flex; }
.hm-sw-header__mode { display: none; }
.hm-sw-header__fields { padding-left: 4px; }
.hm-sw-header__properties { display: none; }
.hm-sw-header__more { margin-left: auto; margin-right: 4px; }
.hm-sw-header__more-item[data-tier="panel"] { display: flex; }
.hm-sw-header__fields > [data-menu="color"] { display: none; }
.hm-sw-header__more-item[data-tier="narrow"] { display: flex; }
.hm-sw-header__document { flex-basis: clamp(96px, calc(100cqw - 529px - var(--hm-sw-header-leading-width, 0px)), 310px); }
@@ -1960,17 +2056,23 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
:is(.hm-sw-header__leading, .hm-sw-header__document[data-tool]) + .hm-sw-header__document > .hm-sw-header__document-button { padding-left: 12px; }
/* While the header Open is shown, the default fields keep their width: a lack of room becomes a measured
overflow that moves Open into More instead of narrowing the layer field. Custom leading content alone keeps
the existing behavior (file name first, then the layer field below 520px). */
.hm-sw-header[data-leading="open"] .hm-sw-header__fields:not([data-configured]) > :is(.hm-sw-header__layer, .hm-sw-header__field--layer, .hm-sw-header__control[data-control="layer"]) { flex-shrink: 0; }
the existing behavior (file name first, then the layer field below 520px). Below 760px only (2026-10-21): from
760px up nothing moves into More, so the fields shrink beside Open like everywhere else. */
@container hm-sw-header (max-width: 759.98px) {
.hm-sw-header[data-leading="open"] .hm-sw-header__fields:not([data-configured]) > :is(.hm-sw-header__layer, .hm-sw-header__field--layer, .hm-sw-header__control[data-control="layer"]) { flex-shrink: 0; }
}
.hm-sw-header__more[data-forced] { display: block; }
/* Tool groups reuse the 상세보기 / print-K surfaces but are host tools: the Header breakpoints that move those
built-in parts into More never hide them (they have no More entry). */
.hm-sw-header__properties.hm-sw-tool__group { display: flex; }
.hm-sw-header__actions.hm-sw-tool__pill { display: flex; }
/* Configured layouts too: while the header Open is shown, controls other than the file name keep their width,
so a lack of room is a measured overflow that moves Open into More (the file name still gives way first). */
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]),
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]) > * { flex-shrink: 0; }
so a lack of room is a measured overflow that moves Open into More (the file name still gives way first).
Below 760px only (2026-10-21), as above. */
@container hm-sw-header (max-width: 759.98px) {
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]),
.hm-sw-header[data-leading="open"] .hm-sw-header__fields[data-configured] > .hm-sw-header__control:not([data-control="document"]) > * { flex-shrink: 0; }
}
/* ...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; }