Files
HMDesign/packages/sw/ai/COMPOSITION_PATTERNS.md
T
shkim4 7621934f69 docs(ai): split SW usage into general / viewer mode in packaged AI guides
- Usage types are now Web / SW (general mode, viewer mode) / OrgChart / mixed.
  Shared AI_GUIDE step 1 asks for the SW mode only when the request or the
  project setting (HMDesign 사용 유형: SW 일반 | SW 뷰어) leaves it open;
  examples B0/B1/B2 cover "HMDesign SW로 화면을 만들어 주세요", general and viewer.
- Missing-component handling in three steps (reuse in the chosen type/mode ->
  build with that mode's tokens -> HMDesign-consistent style, reported as
  "AI 구성 값 — 디자인 확인 필요"), plus SW general/viewer separation rules.
- sw/ai: AI_USAGE §0 mode selection, catalog §8 lists all 33 SWViewer* exports,
  DESIGN_RULES §11 viewer tokens, PATTERNS 7 viewer composition; examples B/F
  marked as general mode. ui/ai and org-chart/ai aligned with the new types.
- AGENTS_SNIPPET, AGENTS.md and MAINTAINING updated; copies synced.
- scripts/ai-docs.mjs: shared-copy check ignores CRLF/LF (core.autocrlf=true
  checks out packages/sw as LF and the rest as CRLF).
- Verification record updated (code samples typechecked, built and rendered).
2026-10-19 17:34:15 +09:00

13 KiB

SW 조합 패턴

모든 패턴은 공개 Export만 사용합니다. 전체 코드는 examples/에 있으며, 설치된 패키지에서 꺼낸 그대로 독립 React 19 + TypeScript + Vite 프로젝트의 tsc·Production Build·브라우저 검증을 통과했습니다(2026-10-19, 예제 A~F. 뷰어 모드 예제 H는 공개 Export만 쓰지만 같은 독립 소비 검증 기록은 이 문서에 없습니다).

0. 어떤 패턴을 고를까

먼저 SW 모드를 정합니다(AI_USAGE.md §0). 1~6은 SW 일반 모드, 7은 SW 뷰어 모드이며 한 화면에서 섞지 않습니다.

요청 모드 패턴
기존 SW Header 전체(일부 부품 제거·메뉴 순서 변경 포함) 일반 1. 완성형
기존 컨트롤 일부만, 다른 배치·순서 일반 2. 조합 Header
기존 Dropdown을 툴바·패널 등 다른 곳에 일반 3. 개별 컨트롤
패키지에 없는 기능 일반(뷰어는 7 + AI_USAGE §0.4) 4. 확장(소비 프로젝트 신규 컴포넌트)
SW Popup 디자인의 설정창·정보창·사용자 정의 폼 일반 5. Popup(외형 고정, 콘텐츠 자유)
일반 모드 SWHeader에 로고 옆 파일 열기·도구 막대(Model/Layout, Fit, 2.5D) 추가 일반 6. 파일 열기 + 도구 막대
뷰어·DWG/DXF 보기·도면 뷰어·읽기 전용 화면(Header 메뉴, 패널, 모달, 불러오기 상태) 뷰어 7. 뷰어 모드 기본 조합

1. 완성형 SW Header — ExampleA-DefaultHeader.tsx

<SWHeader documentTitle={name} documents={documents} layers={layers} colors={colors} onClose={close} />
  • 부품 제거: hiddenControls={["print", "popupGuide"]}(로고는 항상 표시, "home"은 정적 로고).
  • 메뉴 순서: menuOrder={["color", "layer"]}(5개 메뉴끼리, 누락분은 기본 순서로 뒤에).
  • 객체 영역 전체를 직접 나열: controls={[{ id: "document" }, { id: "layer" }, { id: "print" }]}.
  • 추가 도구: tools={<MyTool />}(객체 영역 메뉴 뒤). 새 도구는 아래 4의 규칙을 따른다.
  • 이 경우에만 반응형 More가 동작합니다.

2. 조합 Header (간소화·기능 중심)

<SWTheme>을 Header 막대로 쓰고, 막대·면 배치 CSS는 소비 프로젝트가 작성합니다(example-layout.css). 이 CSS는 --hm-sw-header-* 토큰만 읽고 hm-sw-* class를 건드리지 않습니다.

<SWTheme className="app-sw-bar">                 {/* 49px, #060606, flex, gap 2px */}
  <SWHeaderHome logoAlt="뷰어" onClick={goHome} />  {/* 80px, 다음 파일 면과 겹침 */}
  <SWHeaderDocumentTitle title={name} documents={documents} onDocumentSelect={open} />
  <div className="app-sw-surface">                 {/* 객체 수정 면 토큰 */}
    <SWHeaderLayerMenu value={layer} layers={layers} onLayerSelect={selectLayer} />
    <SWHeaderColorMenu value={color} colors={colors} onColorSelect={selectColor} />
  </div>
  <div className="app-sw-window">                  {/* 창 영역 면 토큰 */}
    <SWHeaderActions showPrint={false} onPopupGuide={showGuide} />
  </div>
</SWTheme>
  • 간소화 Header(일반 모드): ExampleB-ViewerHeader.tsx — 로고 → 파일 → 레이어 → 색상 → K. 파일명·주석의 "Viewer"는 과거 이름이며 SW 뷰어 모드가 아닙니다(뷰어 모드는 7).
  • 기능 중심: ExampleC-FeatureHeader.tsx — 파일 → 선 → 문자 → 치수스타일 → 인쇄.
  • 순서는 JSX 순서입니다(DOM·Tab 순서 동일). CSS order로 바꾸지 않습니다.
  • 로고 없이 파일 면이 처음이면 왼쪽 테두리가 없으므로 wrapper에 1px 테두리를 둡니다(예제 C app-sw-file-edge).
  • K만/인쇄만: SWHeaderActions의 showPrint={false} / showPopupGuide={false}.
  • 창 제어: <SWHeaderWindowControls onWindowMoveStart={startDrag} onMinimize={min} onClose={close} />(창 영역 면 안).
  • 조합 Header에는 반응형 More가 없습니다. 좁은 화면이 필요하면 완성형을 쓰거나 표시할 컨트롤을 줄입니다.

3. 기존 Dropdown을 다른 레이아웃에서

<SWTheme className="app-toolbar">
  <SWHeaderLineStyleMenu value={lineStyle} lineTypes={lineTypes} lineWeights={lineWeights} onLineTypeSelect={setType} />
</SWTheme>
  • Dropdown은 필드 바로 아래에 열리고 뷰포트 안으로 좌우 보정됩니다. 부모에 overflow: hidden을 두지 않습니다.
  • Controlled가 필요하면 open / onOpenChange를 함께 줍니다.
  • Dropdown이 필요 없고 호스트가 자체 팝업을 열면 SWHeaderLayerControl 등 필드 버튼만 씁니다.

4. 확장 — 없는 컨트롤 새로 만들기 — SWSearchControl.tsx, ExampleD-SearchExtension.tsx

요청 예: "레이어와 색상은 기존 SW 컴포넌트, 검색 버튼은 SW 디자인으로 새로."

  1. 카탈로그 검색: 검색 컴포넌트 없음(MISSING).
  2. 조합 검토: 텍스트 입력·확정·취소·IME는 SWHeaderInput으로 충족 → 재사용. 필드 면과 검색 아이콘 버튼은 공개 컴포넌트가 없음 → 신규.
  3. 신규 부분은 소비 프로젝트에 작성: 38px 속성 필드 면, 12px 라벨, gradient Hover 링, 아이콘 버튼은 글자색 Hover #FFC600. 모든 값은 토큰(sw-search-control.css).
  4. 기존 컨트롤과 같은 면(app-sw-surface)에 나란히 배치.
  5. 검증: 레이어 필드와 높이·위치·테두리·radius·배경·그림자·라벨 크기 일치, Hover 링, Enter 확정 → onSearch, Escape 복원.

새 컨트롤은 패키지 공식 컴포넌트로 자동 등록하지 않습니다. 여러 프로젝트에서 반복되면 SW 패키지 승격을 별도로 검토합니다.

5. Popup — 외형 고정, 콘텐츠 자유 — ExampleE-CustomPopup.tsx

요청 예: "HMDesign SW Popup 디자인을 사용해서 내부에 새로운 설정 폼을 만들어 줘."

  1. 카탈로그 3절에서 SWPopup을 찾는다(프레임 = 속성창 외형·닫기·Escape·스크롤).
  2. 내용은 children으로 구성한다. 행 모양은 SWPopupSection + SWPopupRow, 값 입력은 SWHeaderInput.
  3. SW에 없는 컨트롤(Switch, Slider, Select, Button 등)은 @hm-design/ui를 children 안에 그대로 둔다. 구조 제한은 없다.
  4. 프레임(폭, 제목줄, 닫기, 표면색)을 다시 만들지 않고, hm-sw-property* class를 쓰지 않는다.
<SWTheme>
  <SWPopup title="도면 설정" onClose={close}>
    <SWPopupSection title="도면">
      <SWPopupRow label="이름" htmlFor={id}><SWHeaderInput id={id} value={name} onCommit={setName} /></SWPopupRow>
      <SWPopupRow label="축척" unit="%" numeric><SWHeaderInput aria-label="축척" valueType="number" value={scale} onCommit={setScale} /></SWPopupRow>
    </SWPopupSection>
    <MyProjectFields />
  </SWPopup>
</SWTheme>
  • 데이터 기반 속성창이 필요하면 SWHeaderPropertyPanel(properties / groups)을 쓰고, 추가 내용은 같은 컴포넌트의 children으로 붙인다.
  • 위치는 호스트가 정한다: 문서 흐름(style) 또는 anchorRef(기준 요소 아래 고정 Overlay). 열고 닫는 상태도 호스트가 가진다(조건부 렌더링).
  • 여러 Popup을 동시에 띄워도 Escape는 하나씩 닫힌다(카탈로그 3절).

6. 파일 열기 + 도구 막대 — ExampleF-ViewerToolbar.tsx

일반 모드 Header에 도구 막대를 더한 구성입니다. 예제 파일명·주석의 "viewer"는 과거 이름이며 SW 뷰어 모드가 아닙니다. "뷰어"·"도면 보기" 화면은 7(SWViewer*)을 씁니다.

<SWHeader documentTitle={name} documents={documents} documentOpenPlacement="header" documentOpenLabel="파일 열기" onDocumentOpen={open}
  controls={[{ id: "document" }, { id: "layer" }, { id: "color" }, { id: "popupGuide" }]}
  hiddenControls={["documentNew", "layerFreeze", "layerLock"]}
  tools={<SWToolGroup label="보기" selection="single">
    <SWToolButton pressed={view === "model"} onClick={showModel}>Model</SWToolButton>
    <SWToolButton pressed={view === "layout"} onClick={showLayout}>Layout</SWToolButton>
  </SWToolGroup>} />
  • 파일 열기는 tools에 직접 만들지 않는다. documentOpenPlacement="header"가 로고 바로 오른쪽에 파일명과 같은 면으로 그리고 Dropdown의 Open은 숨긴다(중복 없음). 공간이 부족하면 More로 이동한다.
  • 지원하지 않는 기능은 hiddenControls로 뺀다(documentNew, documentOpen, layerFreeze, layerLock). 가짜 버튼을 두지 않는다.
  • SWHeader 없이 같은 순서가 필요하면: SWHeaderHome → SWToolButton appearance="document" → SWHeaderDocumentTitle showOpen={false}(사선 접점·12px 여백은 패키지가 처리). 이 경우 More는 없다.

7. 뷰어 모드 기본 조합 — ExampleH-ViewerMode.tsx

SW 뷰어 모드(도면 보기) 화면입니다. 모드 선택은 AI_USAGE.md §0, Props·상태는 VIEWER.md, 토큰은 DESIGN_RULES.md §11. 이 패턴에는 1~6의 일반 모드 컴포넌트(SWHeader, SWPopup, SWToolButton …)를 섞지 않습니다.

const groups: SWViewerHeaderGroup[] = [
  { id: "file", label: "파일", items: [
    { type: "button", id: "open", label: "파일 열기", icon: icons.open, iconOnlyBelow: 560 },
    { type: "menu", id: "samples", label: "샘플", moreLabel: "샘플 도면", collapseBelow: 1180, entries: sampleEntries },
  ] },
  { id: "space", label: "도면 공간", surface: "field", hidden: !fileName, items: [
    { type: "tabs", id: "space", label: "모델과 배치 선택", collapseBelow: 1180, value: space, onValueChange: setSpace, options },
  ] },
  { id: "view", label: "화면 보기", surface: "field", items: [
    { type: "button", id: "fit", label: "전체 보기", icon: icons.fit, iconOnlyBelow: 560, disabled: !fileName },
    { type: "switch", id: "depth", label: "2.5D", checked: depth, collapseBelow: 1180, onCheckedChange: setDepth },
  ] },
  { id: "panels", label: "패널", placement: "end", items: [
    { type: "toggle", id: "options", label: "설정", icon: icons.options, pressed: optionsOpen, collapseBelow: 900, onPressedChange: setOptionsOpen },
  ] },
]

<SWViewerHeader documentTitle={fileName} groups={groups} onCommand={runCommand}
  trailing={<>
    <SWViewerStatus state={fileName ? "ready" : "idle"} />
    <SWViewerHeaderButton iconOnly icon={icons.info} label="정보" onClick={() => setAbout(true)} />
  </>} />
<SWViewerPanel title="옵션" open={optionsOpen} onClose={() => setOptionsOpen(false)} width={340} height="auto"
  footer={<SWViewerButton variant="primary" onClick={apply}>적용</SWViewerButton>}>
  <SWViewerSection title="표시">
    <SWViewerField label="그리드">{id => <SWViewerSwitch id={id} checked={grid} onCheckedChange={setGrid} />}</SWViewerField>
    <SWViewerField label="줌 속도" layout="stacked" below={<SWViewerSlider aria-label="줌 속도" value={zoom} min={0.2} max={3} step={0.1} onValueChange={setZoom} />}>
      {id => <SWViewerInput id={id} type="number" value={zoom} onChange={e => setZoom(Number(e.currentTarget.value))} />}
    </SWViewerField>
  </SWViewerSection>
</SWViewerPanel>
<SWViewerPanel modal title="정보" open={about} onClose={() => setAbout(false)} width={360} height="auto" resizable={false} collapsible={false}
  footer={<SWViewerButton variant="primary" onClick={() => setAbout(false)}>확인</SWViewerButton>}>…</SWViewerPanel>
구성 사용
Header 메뉴 groups 데이터: 파일(button·menu), 도면 공간(tabs, surface: "field"), 화면 보기(button·switch, field), 패널 토글(toggle, placement: "end")
오른쪽 끝 trailing: SWViewerStatus + SWViewerHeaderButton iconOnly
도구 창 비모달 SWViewerPanel(드래그·크기 조절·접기 기본). 여러 개면 inset·defaultPosition으로 겹치지 않게
확인·정보 SWViewerPanel modal + footer의 SWViewerButton variant="primary"
패널 내용 SWViewerSection > SWViewerField > SWViewerSwitch / SWViewerSlider / SWViewerInput, 목록은 surface="plain" + 프로그램 목록, 설명 SWViewerHint
반응형 참고값 collapseBelow 1180(샘플·도면 공간·2.5D) / 900(패널 토글), iconOnlyBelow 560(파일 열기·전체 보기)
우클릭 메뉴 SWViewerMenu + anchorPoint
  • 상태(파일명·패널 열림·탭 값)는 프로그램이 가지고, 명령은 각 Callback 또는 onCommand 한 곳에서 받는다. 도면 영역 높이·렌더러 크기는 앱 레이아웃 담당(VIEWER.md §7).
  • 없는 기능은 메뉴를 만들지 않거나 disabled + 미연결 보고. 패키지에 없는 UI는 AI_USAGE.md §0.4 뷰어 열을 따른다.

8. 하지 말 것

  • hm-sw-* class를 소비 코드에 쓰거나 CSS로 덮어쓰기
  • dist 구현·styles.css 블록 복사로 같은 컴포넌트 재작성
  • @hm-design/sw/dist/...·src/... 내부 경로 Import
  • 문서에 없는 Props 추측, 새 색·크기 값 도입
  • 같은 화면에 SWHeader와 조합 Header를 겹쳐 두 개의 Header 만들기
  • 같은 화면에 SWHeader와 SWViewerHeader를 함께 두기, 일반 모드 화면에 SWViewer* 사용, 뷰어 화면에서 SWHeader·SWPopup 재스타일(AI_USAGE §0.3)