- SWViewerHeader: data-driven viewer menu (button / toggle / menu / tabs / switch / custom), More by collapseBelow, trailing slot, onCommand; SWHeader logo props with no default logo - SWViewerMenu: dropdown, check / radio, multi-level submenu, context menu at a point - SWViewerPanel: viewer popup frame with free children / footer, drag, corner resize, collapse, modal - Panel controls: SWViewerSection / Field / Hint / Switch / Slider / Input / Button / Badge / Status - SWHeader black text outline tokens on viewer text, matching icon outline (drop-shadow) - Storybook SW/Viewer stories; remove SW/Tool story (SW/Popup kept) - AI guide ai/VIEWER.md, example H, verification scripts and evidence
11 KiB
11 KiB
@hm-design/sw — 뷰어 모드 (Viewer Header · Popup) 사용 지침
기준: 2026-10-19, 참고 DWG/DXF 뷰어(dwg-dxf-viewer-hm) 상단 메뉴·팝업을 같은 해상도(1440 × 900)에서 실측해 패키지 컴포넌트로 옮긴 결과입니다. 아래 이름은 모두 from "@hm-design/sw" 공개 Export이고 CSS는 @hm-design/sw/styles.css 한 개입니다. 예제: examples/ExampleH-ViewerMode.tsx.
1. 언제 무엇을 쓰나
| 화면 | 사용 | 쓰지 않음 |
|---|---|---|
| SW 일반 모드 Header | SWHeader (49px, Figma 26:8944) |
SWViewerHeader |
| SW 뷰어 모드 Header | SWViewerHeader (52px, 참고 뷰어) |
SWHeader에 뷰어 메뉴를 억지로 넣기, 직접 만든 툴바 |
| 뷰어 메뉴 Dropdown·서브메뉴·우클릭 메뉴 | type: "menu" 항목의 entries, 단독은 SWViewerMenu |
@hm-design/ui DropdownMenu를 Header 안에 직접 배치 |
| 뷰어 팝업·설정창·도구 패널·입력 팝업 | SWViewerPanel (+ modal) |
새 프레임 작성, SWPopup(일반 SW 속성창 프레임)을 뷰어 디자인으로 재스타일 |
| 일반 SW 팝업·속성창 | SWPopup, SWHeaderPropertyPanel (변경 없음) |
SWViewerPanel |
- 한 화면에는 Header 하나만 둡니다. 모드 전환은 프로그램이 Header 컴포넌트를 바꾸는 방식입니다(Storybook
SW/Viewer › SWHeader vs Viewer Header). 파일·패널 상태는 프로그램 상태라 Header를 바꿔도 유지됩니다. - 뷰어 모드 진입·종료, 파일 처리, DWG/DXF 렌더링, 메뉴 명령 실행은 프로그램 담당입니다. 패키지는 UI와 공통 인터랙션(열기/닫기, Focus, 키보드, 위치·크기)만 담당합니다.
- 기능이 없는 메뉴는 만들지 않습니다. 연결할 명령이 없으면 항목을 빼거나
disabled로 두고 “미연결”로 보고합니다.
2. SWViewerHeader
로고 면(사선) → 파일명 면 → 객체 수정 면(메뉴 그룹) → 오른쪽 끝 영역(placement: "end" 그룹 + trailing).
| Props | 설명 |
|---|---|
groups: SWViewerHeaderGroup[] |
메뉴 데이터(순서대로 배치). 그룹: id, label(접근성 이름·More 구획 제목), moreLabel?, surface?: "plain" | "field"(field = 사용자 확정 속성 입력 면), placement?: "start" | "end", hidden?, items |
항목 SWViewerHeaderItem |
공통: id, label, icon?(16px, 프로그램 제공), title?, disabled?, hidden?, collapseBelow?(Header 폭 미만이면 More로 이동), iconOnlyBelow?(미만이면 아이콘만), iconOnly?, moreLabel? |
type: "button" |
onSelect |
type: "toggle" |
pressed, onPressedChange (패널 열기/닫기 등) |
type: "menu" |
entries: SWViewerMenuEntry[], menuWidth?, menuAlign?: "start" | "end" |
type: "tabs" |
value, options: { value, label, count?, title?, disabled? }[], onValueChange (Model / Layout) |
type: "switch" |
checked, onCheckedChange (2.5D 등, 위 11px 캡션) |
type: "custom" |
render(), menuEntries?(More에 넣을 항목) |
onCommand(command) |
모든 명령을 한 곳에서 받습니다: { id, itemId, value? }. 항목 자체 Callback 다음에 호출 |
documentTitle, documentPlaceholder, documentTooltip, document? |
파일명 면 |
logo?, logoSrc?, logoAlt?, onHomeClick? |
SWHeader와 같은 로고 Props. 주지 않으면 아무것도 그리지 않습니다(“HM” 문자·기본 아이콘 없음, 로고 면은 사선 끝 24px만). 주면 72px 면(로고가 더 넓으면 그만큼), 높이 최대 24px |
trailing?: ReactNode |
오른쪽 끝 자유 영역: SWViewerStatus, SWViewerHeaderButton |
moreLabel?, toolbarLabel?, <header> 속성, ref |
- 반응형(참고 뷰어와 동일): 항목별
collapseBelow로 More(⋯, 베이지 260px Dropdown)로 이동합니다. 참고 뷰어 기준값: 샘플·도면 공간·2.5D·어두운 배경1180, 패널 토글900, 파일 열기·전체 보기iconOnlyBelow: 560. 1180px 미만은 간격 2px, 560px 미만은 상태 글자 숨김(CSS container). 가로 스크롤은 만들지 않습니다. - More는 그룹/항목 단위 구획으로 자동 구성됩니다: menu·tabs는 자기 구획(
moreLabel), button·toggle·switch는 그룹 구획(group.moreLabel ?? group.label). toggle·switch는 체크 항목, tabs는 라디오 항목이 됩니다. - 상태 표현: Hover = #266651 면, Active·Pressed·열림·선택 탭 = 글자·아이콘 #FFC600(선택 박스 없음), Disabled = 60% 불투명, Focus = 2px #FFC600 외곽선.
- 글자·아이콘 외곽선(SWHeader와 동일): 14px 글자
--hm-sw-header-text-outline(검정 1.3px), 11px 캡션·개수·상태--hm-sw-header-label-outline(검정 1px), 아이콘--hm-sw-viewer-icon-outline(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke + #061A14 glow를drop-shadow로 재현, 아이콘 윤곽을 따름). 색·크기·굵기·간격은 그대로이고 Hover/Active는 채움 색만 바뀝니다. Dropdown·패널 안에는 외곽선이 없습니다(SWHeader More 메뉴와 같음). 프로그램 아이콘은currentColor로 그리면 상태 색과 외곽선이 자동 적용됩니다.
3. SWViewerMenu / SWViewerMenuEntry
entries: item(icon?, shortcut?, onSelect), checkbox(checked, onCheckedChange), radio(checked, onSelect), submenu(entries — 다단계), label, separator. 공통 disabled?, hidden?, keepOpen?.
- Header의
type: "menu"가 이 Dropdown을 엽니다. 다른 위치(도면 우클릭 등)는SWViewerMenu를open/onOpenChange+anchorRef(버튼 아래 4px) 또는anchorPoint(포인터 위치)로 씁니다.surface="beige"는 More 면,initialFocus는first(키보드 열기) /menu(클릭 열기). - 키보드: Enter·Space·ArrowDown 열기, ↑↓ Home End 이동, → 서브메뉴 열기, ← 한 단계 닫기, Escape 닫고 Trigger로 Focus 복귀, Tab·바깥 클릭 닫기. 화면 끝에서는 위/왼쪽으로 뒤집혀 화면 안에 남습니다.
4. SWViewerPanel — 팝업 프레임 + 자유 콘텐츠
외형(베이지 #E0DACE 본문, 2px #897445 테두리, #221D13 제목 막대, #FFC600 제목, 접기, 곡선 녹색 닫기 → Hover #E40000)과 공통 동작은 패키지가, 내용은 children·footer가 담당합니다.
| Props | 설명 |
|---|---|
title, icon?, open, onClose, closeLabel? |
필수는 title·open·onClose |
children |
스크롤 본문. SWViewerSection(카드/plain, 12px 제목), SWViewerField(라벨 + 컨트롤, inline / stacked + below), SWViewerSwitch, SWViewerSlider, SWViewerInput, SWViewerButton, SWViewerBadge, SWViewerHint, 프로그램 목록·표 등 무엇이든 |
footer? |
하단 버튼 영역(본문 스크롤과 무관하게 고정). SWViewerButton variant="outline" | "primary" |
headerActions? |
제목 막대 추가 버튼: SWViewerButton variant="ghost" (기본값 복원, 선택 확대 등) |
width?(320), height?(420, "auto" 가능), minWidth?(250), minHeight?(140) |
메뉴별 크기. 최대 90vw × 90vh |
defaultPosition? |
{ left, top } 또는 "top-end"(기본) / "top-start" / "center"(modal 기본), inset?: { top?, side? }(기본 56 / 24 = 52px Header 아래) |
strategy? |
fixed(기본, 화면) / absolute(가장 가까운 positioned 조상 안 — 미리보기·임베드) |
draggable?, resizable?, collapsible?, defaultCollapsed?, collapsed?, onCollapsedChange?, onPositionChange?, onSizeChange? |
제목 막대 드래그, 오른쪽 아래 모서리 크기 조절, 접기(버튼·제목 더블클릭). 이동·크기는 경계 10px 안 |
modal? |
10% 검정 배경, Focus Trap, 가운데 배치(입력·확인 팝업) |
autoFocus? |
열 때 패널로 Focus, 닫을 때 원래 위치로 복귀(기본 true) |
- Escape는 Focus가 패널 안에 있을 때 그 패널만 닫습니다. 비모달 패널은 바깥 클릭으로 닫히지 않습니다(참고 뷰어와 동일, 도구 창). 마지막으로 누른 패널이 맨 앞으로 옵니다.
- 콘텐츠 교체: 같은 프레임에 다른
children을 넣습니다(레이어 목록 / 옵션 / 속성 / 입력 폼 — StorybookPopup Content Variants). 프레임을 복사하거나hm-sw-viewer-*class로 새 팝업을 만들지 않습니다.
5. 올바른 사용
import { SWViewerHeader, SWViewerPanel, SWViewerSection, SWViewerField, SWViewerSwitch, type SWViewerHeaderGroup } from "@hm-design/sw"
import "@hm-design/sw/styles.css"
const groups: SWViewerHeaderGroup[] = [
{ id: "view", label: "화면 보기", surface: "field", items: [{ type: "button", id: "fit", label: "전체 보기", icon: <FitIcon />, onSelect: fit }] },
{ id: "panels", label: "패널", placement: "end", items: [{ type: "toggle", id: "options", label: "설정", icon: <OptionsIcon />, pressed: open, onPressedChange: setOpen, collapseBelow: 900 }] },
]
<SWViewerHeader documentTitle={fileName} groups={groups} onCommand={runCommand} />
<SWViewerPanel title="옵션" open={open} onClose={() => setOpen(false)}>
<SWViewerSection title="표시">
<SWViewerField label="그리드">{id => <SWViewerSwitch id={id} checked={grid} onCheckedChange={setGrid} />}</SWViewerField>
</SWViewerSection>
</SWViewerPanel>
6. 잘못된 사용
| 하지 말 것 | 이유 / 대신 |
|---|---|
<SWViewerHeader logo="HM" />처럼 로고가 없다고 임의 문자·아이콘 넣기 |
로고는 프로그램이 정합니다. 없으면 생략 |
뷰어 Header를 div + button으로 다시 만들기, hm-sw-viewer-* class를 소비 코드에 붙이기 |
데이터(groups)와 custom / trailing으로 구성 |
Header 버튼 글자에 -webkit-text-stroke·border로 외곽선 만들기 |
외곽선은 패키지 토큰이 이미 적용합니다 |
메뉴 색·간격을 #123456, margin 등 임의 값으로 덮기 |
토큰 --hm-sw-viewer-* / --hm-sw-header-*만 style로 조정(기본값 유지 권장) |
존재하지 않는 Props 추측(items, content, renderBody …) |
위 표와 dist/index.d.ts가 전부 |
일반 모드 화면에서 SWViewerHeader 사용, 뷰어에서 SWHeader 재스타일 |
모드별 Header를 바꿔 끼웁니다 |
| 기능 없는 메뉴를 가짜 동작으로 연결 | 항목 제외 또는 disabled + 미연결 보고 |
7. 디자인 변경 시 주의
- 크기(52px Header, 34px 버튼, 40px 토글·탭·스위치 칸, 41~42px field 면, 72px 로고 면)와 색은 참고 뷰어 실측값입니다. 파일명·객체 수정·속성 면 배경은 SWHeader와 같은 사용자 확정 토큰(
--hm-sw-header-document-*,object-*,property-*)을 공유하므로 바꾸면 두 Header가 함께 바뀝니다. - Light/Dark 분기는 없습니다. 참고 뷰어와의 알려진 차이: 도면 공간(tabs) field 면 높이 42px(참고 41px — 참고 Tabs 루트가 39px인 구현 부산물), 크기 조절은 네이티브
resize대신 같은 위치의 모서리 Grip(모든 브라우저 동작), 패널 Escape 닫기 추가, 로고 기본 표시 없음(사용자 결정).