Files
HMDesign/packages/sw/ai/VIEWER.md
T
shkim4 7f42923ccf feat(sw): viewer mode header, dropdown and popup panel
- 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
2026-10-19 16:09:26 +09:00

11 KiB
Raw Blame History

@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을 넣습니다(레이어 목록 / 옵션 / 속성 / 입력 폼 — Storybook Popup 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 닫기 추가, 로고 기본 표시 없음(사용자 결정).