feat(sw): refine headers, status label, popups and AI guide rules
Deploy Storybook / deploy (push) Successful in 1m10s
Deploy Storybook / deploy (push) Successful in 1m10s
- SW Header: the five field previews hide and return together in one measured step; from 760 to 1920px the groups from 레이어 to 치수스타일 share the room left before 속성보기, above 1920px they keep the 1920 widths (controls bars and < 760px unchanged) - SW Viewer Header Model / Layout: long names ellipsize in equal buttons (minmax(0, 1fr), name counted at --hm-sw-viewer-tab-name-basis) so they never change the box or the measured steps; green outer outline and its tokens removed; 378px / 42px, text-shadow and state unchanged - SWViewerStatus: the label keeps the width of its widest real state text (idle keeps its icon slot), so a state change never moves the header; with too little room it ellipsizes after the file name gives way - Popups: body inset shadow removed, title SVG drop shadow removed, viewer panel title icons use --hm-sw-viewer-panel-icon-color (#c7bda9) - Storybook: SW/Viewer › Model / Layout story removed - AI guide (docs/ai + package copies): design-only changes for every component, content limits for Submodule consumers (Header excluded), Submodule usage rules; VIEWER / DESIGN_RULES synced - viewer.browser.mjs: explicit 1920 / 1440 labels and approved 1280 icon-only expectations
This commit is contained in:
1 parent
325ec25e37
commit
db03e155dd
19 files changed
+190
-119
No files matched your search
@@ -46,8 +46,8 @@ const MORE_BELOW = 760
|
||||
/** File name width from 760px up: Figma 310px, smallest 96px (sw.css .hm-sw-header__document). */
|
||||
const DOCUMENT_MAX = 310
|
||||
const DOCUMENT_MIN = 96
|
||||
/** Field pictures that hide one at a time while the header lacks room (sw.css [data-compact]). */
|
||||
const COMPACT_LEVELS = 5
|
||||
/** Field pictures hide together, in one step, while the header lacks room (sw.css [data-compact], 2026-10-23). */
|
||||
const COMPACT_LEVELS = 1
|
||||
/** From this header width the Figma field boxes apply (sw.css "Responsive"). */
|
||||
const FIGMA_LAYOUT_FROM = 1800
|
||||
|
||||
@@ -304,9 +304,9 @@ export const SWHeader = forwardRef<HTMLElement, SWHeaderProps>(function SWHeader
|
||||
})
|
||||
|
||||
// 2026-10-22 (user request): from 760px to 1800px the space left decides, not fixed widths. While the object area lacks
|
||||
// room, the field pictures hide one at a time (data-compact 1-5: 치수 아이콘, 선 그림, 글꼴명, 색상 칩, 레이어 상태
|
||||
// 아이콘, sw.css), then the file name gives way (310 -> 96px), then the values ellipsize. With room to spare the
|
||||
// pictures stay and the file name keeps 310px.
|
||||
// room, the field pictures hide all at once (data-compact 1: 레이어 상태 아이콘, 색상 칩, 선 그림, 글꼴명, 치수 아이콘,
|
||||
// sw.css; 2026-10-23 user request, formerly one at a time), then the file name gives way (310 -> 96px), then the
|
||||
// values ellipsize. With room for all of them the pictures stay and the file name keeps 310px.
|
||||
// Measured on every render and again once web fonts finish loading (text widths change, the header size does not).
|
||||
const fitFieldsRef = useRef(() => {})
|
||||
fitFieldsRef.current = () => {
|
||||
|
||||
@@ -45,12 +45,24 @@ const StatusIcon = ({ state }: { state: SWViewerStatusState }) => state === "idl
|
||||
</svg>
|
||||
)
|
||||
|
||||
/** Load state of the viewer header (reference status: muted 11px text, icon; text hides below 560px). */
|
||||
/**
|
||||
* Load state of the viewer header (reference status: muted 11px text, icon; text hides below 560px). The label area is
|
||||
* as wide as the widest text it can show (2026-10-23): every state label (and a custom `label`) sits in the same grid
|
||||
* cell, the others hidden, so a state change never resizes it or moves the menus; with too little room it ellipsizes.
|
||||
* The icon slot is kept (empty) for `idle` for the same reason.
|
||||
*/
|
||||
export function SWViewerStatus({ state, label, detail, className, ...rest }: SWViewerStatusProps) {
|
||||
const text = label ?? statusLabel[state]
|
||||
const sizers = Array.from(new Set([text, ...Object.values(statusLabel)])).filter(candidate => candidate !== text)
|
||||
return (
|
||||
<div role="status" className={cx("hm-sw-viewer-status", className)} data-state={state} title={detail} {...rest}>
|
||||
{state !== "idle" && <span className="hm-sw-viewer-status__icon"><SWViewerIcon><StatusIcon state={state} /></SWViewerIcon></span>}
|
||||
<span className="hm-sw-viewer-status__label">{label ?? statusLabel[state]}</span>
|
||||
<span className="hm-sw-viewer-status__icon" data-empty={state === "idle" ? "" : undefined} aria-hidden={state === "idle" ? true : undefined}>
|
||||
{state !== "idle" && <SWViewerIcon><StatusIcon state={state} /></SWViewerIcon>}
|
||||
</span>
|
||||
<span className="hm-sw-viewer-status__label">
|
||||
<span className="hm-sw-viewer-status__text">{text}</span>
|
||||
{sizers.map(candidate => <span key={candidate} className="hm-sw-viewer-status__sizer" aria-hidden="true">{candidate}</span>)}
|
||||
</span>
|
||||
{detail && <span className="hm-sw-viewer-sr-only">: {detail}</span>}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -212,7 +212,8 @@ function Tabs({ item, onCommand }: { item: Extract<SWViewerHeaderItem, { type: "
|
||||
<button key={option.value} type="button" role="radio" aria-checked={selected} tabIndex={option.value === current ? 0 : -1}
|
||||
className="hm-sw-viewer-header__tab" data-state={selected ? "active" : undefined} title={option.title} disabled={item.disabled || option.disabled}
|
||||
onClick={() => { if (!selected) select(option.value) }}>
|
||||
{option.label}
|
||||
{/* The name shrinks first (ellipsis, sw-viewer.css); the full name stays the accessible name. */}
|
||||
<span className="hm-sw-viewer-header__tab-name">{option.label}</span>
|
||||
{option.count !== undefined && option.count > 0 && <span className="hm-sw-viewer-header__count">{option.count.toLocaleString()}</span>}
|
||||
</button>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user