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:
shkim4 committed 2026-10-19 16:34:50 +09:00
1 parent 7f42923ccf
commit 68fbcf6e15
45 files changed
+1463 -377

No files matched your search

+5 -5
View File
@@ -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>