fix(sw): align header logo styles and refine viewer UI
Deploy Storybook / deploy (push) Successful in 1m13s

This commit is contained in:
shkim4 committed 2026-10-23 09:55:13 +09:00
1 parent 1d206c86cf
commit 205864b8a0
15 files changed
+236 -96

No files matched your search

@@ -254,6 +254,13 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
const [headerWidth, setHeaderWidth] = useState(1920)
const [, setAreaWidth] = useState(0)
const [barOffset, setBarOffset] = useState(0)
// 2026-10-23: the property bar strip reaches at least the 속성Bar | 속성창 divider (the 속성Bar toggle when the
// divider is not shown), measured like barOffset; null while the toggle is not in the header row (e.g. in More).
const [barEnd, setBarEnd] = useState<number | null>(null)
const measureBarEnd = (header: Element) => {
const edge = header.querySelector(".hm-sw-header__properties-divider") ?? header.querySelector(".hm-sw-header__properties .hm-sw-header__toggle:not([data-property-panel-trigger])")
setBarEnd(edge?.getClientRects().length ? edge.getBoundingClientRect().right - header.getBoundingClientRect().left : null)
}
// Open next to the logo (documentOpenPlacement="header") moves into More when the measured header has no room.
const openInHeader = documentOpenPlacement === "header" && shown("documentOpen")
const leadingRef = useRef<HTMLDivElement>(null)
@@ -270,6 +277,7 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
// The object area can change alone (leading content, file name, window area): it re-runs the measured More.
setAreaWidth(panelAnchorRef.current?.getBoundingClientRect().width ?? 0)
setBarOffset((panelAnchorRef.current?.getBoundingClientRect().left ?? bounds.left) - bounds.left)
measureBarEnd(header)
}
const observer = new ResizeObserver(update)
update()
@@ -277,6 +285,11 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
if (panelAnchorRef.current) observer.observe(panelAnchorRef.current)
return () => observer.disconnect()
}, [])
// The divider also moves when the measured More or the fields change inside the same object-area width.
useLayoutEffect(() => {
const header = fieldsRef.current?.closest(".hm-sw-header")
if (barOpen && header) measureBarEnd(header)
})
// Space for the parts right of the logo, measured: the file name gives way first (its CSS clamp subtracts
// --hm-sw-header-leading-width). Open and the other parts move into More by measurement (see "Measured More").
useLayoutEffect(() => {
@@ -729,7 +742,7 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
<SWHeaderWindowControls onWindowMoveStart={onWindowMoveStart} onMinimize={onMinimize} onClose={onClose} showWindowMove={shown("windowMove")} showMinimize={shown("minimize")} showClose={shown("close")} />
</div>}
</div>
{barOpen && barData && <div className="hm-sw-header__bar-anchor" style={{ left: barOffset, maxWidth: `calc(100% - ${barOffset}px)` }}><SWHeaderPropertyBar {...barData} disabled={disabled || propertyBar?.disabled} id={barId} /></div>}
{barOpen && barData && <div className="hm-sw-header__bar-anchor" style={{ left: barOffset, maxWidth: `calc(100% - ${barOffset}px)` }}><SWHeaderPropertyBar {...barData} disabled={disabled || propertyBar?.disabled} id={barId} style={barEnd !== null && barEnd > barOffset ? { minWidth: barEnd - barOffset } : undefined} /></div>}
{homeMenu && shown("home") && (
<div className="hm-sw-header__home-menu" style={{ left: homeBox.left, maxWidth: homeBox.width }}>
<SWHomeMenu {...homeMenu} id={homeMenu.id ?? homeMenuId} open={homeOpen} returnFocusRef={homeButtonRef}
@@ -1,10 +1,8 @@
import type { ReactNode } from "react"
import homeBackground from "../../../assets/home-bg.svg"
import homeBackgroundHover from "../../../assets/home-bg-hover.svg"
export interface SWHeaderHomeProps {
onClick?: () => void
/** Logo image URL, fitted (contain) into the 32 x 17 logo slot. */
/** Logo image URL, 17px high at its own ratio (the 32 x 17 example is not forced; 2026-10-23). */
logoSrc?: string
/** Logo element (inline SVG, <img>, ...). Takes precedence over `logoSrc`. */
logo?: ReactNode
@@ -21,22 +19,32 @@ export interface SWHeaderHomeProps {
}
/**
* Home button. The slanted background and the logo slot position are the common SW design;
* the logo itself belongs to each SW program, so no logo is rendered unless one is passed.
* The SW logo slot (sw.css `.hm-sw-header__logo`: 17px high, as wide as the logo at its own ratio): `logo` first,
* else an <img> of `logoSrc`, else nothing. Shared by SWHeaderHome and SWViewerHeader (2026-10-23) so both render the
* logo the same way; `className` adds the host's placement. Internal: not part of the package exports.
*/
export function SWHeaderLogo({ logo, logoSrc, className }: Pick<SWHeaderHomeProps, "logo" | "logoSrc"> & { className?: string }) {
const content = logo ?? (logoSrc ? <img src={logoSrc} alt="" /> : null)
return content ? <span className={`hm-sw-header__logo${className ? ` ${className}` : ""}`} aria-hidden="true">{content}</span> : null
}
/**
* Home button. The slanted background (Figma header/btn_home 26:10655, home-bg / home-bg-hover assets drawn as a
* 9-slice by sw.css, 2026-10-23) and the logo padding are the common SW design; the home is as wide as the logo
* needs. The logo itself belongs to each SW program, so no logo is rendered unless one is passed.
*/
export function SWHeaderHome({ onClick, logoSrc, logo, logoAlt, interactive = true, "aria-expanded": expanded, "aria-controls": controls }: SWHeaderHomeProps) {
const content = logo ?? (logoSrc ? <img src={logoSrc} alt="" /> : null)
if (!interactive) return (
<div className="hm-sw-header__home" data-static="" role={logoAlt ? "img" : undefined} aria-label={logoAlt}>
<img className="hm-sw-header__home-bg" src={homeBackground} alt="" aria-hidden="true" />
{content && <span className="hm-sw-header__logo" aria-hidden="true">{content}</span>}
<span className="hm-sw-header__home-bg" aria-hidden="true" />
<SWHeaderLogo logo={logo} logoSrc={logoSrc} />
</div>
)
return (
<button type="button" className="hm-sw-header__home" aria-label={logoAlt ? `${logoAlt} 홈` : "홈"} aria-expanded={expanded} aria-controls={controls} onClick={onClick}>
<img className="hm-sw-header__home-bg" src={homeBackground} alt="" aria-hidden="true" />
<img className="hm-sw-header__home-bg-hover" src={homeBackgroundHover} alt="" aria-hidden="true" />
{content && <span className="hm-sw-header__logo" aria-hidden="true">{content}</span>}
<span className="hm-sw-header__home-bg" aria-hidden="true" />
<span className="hm-sw-header__home-bg-hover" aria-hidden="true" />
<SWHeaderLogo logo={logo} logoSrc={logoSrc} />
</button>
)
}
@@ -1,6 +1,7 @@
import { forwardRef, useEffect, useId, useLayoutEffect, useRef, useState, type ButtonHTMLAttributes, type HTMLAttributes, type KeyboardEvent, type ReactNode } from "react"
import { moveRovingFocus } from "../shared/roving-focus"
import { SWHeaderDocumentTitle } from "../sw-header/components/SWHeaderDocumentTitle"
import { SWHeaderLogo } from "../sw-header/components/SWHeaderHome"
import { SWHeaderMore } from "../sw-header/components/SWHeaderMore"
import type { SWHeaderDocument } from "../sw-header/types"
import { SWToolButton } from "../sw-tool/SWTool"
@@ -12,9 +13,10 @@ import "../../theme/sw-viewer.css"
export interface SWViewerHeaderProps extends Omit<HTMLAttributes<HTMLElement>, "title"> {
/**
* Logo image URL of the program (same prop as SWHeader), max 24px high on the slanted green cap.
* The logo belongs to each program: nothing is rendered when omitted (no default text or icon) and the cap
* shrinks to its slanted edge.
* Logo image URL of the program (same prop as SWHeader), contained in the SWHeader 32 x 17 logo slot, 12px from
* the header's left edge and vertically centered on the slanted green cap.
* The logo belongs to each program: nothing is rendered when omitted (no default text or icon) and there is no cap
* either (2026-10-23, also with `onHomeClick` / `logoMenu`, which then have no trigger).
*/
logoSrc?: string
/** Logo element (inline SVG, <img>, ...). Takes precedence over `logoSrc` (same prop as SWHeader). */
@@ -296,7 +298,6 @@ export const SWViewerHeader = forwardRef<HTMLElement, SWViewerHeaderProps>(funct
const rootRef = useRef<HTMLElement | null>(null)
// Header width: a resize re-renders, and the layout pass below measures again.
const [, setWidth] = useState(1920)
const gradientId = `hm-sw-viewer-brand-${useId().replace(/:/g, "")}`
useLayoutEffect(() => {
const root = rootRef.current
if (!root) return
@@ -374,27 +375,21 @@ export const SWViewerHeader = forwardRef<HTMLElement, SWViewerHeaderProps>(funct
)
}
const name = documentTitle || documentPlaceholder
const mark = logo ?? (logoSrc ? <img src={logoSrc} alt="" /> : null)
// Same test as SWHeaderLogo (logo, else logoSrc). 2026-10-23: no logo => no cap at all (no slant, no empty box, no
// width), even with logoMenu / onHomeClick: both have no trigger then; the file part starts the row (sw-viewer.css).
const hasLogo = !!(logo ?? logoSrc)
// With a logo the cap is the SWHeader home shape (Figma header/btn_home: the shared 9-slice .hm-sw-header__home-bg)
// around the shared logo; no hover layer: the viewer keeps its own focus-only look.
const brandContent = (
<>
<svg className="hm-sw-viewer-header__brand-shape" viewBox="0 0 88 52" preserveAspectRatio="xMaxYMid slice" aria-hidden="true" focusable="false">
<defs>
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="var(--hm-sw-viewer-brand-top)" />
<stop offset="45%" stopColor="var(--hm-sw-viewer-brand-middle)" />
<stop offset="100%" stopColor="var(--hm-sw-viewer-brand-bottom)" />
</linearGradient>
</defs>
<path d="M 0 0 H 88 L 73 44 Q 70 52 62 52 H 0 Z" fill={`url(#${gradientId})`} />
<path d="M 88 0 L 73 44 Q 70 52 62 52 H 0" fill="none" stroke="var(--hm-sw-viewer-brand-middle)" vectorEffect="non-scaling-stroke" />
</svg>
{mark && <span className="hm-sw-viewer-header__brand-mark" aria-hidden="true">{mark}</span>}
<span className="hm-sw-header__home-bg" aria-hidden="true" />
<SWHeaderLogo logo={logo} logoSrc={logoSrc} className="hm-sw-viewer-header__brand-mark" />
</>
)
return (
<header ref={node => { rootRef.current = node; if (typeof forwardedRef === "function") forwardedRef(node); else if (forwardedRef) forwardedRef.current = node }}
className={`hm-sw-theme hm-sw-viewer-header${className ? ` ${className}` : ""}`} {...rest}>
{logoItems.length > 0
{hasLogo && (logoItems.length > 0
? <SWHeaderMore className="hm-sw-viewer-header__logo-menu" align="start" title={logoMenu?.title} label={logoMenu?.title ?? (logoAlt ? `${logoAlt} 메뉴` : "메뉴")}
open={openMenu === "logo"} onOpenChange={menuOpenChange("logo")}
items={logoItems.map(item => ({
@@ -402,12 +397,12 @@ export const SWViewerHeader = forwardRef<HTMLElement, SWViewerHeaderProps>(funct
onSelect: () => { item.onSelect?.(); command({ id: item.id, itemId: item.id }) },
}))}
renderTrigger={({ ref, ...trigger }) => (
<button ref={ref} type="button" className="hm-sw-viewer-header__brand" data-logo={mark ? "" : undefined} data-state={trigger["aria-expanded"] ? "open" : undefined}
<button ref={ref} type="button" className="hm-sw-viewer-header__brand" data-logo="" data-state={trigger["aria-expanded"] ? "open" : undefined}
aria-label={logoAlt ? `${logoAlt} 메뉴` : "메뉴"} title={logoAlt} {...trigger}>{brandContent}</button>
)} />
: onHomeClick
? <button type="button" className="hm-sw-viewer-header__brand" data-logo={mark ? "" : undefined} aria-label={logoAlt ? `${logoAlt} 홈` : "홈"} title={logoAlt} onClick={onHomeClick}>{brandContent}</button>
: <div className="hm-sw-viewer-header__brand" data-logo={mark ? "" : undefined} role={logoAlt ? "img" : undefined} aria-label={logoAlt}>{brandContent}</div>}
? <button type="button" className="hm-sw-viewer-header__brand" data-logo="" aria-label={logoAlt ? `${logoAlt} 홈` : "홈"} title={logoAlt} onClick={onHomeClick}>{brandContent}</button>
: <div className="hm-sw-viewer-header__brand" data-logo="" role={logoAlt ? "img" : undefined} aria-label={logoAlt}>{brandContent}</div>)}
{onDocumentOpen && (() => {
const label = documentOpenLabel ?? "Open", iconOnly = compact.has(OPEN_ID)
return (
+28 -8
View File
@@ -9,8 +9,11 @@
/* 2026-10-21 user request: the solid color of the Storybook viewer canvas (--sw-story-canvas base #0a0a0b), no gradient. */
--hm-sw-viewer-background: #0a0a0b;
--hm-sw-viewer-border-color: #071e18;
--hm-sw-viewer-brand-width: 72px;
/* No logo: no text or icon is drawn; the cap keeps only its slanted edge (no wide empty space). */
/* 2026-10-23: auto = the SWHeader home box (12px incl. the header border + logo + 36px): 79px with the 32 x 17
example logo, wider or narrower with the logo. */
--hm-sw-viewer-brand-width: auto;
/* These four are unused since 2026-10-23 (the cap is the SWHeader home shape; no cap without a logo); kept so
existing themes that set them stay valid. */
--hm-sw-viewer-brand-empty-width: 24px;
--hm-sw-viewer-brand-top: #123328;
--hm-sw-viewer-brand-middle: #205241;
@@ -100,12 +103,23 @@
margin-right: -2px;
font-weight: 700;
}
/* With a logo: the reference 72px cap (logo at 24px), wider only for a wider logo. Without: the slanted edge only. */
.hm-sw-viewer-header__brand[data-logo] { width: auto; min-width: var(--hm-sw-viewer-brand-width); padding: 0 25px 0 24px; }
/* With a logo (2026-10-23): the SWHeader home — the same shape (shared .hm-sw-header__home-bg 9-slice of the Figma
asset: gradient, stroke, shadow, slant), the same box (12px + logo + 36px, 11px here + the 1px header border; the
logo centered) and the same 17px overlap of the next part, so the logo and the file-name area sit where SWHeader
puts them. No hover layer: the viewer cap keeps its own (focus only) look. Without a logo there is no cap. */
.hm-sw-viewer-header__brand[data-logo] {
justify-content: center;
box-sizing: border-box;
width: var(--hm-sw-viewer-brand-width);
padding: 0 36px 0 11px;
margin-right: -17px;
overflow: hidden;
/* The 17px overlap must not take the clicks of the next part (Open icon / file name): only the surface behind the
shape (::before, up to where the next part starts) is the cap's hit area. Focus and keyboard are unchanged. */
pointer-events: none;
}
.hm-sw-viewer-header__brand[data-logo]::before { pointer-events: auto; }
button.hm-sw-viewer-header__brand { text-align: left; }
.hm-sw-viewer-header__brand-shape { position: absolute; inset: 0 auto 0 0; width: calc(100% + 16px); height: 100%; pointer-events: none; }
.hm-sw-viewer-header__brand-mark { position: relative; display: inline-flex; align-items: center; }
.hm-sw-viewer-header__brand-mark > :is(img, svg) { display: block; max-height: 24px; width: auto; }
.hm-sw-viewer-header__document {
display: flex;
@@ -209,7 +223,13 @@ button.hm-sw-viewer-header__brand { text-align: left; }
/* No file open button before it: the file name starts 10px after the slanted logo edge (at mid height), as in
SWHeader (text 10px after its home slant; measured 10.5px here with 18px). */
.hm-sw-viewer-header__document[data-list] .hm-sw-header__document-button { padding: 0 16px 0 18px; }
/* After the file open button (2026-10-21 user decision): 8px in. */
/* Right after the logo cap (2026-10-23, the SWHeader home shape and 17px overlap): the first part keeps SWHeader's 5px
start (was 24px / 18px for the former viewer slant, which doubled the space after the logo). */
:is(.hm-sw-viewer-header__brand, .hm-sw-viewer-header__logo-menu) + .hm-sw-header__document[data-tool] > .hm-sw-viewer-header__open,
:is(.hm-sw-viewer-header__brand, .hm-sw-viewer-header__logo-menu) + .hm-sw-viewer-header__document[data-list] .hm-sw-header__document-button { padding-left: 5px; }
/* No logo (2026-10-23): no cap, so the first part (Open or the file name) starts the row 12px in, like SWHeader. */
.hm-sw-viewer-header > .hm-sw-header__document[data-tool]:first-child > .hm-sw-viewer-header__open,
.hm-sw-viewer-header > .hm-sw-viewer-header__document[data-list]:first-child .hm-sw-header__document-button { padding-left: 12px; }/* After the file open button (2026-10-21 user decision): 8px in. */
.hm-sw-viewer-header > .hm-sw-header__document[data-tool] + .hm-sw-viewer-header__document[data-list] .hm-sw-header__document-button { padding: 0 16px 0 8px; }
/* The list (SWHeader face, placement and rows unchanged) is never narrower than the file-name button it opens from:
its outer width is at least the button's (100% of the wrapper, plus the two borders the list starts left of it) and
+50 -30
View File
@@ -14,7 +14,8 @@
--hm-sw-header-bar-height: 48px;
--hm-sw-header-field-height: 38px;
--hm-sw-header-gap: 4px;
--hm-sw-header-logo-width: 32px;
/* auto: the logo's own width at the slot height (2026-10-23; the Figma example logo is 32 x 17). */
--hm-sw-header-logo-width: auto;
--hm-sw-header-logo-height: 17px;
/* Typography: SW default is Noto Sans KR (2026-10-20 user decision; the Dotum default of the same day is withdrawn,
@@ -234,27 +235,29 @@
/* Figma 26:10492. Header stays 49px; host content can reserve the extra 28px when open.
The strip ends after its last field (user correction 2026-10-19, overrides the fixed 1222px frame):
it shrinks to its content, so no empty brown area, and is capped by the space right of its left
edge (inline max-width); longer property lists scroll inside. */
edge (inline max-width); longer property lists wrap to more rows (2026-10-23, was a scroll). 2026-10-23: it reaches at least the 속성Bar | 속성창
divider of the header (inline min-width measured by SWHeader); the fields keep their place and the rest is the
same bar background. */
.hm-sw-header__bar-anchor { position: absolute; top: 49px; z-index: 2; }
.hm-sw-property-bar {
--hm-sw-property-bar-background: #271f13;
--hm-sw-property-bar-border: #08251c;
width: max-content;
max-width: 100%;
height: 28px;
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: thin;
/* 2026-10-23: no scroll. Without room the fields wrap to more 27px rows and the strip (background, border, radius)
grows with them; one row stays 28px. */
min-height: 28px;
border: 1px solid var(--hm-sw-property-bar-border);
border-top: 0;
border-radius: 0 0 6px 6px;
background: var(--hm-sw-property-bar-background);
box-shadow: inset 0 4px 4px rgba(0, 0, 0, .6);
}
.hm-sw-property-bar__content { display: flex; align-items: center; width: max-content; min-width: 100%; height: 27px; }
.hm-sw-property-bar__object { flex: 0 0 90px; width: 90px; text-align: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--hm-sw-header-selected-color); font-weight: 700; letter-spacing: .04em; }
.hm-sw-property-bar__fields { display: flex; align-items: center; gap: 16px; padding-right: 6px; }
.hm-sw-property-bar__field { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.hm-sw-property-bar__content { display: flex; align-items: flex-start; min-height: 27px; }
/* The object name stays on the first row; wrapped rows start under the first field with the same 16px gap. */
.hm-sw-property-bar__object { flex: 0 0 90px; width: 90px; line-height: 27px; text-align: center; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--hm-sw-header-selected-color); font-weight: 700; letter-spacing: .04em; }
.hm-sw-property-bar__fields { display: flex; flex: 1 1 auto; flex-wrap: wrap; align-items: center; min-width: 0; gap: 0 16px; padding-right: 6px; }
.hm-sw-property-bar__field { display: flex; align-items: center; gap: 6px; min-height: 27px; 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
@@ -317,55 +320,73 @@
overflow: visible;
}
/* Home: 80 x 48, its curved background overlaps the document well by 15px. */
/* Home (Figma header/btn_home 26:10655): 12px + logo + 36px (the slant part) wide, 80 x 48 with the 32 x 17 example
logo; a wider or narrower logo widens or narrows it (2026-10-23, was a fixed 80px). The logo is centered in the box
between the 12px padding and the slant. Its curved background overlaps the document well by 15px. */
.hm-sw-header__home {
position: relative;
z-index: 2;
flex: 0 0 80px;
width: 80px;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
box-sizing: border-box;
height: var(--hm-sw-header-bar-height);
padding: 0 36px 0 12px;
margin-right: -17px;
overflow: hidden;
}
/* Figma Rectangle 6671: document surface behind the slanted home shape (0-65px only,
so the file name at x=70 stays visible through the transparent part of the 80px box). */
.hm-sw-header__home::before {
/* Figma Rectangle 6671: document surface behind the slanted home shape (up to 15px before the home end, so the file
name stays visible through the transparent part of the box). Also behind the SWViewerHeader logo cap (2026-10-23,
same shape), so no header background shows between its slant and the next part. */
.hm-sw-header__home::before,
.hm-sw-viewer-header__brand[data-logo]::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: 65px;
width: calc(100% - 15px);
border: 1px solid var(--hm-sw-header-document-border-color);
border-right: 0;
background: var(--hm-sw-header-document-background);
}
/* The Figma home shape (92 x 72 asset with its shadow, placed at -8, -12) as a 9-slice: the left 20px and the right
42px (slant + shadow) keep their width, only the flat middle stretches; the 12px shadow rows keep their height and
the 48px body follows the host height (SWHeader 48px: the asset 1:1; also used by the SWViewerHeader logo cap).
The asset gradients are vertical, so the stretch changes nothing else. */
.hm-sw-header__home-bg,
.hm-sw-header__home-bg-hover {
position: absolute;
left: -8px;
top: -12px;
width: 92px;
height: 72px;
max-width: none;
box-sizing: border-box;
width: calc(100% + 12px);
height: calc(100% + 24px);
border: solid transparent;
border-width: 12px 42px 12px 20px;
border-image: url("../assets/home-bg.svg") 12 42 12 20 fill / 12px 42px 12px 20px stretch;
pointer-events: none;
}
/* Logo slot: the Figma home logo frame (32 x 17 at 12, 16). The slot is common design;
the logo itself belongs to each SW program. Any logo (image or element, any ratio)
is contained inside the slot and never overflows it. */
.hm-sw-header__home-bg-hover { border-image-source: url("../assets/home-bg-hover.svg"); }
/* No logo (2026-10-23): the Figma home as before — the 32px logo place stays empty, so 80 x 48 with the 68 x 48 shape
and the file name where it always starts. */
.hm-sw-header__home:not(:has(> .hm-sw-header__logo)) { padding-left: 44px; }/* Logo slot: 17px high (--hm-sw-header-logo-height), as wide as the logo at its own ratio (--hm-sw-header-logo-width
auto; the Figma 32 x 17 is an example, 2026-10-23). The slot is common design; the logo itself belongs to each SW
program. A set slot width contains the logo (ratio kept, never cut). */
.hm-sw-header__logo {
position: absolute;
left: 12px;
top: 16px;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: var(--hm-sw-header-logo-width);
height: var(--hm-sw-header-logo-height);
overflow: hidden;
pointer-events: none;
}
.hm-sw-header__logo > :is(img, svg) {
display: block;
width: 100%;
width: auto;
max-width: 100%;
height: 100%;
object-fit: contain;
}
@@ -2100,8 +2121,7 @@ div.hm-sw-header__menu-row { justify-content: space-between; gap: 8px; padding-l
Its measured width is --hm-sw-header-leading-width, subtracted from the file-name clamp above. */
.hm-sw-header__leading { position: relative; z-index: 1; display: flex; flex: 0 0 auto; gap: 2px; height: var(--hm-sw-header-bar-height); }
/* The file name after an Open button no longer sits under the logo: 12px start padding (D-1), not 5px. */
: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
: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). Below 760px only (2026-10-21): from
760px up nothing moves into More, so the fields shrink beside Open like everywhere else. */