feat(sw): measured header compaction, gauge recolor, popup menu and story cleanups
Deploy Storybook / deploy (push) Successful in 1m12s
Deploy Storybook / deploy (push) Successful in 1m12s
- SWHeader (760-1799px): pictures hide one at a time by measured room (치수 아이콘, 선 그림, 글꼴명, 색상 칩, 레이어 상태 아이콘), then the file name narrows 310 -> 96px, then values ellipsize; re-fit after web fonts load. 가중치/스케일 columns grow for longer values. - Gauge style B / property window range: Figma controll_bar_new 633:77827 colors, ring and shadow on the previous size (6px track, 14px thumb). - Property window: editable text values (e.g. 하이퍼링크) get the editing face like numbers. - SW Header › Logo: same cases, order and layout as SW Viewer › Logo (ID kept). - Storybook inner: 80vw from 1920px. Responsive More 1920px rows use the page's usable width (container sw-story-page) and span 1920px when there is room. - SW/Popup sidebar: Property Panel only; Custom Content / Multiple Popups tagged verification. - SW Viewer Settings Popup: 축척 지정 demo popup removed, frame 620px. - Tests: inventory / simplification sidebar expectations, Popup-only unit select check. Verified: check:ai-docs PASS, changed test syntax, headless Chrome checks of header compaction, Responsive 1920 rows (1920 hidden / 2560, 3840 shown), Popup sidebar, Settings Popup. Not verified: integrated-visual baselines (not updated), full regression, changed tests not run.
This commit is contained in:
1 parent
c1549b2b4f
commit
6d46289fc5
13 files changed
+133
-94
No files matched your search
@@ -18,10 +18,11 @@
|
||||
min-width: 0;
|
||||
margin-inline: auto;
|
||||
}
|
||||
/* Wide screens (2026-10-22): 1440px from 1920px up, fixed beyond it (was 80vw); never wider than the page minus the
|
||||
/* Wide screens (2026-10-22, user request): 80% of the viewport — 1536px at 1920px, 2048px at 2560px, 3072px at
|
||||
3840px — growing with the screen (vw, so a scrollbar does not shrink it); never wider than the page minus the
|
||||
gutters, so no horizontal scroll. Below 1920px the rule above applies. */
|
||||
@media (min-width: 1920px) {
|
||||
.sb-page > .sb-page-inner { width: min(1440px, calc(100% - 2 * var(--sb-page-gutter))); }
|
||||
.sb-page > .sb-page-inner { width: min(80vw, calc(100% - 2 * var(--sb-page-gutter))); }
|
||||
}
|
||||
|
||||
.sb-page .sb-page-header {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
.sw-footer-story-inline { display: inline-flex; flex-direction: column; padding: 0; }
|
||||
.sw-footer-story-width + .sw-footer-story-width { margin-top: 16px; }
|
||||
/* The hidden 1920px row (Responsive More, narrower than 1920px) takes its gap with it. */
|
||||
@container (max-width: 1919.98px) { .sw-story-responsive-wide + .sw-footer-story-width { margin-top: 0; } }
|
||||
@container sw-story-page (max-width: 1999.98px) { .sw-story-responsive-wide + .sw-footer-story-width { margin-top: 0; } }
|
||||
.sw-footer-story-aux-compare { display: grid; grid-template-columns: repeat(auto-fill, minmax(560px, 1fr)); gap: 16px; }
|
||||
.sw-footer-story-aux-row { display: flex; align-items: center; gap: 12px; margin: 0; }
|
||||
.sw-footer-story-aux-row figcaption { flex: 0 0 120px; font-size: 12px; color: #3d4a45; }
|
||||
|
||||
@@ -440,40 +440,30 @@ export function ComposedDemo() {
|
||||
</StoryPage>
|
||||
}
|
||||
|
||||
/** Neutral sample logos with different ratios (no product or brand). */
|
||||
const WIDE_SAMPLE_LOGO = "data:image/svg+xml," + encodeURIComponent(
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20" viewBox="0 0 120 20"><rect width="120" height="20" rx="4" fill="#47AF91"/><rect x="8" y="7" width="104" height="6" rx="3" fill="#F0F7F5"/></svg>',
|
||||
)
|
||||
const TALL_SAMPLE_LOGO = (
|
||||
<svg viewBox="0 0 20 40" aria-hidden="true" focusable="false">
|
||||
<rect width="20" height="40" rx="4" fill="#FFC600" />
|
||||
<circle cx="10" cy="12" r="5" fill="#060606" />
|
||||
</svg>
|
||||
)
|
||||
// 2026-10-22 (user request): sidebar "Logo" (ID sw-header--logo-slot kept), page title "SW Header — 로고". Same cases,
|
||||
// order and layout as SW/Viewer › Logo: 로고 없음 → logoSrc → logo (element), each header fluid up to 1440px (no
|
||||
// scroller). The SW Header home click (callbacks.onHomeClick) is kept.
|
||||
const LOGO_CASES: { title: string; props: Partial<SWHeaderProps> }[] = [
|
||||
{ title: "로고 없음 (기본)", props: { logoSrc: undefined, logoAlt: undefined } },
|
||||
{ title: "logoSrc (이미지 URL)", props: { logoSrc: placeholderLogo, logoAlt: "임시 로고" } },
|
||||
{ title: "logo (요소)", props: { logo: <svg viewBox="0 0 32 17" width="32" height="17" aria-hidden="true"><rect x="0.5" y="0.5" width="31" height="16" rx="3" fill="none" stroke="#fff" /><text x="16" y="12.5" fill="#fff" fontSize="10" fontWeight="700" textAnchor="middle">LOGO</text></svg>, logoAlt: "예시 로고" } },
|
||||
]
|
||||
|
||||
// 2026-10-22 (user request): sidebar "Logo" (ID sw-header--logo-slot kept), page title "SW Header — 로고",
|
||||
// frames fit the page (no scroller).
|
||||
export const LogoSlot: Story = {
|
||||
name: "Logo",
|
||||
parameters: { controls: { disable: true } },
|
||||
render: () => (
|
||||
<StoryPage>
|
||||
<StoryPageHeader
|
||||
title="SW Header — 로고"
|
||||
description="로고 영역의 위치·크기(홈 버튼 안 12 / 16, 32 × 17)는 공통 디자인이고, 로고 이미지는 각 SW 프로그램이 정합니다. Storybook 기본 로고는 임시 자산(sw-header-logo-placeholder.svg)이며 확정 로고가 아닙니다."
|
||||
/>
|
||||
<StorySection title="기본 임시 로고" description="logoSrc에 임시 SVG, logoAlt에 「임시 로고」를 전달합니다.">
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="사용자 지정 로고 — 가로형 이미지(logoSrc)" description="120 × 20 비율의 이미지도 로고 영역 안에 contain으로 맞춰지고 넘치지 않습니다.">
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logoSrc={WIDE_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="사용자 지정 로고 — 세로형 React 요소(logo)" description="logo에 SVG 요소를 전달하면 logoSrc보다 우선합니다.">
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logo={TALL_SAMPLE_LOGO} logoAlt="샘플 SW" /></Frame>
|
||||
</StorySection>
|
||||
<StorySection title="로고 미전달" description="logo·logoSrc가 없으면 로고를 그리지 않고 홈 버튼 배경과 전체 배치를 그대로 유지합니다. 접근성 이름은 「홈」입니다.">
|
||||
<Frame width={1920} fluid><SWHeader {...figmaArgs} {...callbacks} logoSrc={undefined} logoAlt={undefined} /></Frame>
|
||||
</StorySection>
|
||||
<StoryPageHeader title="SW Header — 로고" />
|
||||
<div className="sw-story-stack">
|
||||
{LOGO_CASES.map(row => (
|
||||
<StorySection key={row.title} title={row.title}>
|
||||
<div style={{ width: "100%", maxWidth: 1440 }} data-logo-case={row.title}>
|
||||
<SWHeader {...figmaArgs} {...callbacks} {...row.props} />
|
||||
</div>
|
||||
</StorySection>
|
||||
))}
|
||||
</div>
|
||||
</StoryPage>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -164,7 +164,9 @@ function PopupPage() {
|
||||
)
|
||||
}
|
||||
|
||||
export const CustomContent: Story = { name: "Custom Content", render: () => <PopupPage /> }
|
||||
// 2026-10-22 (user request): SW/Popup keeps only Property Panel in the sidebar; these two stay for the browser tests
|
||||
// (verification tag: ID and direct URL kept).
|
||||
export const CustomContent: Story = { name: "Custom Content", tags: ["verification"], render: () => <PopupPage /> }
|
||||
|
||||
function StackPage() {
|
||||
const [log, record] = useLog()
|
||||
@@ -190,4 +192,4 @@ function StackPage() {
|
||||
)
|
||||
}
|
||||
|
||||
export const MultiplePopups: Story = { name: "Multiple Popups", render: () => <StackPage /> }
|
||||
export const MultiplePopups: Story = { name: "Multiple Popups", tags: ["verification"], render: () => <StackPage /> }
|
||||
@@ -1,9 +1,12 @@
|
||||
/* Story canvas only: frames that simulate a header width. Never styles the header itself. */
|
||||
.sw-story-scroller { max-width: 100%; overflow-x: auto; }
|
||||
/* Responsive More pages (2026-10-22): the 1920px row shows only when the content Inner itself is 1920px or wider
|
||||
(container width, not the browser width); otherwise the whole row — title, header, background, margin — is hidden. */
|
||||
/* Responsive More pages (2026-10-22): the 1920px row shows when the page has 1920px of usable width — the page
|
||||
(container sw-story-page, not the browser) minus its 40px gutters, so 2000px — and then reaches out of the 80vw Inner
|
||||
evenly to its full 1920px; with less room the whole row — title, header, background, margin — is hidden. */
|
||||
.sw-story-responsive { container-type: inline-size; }
|
||||
@container (max-width: 1919.98px) { .sw-story-responsive-wide { display: none; } }
|
||||
.sb-page:has(.sw-story-responsive) { container: sw-story-page / inline-size; }
|
||||
.sw-story-responsive-wide { width: 1920px; max-width: none; margin-inline: calc((100% - 1920px) / 2); }
|
||||
@container sw-story-page (max-width: 1999.98px) { .sw-story-responsive-wide { display: none; } }
|
||||
.sw-story-frame { background: #060606; }
|
||||
/* Shared drawing-area canvas of the SW Stories (2026-10-19): the SW/Viewer black grid — 40px cells, 1px lines of
|
||||
HM green #266651 at 18%, on #0A0A0B. Used behind the SW Header work area and dropdown room and the viewer frames. */
|
||||
|
||||
@@ -42,7 +42,6 @@
|
||||
.sw-viewer-story-props dd { margin: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: var(--hm-sw-viewer-panel-text); user-select: all; }
|
||||
.sw-viewer-story-empty { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 40px 0; text-align: center; font-size: 12px; color: var(--hm-sw-viewer-panel-label); }
|
||||
.sw-viewer-story-steps { margin: 0; padding-left: 20px; font-size: 14px; }
|
||||
.sw-viewer-story-reopen { position: absolute; left: 712px; top: 16px; padding: 6px 10px; border-radius: 4px; background: #f5f0e5; font-size: 12px; }
|
||||
.sw-viewer-story-context { display: flex; align-items: center; justify-content: center; height: 160px; border: 1px dashed #9a9284; border-radius: 4px; background: #f5f0e5; color: #3e3523; font-size: 13px; }
|
||||
.sw-viewer-story-mode { display: flex; gap: 16px; margin-bottom: 8px; font-size: 13px; }
|
||||
/* Room below a single header row so its logo menu / document list are not cut by the scroller. */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef, useState, type ReactNode } from "react"
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite"
|
||||
import {
|
||||
SWHeader, SWHeaderWindowControls, SWSelect, SWViewerButton, SWViewerField, SWViewerHeader, SWViewerHint, SWViewerInput, SWViewerMenu, SWViewerPanel, SWViewerSection, SWViewerSwitch,
|
||||
SWHeader, SWHeaderWindowControls, SWViewerButton, SWViewerHeader, SWViewerHint, SWViewerMenu, SWViewerPanel, SWViewerSection,
|
||||
type SWViewerHeaderGroup, type SWViewerMenuEntry,
|
||||
} from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
@@ -272,29 +272,19 @@ export const SettingsPanel: Story = {
|
||||
|
||||
export const ContentVariants: Story = {
|
||||
// 2026-10-21: the one settings-popup Story (former 설정창 and 같은 프레임, 다른 내부 콘텐츠 pages); ID kept.
|
||||
// 2026-10-22 (user request): the 축척 지정 demo popup was removed.
|
||||
name: "Settings Popup",
|
||||
render: () => {
|
||||
const demo = useViewerDemo({ initialDocument: SAMPLES[1].name, initialPanels: ["layers", "properties", "options"] })
|
||||
const [inputOpen, setInputOpen] = useState(true)
|
||||
const [scale, setScale] = useState("1:500")
|
||||
const [snap, setSnap] = useState(true)
|
||||
return (
|
||||
<StoryPage>
|
||||
<StoryPageHeader title="SW Viewer — 설정창 (SWViewerPanel)" />
|
||||
<Log lines={demo.log} />
|
||||
<ViewerFrame height={760} header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
|
||||
{/* 2026-10-22: 620px fits the tallest popup (옵션, 520px) now that 축척 지정 is gone. */}
|
||||
<ViewerFrame height={620} header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
|
||||
<LayerPanel demo={demo} strategy="absolute" defaultPosition={{ left: 24, top: 16 }} />
|
||||
<PropertiesPanel demo={demo} strategy="absolute" defaultPosition={{ left: 368, top: 16 }} />
|
||||
<SWViewerPanel strategy="absolute" defaultPosition={{ left: 368, top: 452 }} title="축척 지정" open={inputOpen} onClose={() => setInputOpen(false)} width={300} height="auto" resizable={false}
|
||||
footer={<><SWViewerButton onClick={() => setInputOpen(false)}>취소</SWViewerButton><SWViewerButton variant="primary" onClick={() => { demo.record(`scale=${scale}, snap=${snap}`); setInputOpen(false) }}>적용</SWViewerButton></>}>
|
||||
<SWViewerSection title="입력">
|
||||
<SWViewerField label="축척">{id => <SWViewerInput id={id} value={scale} onChange={event => setScale(event.currentTarget.value)} style={{ width: 120 }} />}</SWViewerField>
|
||||
<SWViewerField label="스냅 사용">{id => <SWViewerSwitch id={id} checked={snap} onCheckedChange={setSnap} />}</SWViewerField>
|
||||
<SWViewerField label="단위">{id => <SWSelect id={id} defaultValue="mm" options={[{ value: "mm" }, { value: "m" }]} onValueChange={value => demo.record(`unit=${value}`)} data-sw-select="unit" />}</SWViewerField>
|
||||
</SWViewerSection>
|
||||
</SWViewerPanel>
|
||||
<OptionsPanel demo={demo} strategy="absolute" defaultPosition={{ left: 712, top: 16 }} />
|
||||
{!inputOpen && <button type="button" className="sw-viewer-story-reopen" onClick={() => setInputOpen(true)}>축척 지정 다시 열기</button>}
|
||||
</ViewerFrame>
|
||||
</StoryPage>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user