feat(sw): viewer follow-up — SVG icon outline, shared SW popup frame, story grid, green layer buttons
- A: SWViewerIcon draws the SWHeader black outline as SVG stroke on a copy behind the icon (no CSS filter) - B: SWViewerPanel renders the common SWPopup frame (same DOM / CSS) and adds drag, corner resize, collapse, bounds and modal; SWPopup gains optional titleActions / footer; title art spans any width - C: shared Story canvas grid (--sw-story-canvas) behind the SW Header work area and dropdown room - D: SWViewerButton tone="green" for 전체 켜기 / 전체 끄기: default kept, hover = primary (확인) hover token, pressed = primary face - AI guide, verification scripts (76 checks, element regression) and evidence updated
This commit is contained in:
1 parent
7f42923ccf
commit
68fbcf6e15
45 files changed
+1463
-377
No files matched your search
@@ -1,6 +1,9 @@
|
||||
/* Story canvas only: frames that simulate a header width. Never styles the header itself. */
|
||||
.sw-story-scroller { max-width: 100%; overflow-x: auto; }
|
||||
.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. */
|
||||
.sb-page { --sw-story-canvas: linear-gradient(rgba(38, 102, 81, 0.18) 1px, transparent 1px) 0 0 / 40px 40px, linear-gradient(90deg, rgba(38, 102, 81, 0.18) 1px, transparent 1px) 0 0 / 40px 40px, #0a0a0b; }
|
||||
.sw-story-composed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -10,7 +13,7 @@
|
||||
padding: 5px 8px;
|
||||
background: var(--hm-sw-header-object-background);
|
||||
}
|
||||
.sw-story-scroller--menu { padding-bottom: 360px; }
|
||||
.sw-story-scroller--menu { padding-bottom: 360px; background: var(--sw-story-canvas); }
|
||||
/* Only the two fixed dimension specimens use the full 1920px frame as their anchor. The shared
|
||||
runtime dropdown still clamps to the real viewport. Scroll the specimen to see its right edge. */
|
||||
.sw-story-fixed-dimension .hm-sw-header__list-menu--dimension-style { left: auto !important; right: 0; }
|
||||
@@ -58,7 +61,7 @@
|
||||
.sw-overview-data { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; padding-block: 8px; }
|
||||
.sw-overview-data label { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.sw-overview-slot { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; align-items: center; justify-content: center; }
|
||||
.sw-overview-stage { flex: 0 0 auto; overflow: auto; overscroll-behavior: contain; background: var(--hm-sem-bg-surface); outline: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.sw-overview-stage { flex: 0 0 auto; overflow: auto; overscroll-behavior: contain; background: var(--sw-story-canvas); outline: 1px solid var(--hm-sem-stroke-input-default); }
|
||||
.sw-overview-world { min-width: 390px; min-height: 100%; position: relative; }
|
||||
/* Placement-only Storybook exception: keep the original panel size/content in the Stage scroll area.
|
||||
Header's public API deliberately excludes panel style; package CSS and other Stories stay untouched. */
|
||||
|
||||
@@ -106,12 +106,12 @@ export function LayerPanel({ demo, ...placement }: { demo: ViewerDemo } & PanelP
|
||||
const filtered = LAYERS.filter(layer => layer.name.toLowerCase().includes(query.trim().toLowerCase()))
|
||||
const set = (names: string[]) => demo.setHidden(new Set(names))
|
||||
return (
|
||||
<SWViewerPanel title={`레이어 (${LAYERS.length}개)`} icon={<Layers />} open={demo.panels.layers} onClose={() => demo.toggle("layers", false)} {...placement}>
|
||||
<SWViewerPanel title={`레이어 (${LAYERS.length}개)`} open={demo.panels.layers} onClose={() => demo.toggle("layers", false)} {...placement}>
|
||||
<SWViewerSection surface="plain" aria-label="레이어 목록">
|
||||
<div className="sw-viewer-story-toolbar">
|
||||
<SWViewerInput type="search" placeholder="레이어 검색" aria-label="레이어 검색" value={query} onChange={event => setQuery(event.currentTarget.value)} />
|
||||
<SWViewerButton onClick={() => set([])}>전체 켜기</SWViewerButton>
|
||||
<SWViewerButton onClick={() => set(LAYERS.map(layer => layer.name))}>전체 끄기</SWViewerButton>
|
||||
<SWViewerButton tone="green" onClick={() => set([])}>전체 켜기</SWViewerButton>
|
||||
<SWViewerButton tone="green" onClick={() => set(LAYERS.map(layer => layer.name))}>전체 끄기</SWViewerButton>
|
||||
</div>
|
||||
{filtered.length === 0 ? <SWViewerHint>검색 결과가 없습니다 ("{query}")</SWViewerHint> : (
|
||||
<ul className="sw-viewer-story-list">
|
||||
@@ -152,7 +152,7 @@ export function OptionsPanel({ demo, footer, ...placement }: { demo: ViewerDemo;
|
||||
const { options, setOptions } = demo
|
||||
const set = (patch: Partial<ViewerOptions>) => setOptions({ ...options, ...patch })
|
||||
return (
|
||||
<SWViewerPanel title="옵션" icon={<SlidersHorizontal />} open={demo.panels.options} onClose={() => demo.toggle("options", false)} width={340} height={520} {...placement}
|
||||
<SWViewerPanel title="옵션" open={demo.panels.options} onClose={() => demo.toggle("options", false)} width={340} height={520} {...placement}
|
||||
headerActions={<SWViewerButton variant="ghost" aria-label="기본값으로" title="기본값으로" onClick={() => { setOptions(DEFAULT_OPTIONS); demo.record("options:reset") }}><RotateCcw size={16} /></SWViewerButton>}
|
||||
footer={footer ? <><SWViewerButton onClick={() => demo.toggle("options", false)}>취소</SWViewerButton><SWViewerButton variant="primary" onClick={() => { demo.record("options:apply"); demo.toggle("options", false) }}>적용</SWViewerButton></> : undefined}>
|
||||
<SWViewerSection title="선가중치">
|
||||
@@ -182,7 +182,7 @@ export function PropertiesPanel({ demo, selected = true, ...placement }: { demo:
|
||||
<dl className="sw-viewer-story-props">{items.map(([key, value]) => <div key={key}><dt>{key}</dt><dd title={value}>{value}</dd></div>)}</dl>
|
||||
)
|
||||
return (
|
||||
<SWViewerPanel title="속성" icon={<Info />} open={demo.panels.properties} onClose={() => demo.toggle("properties", false)} {...placement}
|
||||
<SWViewerPanel title="속성" open={demo.panels.properties} onClose={() => demo.toggle("properties", false)} {...placement}
|
||||
headerActions={selected ? <SWViewerButton variant="ghost" aria-label="선택 확대" title="선택 확대" onClick={() => demo.record("zoomToSelection (미연결)")}><ZoomIn size={16} /></SWViewerButton> : undefined}>
|
||||
{selected ? <>
|
||||
<SWViewerSection surface="plain" title="일반 정보 (General)"><SWViewerBadge>LINE · h=2F1A</SWViewerBadge>{rows(ENTITY_ROWS)}</SWViewerSection>
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background:
|
||||
linear-gradient(rgba(38, 102, 81, 0.18) 1px, transparent 1px) 0 0 / 40px 40px,
|
||||
linear-gradient(90deg, rgba(38, 102, 81, 0.18) 1px, transparent 1px) 0 0 / 40px 40px,
|
||||
#0a0a0b;
|
||||
background: var(--sw-story-canvas);
|
||||
}
|
||||
.sw-viewer-story-room { padding-bottom: 320px; }
|
||||
.sw-viewer-story-toolbar { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 12px 4px; }
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type SWViewerHeaderGroup, type SWViewerMenuEntry,
|
||||
} from "@hm-design/sw"
|
||||
import "@hm-design/sw/styles.css"
|
||||
import { Copy, Download, FileText, FolderOpen, Layers, Printer, Ruler, Scan, Search, Settings2 } from "lucide-react"
|
||||
import { Copy, Download, FileText, FolderOpen, Layers, Printer, Ruler, Scan, Search } from "lucide-react"
|
||||
import { StoryPage, StoryPageHeader, StorySection } from "@/components/story-page"
|
||||
import { LAYERS, LayerPanel, OptionsPanel, PropertiesPanel, SAMPLES, useViewerDemo, type ViewerDemo } from "./sw-viewer-fixture"
|
||||
import "./sw-stories.css"
|
||||
@@ -265,7 +265,7 @@ export const ContentVariants: Story = {
|
||||
<ViewerFrame height={640} header={<SWViewerHeader documentTitle={demo.documentName} 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: 712, top: 16 }} title="축척 지정" icon={<Settings2 />} open={inputOpen} onClose={() => setInputOpen(false)} width={300} height="auto" resizable={false}
|
||||
<SWViewerPanel strategy="absolute" defaultPosition={{ left: 712, top: 16 }} 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>
|
||||
|
||||
Reference in new issue
Block a user