@hjmds/design-contracts 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agreement.d.ts +1 -0
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +2 -1
- package/dist/agreement.js.map +1 -1
- package/dist/auth-screen.d.ts +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/calendar.d.ts +1 -0
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -1
- package/dist/calendar.js.map +1 -1
- package/dist/catalog.d.ts +78 -30
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +10 -5
- package/dist/catalog.js.map +1 -1
- package/dist/chart.d.ts +2 -0
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/component-definitions.d.ts +1 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +1 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +53 -6
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +64 -10
- package/dist/component-recipes.js.map +1 -1
- package/dist/data-table.d.ts +11 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +5 -0
- package/dist/data-table.js.map +1 -1
- package/dist/description-list.d.ts +3 -0
- package/dist/description-list.d.ts.map +1 -1
- package/dist/description-list.js +5 -1
- package/dist/description-list.js.map +1 -1
- package/dist/design-profile-layout.d.ts +28 -0
- package/dist/design-profile-layout.d.ts.map +1 -1
- package/dist/design-profile-layout.js +27 -3
- package/dist/design-profile-layout.js.map +1 -1
- package/dist/design-profile.d.ts.map +1 -1
- package/dist/design-profile.js +40 -8
- package/dist/design-profile.js.map +1 -1
- package/dist/field-group.d.ts +13 -0
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +17 -2
- package/dist/field-group.js.map +1 -1
- package/dist/foundations.d.ts +24 -11
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +40 -7
- package/dist/foundations.js.map +1 -1
- package/dist/heading.d.ts +5 -5
- package/dist/image-choice.d.ts +23 -0
- package/dist/image-choice.d.ts.map +1 -0
- package/dist/image-choice.js +26 -0
- package/dist/image-choice.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/masonry.d.ts +8 -0
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +12 -1
- package/dist/masonry.js.map +1 -1
- package/dist/number-field.d.ts +2 -2
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +4 -2
- package/dist/number-field.js.map +1 -1
- package/dist/qr-code-recipe.d.ts +1 -0
- package/dist/qr-code-recipe.d.ts.map +1 -1
- package/dist/qr-code-recipe.js +3 -1
- package/dist/qr-code-recipe.js.map +1 -1
- package/dist/recipes.d.ts +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -1
- package/dist/recipes.js.map +1 -1
- package/dist/result.d.ts +9 -0
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +5 -0
- package/dist/result.js.map +1 -1
- package/dist/screen-patterns.d.ts +139 -2
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +140 -1
- package/dist/screen-patterns.js.map +1 -1
- package/dist/sheet.d.ts +15 -1
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +20 -1
- package/dist/sheet.js.map +1 -1
- package/dist/showcase.d.ts +27 -27
- package/dist/showcase.js +27 -27
- package/dist/showcase.js.map +1 -1
- package/dist/tag.d.ts +10 -2
- package/dist/tag.d.ts.map +1 -1
- package/dist/tag.js +3 -2
- package/dist/tag.js.map +1 -1
- package/dist/tags-input.d.ts +6 -0
- package/dist/tags-input.d.ts.map +1 -1
- package/dist/tags-input.js +3 -1
- package/dist/tags-input.js.map +1 -1
- package/dist/text-formats.d.ts +2 -2
- package/dist/top.d.ts +2 -2
- package/dist/transfer-list.d.ts +1 -1
- package/dist/transfer-list.d.ts.map +1 -1
- package/dist/transfer-list.js +2 -1
- package/dist/transfer-list.js.map +1 -1
- package/dist/upload-item.d.ts +1 -10
- package/dist/upload-item.d.ts.map +1 -1
- package/dist/upload-item.js +1 -2
- package/dist/upload-item.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/activity-chip.md +4 -3
- package/docs/catalog-freeze.json +5 -2
- package/docs/chart.md +1 -1
- package/docs/consumer-policy.md +13 -6
- package/docs/description-list.md +4 -0
- package/docs/design-profile.md +28 -2
- package/docs/font-roles.md +1 -1
- package/docs/generated/component-maturity.md +5 -4
- package/docs/generated/renderer-evidence.json +92 -6
- package/docs/generated/renderer-evidence.md +5 -3
- package/docs/generated/showcase-manifest.json +49 -1
- package/docs/gravity-letters.md +7 -2
- package/docs/masonry.md +4 -2
- package/docs/screen-patterns.md +5 -2
- package/docs/showcase.md +7 -9
- package/docs/stable-promotion.md +13 -1
- package/docs/usage/README.md +40 -10
- package/docs/usage/SELECTION.md +30 -4
- package/docs/usage/STANDARD.md +11 -2
- package/docs/usage/components/account-switcher.md +98 -0
- package/docs/usage/components/activity-chip.md +4 -4
- package/docs/usage/components/badge.md +1 -1
- package/docs/usage/components/bezier-editor.md +99 -0
- package/docs/usage/components/bottom-cta.md +2 -1
- package/docs/usage/components/button.md +26 -12
- package/docs/usage/components/carousel.md +4 -4
- package/docs/usage/components/chart.md +1 -0
- package/docs/usage/components/chat-message.md +18 -12
- package/docs/usage/components/chat-screen.md +6 -3
- package/docs/usage/components/cohort-retention-table.md +83 -0
- package/docs/usage/components/collapsible.md +7 -2
- package/docs/usage/components/comment-thread-row.md +102 -0
- package/docs/usage/components/comment-thread-screen.md +27 -13
- package/docs/usage/components/composition-layout.md +1 -0
- package/docs/usage/components/context-menu.md +32 -15
- package/docs/usage/components/conversation-row.md +11 -4
- package/docs/usage/components/conversion-funnel.md +85 -0
- package/docs/usage/components/correlation-matrix.md +80 -0
- package/docs/usage/components/data-table.md +4 -0
- package/docs/usage/components/description-list.md +2 -1
- package/docs/usage/components/editor-screen.md +9 -5
- package/docs/usage/components/effect-surface.md +4 -2
- package/docs/usage/components/empty-state.md +2 -2
- package/docs/usage/components/event-attendance-card.md +104 -0
- package/docs/usage/components/expandable-search.md +105 -0
- package/docs/usage/components/file-picker.md +5 -0
- package/docs/usage/components/filter-recovery.md +97 -0
- package/docs/usage/components/follow-profile-card.md +100 -0
- package/docs/usage/components/heading.md +6 -2
- package/docs/usage/components/histogram-explorer.md +89 -0
- package/docs/usage/components/icon-button.md +18 -2
- package/docs/usage/components/icon-tile.md +12 -4
- package/docs/usage/components/image-choice-group.md +81 -0
- package/docs/usage/components/import-field-mapping.md +94 -0
- package/docs/usage/components/inline-edit.md +105 -0
- package/docs/usage/components/keyboard-avoidance-context.md +60 -0
- package/docs/usage/components/keyboard-avoiding.md +5 -1
- package/docs/usage/components/list-detail-screen.md +4 -4
- package/docs/usage/components/list.md +1 -1
- package/docs/usage/components/masonry.md +6 -7
- package/docs/usage/components/media-selection-screen.md +7 -5
- package/docs/usage/components/media-transport.md +104 -0
- package/docs/usage/components/message-composer.md +13 -3
- package/docs/usage/components/moderation-screen.md +5 -2
- package/docs/usage/components/notification-inbox-screen.md +3 -3
- package/docs/usage/components/notification-row.md +64 -8
- package/docs/usage/components/onboarding-screen.md +1 -1
- package/docs/usage/components/overview-screen.md +9 -3
- package/docs/usage/components/pagination.md +18 -3
- package/docs/usage/components/password-rules-field.md +90 -0
- package/docs/usage/components/permission-screen.md +1 -1
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/popover.md +6 -3
- package/docs/usage/components/pricing-choice.md +85 -0
- package/docs/usage/components/profile-summary.md +56 -8
- package/docs/usage/components/progress.md +3 -3
- package/docs/usage/components/qr-code.md +5 -9
- package/docs/usage/components/repeatable-field-group.md +92 -0
- package/docs/usage/components/reply-composer.md +83 -0
- package/docs/usage/components/reply-message.md +85 -0
- package/docs/usage/components/result.md +3 -3
- package/docs/usage/components/saved-items-screen.md +7 -5
- package/docs/usage/components/screen-layout.md +21 -10
- package/docs/usage/components/search-field.md +1 -1
- package/docs/usage/components/section.md +2 -2
- package/docs/usage/components/selection-action-bar.md +89 -0
- package/docs/usage/components/settings-screen.md +13 -11
- package/docs/usage/components/sheet.md +19 -7
- package/docs/usage/components/shortcut-recorder.md +77 -0
- package/docs/usage/components/split-action.md +94 -0
- package/docs/usage/components/streaming-answer.md +94 -0
- package/docs/usage/components/switch.md +6 -2
- package/docs/usage/components/table-column-settings.md +88 -0
- package/docs/usage/components/target-summary.md +79 -0
- package/docs/usage/components/text.md +11 -5
- package/docs/usage/components/toggle-feedback-button.md +94 -0
- package/docs/usage/components/top-bar.md +8 -12
- package/docs/usage/components/transfer-task.md +87 -0
- package/docs/usage/compositions/action-recovery-optimistic.md +18 -14
- package/docs/usage/compositions/action-recovery-save.md +18 -14
- package/docs/usage/compositions/action-recovery-undo.md +18 -14
- package/docs/usage/compositions/activity-status.md +1 -1
- package/docs/usage/compositions/adaptive-content.md +2 -1
- package/docs/usage/compositions/collection-detail.md +6 -5
- package/docs/usage/compositions/collection-editing.md +13 -6
- package/docs/usage/compositions/command-records.md +5 -4
- package/docs/usage/compositions/communication.md +7 -5
- package/docs/usage/compositions/compound-controls.md +7 -3
- package/docs/usage/compositions/content-transition-comparison.md +10 -5
- package/docs/usage/compositions/data-layouts.md +12 -16
- package/docs/usage/compositions/data-workflows.md +7 -4
- package/docs/usage/compositions/date-time-selection.md +5 -5
- package/docs/usage/compositions/design-profile-comparison.md +22 -6
- package/docs/usage/compositions/disclosure.md +14 -9
- package/docs/usage/compositions/document-resource.md +1 -1
- package/docs/usage/compositions/environment-matrix.md +16 -12
- package/docs/usage/compositions/expo-interactions.md +9 -8
- package/docs/usage/compositions/family-drawer.md +9 -5
- package/docs/usage/compositions/field-group.md +7 -3
- package/docs/usage/compositions/interaction-adapters.md +11 -9
- package/docs/usage/compositions/interaction-flow-apply.md +17 -15
- package/docs/usage/compositions/interaction-flow-search.md +11 -7
- package/docs/usage/compositions/live-list.md +12 -8
- package/docs/usage/compositions/navigation-bar-collection.md +8 -3
- package/docs/usage/compositions/optional-motion.md +2 -2
- package/docs/usage/compositions/paper-surface.md +11 -6
- package/docs/usage/compositions/purpose-input-comment.md +10 -4
- package/docs/usage/compositions/purpose-input-message.md +10 -4
- package/docs/usage/compositions/reference-first.md +4 -4
- package/docs/usage/compositions/reference-review.md +4 -4
- package/docs/usage/compositions/reference-settings.md +4 -4
- package/docs/usage/compositions/selection-motion.md +2 -1
- package/docs/usage/compositions/social-compositions.md +7 -6
- package/docs/usage/compositions/stea-event-ticket.md +2 -4
- package/docs/usage/compositions/stea-order-progress.md +19 -21
- package/docs/usage/compositions/stea-pixel-empty.md +26 -26
- package/docs/usage/compositions/task-controls.md +7 -2
- package/docs/usage/compositions/texture-comparison.md +5 -5
- package/docs/usage/compositions/toast-layout.md +7 -3
- package/docs/usage/compositions/toggle-feedback.md +1 -1
- package/docs/usage/compositions/visual-foundations.md +10 -5
- package/docs/usage/compositions/web-additions.md +5 -1
- package/docs/usage/screens/common-chat.md +5 -3
- package/docs/usage/screens/common-comments.md +14 -9
- package/docs/usage/screens/common-conversation-list.md +109 -0
- package/docs/usage/screens/common-inbox.md +5 -3
- package/docs/usage/screens/common-login.md +7 -4
- package/docs/usage/screens/common-profile.md +16 -12
- package/docs/usage/screens/common-saved.md +12 -8
- package/docs/usage/screens/common-search.md +16 -8
- package/docs/usage/screens/common-settings.md +12 -11
- package/docs/usage/screens/common-shell.md +12 -10
- package/docs/usage/screens/dashboard.md +59 -56
- package/docs/usage/screens/discovery-gallery.md +18 -9
- package/docs/usage/screens/flow-collection.md +10 -6
- package/docs/usage/screens/flow-editor.md +9 -6
- package/docs/usage/screens/flow-media.md +12 -7
- package/docs/usage/screens/flow-moderation.md +9 -3
- package/docs/usage/screens/flow-onboarding.md +13 -8
- package/docs/usage/screens/flow-permission.md +5 -3
- package/docs/usage/screens/landing.md +20 -14
- package/docs/usage/screens/mockup-studio.md +100 -88
- package/docs/usage/screens/notification-settings.md +9 -7
- package/docs/usage/screens/product-bento.md +22 -10
- package/docs/usage/screens/reference-comparison.md +2 -2
- package/docs/usage/tokens/font-roles.md +18 -2
- package/docs/usage/tokens/typography.md +22 -11
- package/package.json +7 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
8
|
-
- 근거: `showcase/web/src/foundations/MockupStudio.stories.tsx`, `src/
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
|
+
- 근거: `showcase/web/src/foundations/MockupStudio.stories.tsx`, `showcase/web/src/showcase.css`(`.mockup-studio`), `showcase/web/src/studio/MockupStudio.tsx`, `showcase/web/src/studio/SceneTimeline.tsx`, `showcase/shared/mockup-scene.ts`, `showcase/shared/scene-timeline.ts`
|
|
9
9
|
- 스토리북: `배포/화면/화면 틀과 도구/목업 편집`
|
|
10
10
|
|
|
11
11
|
공통 구조·배치·상태·배지의 소유권은 [화면과 구성 선택](../SELECTION.md#소유권)을 따른다. 앱은 데이터·문구·브랜드·추가 기능을 공개 슬롯과 콜백으로 연결한다. 아래 코드 골격을 앱별 기본 구조 복제의 근거로 사용하지 않는다. 전용 화면 API가 없는 제품 콘텐츠는 `ScreenLayout`의 본문에 HJM 구성을 연결한다.
|
|
@@ -21,69 +21,63 @@ Native 스토리는 없다(장면 작성은 Web showcase에 둔다는 통합 계
|
|
|
21
21
|
## 영역 구조
|
|
22
22
|
|
|
23
23
|
```text
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
│ ┌
|
|
30
|
-
│ │
|
|
31
|
-
│ │
|
|
32
|
-
│
|
|
33
|
-
│
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
│
|
|
37
|
-
│
|
|
38
|
-
│
|
|
39
|
-
│
|
|
40
|
-
│
|
|
41
|
-
│
|
|
42
|
-
│
|
|
43
|
-
│
|
|
44
|
-
│
|
|
45
|
-
│
|
|
46
|
-
|
|
47
|
-
│
|
|
48
|
-
|
|
49
|
-
│ 배경 [새벽][잉크][민트] secondary│
|
|
50
|
-
│ 각도 [-12°][-6°][0°][6°][12°] ghost │
|
|
51
|
-
│ 여백 [좁게][보통][넓게] ghost │
|
|
52
|
-
│ [그림자] ← secondary 토글 │
|
|
53
|
-
│ ⑨ TextField 화면 출처 · 허용 사용 범위 │
|
|
54
|
-
│ ⑩ [PNG 내보내기] [장면 설정 저장] [초기화] │ ← 주 행동 줄(맨 아래)
|
|
55
|
-
│ primary secondary ghost │
|
|
56
|
-
│ ⑪ 개인정보·출처 안내 (muted) │
|
|
57
|
-
└──────────────────────────────────────────┘
|
|
24
|
+
ScreenLayout(title "목업 편집", description, presentation "dashboard" → 최대 폭 1200) — 헤더 고정 · 본문 스크롤 · footer 고정
|
|
25
|
+
Grid columns {compact 1 · expanded 2}(창 폭 960 이상 두 열) gap xl 24
|
|
26
|
+
┌ 헤더: 제목(가운데) + 설명 muted ───────────────────────────────┐
|
|
27
|
+
├ notice: 읽기·내보내기 실패 Notice danger(실패일 때만) ─────────┤
|
|
28
|
+
├ 본문 ───────────────────────────────────────────────────────────┤
|
|
29
|
+
│ ┌ 왼쪽 열(한 열이면 위) ───────┐ ┌ 오른쪽 열(한 열이면 아래) ──┐ │
|
|
30
|
+
│ │ ② 미리보기 Surface md 16 │ │ ④ FilePicker 화면 캡처 │ │
|
|
31
|
+
│ │ <canvas role="img"> │ │ FilePicker 장면 설정 │ │
|
|
32
|
+
│ │ ③ "출력 1080 × 1440 · …" │ │ ⑤ 상태(role=status) │ │
|
|
33
|
+
│ │ 두 열이면 sticky(위 0) │ │ ⑥ 장면 타임라인(gap md 16) │ │
|
|
34
|
+
│ └──────────────────────────────┘ │ 제목 Heading level5 · 2 │ │
|
|
35
|
+
│ │ [▶][⏮] ──●── 재생 위치 │ │
|
|
36
|
+
│ │ "0.00 / 3.00초 · 30fps" │ │
|
|
37
|
+
│ │ 움직임·길이·초당 프레임 │ │
|
|
38
|
+
│ │ [포스터로][포스터 PNG] │ │
|
|
39
|
+
│ │ [영상 내보내기]([출력 취소])│ │
|
|
40
|
+
│ │ ⑦ TextField 제목·설명 │ │
|
|
41
|
+
│ │ ⑧ 선택 묶음 SegmentedControl │ │
|
|
42
|
+
│ │ + 그림자 Switch │ │
|
|
43
|
+
│ │ ⑨ TextField 출처·사용 범위 │ │
|
|
44
|
+
│ │ ⑪ 개인정보·출처 안내 muted │ │
|
|
45
|
+
│ └──────────────────────────────┘ │
|
|
46
|
+
├ footer ⑩ [PNG 내보내기] [장면 설정 저장] [초기화] ───────────────┤
|
|
47
|
+
│ primary secondary ghost │
|
|
48
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
58
49
|
```
|
|
59
50
|
|
|
60
51
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
61
52
|
| --- | --- | --- | --- |
|
|
62
|
-
| 바깥 틀 | `
|
|
63
|
-
| ① 제목 |
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
53
|
+
| 바깥 틀 | `ScreenLayout` `title` `description` `presentation="dashboard"` `footer` | route 본문, host 남은 높이. 헤더·footer 고정, 본문 스크롤 | 최대 폭 1200(`layout.contentMaxWidth`), 본문 padding은 ScreenLayout recipe. 안전 영역·키보드는 브라우저 |
|
|
54
|
+
| ① 제목 | ScreenLayout 헤더(제목 + `description`) | 맨 위 고정 | 헤더 recipe(가운데 정렬, 제목 크기 title) |
|
|
55
|
+
| 본문 열 | `Grid columns={{compact:1, expanded:2}} gap={{compact:"xl"}}` | 본문 | 열 사이·행 사이 `spacing.xl` 24. 두 열이면 미리보기 열이 본문 스크롤 안에서 `position: sticky; top: 0` |
|
|
56
|
+
| ② 미리보기 | Stack `gap="sm"` > Surface `padding="md"` > `canvas`(`role="img"`, `aria-label`=제목 + 프레임) + ③ | 왼쪽 열(한 열이면 맨 위) | 안쪽 `spacing.md` 16, 폭 100%, 최대 높이 70vh |
|
|
57
|
+
| ④⑤ 파일·상태 | FilePicker ×2(화면 캡처: PNG·JPEG·WebP·20MB / 장면 설정: JSON·64KB) · Text `role="status"` | 오른쪽 열 맨 위 | 사이 24 |
|
|
58
|
+
| 실패 | ScreenLayout `notice` > Notice `tone="danger"`(내보내기 실패면 `action` 다시 내보내기) | 헤더 아래 고정, 실패 동안 상태 문구 대신 | 거부된 파일도 읽기 실패와 같은 Notice. 본문 밖이라 재시도 버튼이 사라져도 본문 초점 복구가 PNG 행동 복귀를 가로채지 않는다 |
|
|
59
|
+
| ⑥ 타임라인 | Stack `gap="md"` > Heading `level="level5"` `semanticLevel={2}` · 재생 줄(IconButton ×2 + Slider) · 설정 선택 · Button 줄 | 상태 아래 | 사이 `spacing.md` 16, 재생 줄 `Stack axis="inline" gap="sm" align="end"` 12(Slider가 남은 폭), 버튼 줄 `wrap gap="sm"` 12 |
|
|
67
60
|
| ⑦⑨ 텍스트 입력 | TextField ×4 | 타임라인 아래·선택 묶음 아래 | 높이 44(`control.fieldHeight`) |
|
|
68
|
-
| ⑧ 선택 묶음 |
|
|
69
|
-
| ⑩ 내보내기 줄 | Stack `axis="inline" wrap gap="sm"` > Button ×3 |
|
|
61
|
+
| ⑧ 선택 묶음 | Stack `gap="xs"` > Text `emphasis="strong"`(보이는 제목) + SegmentedControl(`label` = 같은 제목), 그림자는 Switch `presentation="row"` | 텍스트 입력 사이 | 제목과 컨트롤 사이 `spacing.xs` 8, 묶음 사이 24. SegmentedControl 항목 최소 44 |
|
|
62
|
+
| ⑩ 내보내기 줄 | ScreenLayout `footer` > Stack `axis="inline" wrap gap="sm"` > Button ×3 | 화면 아래 고정 | 사이 12 |
|
|
70
63
|
|
|
71
64
|
## 버튼과 행동 위치
|
|
72
65
|
|
|
73
66
|
| 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
|
|
74
67
|
| --- | --- | --- | --- |
|
|
75
|
-
| PNG 내보내기(주 행동) | Button 기본 primary | ⑩ 줄 첫째 | 1. 이미지 없음·불러오는 중이면 `disabled`, 내보내는 중이면 `loading` |
|
|
68
|
+
| PNG 내보내기(주 행동) | Button 기본 primary | ⑩ footer 줄 첫째 | 1. 이미지 없음·불러오는 중이면 `disabled`, 내보내는 중이면 `loading` |
|
|
76
69
|
| 장면 설정 저장 | Button `secondary` | ⑩ 둘째 | 1. JSON 파일 내려받기 |
|
|
77
70
|
| 초기화 | Button `ghost` | ⑩ 셋째(끝) | 1. 확인 없이 모든 설정·이미지를 버린다 |
|
|
78
|
-
| 재생·일시 정지 |
|
|
71
|
+
| 재생·일시 정지 | IconButton `secondary`(이름 "재생"/"일시 정지", 재생·정지 아이콘) | ⑥ 재생 줄 첫째(Slider 왼쪽) | 1. 이미지 없음·모션 줄이기·영상 출력 중이면 `disabled` |
|
|
72
|
+
| 처음으로 | IconButton `ghost`(이름 "처음으로", 처음으로 아이콘) | ⑥ 재생 줄 둘째 | 1 |
|
|
79
73
|
| 영상 내보내기 | Button `secondary` + 진행 중 `출력 취소` ghost | ⑥ 아래쪽 | 1(+1). 영상 버튼 아래 `role="progressbar"`에 실제 진행률 0–100% 표시 |
|
|
80
|
-
| 실패 후 다시 시도 | Notice `action` > Button `tone="secondary" size="small"` |
|
|
81
|
-
| 설정 고르기 |
|
|
82
|
-
| 파일 고르기 |
|
|
74
|
+
| 실패 후 다시 시도 | Notice `action` > Button `tone="secondary" size="small"` | `notice` 실패 Notice 안 | 내보내기 실패일 때만 1. 파일 읽기 실패는 다른 파일을 고르는 것이 복구라 버튼을 두지 않는다 |
|
|
75
|
+
| 설정 고르기 | SegmentedControl(출력 비율·프레임·배경·각도·여백·움직임·길이·초당 프레임) · Switch(그림자) | ⑥⑧ 각 묶음 | 묶음마다 선택지 2~5. 불러온 장면의 각도·여백이 선택지에 없으면 그 값을 항목으로 더해 현재 장면을 그대로 보인다 |
|
|
76
|
+
| 파일 고르기 | FilePicker(버튼 "화면 캡처 선택" · "장면 설정 불러오기", 끌어 놓기 가능) | ④ | 2 |
|
|
83
77
|
|
|
84
|
-
주 행동은 PNG 내보내기 하나다. 재생·영상 내보내기는 `secondary`로 둔다.
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
주 행동은 PNG 내보내기 하나다. 재생·영상 내보내기는 `secondary`로 둔다. 설정은 하나 고르기·켜고 끄기라 [선택 조립 규칙](../SELECTION.md#조립-규칙필수)대로
|
|
79
|
+
SegmentedControl·Switch를 쓴다. 내보내기 줄은 ScreenLayout `footer`에 고정해 긴 설정을 지나지 않아도 닿는다.
|
|
80
|
+
재생 제어는 미디어 도구 행동이라 이름 있는 IconButton이다([IconButton](../components/icon-button.md)).
|
|
87
81
|
|
|
88
82
|
## 상태
|
|
89
83
|
|
|
@@ -92,7 +86,7 @@ Native 스토리는 없다(장면 작성은 Web showcase에 둔다는 통합 계
|
|
|
92
86
|
| 기본 | 빈 프레임 미리보기, 상태 "스크린샷을 선택해 시작하세요.", PNG·재생·영상 비활성 | 화면 캡처 선택 |
|
|
93
87
|
| 로딩 | 상태 "스크린샷을 여는 중이에요.", PNG 비활성 | 기다림 |
|
|
94
88
|
| 빈 | 기본과 같다(이미지 없음). 장면 JSON만 불러오면 이미지가 비워지고 "파일을 다시 선택해 주세요" | 같은 캡처 다시 선택 |
|
|
95
|
-
| 오류 | 아래는 제품 편집기의 복구 규칙이며 현재 도구의 영상·포스터는 SceneTimeline 상태 표시를 따른다. 네트워크·서버 요청이 없다(브라우저 안에서만 처리).
|
|
89
|
+
| 오류 | 아래는 제품 편집기의 복구 규칙이며 현재 도구의 영상·포스터는 SceneTimeline 상태 표시를 따른다. 네트워크·서버 요청이 없다(브라우저 안에서만 처리). ScreenLayout `notice` 자리에 [Notice](../components/notice.md) `tone="danger"`. 이미지 읽기 실패(형식 PNG·JPEG·WebP 밖·20MB·16384px 초과): 이전 이미지·설정 유지. 장면 JSON 실패(64KB 초과·형식 오류): 현재 장면 유지. PNG 내보내기 실패: Notice `action` 다시 시도. 포스터 실패: 타임라인 상태 문구와 기존 포스터 버튼으로 재시도, `loading` 해제. 영상: 인코더 미지원·실패면 PNG 대안 안내, 사용자가 취소하면 오류가 아니라 상태 문구. 다시 시도가 또 실패하면 같은 Notice가 같은 자리에 남는다 | 다른 파일 선택 / 다시 시도 / PNG 저장 |
|
|
96
90
|
| 불러옴 | 상태 "파일명 · 가로 × 세로 화면을 불러왔어요.", 미리보기에 그려짐 | 편집·내보내기 |
|
|
97
91
|
| 영상 출력 중 | 진행률(0–100%)과 [출력 취소] 등장. 다른 탭으로 가거나 장면·이미지가 바뀌면 이전 출력 취소 | 취소 |
|
|
98
92
|
| 모션 줄이기 | 재생 비활성, 안내 문구. 슬라이더로 정지 장면 확인 | 슬라이더 |
|
|
@@ -103,15 +97,16 @@ Native 스토리는 없다(장면 작성은 Web showcase에 둔다는 통합 계
|
|
|
103
97
|
| --- | --- |
|
|
104
98
|
| [Stack](../components/stack.md) | 바깥 리듬, 버튼 줄 |
|
|
105
99
|
| [Surface](../components/surface.md) | ② 미리보기 틀 |
|
|
106
|
-
| [
|
|
100
|
+
| [ScreenLayout](../components/screen-layout.md) | 헤더·본문 스크롤·footer |
|
|
101
|
+
| [Grid](../components/grid.md) | 넓은 폭 두 열 |
|
|
102
|
+
| [Heading](../components/heading.md) | ⑥ 타임라인 제목 |
|
|
103
|
+
| [IconButton](../components/icon-button.md) | 재생·처음으로 |
|
|
107
104
|
| [Text](../components/text.md) | 설명·라벨·상태·안내 |
|
|
108
105
|
| [Notice](../components/notice.md) | 읽기·내보내기 실패 |
|
|
109
|
-
| [Container](../components/container.md) | 바깥 폭·좌우 여백 |
|
|
110
106
|
| [Button](../components/button.md) | 모든 선택·실행 |
|
|
111
107
|
| [Field](../components/field.md) | 제목·설명·출처·사용 범위 `TextField` |
|
|
112
108
|
| [Slider](../components/slider.md) | 재생 위치 |
|
|
113
|
-
| [FilePicker](../components/file-picker.md) |
|
|
114
|
-
| [BottomCTA](../components/bottom-cta.md) | 제품 편집기의 고정 내보내기 줄 |
|
|
109
|
+
| [FilePicker](../components/file-picker.md) | ④ 화면 캡처·장면 설정 선택 |
|
|
115
110
|
| [화면 여백과 너비](../tokens/layout.md) | gutter·`layout.contentMaxWidth` |
|
|
116
111
|
| [간격](../tokens/spacing.md) | `spacing.sm`·`md`·`xl` |
|
|
117
112
|
|
|
@@ -128,12 +123,15 @@ Native 스토리는 없다(장면 작성은 Web showcase에 둔다는 통합 계
|
|
|
128
123
|
|
|
129
124
|
```tsx
|
|
130
125
|
// Web
|
|
131
|
-
import {
|
|
126
|
+
import { Grid, Stack, Surface, Text } from "@hjmds/react/layout";
|
|
127
|
+
import { ScreenLayout } from "@hjmds/react/screens";
|
|
132
128
|
import { Heading } from "@hjmds/react/heading";
|
|
133
|
-
import { Button } from "@hjmds/react/actions";
|
|
129
|
+
import { Button, IconButton } from "@hjmds/react/actions";
|
|
134
130
|
import { TextField } from "@hjmds/react/forms";
|
|
131
|
+
import { FilePicker } from "@hjmds/react/file-picker";
|
|
135
132
|
import { Notice } from "@hjmds/react/feedback";
|
|
136
|
-
import {
|
|
133
|
+
import { Slider } from "@hjmds/react/slider";
|
|
134
|
+
import { SegmentedControl, Switch } from "@hjmds/react/selection";
|
|
137
135
|
|
|
138
136
|
// 상태→키 표. 보간이 필요한 문구는 값으로 넘긴다.
|
|
139
137
|
const statusKey = {
|
|
@@ -146,34 +144,41 @@ const statusKey = {
|
|
|
146
144
|
exportFailed: "editor.error.export",
|
|
147
145
|
} as const;
|
|
148
146
|
const failed = status === "imageFailed" || status === "sceneFailed" || status === "exportFailed";
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<Text as="p">{t("editor.intro")}</Text>
|
|
155
|
-
</Stack>
|
|
156
|
-
<Surface padding="md">{/* 미리보기: canvas/img, role="img", aria-label */}</Surface>
|
|
157
|
-
{failed ? <Notice tone="danger" title={t(statusKey[status])}
|
|
158
|
-
{...(status === "exportFailed" ? { action: <Button tone="secondary" size="small" onClick={exportPng}>{t("common.retry")}</Button> } : {})} />
|
|
159
|
-
: <Text role="status">{t(statusKey[status], file ? { name: file.name, width: file.width, height: file.height } : {})}</Text>}
|
|
160
|
-
<Stack gap="md">
|
|
161
|
-
<Heading level="level3" semanticLevel={2}>{t("editor.timeline")}</Heading>
|
|
162
|
-
<Stack axis="inline" wrap gap="sm">
|
|
163
|
-
<Button tone="secondary" disabled={!ready} onClick={togglePlay}>{playing ? t("editor.pause") : t("editor.play")}</Button>
|
|
164
|
-
</Stack>
|
|
165
|
-
</Stack>
|
|
166
|
-
<TextField label={t("editor.caption")} value={caption} maxLength={80} onValueChange={setCaption} />
|
|
167
|
-
<Text emphasis="strong">{t("editor.format")}</Text>
|
|
168
|
-
<Stack axis="inline" wrap gap="sm">
|
|
169
|
-
{formats.map(f => <Button key={f.id} tone="secondary" selected={format === f.id} onClick={() => setFormat(f.id)}>{t(f.labelKey)}</Button>)}
|
|
170
|
-
</Stack>
|
|
171
|
-
<Stack axis="inline" wrap gap="sm">
|
|
147
|
+
|
|
148
|
+
<ScreenLayout title={t("editor.title")} description={t("editor.intro")} presentation="dashboard"
|
|
149
|
+
notice={failed ? <Notice tone="danger" title={t(statusKey[status])}
|
|
150
|
+
{...(status === "exportFailed" ? { action: <Button tone="secondary" size="small" onClick={exportPng}>{t("common.retry")}</Button> } : {})} /> : null}
|
|
151
|
+
footer={<Stack axis="inline" wrap gap="sm">
|
|
172
152
|
<Button loading={exporting} disabled={!ready} onClick={exportPng}>{t("editor.export")}</Button>
|
|
173
153
|
<Button tone="secondary" onClick={saveScene}>{t("editor.saveScene")}</Button>
|
|
174
154
|
<Button tone="ghost" onClick={reset}>{t("editor.reset")}</Button>
|
|
175
|
-
</Stack>
|
|
176
|
-
|
|
155
|
+
</Stack>}>
|
|
156
|
+
<Grid className="editor-columns" columns={{ compact: 1, expanded: 2 }} gap={{ compact: "xl" }}>
|
|
157
|
+
{/* 두 열일 때 [data-columns="2"] > 미리보기에 position: sticky; top: 0 */}
|
|
158
|
+
<Surface padding="md">{/* 미리보기: canvas/img, role="img", aria-label */}</Surface>
|
|
159
|
+
<Stack gap="xl">
|
|
160
|
+
<FilePicker descriptor={{ accept: ["image/png", "image/jpeg", "image/webp"], maxSizeBytes: 20 * 1024 * 1024 }}
|
|
161
|
+
label={t("editor.capture")} buttonLabel={t("editor.chooseCapture")} dropzoneLabel={t("editor.dropCapture")}
|
|
162
|
+
onSelect={(result, files) => { const file = result.accepted[0] && files.get(result.accepted[0].id); if (file) void openImage(file); else if (result.rejected.length) setStatus("imageFailed"); }} />
|
|
163
|
+
{failed ? null : <Text role="status">{t(statusKey[status], file ? { name: file.name, width: file.width, height: file.height } : {})}</Text>}
|
|
164
|
+
<Stack gap="md">
|
|
165
|
+
<Heading level="level5" semanticLevel={2}>{t("editor.timeline")}</Heading>
|
|
166
|
+
<Stack axis="inline" gap="sm" align="end">
|
|
167
|
+
<IconButton label={playing ? t("editor.pause") : t("editor.play")} tone="secondary" disabled={!ready} onClick={togglePlay}>{playing ? <PauseGlyph /> : <PlayGlyph />}</IconButton>
|
|
168
|
+
<IconButton label={t("editor.rewind")} tone="ghost" onClick={rewind}><RewindGlyph /></IconButton>
|
|
169
|
+
<div style={{ flex: 1, minWidth: 0 }}><Slider label={t("editor.position")} min={0} max={total} value={frame} onValueChange={seek} /></div>
|
|
170
|
+
</Stack>
|
|
171
|
+
</Stack>
|
|
172
|
+
<TextField label={t("editor.caption")} value={caption} maxLength={80} onValueChange={setCaption} />
|
|
173
|
+
<Stack gap="xs">
|
|
174
|
+
<Text emphasis="strong">{t("editor.format")}</Text>
|
|
175
|
+
<SegmentedControl label={t("editor.format")} items={formats.map(f => ({ value: f.id, label: t(f.labelKey) }))}
|
|
176
|
+
value={format} onValueChange={setFormat} />
|
|
177
|
+
</Stack>
|
|
178
|
+
<Switch presentation="row" label={t("editor.shadow")} checked={shadow} onCheckedChange={setShadow} />
|
|
179
|
+
</Stack>
|
|
180
|
+
</Grid>
|
|
181
|
+
</ScreenLayout>
|
|
177
182
|
```
|
|
178
183
|
|
|
179
184
|
```tsx
|
|
@@ -186,22 +191,29 @@ const gutter = resolveWindowClass(window.innerWidth) === "compact" ? "compact" :
|
|
|
186
191
|
| 조건 | 바뀌는 것 |
|
|
187
192
|
| --- | --- |
|
|
188
193
|
| 다크 | 화면 UI는 테마 토큰. 출력 이미지 배경은 장면 설정(새벽·잉크·민트)이 정하고 테마와 무관하다 |
|
|
189
|
-
| 좁은 폭 | 한
|
|
190
|
-
| 넓은 폭 |
|
|
194
|
+
| 좁은 폭 | 한 열: 미리보기 → 파일·설정 순서. 미리보기는 폭 100%·최대 70vh, FilePicker도 폭 100% |
|
|
195
|
+
| 넓은 폭 | 창 폭 960(expanded) 이상이면 두 열: 왼쪽 미리보기(sticky), 오른쪽 설정. 전체 최대 1200 |
|
|
191
196
|
|
|
192
197
|
## 함정
|
|
193
198
|
|
|
194
199
|
- 화면 캡처는 브라우저 밖으로 나가지 않는다. 장면 JSON에는 파일 이름·출처·사용 범위만 저장되므로 다른 사람에게 JSON만 주면 이미지를 다시 골라야 한다.
|
|
195
200
|
- PNG는 화면에 보이는 canvas가 아니라 내보낼 때 새로 그린 canvas에서 만든다. 인코딩 중 설정을 바꿔도 결과가 바뀌지 않는다.
|
|
196
201
|
- 초기화는 확인 없이 지운다. 제품 편집기에서 되돌리기 어려운 초기화에는 [InlineConfirm](../components/button.md)이나 [AlertDialog](../components/alert-dialog.md)를 둔다.
|
|
197
|
-
- 구획 제목은 Heading으로 표시한다.
|
|
202
|
+
- 구획 제목은 Heading으로 표시한다.
|
|
198
203
|
- 현재 스토리는 이미지·장면 읽기와 PNG 내보내기 실패를 Notice로 표시하고 PNG 실패에는 같은 내보내기 함수를 다시 실행하는 행동을 둔다. PNG 버튼은 `loading`을 사용한다. 상태 문구는 Showcase 소유이며 제품은 위 보간 키 예제로 지역화한다. 타임라인의 영상·포스터 실패 표현은 SceneTimeline이 별도로 소유한다.
|
|
199
204
|
|
|
200
205
|
|
|
201
206
|
### 출력 작업과 편집 수명
|
|
202
207
|
|
|
203
|
-
|
|
208
|
+
PNG·포스터·영상은 각각 작업 식별자와 즉시 잠금을 사용한다. 초기화·파일 선택·언마운트 뒤 늦은 PNG 결과를 버리며, 타임라인의 장면·이미지 변경/언마운트는 진행 중 영상과 포스터 결과를 무효화한다. 사용자가 영상 취소 후 바로 다시 시작해도 이전 진행률·완료가 새 작업에 반영되지 않는다.
|
|
204
209
|
|
|
205
210
|
주 PNG 내보내기는 시작 시 오프스크린 Canvas에 그린 복사본을 인코딩하므로 제목·비율 편집 중에도 그 출력의 픽셀은 바뀌지 않는다. 재시도 Notice가 사라진 뒤 초점은 완료 시 주 PNG 행동으로 돌아오되, 사용자가 다른 입력이나 빈 영역으로 옮긴 초점은 가져오지 않는다. 타임라인 재생은 부모 callback의 참조가 바뀌어도 기존 시계를 유지한다. 다운로드 안내는 브라우저에 파일 전송을 시작했다는 뜻이며 사용자의 디스크 저장 완료를 판정하지 않는다.
|
|
206
211
|
|
|
207
212
|
최대 글자와 이를 모사하는 확대 조건은 공통 사용자 지침에 따라 설계·검증에서 제외한다. PNG 픽셀·제어 인코더 회귀를 실제 영상 코덱 품질이나 OS 다운로드 완료 근거로 확대하지 않는다.
|
|
213
|
+
|
|
214
|
+
## 근거
|
|
215
|
+
|
|
216
|
+
- 설정에 SegmentedControl·Switch: 2026-10-16 사용자 지적 — 이전 `selected` Button 줄은 선택 상태가 테두리 없는 글자로만 보이고 폭이 제각각이었다.
|
|
217
|
+
- 구획 제목 Heading: 2026-10-06 제목 의미 구조를 맞추면서 이전 Text heading 예제를 수정했다.
|
|
218
|
+
- 출력 작업 식별자·즉시 잠금·늦은 결과 폐기: 2026-10-13 실행 검수에서 같은 렌더의 연속 클릭이 출력을 중복 시작하고, 초기화·장면 교체 뒤 이전 출력이 새 상태에 결과를 쓰는 경합을 확인했다.
|
|
219
|
+
- ScreenLayout·두 열·FilePicker·IconButton 재생 제어·타임라인 level5: 2026-10-17 화면 검토 — `main > Container` 직접 조립으로 제목이 뷰포트 위에 붙었고, 1280 폭에서 설정 컨트롤이 1160px로 늘어나는 반면 미리보기 양옆이 비었다. 파일 입력은 브라우저 기본 영어 UI("Choose File")였고, 구획 제목이 화면 제목과 같은 크기였으며, 재생 제어가 글자 버튼이었다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: `showcase/web/src/patterns/NotificationSettings.stories.tsx`, `showcase/native/src/NotificationSettings.stories.tsx`, `src/component-recipes.ts`(`topBarRecipe`·`bottomCtaRecipe`·`sectionRecipe`·`switchRecipe`), `src/container.ts`, `src/responsive.ts`(`resolveWindowClass`), `packages/react/src/styles.css`(`.hjm-top-bar`·`.hjm-bottom-cta`)
|
|
9
9
|
- 스토리북: `배포/화면/설정/알림 설정`
|
|
10
10
|
|
|
@@ -36,10 +36,10 @@ ScreenLayout: 제목(고정) → 본문(스크롤) → footer(고정)
|
|
|
36
36
|
| 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
|
|
37
37
|
| --- | --- | --- | --- |
|
|
38
38
|
| 저장(주 행동) | Button(primary) | 화면 맨 아래 바 | 1. 저장 중 `loading`, 저장 뒤에는 라벨을 `t("settings.saved")`로 바꾸고 `disabled` |
|
|
39
|
-
| 보조 행동 | Button(secondary) |
|
|
39
|
+
| 보조 행동 | Button(secondary) | footer의 주 행동 아래 | [공통 행동 배치](../SELECTION.md#조립-규칙필수)를 따른다. 같은 폭, `actionGroupRecipe.stackedGap` |
|
|
40
40
|
| 항목 켜기·끄기 | Switch `presentation="row"` 행 전체 | 본문 CompositionLayout 안 | 항목 수만큼, 위→아래 |
|
|
41
41
|
| 다시 불러오기 | Notice `action` > Button `tone="secondary" size="small"` | 불러오기 실패 Notice 안 | 스토리에 없음. 1 |
|
|
42
|
-
| 뒤로 | ScreenLayout `
|
|
42
|
+
| 뒤로 | ScreenLayout `exit`(`kind="back"`) | 헤더 시작 쪽 | 하위 설정 화면이면 1. [ScreenLayout](../components/screen-layout.md)의 설치 버전 지원 범위를 확인 |
|
|
43
43
|
| 파괴 행동 | — | — | 없음 |
|
|
44
44
|
|
|
45
45
|
## 상태
|
|
@@ -57,8 +57,6 @@ ScreenLayout: 제목(고정) → 본문(스크롤) → footer(고정)
|
|
|
57
57
|
|
|
58
58
|
두 스토리는 `showcase/shared/notification-settings.ts`에서 기존 `createActionSession`을 사용한다. 마지막 저장값과 현재 초안을 따로 표시하며, 저장 중에도 스위치를 수정할 수 있다. 성공은 제출 snapshot만 확정하고 새 초안은 남긴다. 저장 실패 후 하단의 같은 버튼으로 **현재 초안**을 다시 제출한다. 중복 저장은 잠그며 값을 저장값으로 되돌리면 불필요한 저장을 막는다. `예제 실행`의 실패 스위치는 다음 저장 한 번에만 적용한다. 실제 조회·권한·푸시·계정 API 없이 화면이 열려 있는 동안만 유지되는 메모리 데모다.
|
|
59
59
|
|
|
60
|
-
2026-10-13 전수 점검에서 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
|
|
61
|
-
|
|
62
60
|
| 상황 | 연결 | 유지·복구 |
|
|
63
61
|
| --- | --- | --- |
|
|
64
62
|
| 최초 조회 실패 | 설정값 대신 오류·재시도 | 기본 false를 실제 서버 설정처럼 저장하지 않는다 |
|
|
@@ -83,9 +81,9 @@ ScreenLayout: 제목(고정) → 본문(스크롤) → footer(고정)
|
|
|
83
81
|
## 코드 골격
|
|
84
82
|
|
|
85
83
|
<!-- usage-example: Web ../../../../../showcase/web/src/patterns/NotificationSettings.stories.tsx -->
|
|
86
|
-
- [Web 실행 예제](
|
|
84
|
+
- [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/NotificationSettings.stories.tsx)
|
|
87
85
|
<!-- usage-example: Native ../../../../../showcase/native/src/NotificationSettings.stories.tsx -->
|
|
88
|
-
- [Native 실행 예제](
|
|
86
|
+
- [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/NotificationSettings.stories.tsx)
|
|
89
87
|
|
|
90
88
|
예제는 ScreenLayout·CompositionLayout에 설정 데이터와 저장 콜백을 연결한다. 메모리 저장 fixture를 운영 요청으로 취급하지 않는다.
|
|
91
89
|
|
|
@@ -112,3 +110,7 @@ ScreenLayout이 화면 여백과 스크롤을, CompositionLayout이 묶음 간
|
|
|
112
110
|
|
|
113
111
|
- Native에서 `safeAreaBottom`을 넘기지 않으면 홈 인디케이터가 저장 버튼 위에 겹친다. 기본값은 0이다.
|
|
114
112
|
- 현재 스토리는 저장 요약 문구를 템플릿 문자열(`내 활동 ${…}`)로 조립한다. 제품은 위 코드처럼 켜짐/꺼짐 키 표와 보간 값을 쓴다.
|
|
113
|
+
|
|
114
|
+
## 근거
|
|
115
|
+
|
|
116
|
+
- 설정 초안의 저장 경계 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.14.0
|
|
7
|
-
- 검토일: 2026-10-
|
|
8
|
-
- 근거: `reference-adoption-previews.tsx`, `reference-adoption.css`
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
|
+
- 근거: `reference-adoption-previews.tsx`, `reference-adoption.css`, `showcase/shared/reference-adoption.ts`
|
|
9
9
|
- 스토리북: `배포/화면/소개/기능 카드와 주 행동`
|
|
10
10
|
|
|
11
11
|
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
@@ -26,18 +26,19 @@
|
|
|
26
26
|
[사용 방법(선택)]
|
|
27
27
|
[보조 행동] [주 행동]
|
|
28
28
|
[항상 보이는 체험·저장 범위]
|
|
29
|
-
[처리 결과 · 저장된 제목]
|
|
29
|
+
[처리 결과 · 저장된 제목] ← 시작했거나 저장한 제목이 있을 때만
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
좁은 Web과 Native는 비교→설명 두 장→첫 행동 순서로 한 열에 놓는다.
|
|
33
33
|
|
|
34
34
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
35
35
|
| --- | --- | --- | --- |
|
|
36
|
-
| 바깥 틀 |
|
|
36
|
+
| 바깥 틀 | ScreenLayout(`titleVisibility="hidden"`, Native는 KeyboardAvoiding 안) → Stack gap lg | 본문 스크롤. 브랜드·제목·설명도 본문과 함께 스크롤하고 본문 Heading이 화면 제목(semanticLevel 1) | 화면 padding `screenPatternRecipe.padding` md=16, 최대 폭 720, 브랜드–제목–설명 사이 md=16 |
|
|
37
37
|
| 기능 | Surface/Stack | 설명 다음 | padding lg=20px, 카드 gap md=16px |
|
|
38
38
|
| 제목/본문 | Text/Stack | 각 카드 안 | sm=12px |
|
|
39
39
|
| 첫 행동 | BottomCTA | 기능·입력 뒤 | description → 보조/주 행동, flow 배치 |
|
|
40
40
|
| 조건 | BottomInfo | 주 행동 바로 아래 | 항상 유지, 예제의 로컬 저장 범위 설명 |
|
|
41
|
+
| 처리 결과 | StatusText(Native PatternStatus) · Text | 조건 아래 | 시작했거나 저장한 제목이 있을 때만. 손대지 않은 소개에는 저장 상태가 없다 |
|
|
41
42
|
|
|
42
43
|
## 버튼과 행동 위치
|
|
43
44
|
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
|
|
53
54
|
| 상태 | 화면 모습 | 행동 |
|
|
54
55
|
| --- | --- | --- |
|
|
55
|
-
| 기본 | 설명과 비교·주/보조 행동·조건
|
|
56
|
+
| 기본 | 설명과 비교·주/보조 행동·조건 안내. 저장 상태 문구 없음 | 첫 입력 또는 사용 방법으로 이동 |
|
|
56
57
|
| 로딩 | 현재 예제는 로컬 이미지 비교. 제품 네트워크 이미지가 있으면 미리보기 내부에 상태 배치 | 주 행동은 제품 가용성에 맞춤 |
|
|
57
58
|
| 빈 | 제품 자료가 없으면 비교 영역 생략 | 문구와 첫 행동 유지 |
|
|
58
59
|
| 오류 | 이미지 실패 대체 또는 저장 실패 문구·제목 유지 | 현재 제목으로 다시 저장 |
|
|
@@ -82,22 +83,28 @@
|
|
|
82
83
|
```tsx
|
|
83
84
|
// Web
|
|
84
85
|
import { Heading } from "@hjmds/react/heading";
|
|
86
|
+
import { ScreenLayout } from "@hjmds/react/screens";
|
|
87
|
+
<ScreenLayout title={title} titleVisibility="hidden">
|
|
85
88
|
<Stack gap="lg">
|
|
86
|
-
<Heading level="level3" semanticLevel={1}>{title}</Heading>
|
|
89
|
+
<Stack gap="md"><Text tone="brand" variant="label">{brand}</Text><Heading level="level3" semanticLevel={1}>{title}</Heading><Text>{intro}</Text></Stack>
|
|
87
90
|
<Surface padding="lg"><ImageComparison {...comparison} /></Surface>
|
|
88
91
|
<BottomCTA primaryAction={{ label: startLabel, onClick: start }} secondaryAction={{ label: helpLabel, onClick: showHelp }} />
|
|
89
92
|
<BottomInfo items={conditions} />
|
|
93
|
+
{started || savedTitle ? <StatusText>{status}</StatusText> : null}
|
|
90
94
|
</Stack>
|
|
95
|
+
</ScreenLayout>
|
|
91
96
|
```
|
|
92
97
|
|
|
93
98
|
```tsx
|
|
94
99
|
// Native: screen-owned scrolling keeps the CTA reachable below long previews.
|
|
95
100
|
<KeyboardAvoiding style={{ flex: 1 }}>
|
|
96
|
-
<ScreenLayout title={title}
|
|
101
|
+
<ScreenLayout title={title} titleVisibility="hidden">
|
|
97
102
|
<Stack gap="lg">
|
|
103
|
+
<Stack gap="md"><Text tone="brand" variant="label">{brand}</Text><Heading level="level3" semanticLevel={1}>{title}</Heading><Text>{intro}</Text></Stack>
|
|
98
104
|
<Surface padding="lg"><ImageComparison {...comparison} decrementLabel={decrementLabel} incrementLabel={incrementLabel} /></Surface>
|
|
99
105
|
<BottomCTA primaryAction={{ label: startLabel, onPress: start }} secondaryAction={{ label: helpLabel, onPress: showHelp }} />
|
|
100
106
|
<BottomInfo items={conditions} />
|
|
107
|
+
{started || savedTitle ? <PatternStatus>{status}</PatternStatus> : null}
|
|
101
108
|
</Stack>
|
|
102
109
|
</ScreenLayout>
|
|
103
110
|
</KeyboardAvoiding>
|
|
@@ -116,8 +123,7 @@ Native ImageComparison에는 증감 행동 label을 추가한다. 제품의 계
|
|
|
116
123
|
|
|
117
124
|
### CTA 레퍼런스 흡수와 저장 소유권
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
Ente 캡처에서 플랫폼 조건에 맞춘 행동 묶음을 확인했다. HJM에는 기존 BottomCTA/BottomInfo로
|
|
126
|
+
주/보조 행동 위계와 플랫폼 조건별 행동 묶음은 기존 BottomCTA/BottomInfo로
|
|
121
127
|
역할을 분리하며 외부 브랜드 색·스토어 배지·가격을 복제하지 않는다. 별도 CTA 엔진은 추가하지 않는다.
|
|
122
128
|
|
|
123
129
|
제목은 화면의 controlled state에 둔다. 소개로 돌아가 입력을 숨겨도 제목은 남고 다시 시작하면
|
|
@@ -125,4 +131,10 @@ Ente 캡처에서 플랫폼 조건에 맞춘 행동 묶음을 확인했다. HJM
|
|
|
125
131
|
중복 요청·pending·실패 후 재시도를 검증하며 350ms 지연은 데모 전용이다. 실패 시 현재 제목은
|
|
126
132
|
유지하고 다시 저장은 현재 입력 snapshot을 제출한다. 저장된 제목은 마지막으로 확정한 값이다.
|
|
127
133
|
|
|
128
|
-
화면 host가 스크롤·키보드·safe area를 제공한다. Native 예제에는 기존 ScreenLayout과 KeyboardAvoiding을 연결한다.
|
|
134
|
+
화면 host가 스크롤·키보드·safe area를 제공한다. Native 예제에는 기존 ScreenLayout과 KeyboardAvoiding을 연결한다. 제품은 host safe area/keyboard offset을 실제 내비게이션에 맞춘다.
|
|
135
|
+
|
|
136
|
+
## 근거
|
|
137
|
+
|
|
138
|
+
- BottomCTA/BottomInfo 역할 분리: 2026-10-07 CTA Gallery의 Webflow 데스크톱·모바일 캡처에서 주/보조 행동의 위계를, Ente 캡처에서 플랫폼 조건에 맞춘 행동 묶음을 확인했다.
|
|
139
|
+
- Native 예제의 ScreenLayout·KeyboardAvoiding: 2026-10-07 스크롤 없는 Canvas에서 CTA가 화면 밖으로 잘리는 것을 실제로 확인했다.
|
|
140
|
+
- 두 플랫폼 ScreenLayout·기본 inset·결과 문구 위치: 2026-10-17 화면 검토 — Web은 바깥 틀 없이 화면 끝에 붙고 넓은 폭에서 CTA만 가운데 열에 놓였으며, Native는 `contentInset="none"`으로 카드 모서리가 화면 끝에 닿았다. 손대지 않은 소개에 "아직 저장하지 않았어요."가 CTA 위에 보였다.
|
|
@@ -24,7 +24,7 @@ host: 남은 높이·safe area·키보드
|
|
|
24
24
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
25
25
|
| --- | --- | --- | --- |
|
|
26
26
|
| 바깥 틀 | ScreenLayout | route 본문 | [API 배치 규칙](../components/screen-layout.md#배치), host 남은 높이 |
|
|
27
|
-
| 내용 | Stack `gap="xl"` > SegmentedControl → SearchField → 상태 문구 → 카드(Surface `padding="lg"`) | 표현 방식 선택 → 검색 → 같은 데이터 목록·읽기·이미지 표현 | 구획 사이 `spacing.xl` 24. 목록·읽기는 Stack `gap="lg"` 한 열, 이미지는 Grid 두 열(`minColumnWidth` 240 = `spacing.xxxl` 40 × 6) |
|
|
27
|
+
| 내용 | Stack `gap="xl"` > SegmentedControl → SearchField(제품 돋보기 adapter + placeholder) → 상태 문구 → 카드(Surface `padding="lg"`) | 표현 방식 선택 → 검색 → 같은 데이터 목록·읽기·이미지 표현 | 구획 사이 `spacing.xl` 24. 목록·읽기는 Stack `gap="lg"` 한 열, 이미지는 Grid 두 열(`minColumnWidth` 240 = `spacing.xxxl` 40 × 6) |
|
|
28
28
|
| 상태 | state 또는 해당 API 상태 | 본문 자리·비차단 notice | 입력 중 실패는 본문 높이와 초안을 유지 |
|
|
29
29
|
|
|
30
30
|
## 버튼과 행동 위치
|
|
@@ -32,7 +32,7 @@ host: 남은 높이·safe area·키보드
|
|
|
32
32
|
| 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
|
|
33
33
|
| --- | --- | --- | --- |
|
|
34
34
|
| 보기 방식 | SegmentedControl(`label` 보기 방식) | 검색 위 | 세 값, 항상 하나 선택 |
|
|
35
|
-
| 읽기 · 저장 | Button secondary · Button secondary `selected` | 카드 아래 | 카드마다 [읽기][저장] |
|
|
35
|
+
| 읽기 · 저장 | Button secondary · Button secondary `selected` | 카드 아래 | 카드마다 한 줄 [읽기][저장](선택하면 `저장 취소`). 보이는 문구는 짧은 동사이고 카드 제목을 포함한 이름은 접근성 이름(Web `aria-label`, Native `accessibilityLabel`)으로 둔다. 저장은 그 카드에 대한 행동의 눌림 토글이라 `selected`를 허용한다. 보기 방식처럼 선택지를 고르는 곳에는 쓰지 않는다([선택 조립 규칙](../SELECTION.md#조립-규칙필수)) |
|
|
36
36
|
| 검색 초기화 | EmptyState `action` > Button secondary | 결과 없음 자리 | 하나. 이 화면에는 primary가 없다 |
|
|
37
37
|
| 읽기 마치기 | Button(Sheet footer) | 상세 Sheet | 하나 |
|
|
38
38
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.16.0
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [서체 역할 계약](../../font-roles.md), `src/foundations.ts`, `src/design-profile.ts`, 양 renderer Text/Heading/Provider
|
|
9
9
|
- 스토리북: `배포/토큰/색과 글자/표시·읽기·기술 글자`
|
|
10
10
|
|
|
@@ -19,12 +19,24 @@
|
|
|
19
19
|
|
|
20
20
|
| 토큰 | 값 | Web CSS 변수 | Native 경로 | 용도 |
|
|
21
21
|
| --- | --- | --- | --- | --- |
|
|
22
|
-
| `tokens.fontFamily.ui` |
|
|
22
|
+
| `tokens.fontFamily.ui` | Pretendard Variable → Pretendard → OS 서체. Native는 `fonts="pretendard"`일 때 굵기별 Pretendard face, 그 전에는 OS 서체 | `--hjm-font-family-ui` | `theme.tokens.fontFamily.ui` | 입력·버튼·caption·label |
|
|
23
23
|
| `tokens.fontFamily.display` | optional stack, 생략하면 현재 ui | `--hjm-font-family-display` | `resolveFontFamilyStack(theme.tokens.fontFamily, "display")` | Heading·Text title/titleLarge/heading |
|
|
24
24
|
| `tokens.fontFamily.reading` | optional stack, 생략하면 현재 ui | `--hjm-font-family-reading` | `resolveFontFamilyStack(theme.tokens.fontFamily, "reading")` | Text body/bodyLarge |
|
|
25
25
|
| `tokens.fontFamily.code` | 기존 ui-monospace 등 stack | `--hjm-font-family-code` | `theme.tokens.fontFamily.code` | CodeBlock·Text fontRole=code |
|
|
26
26
|
| Text `fontRole` | ui/display/reading/code, 생략하면 위 variant 역할 | 같은 역할 변수 | 같은 역할의 첫 named font | 문구 역할만 변경. 제목 level·variant 크기/굵기는 유지 |
|
|
27
27
|
|
|
28
|
+
### 기본 서체 Pretendard 불러오기(필수)
|
|
29
|
+
|
|
30
|
+
HJM은 Pretendard(SIL OFL 1.1)를 함께 배포한다. 불러오지 않으면 OS 서체로 대체돼 기기마다 글꼴이 달라진다.
|
|
31
|
+
|
|
32
|
+
| 플랫폼 | 할 일 | 배포 범위 |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| Web | 앱 진입점에서 `import "@hjmds/react/fonts/pretendard.css";` | 가변 굵기 동적 서브셋. 화면에 쓰인 글자 묶음만 내려받는다 |
|
|
35
|
+
| Native | `const [loaded] = useFonts(hjmPretendardFonts)`(expo-font, `@hjmds/react-native/fonts`) 후 `<HjmNativeProvider fonts={loaded ? "pretendard" : "system"}>` | 400·500·600·700·800 정적 face 5개. 토큰이 쓰는 굵기만 담는다 |
|
|
36
|
+
|
|
37
|
+
Native는 굵기마다 별도 face를 등록하므로 Text가 최종 굵기에 맞는 face(`pretendardNativeFaces`)로 바꾸고 `fontWeight`를 지운다.
|
|
38
|
+
등록 전에 family를 지정하면 iOS가 오류를 내므로 로드가 끝난 뒤에만 `fonts="pretendard"`를 준다. 제품 프로필이 자체 `fontFamily`를 정하면 그 값이 우선한다.
|
|
39
|
+
|
|
28
40
|
순수 helper `resolveFontFamilyStack`·`resolveTextFontRole`는 `@hjmds/design-contracts/foundations`에 있다.
|
|
29
41
|
Web은 전체 stack, Native는 기존 host helper로 단일 font name을 해석한다. Native의 기본 code는 iOS Menlo/그 외 monospace다.
|
|
30
42
|
|
|
@@ -85,3 +97,7 @@ Text의 `fontRole="ui"`를 명시한다. 크기 variant는 그대로 유지하
|
|
|
85
97
|
추가 1곳인 TopBar 제목은 Web과 같은 경계를 유지한다. 클릭형 제목은 일반 버튼 라벨이므로 ui,
|
|
86
98
|
비클릭형 제목은 의미 heading이므로 display다. 제목 행동·접근성 이름·정적 heading 역할은 유지한다.
|
|
87
99
|
서체 선택 검사는 Native host 모사에서 수행했으며 폰트 자산 설치·실제 기기 표시 검증을 뜻하지 않는다.
|
|
100
|
+
|
|
101
|
+
## 근거
|
|
102
|
+
|
|
103
|
+
- Pretendard 함께 배포: 2026-10-17 사용자 결정 — 계약에 Inter·Pretendard가 있었지만 어디서도 불러오지 않아 기기마다 서체가 달랐다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: `src/foundations.ts`(`typography`·`heading`·`fontFamily`·`fontWeight`·`letterSpacing`·`numeric`·`largeTextThreshold`), `src/component-recipes.ts`(`textRecipe`), `packages/react/src/theme.ts`, `packages/react/src/provider.tsx`, `packages/react-native/src/provider.tsx`
|
|
9
9
|
- 스토리북: `배포/토큰/색과 글자/타이포그래피`
|
|
10
10
|
|
|
@@ -20,23 +20,25 @@
|
|
|
20
20
|
| 토큰 | 값 | Web CSS 변수 | Native 경로 | 용도 |
|
|
21
21
|
| --- | --- | --- | --- | --- |
|
|
22
22
|
| `typography.caption` | 11 / 줄 16 / 400 | `--hjm-type-caption-size` · `-line-height` · `-weight` | `theme.tokens.typography.caption` | 가장 작은 보조 표기, AuthScreen 정책 링크 |
|
|
23
|
-
| `typography.label` | 12 / 줄 18 /
|
|
23
|
+
| `typography.label` | 12 / 줄 18 / 500 | `--hjm-type-label-*` | `theme.tokens.typography.label` | 라벨·도움말·small 버튼 글자 |
|
|
24
24
|
| `typography.body` | 14 / 줄 20 / 400 | `--hjm-type-body-*` | `theme.tokens.typography.body` | 기본 본문·medium 버튼·필드 글자 |
|
|
25
25
|
| `typography.bodyLarge` | 16 / 줄 24 / 400 | `--hjm-type-body-large-*` | `theme.tokens.typography.bodyLarge` | 읽기용 긴 본문·large 버튼 글자 |
|
|
26
|
-
| `typography.title` | 18 / 줄 26 /
|
|
27
|
-
| `typography.titleLarge` | 20 / 줄 28 /
|
|
28
|
-
| `typography.heading` | 24 / 줄 32 /
|
|
26
|
+
| `typography.title` | 18 / 줄 26 / 600 | `--hjm-type-title-*` | `theme.tokens.typography.title` | 카드 제목(`heading.level5`) |
|
|
27
|
+
| `typography.titleLarge` | 20 / 줄 28 / 600 | `--hjm-type-title-large-*` | `theme.tokens.typography.titleLarge` | 큰 구획 제목(`heading.level4`) |
|
|
28
|
+
| `typography.heading` | 24 / 줄 32 / 600 | `--hjm-type-heading-*` | `theme.tokens.typography.heading` | 화면의 가장 큰 제목 |
|
|
29
29
|
|
|
30
30
|
굵기 열은 토큰 원본값이다. 프로필 없는 Text는 기본 `emphasis="regular"`(400)를 사용하므로
|
|
31
|
-
variant만 지정해도 위 굵기가 자동 적용되는 것은 아니다.
|
|
32
|
-
|
|
31
|
+
variant만 지정해도 위 굵기가 자동 적용되는 것은 아니다. 예외는 `label`이다. `label`은 프로필이 없어도
|
|
32
|
+
생략한 emphasis가 토큰 굵기 500을 따른다(`textRecipe.variantWeight`, `resolveTextDefaultWeight`).
|
|
33
|
+
프로필이 있으면 생략한 emphasis는 프로필의 variant 굵기를 읽고, 명시한 emphasis는 언제나 우선한다.
|
|
34
|
+
Heading은 제목 단계의 굵기를 쓴다. 두 renderer가 같은 resolver를 쓴다.
|
|
33
35
|
|
|
34
36
|
### 문서 제목 단계(`heading`, Heading `level`)
|
|
35
37
|
|
|
36
38
|
| 토큰 | 값 | Web CSS 변수 | Native 경로 | 용도 |
|
|
37
39
|
| --- | --- | --- | --- | --- |
|
|
38
|
-
| `heading.level1` | 40 / 줄 48 /
|
|
39
|
-
| `heading.level2` | 32 / 줄 40 /
|
|
40
|
+
| `heading.level1` | 40 / 줄 48 / 700 | — | `heading.level1` | 랜딩·문서의 대표 제목 |
|
|
41
|
+
| `heading.level2` | 32 / 줄 40 / 700 | — | `heading.level2` | 긴 페이지의 큰 구획 제목 |
|
|
40
42
|
| `heading.level3` | = `typography.heading` 24 | `--hjm-type-heading-*` | `heading.level3` | 화면 제목 |
|
|
41
43
|
| `heading.level4` | = `typography.titleLarge` 20 | `--hjm-type-title-large-*` | `heading.level4` | 구획 제목 |
|
|
42
44
|
| `heading.level5` | = `typography.title` 18 | `--hjm-type-title-*` | `heading.level5` | 카드 제목 |
|
|
@@ -45,16 +47,25 @@ variant만 지정해도 위 굵기가 자동 적용되는 것은 아니다. 프
|
|
|
45
47
|
|
|
46
48
|
| 토큰 | 값 | Web CSS 변수 | Native 경로 | 용도 |
|
|
47
49
|
| --- | --- | --- | --- | --- |
|
|
48
|
-
| `fontFamily.ui` |
|
|
49
|
-
| `fontFamily.code` | ui-monospace, SFMono-Regular, Consolas, Liberation Mono, monospace | — (`--hjm-font-family-code` fallback) | `fontFamily.code` | 코드 |
|
|
50
|
+
| `fontFamily.ui` | Pretendard Variable, Pretendard, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif | `--hjm-font-family-ui` | `fontFamily.ui` | 화면 글자. Native는 `fonts="pretendard"`일 때 굵기별 Pretendard face, 그 전에는 OS 서체. 불러오기는 [글꼴 역할](font-roles.md#기본-서체-pretendard-불러오기필수) |
|
|
51
|
+
| `fontFamily.code` | ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Cascadia Mono, Consolas, Liberation Mono, monospace | — (`--hjm-font-family-code` fallback) | `fontFamily.code` | 코드 |
|
|
50
52
|
| `fontWeight.regular` · `medium` · `semibold` · `bold` · `heavy` | 400 · 500 · 600 · 700 · 800 | `--hjm-font-weight-<이름>` | `fontWeight.<이름>` | Text `emphasis`: `regular` 400 · `medium` 600 · `strong` 700 |
|
|
51
53
|
| `letterSpacing.tight` · `normal` · `wide` | -0.2 · 0 · 0.2 | — | `letterSpacing.<이름>` | 자간 조정이 필요한 제품 고유 글자. HJM 컴포넌트는 자간을 지정하지 않는다 |
|
|
52
54
|
| `numeric.tabular` · `proportional` | `tabular-nums` · `proportional-nums` | — | `numeric.<이름>` | 바뀌는 숫자(카운터·통계·날짜 칸)는 `tabular`로 폭을 고정 |
|
|
53
55
|
| `largeTextThreshold` | 1.6 | 루트 `data-large-text="true"` | `largeTextThreshold` | 글자 배율이 이 값 이상이면 큰 글자 배치로 바꾼다 |
|
|
54
56
|
|
|
57
|
+
Native는 `fonts="pretendard"`일 때 500을 `Pretendard-Medium`, 700을 `Pretendard-Bold` face로 그린다.
|
|
58
|
+
`@hjmds/react-native/fonts`의 `hjmPretendardFonts`가 400~800 다섯 face를 모두 번들한다. Web은 Pretendard Variable 한 파일이 모든 굵기를 덮는다.
|
|
59
|
+
|
|
55
60
|
Native 경로의 `theme`은 `useHjmNativeTheme()`, 나머지 이름은 `@hjmds/design-contracts/foundations` import다.
|
|
56
61
|
Web 크기 변수는 `rem × --hjm-text-scale`이라 Provider의 `textScale`을 따른다.
|
|
57
62
|
|
|
63
|
+
## 근거
|
|
64
|
+
|
|
65
|
+
2026-10-17 사용자 결정: `label`은 폼 라벨·섹션 부제목이 본문 대비 약해 500(본문 400과 제목 600 사이)으로 정하고
|
|
66
|
+
Web·Native 모두 기본으로 그린다. `heading.level1`·`level2`는 Pretendard 800이 큰 크기에서 무겁고 거칠어 700으로
|
|
67
|
+
낮췄다(제목 단계 3~5는 같은 날 600). 브루탈리즘 프리셋은 강한 강조 프리셋이라 level1·2를 800으로 명시해 유지한다.
|
|
68
|
+
|
|
58
69
|
## 쓰는 법
|
|
59
70
|
|
|
60
71
|
```tsx
|