feat(sw): refine viewer model layout design and theme tokens
Deploy Storybook / deploy (push) Successful in 1m13s
Deploy Storybook / deploy (push) Successful in 1m13s
- Model / Layout tabs follow Figma 7652:40344: gradient faces, gold / green gradient borders, blurred outline, inner shadow and top gloss, with every color in its own --hm-sw-viewer-tab(s)-* token (consumer-overridable) - Box height matches the adjacent field group (42px, from the control height token); padding 1px 1px 1.5px, button side padding 5px, 1px right margin for a tabs-only first group; 378px max width, equal buttons and the measured More steps are unchanged; existing text-shadow kept - Storybook: SW/Viewer > Model / Layout story (states and token override); Logo story listed before Responsive More - AI docs (VIEWER.md, DESIGN_RULES.md) and viewer / text-weight test expectations updated
This commit is contained in:
1 parent
7a5236f3ad
commit
325ec25e37
6 files changed
+197
-56
No files matched your search
@@ -28,6 +28,40 @@
|
||||
--hm-sw-viewer-button-height: 34px;
|
||||
/* Model / Layout field face: widest size (2026-10-23); narrower when the header has less room. */
|
||||
--hm-sw-viewer-tabs-max-width: 378px;
|
||||
/* Model / Layout colors and shape (Figma 7652:40344, 2026-10-23). Used only by .hm-sw-viewer-header__tabs / __tab:
|
||||
set any of them on the SW scope or the header to recolor the tabs alone. Container: */
|
||||
/* The outer height of the adjacent field group (40px control row + its 1px top and bottom border = 42px). */
|
||||
--hm-sw-viewer-tabs-height: calc(var(--hm-sw-viewer-control-height) + 2px);
|
||||
--hm-sw-viewer-tabs-radius: 6px;
|
||||
--hm-sw-viewer-tabs-background: rgba(0, 0, 0, 0.8);
|
||||
/* Outline: a 1px line just outside the frame, blurred (Figma Rectangle 9333: stroke outside + layer blur 2). */
|
||||
--hm-sw-viewer-tabs-outline-color: #349f7f;
|
||||
--hm-sw-viewer-tabs-outline-blur: 1px;
|
||||
/* Extra glow around the frame: none in Figma (the blurred outline is the glow); set a color to add one. */
|
||||
--hm-sw-viewer-tabs-glow-color: transparent;
|
||||
--hm-sw-viewer-tabs-glow: 0 0 4px var(--hm-sw-viewer-tabs-glow-color);
|
||||
--hm-sw-viewer-tabs-shadow: inset 0 2.857px 2.857px rgba(0, 0, 0, 0.25);
|
||||
/* Buttons (both states share the face): */
|
||||
--hm-sw-viewer-tab-radius: 5px;
|
||||
--hm-sw-viewer-tab-border-width: 1.428px;
|
||||
--hm-sw-viewer-tab-gap: 14px;
|
||||
--hm-sw-viewer-tab-background: #0e3d2f;
|
||||
--hm-sw-viewer-tab-gradient: linear-gradient(180deg, #081813 0%, #09271e 8.6884%, #0b3227 25.428%, #0e3d2f 51.252%, #0b3227 80.027%, #0a2b21 89.759%, #081813 100%);
|
||||
/* Selected (Model in Figma) / not selected (Layout in Figma). Borders are images (Figma gradient strokes, top to
|
||||
bottom); a solid border is linear-gradient(<color>, <color>). */
|
||||
--hm-sw-viewer-tab-active-border: linear-gradient(180deg, #a67d03 0%, #f2cc0f 16.4%, #fadd4f 37.2%, #f2cc0f 62%, #926e03 87.9%);
|
||||
--hm-sw-viewer-tab-active-text: var(--hm-sw-viewer-accent);
|
||||
--hm-sw-viewer-tab-inactive-border: linear-gradient(180deg, #189b6e 0%, #287a5e 100%);
|
||||
--hm-sw-viewer-tab-inactive-text: var(--hm-sw-viewer-text);
|
||||
/* Top gloss of the not-selected button: */
|
||||
--hm-sw-viewer-tab-highlight-color: #2d8b6b;
|
||||
--hm-sw-viewer-tab-highlight-end: rgba(11, 45, 34, 0);
|
||||
--hm-sw-viewer-tab-highlight-opacity: 0.67;
|
||||
--hm-sw-viewer-tab-highlight-height: 20px;
|
||||
/* Interaction (no Figma hover: the former text-only #FFC600 hover is kept): */
|
||||
--hm-sw-viewer-tab-hover-border: var(--hm-sw-viewer-tab-inactive-border);
|
||||
--hm-sw-viewer-tab-hover-text: var(--hm-sw-viewer-accent);
|
||||
--hm-sw-viewer-tab-focus-color: var(--hm-sw-viewer-accent);
|
||||
--hm-sw-viewer-font-size: 14px;
|
||||
--hm-sw-viewer-label-size: 11px;
|
||||
--hm-sw-viewer-icon-size: 16px;
|
||||
@@ -353,31 +387,94 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
.hm-sw-viewer-icon__glyph { position: relative; }
|
||||
.hm-sw-viewer-icon svg { width: 100%; height: 100%; overflow: visible; }
|
||||
|
||||
/* Tabs (Model / Layout): 40px triggers with an 11px count; the active one turns #FFC600.
|
||||
Width (2026-10-23): equal columns (1fr: each at least the widest name + count, so they never overlap). A group
|
||||
/* Tabs (Model / Layout), Figma 7652:40344 (2026-10-23): a dark container (overlay face with a 1px margin, a blurred
|
||||
green line outside it, inner top shadow over the buttons) holding buttons 1px apart; each button has the gradient
|
||||
face, a gold (selected) or green (not selected) gradient border and the name (Bold) + count (Regular) at 14px; the
|
||||
not-selected one has a green top gloss. Every color is a --hm-sw-viewer-tab(s)-* token (see the token block).
|
||||
Width (2026-10-23, kept): equal columns (1fr: each at least the widest name + count, so they never overlap). A group
|
||||
holding only the tabs starts at that natural width and takes the object's free room up to
|
||||
--hm-sw-viewer-tabs-max-width (outer field face); with less room it stays narrower, and below the natural width the
|
||||
measured steps move it into More as before (the measurement, `data-measuring`, uses the natural width). */
|
||||
.hm-sw-viewer-header__tabs { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; align-items: center; gap: var(--hm-sw-viewer-gap); }
|
||||
.hm-sw-viewer-header__group:has(> .hm-sw-viewer-header__tabs:only-child) { flex: 1 0 auto; box-sizing: border-box; max-width: var(--hm-sw-viewer-tabs-max-width); }
|
||||
--hm-sw-viewer-tabs-max-width (the container is the group face then); with less room it stays narrower, and below
|
||||
the natural width the measured steps move it into More as before (`data-measuring` uses the natural width). */
|
||||
.hm-sw-viewer-header__tabs {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: 1fr;
|
||||
align-items: stretch;
|
||||
gap: 1px;
|
||||
box-sizing: border-box;
|
||||
height: var(--hm-sw-viewer-tabs-height);
|
||||
/* The transparent 1px edge is the room of the blurred outline (it stays inside the box height); the padding is the
|
||||
black margin around the buttons, which fill the rest. 1.5px at the bottom (2026-10-23 user value): the outline
|
||||
blur covers part of a 1px bottom margin, while the inner shadow darkens the top, so 1px read as no room below. */
|
||||
padding: 1px 1px 1.5px 1px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: calc(var(--hm-sw-viewer-tabs-radius) + 1px);
|
||||
background: var(--hm-sw-viewer-tabs-background) padding-box;
|
||||
box-shadow: var(--hm-sw-viewer-tabs-glow);
|
||||
}
|
||||
/* The outline (blurred, on the frame edge) and the inner shadow lie over the buttons, as the last Figma layers do. */
|
||||
.hm-sw-viewer-header__tabs::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
/* The outer edge of the box (its border box, over the transparent 1px edge row): same size as the tabs box. */
|
||||
inset: -1px;
|
||||
border: 1px solid var(--hm-sw-viewer-tabs-outline-color);
|
||||
border-radius: inherit;
|
||||
filter: blur(var(--hm-sw-viewer-tabs-outline-blur));
|
||||
/* The blur may soften 1px past that edge, no further (2026-10-23 user decision). */
|
||||
clip-path: inset(-1px round calc(var(--hm-sw-viewer-tabs-radius) + 1px));
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-viewer-header__tabs::after { content: ""; position: absolute; z-index: 1; inset: 0; border-radius: var(--hm-sw-viewer-tabs-radius); box-shadow: var(--hm-sw-viewer-tabs-shadow); pointer-events: none; }
|
||||
/* A group holding only the tabs: the container is its face (no field face around it, as in Figma: the tabs sit on
|
||||
the header object surface). */
|
||||
.hm-sw-viewer-header__group:has(> .hm-sw-viewer-header__tabs:only-child) { flex: 1 0 auto; box-sizing: border-box; max-width: var(--hm-sw-viewer-tabs-max-width); min-height: 0; padding: 0; border: 0; background: none; box-shadow: none; }
|
||||
.hm-sw-viewer-header__group > .hm-sw-viewer-header__tabs:only-child { flex: 1 1 auto; }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab {
|
||||
/* 2026-10-23 user value: the tabs-only group that starts the object surface gets 1px more on its right (the buttons
|
||||
give it back in their side padding). Other groups keep the object gap. */
|
||||
.hm-sw-viewer-header__object > .hm-sw-viewer-header__group:first-child:has(> .hm-sw-viewer-header__tabs:only-child) {
|
||||
margin-right: 1px;
|
||||
}.hm-sw-viewer-header .hm-sw-viewer-header__tab {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--hm-sw-viewer-gap);
|
||||
height: var(--hm-sw-viewer-control-height);
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--hm-sw-viewer-radius);
|
||||
color: var(--hm-sw-viewer-text);
|
||||
gap: var(--hm-sw-viewer-tab-gap);
|
||||
box-sizing: border-box;
|
||||
padding: 0 5px; /* 6px less the 2px of the tabs edge row and the 1px group margin above, so the natural width (measured steps) is unchanged */
|
||||
/* The border is the state's border image painted under a transparent border (keeps the radius). */
|
||||
--hm-sw-viewer-tab-border-face: var(--hm-sw-viewer-tab-inactive-border);
|
||||
border: var(--hm-sw-viewer-tab-border-width) solid transparent;
|
||||
border-radius: 0;
|
||||
background: var(--hm-sw-viewer-tab-gradient) padding-box, linear-gradient(var(--hm-sw-viewer-tab-background), var(--hm-sw-viewer-tab-background)) padding-box, var(--hm-sw-viewer-tab-border-face) border-box;
|
||||
color: var(--hm-sw-viewer-tab-inactive-text);
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:hover:not(:disabled) { color: var(--hm-sw-viewer-accent); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab[data-state="active"] { color: var(--hm-sw-viewer-accent); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:first-child { border-start-start-radius: var(--hm-sw-viewer-tab-radius); border-end-start-radius: var(--hm-sw-viewer-tab-radius); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:last-child { border-start-end-radius: var(--hm-sw-viewer-tab-radius); border-end-end-radius: var(--hm-sw-viewer-tab-radius); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:not([data-state="active"])::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
inset: 0 0 auto;
|
||||
height: var(--hm-sw-viewer-tab-highlight-height);
|
||||
border-radius: inherit;
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
background: linear-gradient(180deg, var(--hm-sw-viewer-tab-highlight-color), var(--hm-sw-viewer-tab-highlight-end));
|
||||
opacity: var(--hm-sw-viewer-tab-highlight-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:hover:not(:disabled, [data-state="active"]) { --hm-sw-viewer-tab-border-face: var(--hm-sw-viewer-tab-hover-border); color: var(--hm-sw-viewer-tab-hover-text); }
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab[data-state="active"] { --hm-sw-viewer-tab-border-face: var(--hm-sw-viewer-tab-active-border); color: var(--hm-sw-viewer-tab-active-text); }
|
||||
.hm-sw-viewer-header__count { font-size: var(--hm-sw-viewer-label-size); font-weight: 400; font-variant-numeric: tabular-nums; }
|
||||
/* In a tab the count has the name's size (Figma: 14px Regular) and color. */
|
||||
.hm-sw-viewer-header__tab .hm-sw-viewer-header__count { font-size: inherit; }
|
||||
|
||||
/* Switch fields (2.5D, 어두운 배경): 11px muted caption above the switch. */
|
||||
.hm-sw-viewer-header__switch-field {
|
||||
@@ -391,17 +488,21 @@ button.hm-sw-viewer-header__brand { text-align: left; }
|
||||
}
|
||||
.hm-sw-viewer-header__switch-label { color: var(--hm-sw-viewer-muted); font-size: var(--hm-sw-viewer-label-size); font-weight: 400; line-height: 1; white-space: nowrap; }
|
||||
|
||||
.hm-sw-viewer-header :is(.hm-sw-viewer-header__button, .hm-sw-viewer-header__tab, .hm-sw-viewer-header__brand, .hm-sw-viewer-switch):focus-visible {
|
||||
.hm-sw-viewer-header :is(.hm-sw-viewer-header__button, .hm-sw-viewer-header__brand, .hm-sw-viewer-switch):focus-visible {
|
||||
outline: 2px solid var(--hm-sw-viewer-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
/* Tabs: inside the button (the neighbor is 1px away and the container shadow lies on top). */
|
||||
.hm-sw-viewer-header .hm-sw-viewer-header__tab:focus-visible { z-index: 2; outline: 2px solid var(--hm-sw-viewer-tab-focus-color); outline-offset: -2px; }
|
||||
|
||||
/* Text and icon stroke (2026-10-19 request): the SWHeader black outline on the viewer header, same tokens and
|
||||
method — an outside outline of the glyph / icon shape, never a box border.
|
||||
Text: 14px = --hm-sw-header-text-outline (1.3px black), 11px captions / counts / status = --hm-sw-header-label-outline
|
||||
(1px black), as SWHeader's 12px labels. Icons: SWViewerIcon (SVG stroke, see the token). Fill color, size,
|
||||
weight and spacing are unchanged; hover / active / open change only the fill, disabled fades the whole button
|
||||
(opacity) like SWHeader. Dropdown faces (light / beige) and panels get no outline, like SWHeader's More menu. */
|
||||
(opacity) like SWHeader. Dropdown faces (light / beige) and panels get no outline, like SWHeader's More menu.
|
||||
Model / Layout (2026-10-23 user decision): the Figma 7652:40344 look keeps these outlines unchanged (name 1.3px,
|
||||
count 1px, also at the count's new 14px). */
|
||||
.hm-sw-viewer-header :is(
|
||||
.hm-sw-viewer-header__brand-mark, .hm-sw-viewer-header__document-name, .hm-sw-viewer-header__label, .hm-sw-viewer-header__tab
|
||||
) {
|
||||
|
||||
Reference in new issue
Block a user