fix(sw): 2026-10-21 SW design review and responsive header without More from 760px

User-approved design review (2026-10-21):
- SW Header: from 760px up nothing moves into More; the five menus stay with their
  titles (titles and values ellipsize), file name shrinks to 96px. Open next to the
  logo no longer blocks field shrinking above 760px (760/768 overflow fixed, also for
  configured `controls` headers). Dropdown yellow ring inside the button, black seam.
- Property Bar values as read-only red text; property popup "=" labels, read-only
  black values, per-entry hover with pointer.
- Home Menu recent files (3 rows, path + name caption, empty preview), Footer + slot
  guide off by default, Viewer file list without icons, Storybook widths 1920-760.
- Tests: old More expectations (integrated-improvement, keyboard-dismiss,
  footer-popup-viewer-refinement, storybook-cleanup-stage1) moved to the new rule;
  functional checks kept.
This commit is contained in:
shkim4 committed 2026-10-21 17:29:25 +09:00
1 parent a25d2b31c5
commit 2fc1b3e108
37 files changed
+447 -211

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

+1 -3
View File
@@ -9,7 +9,7 @@ import "@hm-design/sw/styles.css"
import { Crosshair, Grid3x3, Magnet, Move, RotateCw, Scissors, Spline, Square } from "lucide-react"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import {
AUX_ITEMS, CATEGORIES, DRAW_TOOLS, GuideFrame, GuideMovie, HISTORY, LAYOUTS, Log, Pin, REFERENCE_AUX, REFERENCE_CATEGORIES, REFERENCE_HISTORY,
AUX_ITEMS, CATEGORIES, DRAW_TOOLS, GuideFrame, HISTORY, LAYOUTS, Log, Pin, REFERENCE_AUX, REFERENCE_CATEGORIES, REFERENCE_HISTORY,
useFooterDemo,
} from "./sw-footer-fixture"
import "./sw-stories.css"
@@ -66,7 +66,6 @@ function Workspace({ width = "100%" }: { width?: number | string }) {
status="7.4177, -0.2807, 0.0000"
command={<SWFooterCommandLine history={demo.history} onSubmit={demo.onCommand} expanded={commandOpen} onExpandedChange={setCommandOpen} />}
activeToolId={demo.activeToolId} onToolSelect={demo.onToolSelect} onSlotAdd={demo.onSlotAdd} onCategoryAdd={demo.onCategoryAdd}
slotGuide={{ media: <GuideMovie /> }}
auxItems={demo.aux} onAuxPressedChange={demo.onAuxPressedChange}
auxAll={{ onClick: demo.onSide("auxAll") }} commandAll={{ onClick: () => { setCommandOpen(open => !open); demo.onSide("commandAll")() }, pressed: commandOpen }}
layoutTabs={<SWFooterLayoutTabs layouts={demo.layouts} value={demo.space} onValueChange={demo.onSpaceChange}
@@ -338,7 +337,6 @@ function CustomFooter() {
setCategories(list => list.map(item => (item.id === category ? { ...item, tools: [...item.tools, { id: `pt${index}`, label: `Pt ${index + 1}`, ...swFooterToolIcons.point }] } : item)))
note(`slotAdd:${category}#${index}`)
}}
slotGuide={{ title: "명령 추가", caption: "클릭", media: <span className="sw-footer-story-guide-text">빈 칸을 눌러 Point 명령 추가</span> }}
onCategoryAdd={() => { setCategories(list => [...list, { id: `c${list.length}`, label: `분류 ${list.length + 1}`, tools: [] }]); note("categoryAdd") }}
auxItems={aux} onAuxPressedChange={(id, pressed) => setAux(items => items.map(item => (item.id === id ? { ...item, pressed } : item)))}
onAuxItemClick={id => note(`aux:${id}`)}
+2 -6
View File
@@ -10,7 +10,7 @@ import "@hm-design/sw/styles.css"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import "./sw-home-menu.css"
import {
figmaHomeMenuItemIds, figmaHomeMenuSections, figmaRecentFiles, withRecentFiles,
figmaHomeMenuSections, figmaRecentFiles, withRecentFiles,
} from "./sw-home-menu-fixture"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
import figmaPropertyBar from "./assets/home-menu/figma-26-10492.png"
@@ -98,11 +98,7 @@ export const HomeMenu: Story = {
<StorySection title="열림 (현재 폭)" description="열림 동안 로고는 Home On(노란 테두리), Header 상·하단에 #5F4B0F 2px 선이 표시되고 닫으면 기본으로 돌아갑니다(Figma 26:10591 / 662:67267).">
<HeaderWithMenu defaultOpen autoFocus={false} />
</StorySection>
<StorySection title="Default / Variant2 · Hover / Variant3 · Active" description="Figma 버튼 세트 15개(26:11355 … 26:11631)는 모두 같은 SWHomeMenuItem입니다. Variant2(Hover)는 마우스를 올려 확인하며 Figma에서 Variant3와 시각 속성이 같습니다. Variant3는 activeIds로 지정합니다.">
<SWTheme className="sw-home-menu-story__stage"><SWHomeMenu defaultOpen autoFocus={false} sections={figmaHomeMenuSections} closeOnBackgroundClick={false} aria-label="Default" /></SWTheme>
<SWTheme className="sw-home-menu-story__stage"><SWHomeMenu defaultOpen autoFocus={false} sections={figmaHomeMenuSections} activeIds={figmaHomeMenuItemIds} closeOnBackgroundClick={false} aria-label="Active" /></SWTheme>
</StorySection>
<StorySection title="최근파일 하위 패널 (662:67267)" description="최근파일 타일을 누르면(Variant3) 메뉴 하단 강조가 전체 2px 외곽선으로 바뀌고 그 아래에 최근파일 목록이 열립니다. 행에 마우스를 올리면 2뎁스 미리보기가 옆에 나타납니다. 다시 누르거나 Escape로 닫습니다. 최근파일이 더보기 안에 있으면 목록은 더보기 타일 아래에 열리고 더보기 타일이 Variant3가 됩니다. 목록과 미리보기 이미지는 Story 데이터입니다.">
<StorySection title="최근파일 하위 패널 (662:67267)" description="최근파일 타일을 누르면(Variant3) 메뉴 하단 강조가 전체 2px 외곽선으로 바뀌고 그 아래에 최근파일 목록이 열립니다. 행에 마우스를 올리면 2뎁스 미리보기 영역이 옆에 나타나며 이미지는 표시하지 않고 빈 영역으로 둡니다(2026-10-21). 다시 누르거나 Escape로 닫습니다. 최근파일이 더보기 안에 있으면 목록은 더보기 타일 아래에 열리고 더보기 타일이 Variant3가 됩니다. 목록은 Story 데이터입니다.">
<div className="sw-home-menu-story__tall"><HeaderWithMenu defaultOpen autoFocus={false} defaultActiveIds={["recent"]}>{log => log}</HeaderWithMenu></div>
</StorySection>
<StorySection title="통합 · 속성창 · SWPopup · Escape" description="Escape는 Focus가 있는 곳을 먼저 닫습니다(메뉴 안: 하위 패널 → 메뉴, Popup 안: 그 Popup). Focus가 모두의 밖이면 가장 최근에 열거나 사용한 하나만 닫습니다.">
+10 -7
View File
@@ -15,12 +15,14 @@ interface OverviewProps {
propertyFixture: (selection: Selection) => SWHeaderProperty[]
propertySelection: (selection: Selection) => SWHeaderPropertySelection
barFixture: SWHeaderProperty[]
/** Object text of the property Bar (2026-10-21: the object type, e.g. 원). Default: the selection title. */
barTitle?: string
/** Logo (home) menu of the SW normal mode: the logo opens and closes it (SWHeader homeMenu). */
homeMenuSections?: readonly SWHomeMenuSectionData[]
}
/** Storybook host only; dropdowns, validation, editing and focus use the public SW components. */
export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFixture, propertySelection, barFixture, homeMenuSections }: OverviewProps) {
export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFixture, propertySelection, barFixture, barTitle, homeMenuSections }: OverviewProps) {
const controlId = useId()
const slotRef = useRef<HTMLDivElement>(null)
const headerRef = useRef<HTMLElement>(null)
@@ -117,11 +119,12 @@ export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFi
onColorPick: () => record("onColorPick"), onColorDetail: () => record("onColorDetail"),
}
const guardedProperties = properties.map(property => ({ ...property, disabled: disabled || property.disabled, readOnly: readOnly || property.readOnly }))
const barProperties = guardedProperties.flatMap(property => {
const bar = barFixture.find(item => item.id === property.id)
return bar ? [{ ...bar, value: property.value, disabled: property.disabled, readOnly: property.readOnly }]
: property.id.startsWith("preview-extra-") ? [property] : []
})
// Bar order = barFixture order (2026-10-21: 반지름 / 지름 / 원주 / 면적), then the preview fields.
const barProperties = [
...barFixture.flatMap(bar => guardedProperties.filter(property => property.id === bar.id)
.map(property => ({ ...bar, value: property.value, disabled: property.disabled, readOnly: property.readOnly }))),
...guardedProperties.filter(property => property.id.startsWith("preview-extra-")),
]
const layer = layers.find(item => item.id === value("layer"))
const line = fixtures.lineTypes.find(item => item.id === value("line"))
const color = parseSWHeaderColor(value("color"))
@@ -184,7 +187,7 @@ export function SWHeaderOverview({ initial, fixtures, propertyGroups, propertyFi
onDimensionStyleOpen={() => record("onDimensionStyleOpen")} onDimensionStyleDetail={() => record("onDimensionStyleDetail")}
propertyBarOpen={barOpen} onPropertyBarOpenChange={next => { setBarOpen(next); record(`propertyBarOpen(${next})`) }}
propertyPanelOpen={panelOpen} onPropertyPanelOpenChange={next => { setPanelOpen(next); record(`propertyPanelOpen(${next})`) }}
propertyBar={{ selection: propertySelection(example), properties: barProperties, onPropertyChange, readOnly, disabled }}
propertyBar={{ selection: { ...propertySelection(example), title: (propertySelection(example).kind === "single" && barTitle) || propertySelection(example).title }, properties: barProperties, onPropertyChange, readOnly, disabled }}
propertyPanel={{ selection: propertySelection(example), properties: guardedProperties, groups: propertyGroups,
expandedGroupIds: expanded, onExpandedGroupsChange: setExpanded, onPropertyChange, menus,
shapeContent: emptyShape ? null : undefined, onOverviewClick: () => changeExample("all"),
+16 -11
View File
@@ -190,7 +190,7 @@ export const Overview: Story = {
render: () => <SWHeaderOverview initial={{ ...figmaArgs, ...callbacks }}
fixtures={{ documents: SAMPLE_DOCUMENTS, layers: PROPERTY_LAYERS, colors: FIGMA_COLORS, userColors: FIGMA_USER_COLORS,
lineTypes: PROPERTY_LINE_TYPES, lineWeights: FIGMA_LINE_WEIGHTS, textStyles: FIGMA_TEXT_STYLES, dimensionStyles: FIGMA_DIMENSION_STYLES }}
propertyGroups={PROPERTY_GROUPS} propertyFixture={propertyFixture} propertySelection={propertySelection} barFixture={BAR_FIXTURE}
propertyGroups={PROPERTY_GROUPS} propertyFixture={propertyFixture} propertySelection={propertySelection} barFixture={BAR_FIXTURE} barTitle={BAR_TITLE}
homeMenuSections={OVERVIEW_HOME_MENU} />,
}
@@ -422,7 +422,7 @@ export function ComposedDemo() {
onPanelToggle={() => { setActiveMenu(null); setPanel(next => !next); record(`onPropertyPanelOpenChange(${!panel})`) }} />
</div>
</div>
{bar && <SWHeaderPropertyBar selection={propertySelection("circle")} properties={panelProperties.filter(item => BAR_FIXTURE.some(barItem => barItem.id === item.id))}
{bar && <SWHeaderPropertyBar selection={{ ...propertySelection("circle"), title: BAR_TITLE }} properties={BAR_FIXTURE.flatMap(barItem => panelProperties.filter(item => item.id === barItem.id)).map(item => ({ ...item, readOnly: true }))}
onPropertyChange={(property, next) => change(property.id, next, "onPropertyChange")} />}
{panel && <SWHeaderPropertyPanel id={panelId} anchorRef={detailRef} selection={propertySelection("circle")} properties={panelProperties} groups={PROPERTY_GROUPS}
menus={{ ...menus, layers: availableLayers.map(layer => ({ ...layer, id: layer.name })) }}
@@ -776,7 +776,9 @@ function StyleHeader({ onLog, showState, ...props }: StyleHostProps) {
)
}
const MORE_WIDTHS = [1440, 768, 390]
// 2026-10-21: same widths as the SW Footer Responsive More. From 760px no More; the five menus stay in the header
// (그리기모드 text hides, then the file name and values shorten).
const MORE_WIDTHS = [1920, 1440, 1280, 950, 860, 760]
export const ResponsiveMore: Story = {
name: "Responsive More",
@@ -802,17 +804,17 @@ const PROPERTY_GROUPS: SWHeaderPropertyGroup[] = [
const PROPERTY_FIXTURE: SWHeaderProperty[] = [
{ id: "layer", name: "레이어", value: "0_Layer_Cludmark", type: "text", groupId: "common", picker: "layer", validate: draft => draft.length <= 40 },
{ id: "color", name: "색상", value: "255,229,0", type: "color", groupId: "common" },
{ id: "line", name: "선스타일", value: "Continuous", type: "select", groupId: "common", unit: "(ByL)", linePreview: true, options: [{ id: "Continuous", name: "Continuous" }, { id: "CENTER", name: "CENTER" }, { id: "HIDDEN", name: "HIDDEN" }] },
{ id: "weight", name: "선가중치", value: "0.25", type: "number", groupId: "common", min: 0 },
{ id: "line", name: "선꼴", value: "Continuous", type: "select", groupId: "common", unit: "(ByL)", linePreview: true, options: [{ id: "Continuous", name: "Continuous" }, { id: "CENTER", name: "CENTER" }, { id: "HIDDEN", name: "HIDDEN" }] },
{ id: "weight", name: "선가중치", value: "0.25", type: "number", groupId: "common", min: 0, annotation: "(ByD, ByL)" },
{ id: "scale", name: "선축적", value: "1.0000", type: "number", groupId: "common", min: .0001, decimals: 4, unit: "(LTscale:1)" },
{ id: "opacity", name: "투명도", value: "0.25", type: "slider", groupId: "common", min: 0, max: 1, step: .01, annotation: "(ByL)", unit: "%" },
{ id: "link", name: "하이퍼링크", value: "--", type: "text", groupId: "common" },
{ id: "thickness", name: "두께", value: "0.25", type: "number", groupId: "common", unit: "mm", min: 0 },
{ id: "material", name: "재료", value: "-", type: "computed", groupId: "common", unit: "(ByL)" },
{ id: "circumference", name: "원주", value: "0.25", type: "computed", groupId: "shape", graphicSlot: "circumference" },
{ id: "circumference", name: "원주", value: "0.25", type: "number", groupId: "shape", graphicSlot: "circumference", min: 0 },
{ id: "diameter", name: "지름", value: "8.1502", type: "number", groupId: "shape", graphicSlot: "diameter", min: 0 },
{ id: "radius", name: "반지름", value: "4.0526", type: "number", groupId: "shape", graphicSlot: "radius", min: 0 },
{ id: "area", name: "면적", value: "51.5962", type: "computed", groupId: "shape", graphicSlot: "area" },
{ id: "area", name: "면적", value: "51.5962", type: "number", groupId: "shape", graphicSlot: "area", min: 0 },
{ id: "x", name: "X", value: "48.0808", type: "number", groupId: "position" },
{ id: "y", name: "Y", value: "14.9160", type: "number", groupId: "position" },
{ id: "z", name: "Z", value: "0.0000", type: "number", groupId: "position" },
@@ -886,11 +888,14 @@ export const KGuide: Story = {
parameters: { controls: { disable: true } },
render: () => <StoryPage><StoryPageHeader kicker={KICKER} title="K 가이드 반복 애니메이션" description="기존 K 버튼 Hover/Focus → 원본 이미지 Pan·Zoom 반복. 영역을 벗어나면 정지, 재진입은 처음부터. Reduced Motion은 정지 이미지." /><div className="sw-story-property-room"><SWHeader {...figmaArgs} {...callbacks} /></div></StoryPage>,
}
/* 2026-10-21: the property Bar shows the object's calculated values in this order; they are not edited there (computed,
plain text without focus). The Bar's object text is the object type only (원). */
const BAR_TITLE = "원"
const BAR_FIXTURE: SWHeaderProperty[] = [
{ id: "radius", name: "반지름", value: "4.0526", type: "number", groupId: "shape", min: 0 },
{ id: "diameter", name: "지름", value: "8.1052", type: "number", groupId: "shape", min: 0 },
{ id: "circumference", name: "원주", value: "8.1052", type: "number", groupId: "shape", min: 0 },
{ id: "area", name: "면적", value: "51.5962", type: "number", groupId: "shape", min: 0 },
{ id: "radius", name: "반지름", value: "4.0526", type: "computed", groupId: "shape" },
{ id: "diameter", name: "지름", value: "8.1052", type: "computed", groupId: "shape" },
{ id: "circumference", name: "원주", value: "8.1052", type: "computed", groupId: "shape" },
{ id: "area", name: "면적", value: "51.5962", type: "computed", groupId: "shape" },
]
/* Property panel: the four Figma states with the same component and host data. */
const PROPERTY_STATES: { example: PropertyExample; title: string }[] = [
+4 -7
View File
@@ -4,7 +4,6 @@ import type { ReactNode } from "react"
import type { SWFileEntry, SWHomeMenuSectionData } from "@hm-design/sw"
import monitorA from "./assets/home-menu/file-open-monitor-a.svg"
import monitorB from "./assets/home-menu/file-open-monitor-b.svg"
import previewImage from "./assets/home-menu/figma-preview-h1100.png"
import save from "./assets/home-menu/save.svg"
import saveHover from "./assets/home-menu/save-hover.svg"
import saveAs from "./assets/home-menu/save-as.svg"
@@ -89,9 +88,9 @@ export const figmaHomeMenuSections: SWHomeMenuSectionData[] = [
export const figmaHomeMenuItemIds = figmaHomeMenuSections.flatMap(section => section.items.map(item => item.id))
// Figma 최근파일 662:67331 sample rows (15) and the 2-depth preview 662:67471 (sample data only).
// Figma 최근파일 662:67331 sample rows (15) and the 2-depth preview 662:67471 (sample data only; 2026-10-21: the preview shows the hovered file name over an empty black body).
const recentNames = [
["001-연결종합 계획평면도22222222.dwg", "a"], ["03.암발파공법적용도_231011.grm", "b"], ["1.배수공-일반사항_231004.dwg", "b"],
["001-연결종합 계획평면도.dwg", "a"], ["03.암발파공법적용도_231011.grm", "b"], ["1.배수공-일반사항_231004.dwg", "b"],
["00.시공상세도 분석 및 목차_20240416.grm"], ["0_목차.dwg"], ["001-연결종합 계획평면도22222222.dwg"], ["03.암발파공법적용도_231011.grm"],
["001-연결종합 계획평면도22222222.dwg"], ["03.암발파공법적용도_231011.grm"], ["0_목차.dwg"], ["00.시공상세도 분석 및 목차_20240416.grm"],
["001-연결종합 계획평면도22222222.dwg"], ["03.암발파공법적용도_231011.grm"], ["0_목차.dwg"], ["00.시공상세도 분석 및 목차_20240416.grm"],
@@ -100,11 +99,9 @@ const monitor = { a: monitorA, b: monitorB }
export const figmaRecentFiles: SWFileEntry[] = recentNames.map(([name, badge], index) => ({
id: `recent-${index + 1}`,
name,
path: index < 2 ? "Z:\\01.기술개발센터\\04_그리미시스템\\Design..." : "Z:\\01.기술개발센터\\04_그리미시스템\\D...",
// Full folder (the list cuts it with CSS ellipsis); the preview shows this folder + the file name (2026-10-21).
path: "Z:\\01.기술개발센터\\04_그리미시스템\\Design\\도면",
badge: badge ? <img src={monitor[badge]} alt="" width={37} height={32} style={{ transform: "translateY(-2px)" }} /> : undefined,
// Figma image 918 crop inside the 310 x 244 frame.
preview: <img src={previewImage} alt="" style={{ position: "absolute", left: "-25.97%", top: "-52.46%", width: "158.67%", height: "191.87%", maxWidth: "none", objectFit: "fill" }} />,
previewTitle: "C:/Users/user/Desktop/H1100.DWG",
}))
/** The Figma menu with the optional 최근파일 sub panel attached to the 최근파일 tile. */
+1 -1
View File
@@ -192,7 +192,7 @@ function StackPage() {
description="Popup 안의 Escape는 그 Popup만 닫습니다. 바깥의 Escape는 가장 최근에 열리거나 사용한 Popup 하나만 닫습니다. Dropdown·편집 중인 Input이 처리한 Escape로는 닫히지 않습니다. 긴 콘텐츠는 본문이 스크롤됩니다." />
{log}
<SWTheme className="sw-story-popups" data-stack="">
{open.includes("a") && <SWPopup title="Popup A" onClose={() => close("a")} data-popup="a"><SWPopupSection title="A"><SWPopupRow label="값"><SWHeaderInput value="A" aria-label="A 값" onCommit={next => record(`a=${next}`)} /></SWPopupRow></SWPopupSection></SWPopup>}
{open.includes("a") && <SWPopup title="Popup A" onClose={() => close("a")} data-popup="a"><SWPopupSection title="A"><SWPopupRow label="값" field="none" readOnly><span data-readonly-value="a">A</span></SWPopupRow></SWPopupSection></SWPopup>}
{open.includes("b") && <SWPopup title="Popup B" onClose={() => close("b")} data-popup="b"><SWPopupSection title="B">
<SWPopupPropertyRow property={layerProperty(layerB)} menus={MENUS} onPropertyChange={(_, value) => { setLayerB(value); record(`b.layer=${value}`) }} />
</SWPopupSection></SWPopup>}
+1 -1
View File
@@ -96,7 +96,7 @@ export const ThemeInheritance: Story = {
layers={[{ id: "0", name: "0_Layer_Cludmark" }, { id: "1", name: "A-WALL" }]} />
</div>
<div style={{ width: 1440, marginTop: 240 }}>
<SWFooter categories={CATEGORIES} defaultCategoryId="modify" auxItems={AUX_ITEMS} onSlotAdd={() => {}}
<SWFooter categories={CATEGORIES} defaultCategoryId="modify" auxItems={AUX_ITEMS} onSlotAdd={() => {}} slotGuide={{}}
command={<SWFooterCommandLine history={HISTORY} expanded />} />
</div>
</div>
+16 -4
View File
@@ -1,7 +1,7 @@
import { useRef, useState, type ReactNode } from "react"
import type { Meta, StoryObj } from "@storybook/react-vite"
import {
SWHeader, SWSelect, SWViewerButton, SWViewerField, SWViewerHeader, SWViewerHint, SWViewerInput, SWViewerMenu, SWViewerPanel, SWViewerSection, SWViewerSwitch,
SWHeader, SWHeaderWindowControls, SWSelect, SWViewerButton, SWViewerField, SWViewerHeader, SWViewerHint, SWViewerInput, SWViewerMenu, SWViewerPanel, SWViewerSection, SWViewerSwitch,
type SWViewerHeaderGroup, type SWViewerMenuEntry,
} from "@hm-design/sw"
import "@hm-design/sw/styles.css"
@@ -58,12 +58,21 @@ function AboutPanel({ demo }: { demo: ViewerDemo }) {
)
}
function ViewerScreen({ width = 1440, initialDocument = SAMPLES[1].name as string | null, open = [] as ("layers" | "options" | "properties")[] }) {
function ViewerScreen({ width = 1440, initialDocument = SAMPLES[1].name as string | null, open = [] as ("layers" | "options" | "properties")[], windowControls = false }) {
const demo = useViewerDemo({ initialDocument, initialPanels: open })
// 2026-10-21 new viewer version: SWHeader's own minimize / close (SWHeaderWindowControls, same icons, size, hover)
// at the right end after the existing trailing items (the `trailing` slot, no new API). The program owns the real
// window actions (onMinimize / onClose, as in SWHeader); the Story only logs them.
const trailing = windowControls ? (
<>
{demo.trailing}
<SWHeaderWindowControls showWindowMove={false} onMinimize={() => demo.record("minimize")} onClose={() => demo.record("close")} />
</>
) : demo.trailing
return (
<>
<Log lines={demo.log} />
<ViewerFrame width={width} header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
<ViewerFrame width={width} header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={trailing} onCommand={demo.onCommand} />}>
<LayerPanel demo={demo} strategy="absolute" />
<OptionsPanel demo={demo} strategy="absolute" inset={{ side: 356 }} />
<PropertiesPanel demo={demo} strategy="absolute" inset={{ side: 24, top: 488 }} />
@@ -81,6 +90,9 @@ export const ViewerHeader: Story = {
<StorySection title="기본형 (1440px, 도면 열림)">
<ViewerScreen />
</StorySection>
<StorySection title="최소화 · 닫기 버튼 (1440px, 도면 열림)" description="SWHeader의 최소화·닫기 버튼(SWHeaderWindowControls)을 trailing 오른쪽 끝에 그대로 둔 버전입니다. 실제 창 최소화·닫기는 프로그램이 처리합니다.">
<div data-viewer-window-controls=""><ViewerScreen windowControls /></div>
</StorySection>
</StoryPage>
),
}
@@ -324,7 +336,7 @@ export const Responsive: Story = {
<StoryPageHeader kicker={KICKER} title="SW Viewer — 화면 너비" />
<Log lines={demo.log} />
<div className="sw-story-stack">
{[1440, 1100, 860, 520].map(width => (
{[1920, 1440, 1280, 950, 860, 760, 520].map(width => (
<StorySection key={width} title={`${width}px`}>
<div className="sw-story-scroller sw-viewer-story-menu-row"><div style={{ width }} data-width={width}><SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} /></div></div>
</StorySection>