fix(sw): align header logo styles and refine viewer UI
Deploy Storybook / deploy (push) Successful in 1m13s
Deploy Storybook / deploy (push) Successful in 1m13s
This commit is contained in:
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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in new issue
Block a user