Files
HMDesign/packages/sw/ai/VIEWER.md
T
shkim4 942f72ae12 docs: SW Dotum / Web Noto Sans KR font rule in AI guides and README
- Common AI_GUIDE (synced copies in ui / sw / org-chart / org-chart-react ai/): mixed-use rule 1 adds the font split
  (SW = Dotum, Web = Noto Sans KR, OrgChart keeps its own) and forbids copying one package's font into another area.
- SW AI_USAGE / DESIGN_RULES / VIEWER / COMPOSITION_PATTERNS / Example J: Dotum is the default (no "approval pending"),
  SW screens use SWTheme and SW tokens, no Web font copy, synthesized-bold width note; example override = Malgun Gothic.
- README: SW default Dotum, Web default Noto Sans KR.
2026-10-20 16:59:26 +09:00

16 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.

모드 선택 먼저: 이 문서는 SW 뷰어 모드 전용입니다. 화면이 뷰어 모드인지는 AI_USAGE.md §0(설정 HMDesign 사용 유형: SW 뷰어 → 요청 문구 → 불명확하면 질문)으로 정합니다. 전체 Export 목록은 COMPONENT_CATALOG.md §9, 토큰·상태 요약은 DESIGN_RULES.md §11, 조합은 COMPOSITION_PATTERNS.md 7입니다.

일반 모드 금지 사항(상호 참조): 뷰어 화면에 SWHeader·SWPopup·SWPopupSection·SWPopupRow·SWToolButton·SWToolGroup·SWHeaderPropertyPanel을 쓰거나 재스타일하지 않고, 일반 모드 화면에 SWViewer*와 --hm-sw-viewer-* 토큰을 쓰지 않습니다. SWViewerPanel이 내부에서 SWPopup 프레임을 렌더링하는 것과 두 Header가 --hm-sw-header-document-*·object-*·property-*·외곽선 토큰을 공유하는 것은 패키지 구현이며 모드 혼합이 아닙니다(AI_USAGE.md §0.3). 패키지에 없는 뷰어 UI는 AI_USAGE.md §0.4 뷰어 열을 따릅니다.

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), 아이콘 SWViewerIcon(SWHeader 아이콘 자산의 1px 검정 바깥 Stroke를 SVG stroke로 재현: 아이콘 뒤 복사본의 모든 도형을 검정, 원래 선 두께 + 화면 기준 양쪽 1px(--hm-sw-viewer-icon-outline-width)로 그림. 위 아이콘은 그대로라 색·모양·선 두께 유지. CSS filter 미사용, glow는 생략). Header 항목·SWViewerHeaderButton의 icon은 자동 적용, custom / trailing의 직접 그린 아이콘은 <SWViewerIcon>으로 감쌉니다. 색·크기·굵기·간격은 그대로이고 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?.

  • 테마: SWViewerMenu에는 style Prop이 없습니다. 토큰은 메뉴를 감싼 바깥 SW 범위(SWViewerHeader·SWViewerPanel·<SWTheme>)에서 상속됩니다. 단독 우클릭 메뉴의 값을 바꾸려면 메뉴를 <SWTheme style={theme}> 안에 둡니다(AI_USAGE.md §3.1).
  • 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 — 팝업 프레임 + 자유 콘텐츠

프레임은 공통 SW 팝업 SWPopup과 같은 컴포넌트·DOM입니다(30px 제목 막대·Figma 제목 아트, #FFC600 700 제목 + 검정 1px 외곽선, SW 닫기 버튼, 1px #A67D03 테두리, 반경 4, #E4DBC9 면). 뷰어는 여기에 드래그·모서리 크기 조절·접기·경계 유지·앞으로 가져오기·모달을 더합니다. 314px가 아닌 폭에서도 제목 아트 곡선은 닫기 버튼에 붙어 있고 왼쪽 평면이 늘어납니다. 내용은 children·footer가 담당합니다. 일반 SW 팝업은 SWPopup(titleActions?, footer? 추가)을 그대로 씁니다.

Props 설명
title, open, onClose, closeLabel? 필수는 title·open·onClose. 제목 아이콘은 없습니다(SW 팝업 디자인)
children 스크롤 본문. SWViewerSection(카드/plain, 12px 제목), SWViewerField(라벨 + 컨트롤, inline / stacked + below), SWViewerSwitch, SWViewerSlider, SWViewerInput, SWViewerButton, SWViewerBadge, SWViewerHint, 프로그램 목록·표 등 무엇이든
footer? 하단 버튼 영역(본문 스크롤과 무관하게 고정). SWViewerButton variant="outline" | "primary". tone="green" = 기본은 그대로, Hover는 primary(확인) 버튼 Hover와 같은 토큰 --hm-sw-viewer-hover, 누르는 동안 primary 면 --hm-sw-viewer-panel-primary (예: 레이어 전체 켜기 / 전체 끄기)
headerActions? 제목 막대 추가 버튼: SWViewerButton variant="ghost" (26px, 글자색만 Hover #FFC600 — SW 제목 막대 방식)
width?(314 = SWPopup), 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·footer는 타입상 ReactNode이고 소스 주석도 자유 콘텐츠(“any component”)라고 적습니다. 그래도 기본은 SWViewerSection·SWViewerField·SWViewerSwitch·SWViewerSlider·SWViewerInput·SWViewerButton·SWViewerBadge·SWViewerHint와 선택 목록 SWSelect(축척 지정 단위, 뷰어 패널 토큰으로 표시) 우선입니다. 이것으로 안 되는 컨트롤(예: Select·Checkbox·표)만 @hm-design/ui 컴포넌트나 소비 프로젝트 요소를 넣을 수 있습니다. @hm-design/ui는 SW 패키지 dependency가 아니어서 별도 설치(사용 유형 혼합)가 필요하고, SWPopup과 달리 뷰어 패널 안 @hm-design/ui 외형 정합성은 문서화·검증 기록이 없으므로 결과 보고에 “디자인 확인 필요”로 적습니다. Header 안에는 @hm-design/ui DropdownMenu 등을 직접 두지 않습니다(§1 표).
  • 콘텐츠 교체: 같은 프레임에 다른 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" />처럼 로고가 없다고 임의 문자·아이콘 넣기 로고는 프로그램이 정합니다. 없으면 생략
뷰어 화면에 로고(홈) 메뉴(SWHomeMenu·SWRecentFiles, SWHeader homeMenu) 넣기 또는 비슷한 2단 메뉴 직접 만들기 홈 메뉴는 SW 일반 모드 전용입니다. 뷰어 로고 클릭은 onHomeClick, 파일 열기·샘플은 groups의 button·menu 항목
뷰어 Header를 div + button으로 다시 만들기, hm-sw-viewer-* class를 소비 코드에 붙이기 데이터(groups)와 custom / trailing으로 구성
Header 버튼 글자에 -webkit-text-stroke·border로 외곽선 만들기 외곽선은 패키지 토큰이 이미 적용합니다
메뉴 색·간격을 #123456, margin 등 임의 값으로 덮기 토큰 --hm-sw-viewer-* / --hm-sw-header-*만 가장 바깥 SW 범위(<SWTheme> 또는 단독 컴포넌트 루트)의 style로 조정(기본값 유지 권장, AI_USAGE.md §3.1)
Header·Dropdown·More·Panel의 글꼴을 각각 따로 지정, .hm-sw-viewer-menu 등 class로 덮기 화면을 <SWTheme style={theme}> 하나로 감싸면 Header·Dropdown·More·Panel이 같은 값을 상속합니다. 글꼴 기본값은 돋움(Dotum)이며 Web UI 글꼴(Noto Sans KR)을 복사하지 않습니다
존재하지 않는 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가 함께 바뀝니다.
  • 높이: 패키지는 뷰어(도면) 영역 높이를 정하지 않습니다. Header는 52px 한 줄, 팝업만 최대 90vh입니다. 화면 높이는 앱의 상위 레이아웃이 정하고(예: html, body, #root { height: 100% } → 세로 flex, Header 다음 도면 영역 flex: 1; min-height: 0), 도면 영역 크기 변화에 맞춰 렌더러 크기를 바꾸는 것은 프로그램 담당입니다(ResizeObserver 권장). Storybook 예시의 고정 높이는 미리보기 프레임 값일 뿐 컴포넌트 값이 아닙니다.
  • Light/Dark 분기는 없습니다. 참고 뷰어와의 알려진 차이: 도면 공간(tabs) field 면 높이 42px(참고 41px — 참고 Tabs 루트가 39px인 구현 부산물), 크기 조절은 네이티브 resize 대신 같은 위치의 모서리 Grip(모든 브라우저 동작), 패널 Escape 닫기 추가, 로고 기본 표시 없음(사용자 결정).