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

+5 -2
View File
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react-vite"
import { useId, useState, type ComponentProps } from "react"
import { useId, useState, type ComponentProps, type CSSProperties } from "react"
import {
Card, CardContent, CardHeader, CardTitle, Input, Label, Textarea,
Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
@@ -85,7 +85,10 @@ function SurfaceUsage({ inCard = false }: { inCard?: boolean }) {
</div>
</div>
</TabsContent>
<TabsContent value="schedule" className="px-0 py-6">
{/* 2026-10-23: this panel can be narrower than the Table default 520px minimum (--hm-table-min-width), which made the
table container scroll. The documented token is reset for this panel only: the table follows the panel width
and long cell text wraps (Table overflow-wrap). */}
<TabsContent value="schedule" className="px-0 py-6" style={{ "--hm-table-min-width": "0px" } as CSSProperties}>
<Table aria-label="프로젝트 산출물 일정">
<TableHeader><TableRow><TableHead scope="col">산출물</TableHead><TableHead scope="col">마감일</TableHead></TableRow></TableHeader>
<TableBody><TableRow><TableCell>화면 설계서</TableCell><TableCell>10월 14일</TableCell></TableRow></TableBody>
+4 -1
View File
@@ -367,11 +367,14 @@ function ReferenceFooter() {
const [open, setOpen] = useState(false)
const [aux, setAux] = useState(REFERENCE_AUX)
const [active, setActive] = useState<string | null>(null)
// 2026-10-23: onRemove makes the layout tabs removable, so the × shows on hover as in SW Footer (no onRemove =
// a disabled, invisible ×). Removal is per screen row.
const [layouts, setLayouts] = useState(LAYOUTS_2)
return (
<SWFooter categories={REFERENCE_CATEGORIES} auxItems={aux} onAuxPressedChange={(id, pressed) => setAux(items => items.map(item => (item.id === id ? { ...item, pressed } : item)))}
activeToolId={active} onToolSelect={id => setActive(id)} onSlotAdd={noop} onCategoryAdd={noop} status="10.5939, 6.0728, 0.0000"
command={<SWFooterCommandLine history={REFERENCE_HISTORY} expanded={open} onExpandedChange={setOpen} />}
layoutTabs={<SWFooterLayoutTabs layouts={LAYOUTS_2} value={SW_FOOTER_MODEL} onAdd={noop} />} />
layoutTabs={<SWFooterLayoutTabs layouts={layouts} value={SW_FOOTER_MODEL} onAdd={noop} onRemove={id => setLayouts(items => items.filter(item => item.id !== id))} />} />
)
}
+2 -2
View File
@@ -7,7 +7,7 @@ import {
import "@hm-design/sw/styles.css"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import { AUX_ITEMS, CATEGORIES, HISTORY } from "./sw-footer-fixture"
import { LayerPanel, OptionsPanel, SAMPLES, useViewerDemo } from "./sw-viewer-fixture"
import { LayerPanel, OptionsPanel, SAMPLES, VIEWER_STORY_LOGO, useViewerDemo } from "./sw-viewer-fixture"
import "./sw-stories.css"
import "./sw-viewer-stories.css"
@@ -42,7 +42,7 @@ function ViewerArea({ width, panels, ...rest }: { width: number; panels?: boolea
const demo = useViewerDemo({ initialDocument: SAMPLES[1].name, initialPanels: panels ? ["layers", "options"] : [] })
return (
<Frame width={width} height={panels ? 600 : 220} {...rest}
header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
header={<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
{panels && <>
<LayerPanel demo={demo} strategy="absolute" defaultPosition="top-start" />
<OptionsPanel demo={demo} strategy="absolute" />
+5
View File
@@ -6,6 +6,11 @@ import {
type SWHeaderDocument, type SWViewerCommand, type SWViewerHeaderGroup, type SWViewerHeaderProps, type SWViewerPanelProps,
} from "@hm-design/sw"
import { Info, Layers, List, MousePointerClick, RotateCcw, Scan, SlidersHorizontal, ZoomIn } from "lucide-react"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
// 2026-10-23: the regular viewer Stories show the TEMPORARY placeholder logo of the SW Header Stories (not a product
// logo); only SW/Viewer › Logo › 로고 없음 renders without one.
export const VIEWER_STORY_LOGO = { logoSrc: placeholderLogo, logoAlt: "임시 로고" } satisfies Partial<SWViewerHeaderProps>
export const SAMPLES = [
{ id: "sample-basic", label: "기본 도면 · DWG", name: "BasicSample.dwg" },
+12 -11
View File
@@ -8,7 +8,7 @@ import "@hm-design/sw/styles.css"
import { Copy, Download, FileText, FolderOpen, Layers, Printer, Ruler, Scan, Search } from "lucide-react"
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
import { showVerificationControls } from "./sw-story-flags"
import { LAYERS, LayerPanel, OptionsPanel, PropertiesPanel, SAMPLES, useViewerDemo, type ViewerDemo } from "./sw-viewer-fixture"
import { LAYERS, LayerPanel, OptionsPanel, PropertiesPanel, SAMPLES, VIEWER_STORY_LOGO, useViewerDemo, type ViewerDemo } from "./sw-viewer-fixture"
import "./sw-stories.css"
import "./sw-viewer-stories.css"
import placeholderLogo from "./assets/sw-header-logo-placeholder.svg"
@@ -73,7 +73,7 @@ function ViewerScreen({ width = null as number | null, initialDocument = SAMPLES
return (
<>
<Log lines={demo.log} />
<ViewerFrame width={width} header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={trailing} onCommand={demo.onCommand} />}>
<ViewerFrame width={width} header={<SWViewerHeader {...VIEWER_STORY_LOGO} 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 }} />
@@ -121,7 +121,7 @@ export const MenuGroups: Story = {
<div className="sw-story-stack">
{rows.map(row => (
<StorySection key={row.title} title={row.title}>
<div className="sw-story-scroller"><div style={{ width: 1440 }}><SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={pick(row.ids)} trailing={row.ids.includes("panels") ? demo.trailing : undefined} onCommand={demo.onCommand} /></div></div>
<div className="sw-story-scroller"><div style={{ width: 1440 }}><SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={pick(row.ids)} trailing={row.ids.includes("panels") ? demo.trailing : undefined} onCommand={demo.onCommand} /></div></div>
</StorySection>
))}
</div>
@@ -162,7 +162,7 @@ export const States: Story = {
<StoryPageHeader kicker={KICKER} title="SW Viewer — Active / Hover / Disabled" />
<Log lines={log} />
<div className="sw-story-scroller"><div style={{ width: 1440 }}>
<SWViewerHeader documentTitle="상태 확인용.dwg" groups={groups} onCommand={command => record(`${command.id}${command.value !== undefined ? `=${String(command.value)}` : ""}`)} />
<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle="상태 확인용.dwg" groups={groups} onCommand={command => record(`${command.id}${command.value !== undefined ? `=${String(command.value)}` : ""}`)} />
</div></div>
</StoryPage>
)
@@ -216,7 +216,7 @@ export const Dropdowns: Story = {
<StoryPageHeader kicker={KICKER} title="SW Viewer — 상단 메뉴 + Dropdown" />
<Log lines={log} />
<div className="sw-story-scroller sw-viewer-story-room"><div style={{ width: 1440 }}>
<SWViewerHeader documentTitle="C0060202-001-평면및종단면도.dwg" groups={groups}
<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle="C0060202-001-평면및종단면도.dwg" groups={groups}
onCommand={command => { const entry = findEntry(entries, command.id); if (entry) apply(entry); record(`${command.itemId}/${command.id}${command.value !== undefined ? `=${String(command.value)}` : ""}`) }} />
</div></div>
{showVerificationControls() && <StorySection title="SWViewerMenu 단독 (Context Menu)">
@@ -263,7 +263,7 @@ export const SettingsPanel: Story = {
<StoryPage>
<StoryPageHeader kicker={KICKER} title="SW Viewer — 설정창 (SWViewerPanel)" />
<Log lines={demo.log} />
<ViewerFrame header={<SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
<ViewerFrame header={<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />}>
<OptionsPanel demo={demo} strategy="absolute" />
</ViewerFrame>
</StoryPage>
@@ -282,7 +282,7 @@ export const ContentVariants: Story = {
<StoryPageHeader title="SW Viewer — 설정창 (SWViewerPanel)" />
<Log lines={demo.log} />
{/* 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} />}>
<ViewerFrame height={620} header={<SWViewerHeader {...VIEWER_STORY_LOGO} 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 }} />
<OptionsPanel demo={demo} strategy="absolute" defaultPosition={{ left: 712, top: 16 }} />
@@ -307,7 +307,7 @@ export const VariableSize: Story = {
return (
<StoryPage>
<StoryPageHeader kicker={KICKER} title="SW Viewer — 가변 크기 팝업" />
<ViewerFrame height={520} header={<SWViewerHeader documentTitle="크기 비교.dwg" groups={[]} />}>
<ViewerFrame height={520} header={<SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle="크기 비교.dwg" groups={[]} />}>
{sizes.map(size => (
<SWViewerPanel key={size.id} strategy="absolute" defaultPosition={{ left: size.left, top: 16 }} title={size.title} open={open[size.id]} onClose={() => setOpen({ ...open, [size.id]: false })} width={size.width} height={size.height}>
<SWViewerHint>{size.note}</SWViewerHint>
@@ -334,7 +334,7 @@ export const Responsive: Story = {
// like the other rows it is a maximum inside the Story column.
<div key={width}><StorySection title={`${width}px`}>
{/* The row width is a maximum (2026-10-22): on a narrower screen the header shrinks with it instead of scrolling. */}
<div className="sw-story-scroller sw-viewer-story-menu-row"><div style={{ width: "100%", maxWidth: width }} data-width={width}><SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} /></div></div>
<div className="sw-story-scroller sw-viewer-story-menu-row"><div style={{ width: "100%", maxWidth: width }} data-width={width}><SWViewerHeader {...VIEWER_STORY_LOGO} documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} /></div></div>
</StorySection></div>
))}
</div>
@@ -373,7 +373,7 @@ export const CompareWithSWHeader: Story = {
<StoryPageHeader title="SW Viewer — 일반 SW Header와 비교" />
<StorySection title="나란히 비교 (1440px)">
<div className="sw-story-stack">
<div className="sw-story-scroller"><div style={{ width: 1440 }}><SWHeader documentTitle="C0060202-001-평면및종단면도" /></div></div>
<div className="sw-story-scroller"><div style={{ width: 1440 }}><SWHeader logoSrc={placeholderLogo} logoAlt="임시 로고" documentTitle="C0060202-001-평면및종단면도" /></div></div>
<div className="sw-story-scroller"><div style={{ width: 1440 }}><ViewerOnly /></div></div>
</div>
</StorySection>
@@ -414,5 +414,6 @@ export const Logo: Story = {
function ViewerOnly() {
const demo = useViewerDemo({ initialDocument: SAMPLES[1].name })
return <SWViewerHeader documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />
// 2026-10-23: the same placeholder logo as the SWHeader row, so the two logo slots compare at the same width.
return <SWViewerHeader logoSrc={placeholderLogo} logoAlt="임시 로고" documentTitle={demo.documentName} {...demo.fileProps} groups={demo.groups} trailing={demo.trailing} onCommand={demo.onCommand} />
}