feat(sw): viewer layer eye-icon rule, header hover, trailing strip and file name width

User-requested SW Viewer changes (2026-10-21); no new Props, public .d.ts unchanged.
- Layer popup visibility: AI docs (VIEWER §4.1, catalog, design rules, patterns,
  AI_GUIDE) require the Eye / EyeOff icon button, never a switch; example H now
  renders a real layer list with eye buttons; Storybook eye button gets the pointer.
- Viewer header hover: text and icon #FFC600, the #266651 hover face removed; size and
  position unchanged. Status stays display only.
- Trailing strip: status, the trailing icon button (info / license) and SWHeader
  minimize / close are full-height (48px) segments on the window-area face, no gap, top
  and bottom on the object surface border; no line around the license button.
- File name: the measured steps use the file-name basis (header width), not the text
  width, so a short and a long name give the same Open / file-name widths.
- File open icon: the black icon outline of every viewer icon (SWViewerIcon), no frame.
This commit is contained in:
shkim4 committed 2026-10-21 18:52:38 +09:00
1 parent 32e46c32be
commit caf4c32592
14 files changed
+109 -26

No files matched your search

@@ -3,10 +3,11 @@
// open / close / focus / keyboard / drag behavior. Icons are the program's own (16px, currentColor).
import { useState } from "react"
import {
SWViewerButton, SWViewerField, SWViewerHeader, SWViewerHeaderButton, SWViewerHint, SWViewerInput, SWViewerPanel,
SWViewerBadge, SWViewerButton, SWViewerField, SWViewerHeader, SWViewerHeaderButton, SWViewerHint, SWViewerInput, SWViewerPanel,
SWViewerSection, SWViewerSlider, SWViewerStatus, SWViewerSwitch,
type SWViewerHeaderGroup,
} from "@hm-design/sw"
import "./example-layout.css"
const Icon = ({ d }: { d: string }) => (
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={d} /></svg>
@@ -16,7 +17,11 @@ const icons = {
layers: <Icon d="M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5" />,
options: <Icon d="M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0" />,
info: <Icon d="M12 8h.01M11 12h1v5h1M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z" />,
// Layer visibility: eye (shown) / eye with a slash (hidden). Never a switch or checkbox (VIEWER.md §4.1).
eye: <Icon d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z" />,
eyeOff: <Icon d="M2.06 12.35a1 1 0 0 1 0-.7 10.75 10.75 0 0 1 19.88 0 1 1 0 0 1 0 .7 10.75 10.75 0 0 1-19.88 0M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM3 3l18 18" />,
}
const layers = [{ name: "0", color: "#ffffff", count: 120 }, { name: "WALL", color: "#ff3b30", count: 42 }, { name: "DIM", color: "#34c759", count: 18 }]
export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => void }) {
const [fileName, setFileName] = useState<string | null>(null)
@@ -26,6 +31,14 @@ export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => vo
const [grid, setGrid] = useState(true)
const [zoomSpeed, setZoomSpeed] = useState(1)
const [about, setAbout] = useState(false)
// Program-owned visibility per layer; each eye button toggles only its own layer.
const [hiddenLayers, setHiddenLayers] = useState<ReadonlySet<string>>(new Set())
const toggleLayer = (name: string) => {
const next = new Set(hiddenLayers), visible = !next.has(name)
if (visible) next.add(name); else next.delete(name)
setHiddenLayers(next)
onEvent?.(`layer:${name}=${!visible}`)
}
// File open: the header button after the logo (icon + label, icon only when the header is narrow). File name click:
// the sample list only (no Open / + New).
@@ -60,7 +73,22 @@ export function ExampleHViewerMode({ onEvent }: { onEvent?: (name: string) => vo
<SWViewerPanel title="레이어" open={panels.layers} onClose={() => setPanels({ ...panels, layers: false })}>
<SWViewerSection surface="plain" aria-label="레이어 목록">
<SWViewerHint>프로그램의 레이어 목록을 여기에 둡니다.</SWViewerHint>
{/* Layer list: swatch · name · count · eye button. Visibility is the eye icon button, not SWViewerSwitch. */}
<ul className="app-viewer-layers">
{layers.map(layer => {
const visible = !hiddenLayers.has(layer.name)
return (
<li key={layer.name} data-visible={visible}>
<span className="app-viewer-layer-swatch" style={{ background: layer.color }} aria-hidden="true" />
<span className="app-viewer-layer-name" title={layer.name}>{layer.name}</span>
<SWViewerBadge>{layer.count}</SWViewerBadge>
<button type="button" className="app-viewer-layer-eye"
aria-label={`${layer.name} ${visible ? "숨기기" : "표시하기"}`} title={visible ? "숨기기" : "표시하기"}
onClick={() => toggleLayer(layer.name)}>{visible ? icons.eye : icons.eyeOff}</button>
</li>
)
})}
</ul>
</SWViewerSection>
</SWViewerPanel>