@hjmds/design-contracts 1.26.1 → 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
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
| `getShowcaseEnvironmentInput` | 프리셋을 Provider 입력(`theme`·`direction`·`textScale`·`reducedMotion`)으로 변환 | [DesignSystemProvider](../components/design-system-provider.md) |
|
|
28
28
|
| `HjmProvider`(Web)·`HjmNativeProvider`(Native) | 제품에서 환경 축을 실제로 바꾸는 곳 | [DesignSystemProvider](../components/design-system-provider.md) |
|
|
29
29
|
|
|
30
|
-
모두 `@hjmds/design-contracts/showcase`에서 import한다.
|
|
30
|
+
모두 `@hjmds/design-contracts/showcase`에서 import한다. 스토리 페이지는 `Container size="content"` > Heading·Text > `Grid`(1·2·3열) > `Card`·`Badge`로 조립한다. 프리셋 이름 Badge는 같은 id의 `showcaseScenarios` 이름을 쓴다.
|
|
31
31
|
|
|
32
32
|
## 배치
|
|
33
33
|
|
|
@@ -39,16 +39,16 @@
|
|
|
39
39
|
│ └───────────────────────────────────────────────────────┘ │
|
|
40
40
|
└───────────────────────────────────────────────────────────┘
|
|
41
41
|
|
|
42
|
-
스토리 페이지(
|
|
42
|
+
스토리 페이지(참고)
|
|
43
43
|
┌──────────────────────────────────────────────────────────┐
|
|
44
|
-
│
|
|
45
|
-
│ ┌
|
|
46
|
-
│ │ [
|
|
44
|
+
│ 환경 조합 검증 / 안내 │
|
|
45
|
+
│ ┌ 환경 조합 (Card 격자) ───────────────────────────────┐ │
|
|
46
|
+
│ │ [기본] [어두운 테마] [200% 글자] [오른쪽→왼쪽] [동작 줄이기] │ │
|
|
47
47
|
│ └──────────────────────────────────────────────────────┘ │
|
|
48
|
-
│ ┌
|
|
49
|
-
│ │ [
|
|
50
|
-
│ │ [
|
|
51
|
-
│ │ [
|
|
48
|
+
│ ┌ 스토리 확인 항목 (Card 격자) ────────────────────────┐ │
|
|
49
|
+
│ │ [계약] [기본] [어두운 테마] [긴 문구] [200% 글자] │ │
|
|
50
|
+
│ │ [오른쪽→왼쪽] [동작 줄이기] [접근성] [키보드] │ │
|
|
51
|
+
│ │ [앱 동작] [웹·앱 동등성] │ │
|
|
52
52
|
│ └──────────────────────────────────────────────────────┘ │
|
|
53
53
|
└──────────────────────────────────────────────────────────┘
|
|
54
54
|
상단 Storybook toolbar: light/dark · LTR/RTL · 100/150/200% · full/reduced
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
58
58
|
| --- | --- | --- | --- |
|
|
59
59
|
| 바깥 틀 | `HjmProvider`(Web)·`HjmNativeProvider`(Native) | 검증할 제품 화면의 맨 바깥. 화면의 스크롤·`Container`·안전 영역은 바꾸지 않는다 | 환경 입력만 바꾼다. 여백·크기는 제품 화면 값 그대로. Native는 `safeAreaInsets`도 실제 기기 값으로 넘긴다 |
|
|
60
|
-
| 환경 프리셋 | `showcaseEnvironmentMatrix`
|
|
61
|
-
| 검증 항목 | `showcaseScenarios`
|
|
60
|
+
| 환경 프리셋 | `showcaseEnvironmentMatrix` Card 5개(Badge 이름·조합·동작) | 스토리 첫 섹션 | Grid compact 1·medium 2·expanded 3열, gap `spacing.md` 16 |
|
|
61
|
+
| 검증 항목 | `showcaseScenarios` Card 11개 | 스토리 둘째 섹션 | 같은 Grid |
|
|
62
62
|
| 환경 전환 | Storybook toolbar | 페이지 밖 상단 | — |
|
|
63
63
|
|
|
64
64
|
### 비교하는 다섯 환경
|
|
@@ -135,7 +135,11 @@ const preset = showcaseEnvironmentMatrix.find((env) => env.id === "rtl")!;
|
|
|
135
135
|
|
|
136
136
|
## 함정
|
|
137
137
|
|
|
138
|
-
-
|
|
138
|
+
- 제품 검증 화면의 제목은 [Heading](../components/heading.md), 문구는 i18n 키로 둔다. showcase 계약의 한국어 이름·설명을 제품 문구로 가져오지 않는다.
|
|
139
139
|
- `getShowcaseEnvironmentInput`의 결과는 펼쳐서(`{...input}`) 넘긴다. 필드를 하나씩 꺼내 넘기면 optional 타입 때문에
|
|
140
140
|
`exactOptionalPropertyTypes`에서 TS2375가 난다(Web·Native 같다).
|
|
141
141
|
- 검증용 Provider로 화면을 감쌀 때 바깥 틀(스크롤·Container·안전 영역)을 따로 바꾸면 실제 화면과 다른 것을 검증하게 된다.
|
|
142
|
+
|
|
143
|
+
## 근거
|
|
144
|
+
|
|
145
|
+
- 2026-10-17 구성 리뷰: 스토리 문구 대부분이 영어였고 raw HTML과 showcase 전용 클래스로 조립돼 있어 계약 문구를 한국어로 바꾸고 HJM 공개 컴포넌트로 다시 조립했다.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
- 지원: Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
7
|
- 검토일: 2026-10-15
|
|
8
|
-
- 근거: `showcase/native/src/ExpoInteractions.stories.tsx`, `src/content-transition.ts`, `src/foundations.ts`(`spacing`, `layout`), `packages/react-native/src/content-transition.tsx`, `packages/react-native/src/
|
|
8
|
+
- 근거: `showcase/native/src/ExpoInteractions.stories.tsx`, `src/content-transition.ts`, `src/foundations.ts`(`spacing`, `layout`), `packages/react-native/src/content-transition.tsx`, `packages/react-native/src/inputs.tsx`(`Switch`)
|
|
9
9
|
- 스토리북: `배포/구성/피드백과 복구/중단해도 남는 현재 상태`
|
|
10
10
|
|
|
11
11
|
공통 구조·배치·상태·배지의 소유권은 [화면과 구성 선택](../SELECTION.md#소유권)을 따른다. 앱은 데이터·문구·브랜드·추가 기능을 공개 슬롯과 콜백으로 연결한다. 아래 코드 골격을 앱별 기본 구조 복제의 근거로 사용하지 않는다. 전용 구성 API가 없는 콘텐츠 묶음은 [CompositionLayout](../components/composition-layout.md)의 제목·배지·행동·본문·상태·footer 슬롯을 사용한다.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
| --- | --- | --- |
|
|
23
23
|
| `ScrollView` · `Container` | 바깥 틀. HJM 화면 골격이 소유한다 | [Container](../components/container.md), [화면 여백](../tokens/layout.md) |
|
|
24
24
|
| `Heading level="level5"`, `Text` | 영역 제목(18)과 설명 | [Heading](../components/heading.md), [Text](../components/text.md) |
|
|
25
|
-
| `
|
|
25
|
+
| `Switch presentation="row"` | 동작 줄이기 같은 켜고 끄는 설정 | [Switch](../components/switch.md) |
|
|
26
26
|
| `Button` `primary`·`secondary`·`ghost` | 다음 상태 · 처음으로 · 내용 닫기/열기 | [Button](../components/button.md) |
|
|
27
27
|
| `Stack` | 영역 세로 쌓기 `gap="md"`, 행동 묶음 `gap="sm"` | [Stack](../components/stack.md) |
|
|
28
28
|
| `ContentTransition` | `stateKey`가 바뀔 때 새 내용을 `preset="rise"`로 들인다 | [ContentTransition](../components/content-transition.md) |
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
│ ┌ Stack gap="md" 16 ─────────────────────────────┐ │
|
|
38
38
|
│ │ 제목 (Heading level5) │ │
|
|
39
39
|
│ │ 설명 (Text) │ │
|
|
40
|
-
│ │
|
|
40
|
+
│ │ 동작 줄이기 [ ○] ← Switch row│ │
|
|
41
41
|
│ │ ┌ 행동 묶음 Stack gap="sm" 12 ───────────────┐ │ │
|
|
42
42
|
│ │ │ [ 다음 상태 ] primary ← 주 행동이 맨 위 │ │ │
|
|
43
43
|
│ │ │ [ 처음으로 ] secondary ← 보조 행동 │ │ │
|
|
@@ -54,13 +54,13 @@
|
|
|
54
54
|
| --- | --- | --- | --- |
|
|
55
55
|
| 바깥 틀 | `ScrollView` > `Container size="reading"` | 화면 전체, 스크롤. 상단 안전 영역은 내비게이션 헤더(또는 [TopBar](../components/top-bar.md))가 맡는다. 입력이 없어 키보드 처리는 없다 | 위아래 `contentContainerStyle` `paddingVertical: spacing.lg` 20. 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20([화면 여백](../tokens/layout.md)) |
|
|
56
56
|
| 머리 | `Heading` + `Text` | Stack 맨 위 | 사이 `spacing.md` 16 |
|
|
57
|
-
| 설정 토글 | `
|
|
57
|
+
| 설정 토글 | `Switch presentation="row"` | 머리 아래 | 꽉 찬 폭 행, track `medium` 52×32. 큰 글자에서는 track이 라벨 아래로 내려간다 |
|
|
58
58
|
| 행동 묶음 | `Stack gap="sm"` + Button ×3 세로 | 토글 아래, 주 행동이 위 | 버튼 사이 `spacing.sm` 12, 각 높이 44 |
|
|
59
59
|
| 바뀌는 내용 | `ContentTransition` | 행동 묶음 아래, 함께 스크롤 | `rise` 세로 12 이동, 시간 `motion.normal`, 곡선 `easing.enter`. 래퍼 자체 크기·여백 없음 |
|
|
60
60
|
|
|
61
61
|
- 한 영역(구획 하나) 안이므로 요소 사이는 `layout.contentGap`(`Stack gap="md"` 16)이다([화면 여백](../tokens/layout.md)).
|
|
62
|
-
- 한 화면의 primary는 "다음 상태" 하나다.
|
|
63
|
-
|
|
62
|
+
- 한 화면의 primary는 "다음 상태" 하나다. 켜고 끄는 설정은 [선택 조립 규칙](../SELECTION.md#조립-규칙필수)대로 `Switch`로 두고 `selected` Button으로 흉내 내지 않는다.
|
|
63
|
+
2026-10-16 사용자 지적으로 글자 버튼 토글은 켜짐/꺼짐이 테두리 없는 글자로만 구분돼 설정으로 읽히지 않았다.
|
|
64
64
|
|
|
65
65
|
## 흐름과 상태
|
|
66
66
|
|
|
@@ -98,6 +98,7 @@ import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
|
|
|
98
98
|
import { Button } from "@hjmds/react-native/actions";
|
|
99
99
|
import { ContentTransition } from "@hjmds/react-native/content-transition";
|
|
100
100
|
import { Heading } from "@hjmds/react-native/heading";
|
|
101
|
+
import { Switch } from "@hjmds/react-native/inputs";
|
|
101
102
|
import { Container, Stack, Text } from "@hjmds/react-native/primitives";
|
|
102
103
|
|
|
103
104
|
const flowStateKey = ["flow.state.ready", "flow.state.review", "flow.state.done"] as const;
|
|
@@ -113,7 +114,7 @@ function FlowStatus() {
|
|
|
113
114
|
<Stack gap="md">
|
|
114
115
|
<Heading level="level5" semanticLevel={2}>{t("flow.title")}</Heading>
|
|
115
116
|
<Text>{t("flow.description")}</Text>
|
|
116
|
-
<
|
|
117
|
+
<Switch presentation="row" label={t("flow.reduceMotion")} checked={reduced} onCheckedChange={setReduced} />
|
|
117
118
|
<Stack gap="sm">
|
|
118
119
|
<Button onPress={() => setIndex((value) => (value + 1) % flowStateKey.length)}>{t("flow.next")}</Button>
|
|
119
120
|
<Button tone="secondary" onPress={() => setIndex(0)}>{t("flow.reset")}</Button>
|
|
@@ -149,4 +150,4 @@ function FlowStatus() {
|
|
|
149
150
|
- 다음 상태 계산을 전환 완료 콜백에 묶으면 빠른 연속 누름과 중간 닫기에서 상태가 어긋난다.
|
|
150
151
|
- ContentTransition은 움직이는 동안 래퍼 밖으로 세로 12만큼 밀려 나온다. 바로 아래 요소와 붙이지 않는다.
|
|
151
152
|
- 예제는 `Container gutter="compact"`와 `Stack gap="md"`로 바깥 틀을 공유하고 행동 묶음은 `Stack gap="sm"`으로 둔다. Native 위아래 여백은 `spacing.md` 16이다.
|
|
152
|
-
- 제목은 Heading, 동작
|
|
153
|
+
- 제목은 Heading, 동작 줄이기는 Switch, 작업 결과는 iOS 알림도 처리하는 예제 helper를 쓴다. 제품은 같은 계약을 자체 상태 알림에 연결한다.
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
| 컴포넌트 | 역할 | 지침 |
|
|
21
21
|
| --- | --- | --- |
|
|
22
22
|
| `Container` · `ScrollView` | 시작 화면의 바깥 틀. HJM 화면 골격이 소유한다 | [Container](../components/container.md), [화면 여백](../tokens/layout.md) |
|
|
23
|
-
| `Heading level="
|
|
23
|
+
| `Heading level="level3"`, `Text` | 시작 화면의 제목·설명. 화면 제목은 semibold `level3`이며 bold 표시 역할 `level1`·`level2`는 쓰지 않는다 | [Heading](../components/heading.md), [Text](../components/text.md) |
|
|
24
24
|
| `Button` primary | 시트를 여는 트리거(시작 화면의 주 행동) | [Button](../components/button.md) |
|
|
25
25
|
| `Sheet` | 하단 시트. 제목·닫기·`footer`, 안전 영역·스크롤·`busy` | [Sheet](../components/sheet.md) |
|
|
26
26
|
| `Steps` | 현재 단계와 전체 단계 수, 실패하면 현재 단계 `error` | [Steps](../components/steps.md) |
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
┌ Web 문서 스크롤 · Native ScrollView ┐ ┌──────────── backdrop ─────────────┐
|
|
38
38
|
│ ←gutter 16|20→ Container │ │╭──────────────────────────────────╮│
|
|
39
39
|
│ ┌ Stack gap="xl" 24 ──────────────┐ │ ││ 함께하는 공간 [닫기] ││ ← 머리 고정, 최소 44
|
|
40
|
-
│ │ 공간 제목 (Heading
|
|
40
|
+
│ │ 공간 제목 (Heading level3) │ │ ││──────────────────────────────────││
|
|
41
41
|
│ │ 설명 (Text) │ │ ││ ① 초대 ─ ② 설정 ─ ③ 확인 ││ ← Steps
|
|
42
42
|
│ │ [ 공간 설정하기 ] primary │ │ ││ ↕ spacing.xl 24 ││ 본문(스크롤)
|
|
43
43
|
│ │ 설정을 마쳤어요. (상태 문구) │ │ ││ (실패 시 Notice danger) ││
|
|
@@ -111,7 +111,7 @@ const isLast = step === stepIds.length - 1;
|
|
|
111
111
|
|
|
112
112
|
<Container size="reading" gutter={gutter}>
|
|
113
113
|
<Stack gap="xl">
|
|
114
|
-
<Heading level="
|
|
114
|
+
<Heading level="level3">{t("space.title")}</Heading>
|
|
115
115
|
<Text as="p">{t("space.description")}</Text>
|
|
116
116
|
<Button onClick={openSetup}>{t("space.setup.open")}</Button>
|
|
117
117
|
{saved ? <Text as="p" role="status">{t("space.setup.saved")}</Text> : null}
|
|
@@ -155,7 +155,7 @@ import { Steps } from "@hjmds/react-native/steps";
|
|
|
155
155
|
<ScrollView contentContainerStyle={{ paddingVertical: spacing.lg }}>
|
|
156
156
|
<Container size="reading" gutter={gutter}>
|
|
157
157
|
<Stack gap="xl">
|
|
158
|
-
<Heading level="
|
|
158
|
+
<Heading level="level3">{t("space.title")}</Heading>
|
|
159
159
|
<Text>{t("space.description")}</Text>
|
|
160
160
|
<Button onPress={openSetup}>{t("space.setup.open")}</Button>
|
|
161
161
|
{saved ? <StatusText announceOnMount>{t("space.setup.saved")}</StatusText> : null}
|
|
@@ -200,9 +200,13 @@ import { Steps } from "@hjmds/react-native/steps";
|
|
|
200
200
|
- 저장을 시트 닫힘 사유와 묶지 않는다. 닫기·바깥·back 닫힘은 취소다.
|
|
201
201
|
- 중단 후 다시 열면 진행 중이던 단계로 이어간다. 성공 뒤 새 작업을 시작할 때만 0으로 되돌린다. 영속 초안이 필요한 제품은 별도 저장소 정책을 연결한다.
|
|
202
202
|
- 저장 중 `busy`를 빼면 사용자가 시트를 닫아 저장 결과를 볼 곳이 사라진다.
|
|
203
|
-
-
|
|
203
|
+
- 스토리는 시작 화면을 `Container size="reading"`으로 감싼다. Native 제품은 그 바깥에 위 바깥 틀 행의 `ScrollView`를 둔다.
|
|
204
204
|
- 스토리는 데모 비동기 저장의 진행 중·실패·재시도를 포함한다. 실제 서버 저장은 제품이 연결한다.
|
|
205
205
|
|
|
206
206
|
## 실행 예제의 복구 경계
|
|
207
207
|
|
|
208
208
|
2026-10-13 Web·Native 스토리는 `createActionSession` 기반 데모 저장을 연결한다. 마지막 단계 저장 중에는 이동·닫기를 잠그고, 실패하면 `Steps.currentStepStatus="error"`와 Notice를 표시한 채 현재 단계를 유지한다. 같은 주 버튼이 다시 저장을 수행한다. 중간에 닫았다 열면 현재 단계로 이어지고, 성공 뒤 다시 시작할 때만 첫 단계로 초기화한다. footer는 기본 Sheet 배치를 따르는 직접 자식이다. Web은 이전 → 주 행동, Native는 주 행동 → 이전이며 Native에서 가로 Stack으로 재정의하지 않는다. 실제 서버 저장·영속 초안·제품별 단계 입력은 포함하지 않는다.
|
|
209
|
+
|
|
210
|
+
## 근거
|
|
211
|
+
|
|
212
|
+
- 2026-10-17 구성 리뷰: 시작 화면 제목이 heavy 800 `level2`로 화면 제목 규칙(semibold)보다 무거웠고, 데스크톱에서 버튼과 문구가 1280 폭 전체로 늘어났다. 그래서 제목을 `level3`으로 낮추고 스토리에 읽기 폭 Container를 적용했다.
|
|
@@ -41,13 +41,13 @@ CheckboxGroup/RadioGroup, 날짜 조각이면 DateEntry를 먼저 사용한다.
|
|
|
41
41
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
42
42
|
| --- | --- | --- | --- |
|
|
43
43
|
| 바깥 틀 | Web fieldset / Native View | 기존 폼 안 | 테두리 없음, Web 최소 폭 0 |
|
|
44
|
-
| 그룹 이름 | Web legend / Native Text label | 맨 위 | Web 아래 spacing.sm 12, Native 그룹 간격 spacing.sm 12 |
|
|
45
|
-
| 입력 묶음 | renderField | descriptor.fields 순서 | `layout="stack"`은 세로, `layout="inline"`은
|
|
44
|
+
| 그룹 이름 | Web legend / Native Text, `fieldGroupRecipe.heading`(bodyLarge·semibold, 각 칸 label보다 크게) | 맨 위 | Web 아래 spacing.sm 12, Native 그룹 간격 spacing.sm 12 |
|
|
45
|
+
| 입력 묶음 | renderField | descriptor.fields 순서 | `layout="stack"`은 세로, `layout="inline"`은 모든 칸이 한 줄에 들어갈 때만 한 행, 아니면 모두 세로. 간격 spacing.md 16 |
|
|
46
46
|
| 개별 도움말 | FieldGroup | 해당 입력 바로 아래 | Native 간격 spacing.xs 8, 글자 크기는 Text 기본 |
|
|
47
47
|
|
|
48
48
|
### 배치 축
|
|
49
49
|
|
|
50
|
-
`layout`은 `stack`(기본) 또는 `inline`이다. `inline`은 날짜·소요 시간처럼 짧은 값들이 하나의 질문을 이루는 입력에 쓴다.
|
|
50
|
+
`layout`은 `stack`(기본) 또는 `inline`이다. `inline`은 날짜·소요 시간처럼 짧은 값들이 하나의 질문을 이루는 입력에 쓴다. 두 renderer 모두 `fieldGroupRecipe.inline`의 `minWidth`(글자 배율 비례)로 모든 칸이 들어가는지 보고, 들어가면 한 행에 칸마다 최대 `maxWidth`까지, 아니면 모든 칸을 세로로 쌓는다(`wrap: "all-or-none"`). 2+1처럼 일부만 줄바꿈하지 않는다. `layoutStyle`은 바깥 배치만, Web `className`은 그룹 host에 적용된다.
|
|
51
51
|
|
|
52
52
|
2026-10-13 DateEntry와 DurationField가 같은 그룹 제목·입력 행을 직접 만들고 있어 `fieldGroupRecipe`를 `@hjmds/design-contracts/field-group`에 모았다. 두 컴포넌트는 FieldGroup의 descriptor·renderField·guardChange를 재사용한다. 날짜 파싱과 시간 단위 변환은 각각의 기존 계약에 남는다.
|
|
53
53
|
|
|
@@ -110,3 +110,7 @@ controlProps는 TextField에 바로 연결되는 형태이며 Checkbox/Select
|
|
|
110
110
|
|
|
111
111
|
- 실제 스크린리더·Native 기기·제품 팔레트 검증은 아직 남았다. 자동 검사 통과를 승격 근거로 단독 사용하지 않는다.
|
|
112
112
|
- 국가·주소·연락처의 필드 순서와 autocomplete는 제품별로 정한다. 예제의 한국 주소 순서를 공통 규칙으로 복사하지 않는다.
|
|
113
|
+
|
|
114
|
+
## 근거
|
|
115
|
+
|
|
116
|
+
- 2026-10-17 구성 리뷰: DurationField 칸이 데스크톱에서 약 400px로 늘어나 짧은 값이 넓은 칸 가운데 떠 있었고, 402pt에서는 시·분 / 초로 끊겼다. 그룹 이름은 regular라 굵은 칸 이름보다 약했다. 그래서 칸 최대 폭, 전부-또는-세로 줄바꿈, 그룹 제목 typography를 recipe에 두었다.
|
|
@@ -27,7 +27,7 @@ Web·Native stable, Native `screen-transition`은 experimental이다. 각 어댑
|
|
|
27
27
|
| `CarouselMotion` | 카드 넘기기. 이전/다음 버튼, 반복·자동 재생 없음 | [Carousel](../components/carousel.md) |
|
|
28
28
|
| `Celebration` | 제품이 확인한 성공 뒤 입자 효과(장식) | [Celebration](../components/celebration.md) |
|
|
29
29
|
| `createHjmTransitionStack`·`SharedTransitionElement`·`SharedTransitionScreen`·`useSharedTransitionOptions` | Native 전용: 목록 카드 → 상세 화면 확대 전환 | [SharedTransitionScreen](../components/shared-transition-screen.md), [SharedTransitionElement](../components/shared-transition-element.md) |
|
|
30
|
-
| `Button`·`
|
|
30
|
+
| `Button`·`StatusText`·`Section`·`Surface` | 상태 변경 트리거, 결과 문구, 구획 제목, 카드 틀 | [Button](../components/button.md), [StatusText](../components/text.md), [Section](../components/section.md), [Surface](../components/surface.md) |
|
|
31
31
|
|
|
32
32
|
## 배치
|
|
33
33
|
|
|
@@ -59,14 +59,14 @@ Native(전체 화면 host) Web(문서 흐름)
|
|
|
59
59
|
| 구획 제목 | [Section](../components/section.md) `title` | 각 어댑터 묶음 위 | 제목과 본문 사이 `spacing.xs` 8(`sectionRecipe`) |
|
|
60
60
|
| 순서 목록 | `SortableCollection` | 본문, 스크롤 | Native 행 padding·간격 `spacing.xs` 8, 손잡이 줄 최소 높이 44. Web 손잡이 44×44, 행 위아래 `spacing.xs` 8 |
|
|
61
61
|
| 행 작업 | `SwipeActions` + [ListRow](../components/list-row.md) | 목록 행 | 행 높이·여백은 `ListRow`(한 줄 56), 작업 버튼 높이 44·간격 `spacing.xs` 8 |
|
|
62
|
-
| 결과 문구 | `
|
|
62
|
+
| 결과 문구 | `StatusText`(Web `role="status"`, Native live region + iOS 알림) | 작업·축하 바로 아래 | 구획 안 간격은 Section이 정한다 |
|
|
63
63
|
| 내용 전환 | `ContentTransition`·`TextTransition` | 바뀌는 내용 자리 | 크기 변화 없음(전환은 투명도·짧은 이동만) |
|
|
64
|
-
| 카드 | `CarouselMotion` | 본문 | Native는 `onLayout`으로 잰 폭 × 제품이 정한 높이, Web은 부모
|
|
64
|
+
| 카드 | `CarouselMotion` | 본문 | Native는 `onLayout`으로 잰 폭 × 제품이 정한 높이, Web은 부모 폭. 이전·다음은 슬라이드 아래 끝 정렬 아이콘 버튼(`spacing.sm` 간격) |
|
|
65
65
|
| 축하 | `Celebration` | Native: 스크롤 **밖** 화면 오버레이(host의 형제), Web: 자체 canvas | 입자 32/64개, 1.6/2.4초(`celebrationRecipe`) |
|
|
66
66
|
|
|
67
67
|
- Native `Celebration`을 스크롤 내용 안에 두면 화면 밖에서 시작해 보이지 않는다(기기 QA 확인). 항상 화면 host의 형제로 둔다.
|
|
68
68
|
- `SwipeActions`는 Web에서 버튼을 바로 보이고, Native에서 스와이프·상시 "작업 보기" 버튼·동작 줄이기 모두 같은 버튼을 드러낸다. 목록 전체가 `openRowId` 하나를 공유한다.
|
|
69
|
-
- 카드 높이·색·이미지는 제품 소유다.
|
|
69
|
+
- 카드 높이·색·이미지는 제품 소유다. 예제 카드는 `Surface padding="lg"` 안 가운데 정렬 `Stack`의 [Heading](../components/heading.md) `level4`이고, Native 높이는 첫 카드의 실제 높이를 잰 값이다.
|
|
70
70
|
|
|
71
71
|
근거: `packages/react-native/src/sortable.tsx`, `packages/react/src/sortable.tsx`, `packages/react-native/src/swipe-actions.tsx`, `src/interaction-adapters.ts`(`celebrationRecipe`), `src/foundations.ts`(`layout`)
|
|
72
72
|
|
|
@@ -94,6 +94,7 @@ Native(전체 화면 host) Web(문서 흐름)
|
|
|
94
94
|
```tsx
|
|
95
95
|
// Web
|
|
96
96
|
import { Container, Section, Stack, Text } from "@hjmds/react/layout";
|
|
97
|
+
import { StatusText } from "@hjmds/react/status-text";
|
|
97
98
|
import { ListRow } from "@hjmds/react/display";
|
|
98
99
|
import { Heading } from "@hjmds/react/heading";
|
|
99
100
|
import { SortableCollection } from "@hjmds/react/sortable";
|
|
@@ -108,14 +109,14 @@ const recordKey = { start: "record.start", ready: "record.ready" } as const;
|
|
|
108
109
|
<Stack gap="xl">
|
|
109
110
|
<Section title={t("fav.title")}>
|
|
110
111
|
<SortableCollection items={items} label={t("fav.label")} labels={sortLabels} disabled={savingOrder}
|
|
111
|
-
renderItem={(item) => <
|
|
112
|
+
renderItem={(item) => <Text emphasis="medium">{item.label}</Text>} onCommit={(intent) => void commitOrder(intent.orderedIds)} />
|
|
112
113
|
</Section>
|
|
113
114
|
<Section title={t("note.title")}>
|
|
114
115
|
<SwipeActions label={t("note.actions")} actions={[{ id: "archive", label: t("note.archive") }]} busy={busy}
|
|
115
116
|
onAction={runAction} onError={() => setNoteStatus("failed")}>
|
|
116
117
|
<ListRow title={note.title} />
|
|
117
118
|
</SwipeActions>
|
|
118
|
-
<
|
|
119
|
+
<StatusText>{t(noteStatusKey[noteStatus])}</StatusText>
|
|
119
120
|
</Section>
|
|
120
121
|
<ContentTransition stateKey={record}>
|
|
121
122
|
<Heading level="level4" semanticLevel={3}>{t(recordKey[record])}</Heading>
|
|
@@ -131,6 +132,7 @@ import { ScrollView, View, useWindowDimensions } from "react-native";
|
|
|
131
132
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
132
133
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
133
134
|
import { Container, Section, Stack, Text } from "@hjmds/react-native/primitives";
|
|
135
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
134
136
|
import { ListRow } from "@hjmds/react-native/data-display";
|
|
135
137
|
import { useHjmNativeTheme } from "@hjmds/react-native/provider";
|
|
136
138
|
import { SortableCollection } from "@hjmds/react-native/sortable";
|
|
@@ -149,7 +151,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
149
151
|
<Stack gap="xl">
|
|
150
152
|
<Section title={t("fav.title")}>
|
|
151
153
|
<SortableCollection items={items} label={t("fav.label")} labels={sortLabels} disabled={savingOrder}
|
|
152
|
-
renderItem={() =>
|
|
154
|
+
renderItem={(item) => <Text emphasis="medium">{item.label}</Text>} onCommit={(intent) => void commitOrder(intent.orderedIds)} />
|
|
153
155
|
</Section>
|
|
154
156
|
<Section title={t("note.title")}>
|
|
155
157
|
<SwipeActions rowId={note.id} label={note.title} actionsLabel={t("note.showActions")}
|
|
@@ -157,7 +159,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
157
159
|
actions={[{ id: "archive", label: t("note.archive") }]} onAction={runAction} onError={() => setNoteStatus("failed")}>
|
|
158
160
|
<ListRow title={note.title} />
|
|
159
161
|
</SwipeActions>
|
|
160
|
-
<
|
|
162
|
+
<StatusText>{t(noteStatusKey[noteStatus])}</StatusText>
|
|
161
163
|
</Section>
|
|
162
164
|
<View onLayout={(e) => setCardWidth(e.nativeEvent.layout.width)}>
|
|
163
165
|
<CarouselMotion width={cardWidth} height={cardHeight} slides={slides} currentKey={slideKey} onCurrentKeyChange={setSlideKey}
|
|
@@ -185,7 +187,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
185
187
|
|
|
186
188
|
## 함정
|
|
187
189
|
|
|
188
|
-
-
|
|
190
|
+
- `SortableCollection` 손잡이는 항목 이름을 보조 기술에만 알린다. 보이는 이름은 두 플랫폼 모두 `renderItem`이 그린다.
|
|
189
191
|
- Native 화면 전환은 현재 포트폴리오 Expo 앱(expo-router, React Navigation 미사용)에서 바로 쓸 수 없다. exports patch를 제품 패키지 관리자에 등록해야 한다.
|
|
190
192
|
- 어댑터 peer 없이 subpath를 import하면 자동 대체 없이 기기 Metro 번들이 실패한다.
|
|
191
193
|
- 예제는 Heading·Container·Stack·ListRow를 사용한다. 플랫폼별 드래그/스와이프 host는 유지하고 동일한 배치 역할을 재사용한다.
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
| `Text` 상태 | 현재 적용된 값 `t("display.current", { value })` | [Text](../components/text.md) |
|
|
30
30
|
| `Button` 트리거 | 시트를 연다. 열 때 임시 선택을 적용 값으로 초기화. 화면의 primary 하나 | [Button](../components/button.md) |
|
|
31
31
|
| `Sheet` | 선택 영역. `title`·`closeLabel` 필수, `returnFocusRef`로 트리거에 포커스 복귀 | [Sheet](../components/sheet.md) |
|
|
32
|
-
| `
|
|
32
|
+
| `RadioGroup` | 선택지. 임시 선택(draft)만 바꾼다 | [RadioGroup](../components/radio-group.md) |
|
|
33
33
|
| `Button` 적용 / `Button` 취소(secondary) | 시트 `footer`. 적용만 확정 값을 바꾼다 | [Button](../components/button.md) |
|
|
34
34
|
|
|
35
35
|
## 배치
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
┌ Sheet ───────────────────────────────┐
|
|
48
48
|
│ 표시 방식 선택 [×] │ ← header 고정, 닫기 = 취소와 같음
|
|
49
49
|
├──────────────────────────────────────┤
|
|
50
|
-
│
|
|
51
|
-
│
|
|
52
|
-
│
|
|
50
|
+
│ (●) 간결하게 │ ↑ body 스크롤
|
|
51
|
+
│ ( ) 자세하게 │ │ RadioGroup card, 행 56
|
|
52
|
+
│ ( ) 크게 보기 │ │
|
|
53
53
|
│ 안내: 적용해야 바뀐다 │ ↓
|
|
54
54
|
├──────────────────────────────────────┤
|
|
55
55
|
│ Web: [선택 취소] [선택 적용] │ ← footer 고정, 오른쪽 정렬, gap 12
|
|
@@ -65,21 +65,21 @@
|
|
|
65
65
|
| 현재 값 | `Text` | 제목 아래, 트리거 위 | `spacing.md` 16 |
|
|
66
66
|
| 트리거 | `Button`(primary) | 현재 값 아래 | `medium` 44(`control.buttonHeight`) |
|
|
67
67
|
| 시트 머리 | `Sheet` header | 시트 위 고정 | Native 최소 높이 `control.minTouchTarget` 44, 제목–닫기 `spacing.sm` 12; Web 여백 `spacing.lg` 20, 제목–닫기 `spacing.md` 16 |
|
|
68
|
-
| 선택지 | `
|
|
68
|
+
| 선택지 | `RadioGroup`(`presentation="card"`, `accessibilityLabel` = 시트 제목) | 시트 body, 스크롤 | 행 최소 높이 56(`layout.rowHeight.singleLine`), 항목 사이 `spacing.xs` 8(`selectionGroupRecipe`). 안내 문구와는 `Stack gap="md"` 16. 설치한 버전의 Sheet body gap과 중첩 여부를 확인하며 checkout 상태를 npm 게시 증거로 삼지 않는다 |
|
|
69
69
|
| 행동 | `Button` 적용·취소 | 시트 footer, 스크롤 밖 고정 | 간격 `spacing.sm` 12(`sheetRecipe.footer.gap`), Native 위 여백 `spacing.sm` 12 |
|
|
70
70
|
| 시트 틀 | `Sheet` | 하단, 모달 | Web 최대 폭 640(`sheetRecipe.web.maxWidth`)·최대 높이 90%, Native 가로 여백 `spacing.lg` 20·최대 높이 90%(`maxHeightRatio` 0.9), 하단 안전 영역 추가 |
|
|
71
71
|
|
|
72
72
|
- footer 순서는 [Sheet](../components/sheet.md) 배치 규칙을 따른다. Web footer는 오른쪽 정렬 가로 줄 **[취소][적용]**(보조 → 주 행동), Native footer는 세로 열이라
|
|
73
73
|
**적용(primary) 먼저, 취소(secondary) 아래**다. 두 renderer의 footer가 이미 `gap` 12를 주므로 버튼을 `Stack`으로 다시 감싸지 않는다.
|
|
74
|
-
- primary 개수: 화면에서는 트리거 하나, 시트 안에서는 적용 하나를 센다. 선택지는
|
|
75
|
-
-
|
|
74
|
+
- primary 개수: 화면에서는 트리거 하나, 시트 안에서는 적용 하나를 센다. 선택지는 RadioGroup이라 버튼으로 세지 않는다.
|
|
75
|
+
- 선택지를 `selected` Button 나열로 만들지 않는다. 2026-10-16 사용자 지적으로 선택 버튼 줄이 테두리 없는 글자처럼 보였다. 하나 고르기는 [선택 조립 규칙](../SELECTION.md#조립-규칙필수)대로 RadioGroup(선택지가 짧은 2~4개면 SegmentedControl)을 쓴다. 의미(임시 선택 → 적용)는 같다.
|
|
76
76
|
|
|
77
77
|
근거: `src/component-recipes.ts`(`sheetRecipe`), `packages/react/src/styles.css`(`.hjm-sheet__footer`, `.hjm-sheet__body`), `packages/react-native/src/overlays.tsx`(Sheet footer `View`), `src/foundations.ts`(`layout`, `heading`)
|
|
78
78
|
|
|
79
79
|
## 흐름과 상태
|
|
80
80
|
|
|
81
81
|
1. 트리거를 누르면 임시 선택을 현재 적용 값으로 초기화하고 시트를 연다.
|
|
82
|
-
2. 선택지를 누르면 임시 선택만 바뀐다(
|
|
82
|
+
2. 선택지를 누르면 임시 선택만 바뀐다(라디오 선택 표시). 화면 본문의 현재 값은 그대로다.
|
|
83
83
|
3. 적용을 누르면 확정 값을 임시 선택으로 바꾸고 시트를 닫는다.
|
|
84
84
|
4. 취소·닫기(×)·바깥 누름·Escape·Android back은 확정 값을 바꾸지 않고 닫는다.
|
|
85
85
|
5. 닫히면 포커스가 트리거로 돌아온다.
|
|
@@ -105,6 +105,7 @@ import { Button } from "@hjmds/react/actions";
|
|
|
105
105
|
import { Heading } from "@hjmds/react/heading";
|
|
106
106
|
import { Container, Stack, Text } from "@hjmds/react/layout";
|
|
107
107
|
import { Sheet } from "@hjmds/react/overlays";
|
|
108
|
+
import { RadioGroup } from "@hjmds/react/selection";
|
|
108
109
|
|
|
109
110
|
const displayKey = { compact: "display.compact", detailed: "display.detailed", large: "display.large" } as const;
|
|
110
111
|
type DisplayId = keyof typeof displayKey;
|
|
@@ -128,10 +129,10 @@ const trigger = useRef<HTMLButtonElement>(null);
|
|
|
128
129
|
<Button tone="secondary" onClick={() => setOpen(false)}>{t("display.cancel")}</Button>
|
|
129
130
|
<Button onClick={() => { setApplied(draft); setOpen(false); }}>{t("display.apply")}</Button>
|
|
130
131
|
</>}>
|
|
131
|
-
<Stack gap="md">
|
|
132
|
-
{
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
<Stack gap="md">
|
|
133
|
+
<RadioGroup accessibilityLabel={t("display.choose")} presentation="card"
|
|
134
|
+
items={options.map((id) => ({ value: id, label: t(displayKey[id]) }))}
|
|
135
|
+
value={draft} onValueChange={(next) => setDraft(next as DisplayId)} />
|
|
135
136
|
<Text>{t("display.applyHint")}</Text>
|
|
136
137
|
</Stack>
|
|
137
138
|
</Sheet>
|
|
@@ -145,6 +146,7 @@ import { useRef, useState } from "react";
|
|
|
145
146
|
import { ScrollView, View, useWindowDimensions } from "react-native";
|
|
146
147
|
import { Button } from "@hjmds/react-native/actions";
|
|
147
148
|
import { Heading } from "@hjmds/react-native/heading";
|
|
149
|
+
import { RadioGroup } from "@hjmds/react-native/inputs";
|
|
148
150
|
import { Sheet } from "@hjmds/react-native/overlays";
|
|
149
151
|
import { Container, Stack } from "@hjmds/react-native/primitives";
|
|
150
152
|
import { useHjmNativeTheme } from "@hjmds/react-native/provider";
|
|
@@ -172,9 +174,9 @@ const trigger = useRef<View>(null);
|
|
|
172
174
|
<Button onPress={() => { setApplied(draft); setOpen(false); }}>{t("display.apply")}</Button>
|
|
173
175
|
<Button tone="secondary" onPress={() => setOpen(false)}>{t("display.cancel")}</Button>
|
|
174
176
|
</>}>
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
<RadioGroup<DisplayId> accessibilityLabel={t("display.choose")} presentation="card"
|
|
178
|
+
items={options.map((id) => ({ value: id, label: t(displayKey[id]) }))}
|
|
179
|
+
value={draft} onValueChange={(next) => next && setDraft(next)} />
|
|
178
180
|
</Sheet>
|
|
179
181
|
```
|
|
180
182
|
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
| `Button` 검색 / `Button` 다시 검색(secondary) | 검색 실행(primary 하나), 실패했을 때만 보이는 재시도 | [Button](../components/button.md) |
|
|
32
32
|
| `Text` | 진행·결과·실패 문구 | [Text](../components/text.md) |
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
`다음 검색
|
|
34
|
+
스토리는 검색 버튼 하나(기본 150ms)를 두고, 역순 응답은 미리보기 도구 `다음 검색 느리게`(900ms)로 재현한다. 제품은 검색 버튼 하나(또는 입력 중 검색)로 둔다.
|
|
35
|
+
`다음 검색 실패시키기`도 실패 경로를 여는 미리보기 도구다. 스토리는 기본·어두운 테마, `검색 중`, `실패와 다시 검색`, 제품 팔레트 두 가지(`보라 테마`, `초록 테마 · 어둡게`)다.
|
|
36
36
|
2026-10-06 같은 SearchField 진행 상태와 팔레트를 보이던 `입력과 작성/검색 입력`을 이 항목으로 합쳤다([옛 링크](../../../../../docs/STORYBOOK_NAVIGATION.md#실험-정리와-합친-항목-2026-10-06)).
|
|
37
37
|
검색 입력 칸·결과 목록이 필요한 화면이면 [SearchField](../components/search-field.md)·[List](../components/list.md)에 같은 세대 규칙을 연결한다.
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
┌ 화면 ────────────────────────────────┐
|
|
43
43
|
│ Native ScrollView 위아래 16 │
|
|
44
44
|
│ (keyboardShouldPersistTaps=handled) │
|
|
45
|
-
│ └ Container gutter 16/20
|
|
45
|
+
│ └ Container reading, gutter 16/20 │
|
|
46
46
|
│ Heading 제목 │
|
|
47
47
|
│ ↕ spacing.md 16 │
|
|
48
48
|
│ 검색어 │
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
59
59
|
| --- | --- | --- | --- |
|
|
60
|
-
| 바깥 틀 | Native: `ScrollView keyboardShouldPersistTaps="handled"` → `Container` → `Stack gap="xl"`. Web: `Container` → `Stack gap="xl"` | 화면 본문. 안전 영역은 화면 셸이 준다. 키보드가 열린 채 검색 버튼을 누를 수 있게 Native 스크롤은 탭을 통과시킨다. 입력이 화면 아래쪽에 있으면 [KeyboardFormScrollView](../components/keyboard-form-scroll-view.md)를 쓴다 | Native `ScrollView` 위아래 `spacing.md` 16, 좌우 `Container` `gutter`(폭 600 미만 `compact` 16, 이상 `regular` 20). 구획 사이 `layout.sectionGap` 24 |
|
|
60
|
+
| 바깥 틀 | Native: `ScrollView keyboardShouldPersistTaps="handled"` → `Container size="reading"` → `Stack gap="xl"`. Web: `Container size="reading"` → `Stack gap="xl"` | 화면 본문. 안전 영역은 화면 셸이 준다. 키보드가 열린 채 검색 버튼을 누를 수 있게 Native 스크롤은 탭을 통과시킨다. 입력이 화면 아래쪽에 있으면 [KeyboardFormScrollView](../components/keyboard-form-scroll-view.md)를 쓴다 | Native `ScrollView` 위아래 `spacing.md` 16, 좌우 `Container` `gutter`(폭 600 미만 `compact` 16, 이상 `regular` 20). 구획 사이 `layout.sectionGap` 24 |
|
|
61
61
|
| 제목 | `Heading level="level3"` | 묶음 맨 위 | 24/32(`heading.level3`) |
|
|
62
62
|
| 입력 | `SearchField` | 제목 아래 | 높이 `control.fieldHeight` 44, `layout.contentGap` 16 |
|
|
63
63
|
| 주 행동 | `Button` 검색 | 입력 아래 | `medium` 44(`control.buttonHeight`), `spacing.md` 16 |
|
|
@@ -109,7 +109,7 @@ const search = () => {
|
|
|
109
109
|
const message = state.status === "pending" || state.status === "error" ? t(searchStatusKey[state.status])
|
|
110
110
|
: state.value ? renderResult(state.value) : t(searchStatusKey.idle);
|
|
111
111
|
|
|
112
|
-
<Container gutter={gutter}>
|
|
112
|
+
<Container size="reading" gutter={gutter}>
|
|
113
113
|
<Stack gap="xl">
|
|
114
114
|
<Stack gap="md">
|
|
115
115
|
<Heading level="level3" semanticLevel={2}>{t("search.title")}</Heading>
|
|
@@ -138,7 +138,7 @@ const { spacing } = useHjmNativeTheme().tokens;
|
|
|
138
138
|
const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "compact" : "regular";
|
|
139
139
|
|
|
140
140
|
<ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{ paddingVertical: spacing.md }}>
|
|
141
|
-
<Container gutter={gutter}>
|
|
141
|
+
<Container size="reading" gutter={gutter}>
|
|
142
142
|
<Stack gap="xl">
|
|
143
143
|
<Stack gap="md">
|
|
144
144
|
<Heading level="level3" semanticLevel={2}>{t("search.title")}</Heading>
|
|
@@ -162,7 +162,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
162
162
|
| --- | --- | --- |
|
|
163
163
|
| 입력 이벤트 | `onValueChange`(문자열 값). DOM 이벤트가 필요할 때만 `onChange`(둘 다 호출된다) | `onValueChange` |
|
|
164
164
|
| 상태 알림 | `role="status"` | live region(Android) + iOS 알림 호출 |
|
|
165
|
-
| 바깥 틀 | `Container` | `ScrollView`(세로 여백, `keyboardShouldPersistTaps="handled"`) 안 `Container` |
|
|
165
|
+
| 바깥 틀 | `Container size="reading"` | `ScrollView`(세로 여백, `keyboardShouldPersistTaps="handled"`) 안 `Container size="reading"` |
|
|
166
166
|
| 검색 중 표시 | SearchField `loading` | SearchField `busy` + `busyLabel`(필수) |
|
|
167
167
|
|
|
168
168
|
## 함정
|
|
@@ -174,3 +174,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
174
174
|
- 실패와 결과 없음은 다른 상태다. 실패는 `다시 검색`(`session.retry()`), 결과 없음은 검색어를 바꾸는 경로를 준다.
|
|
175
175
|
|
|
176
176
|
2026-10-13 실패 예약은 다음 요청이 **시작할 때** 그 요청에 귀속한다. 이미 진행 중인 응답이 이후에 켠 예약을 소비하지 않으며, 오래된 실패도 최신 성공을 덮지 않는다. 재시도는 기존 계약대로 실패한 제출 검색어를 다시 요청하고 작성 중인 검색어는 보존한다. 재시도 버튼이 사라질 때 입력 ref로 초점을 돌린다. unmount에서는 로컬 데모 지연 타이머를 정리하고 분리된 작업을 종료하지만 실제 네트워크 취소를 구현한 것은 아니다.
|
|
177
|
+
|
|
178
|
+
## 근거
|
|
179
|
+
|
|
180
|
+
- 2026-10-17 구성 리뷰: 데모 지연 버튼 두 개("느린/빠른 응답으로 검색")가 secondary 제품 행동처럼 보였고 primary 검색이 없었으며, 데스크톱에서 입력과 버튼이 1280 폭 전체로 늘어났다. 그래서 primary 검색 하나와 미리보기 도구 토글로 바꾸고 읽기 폭 Container를 적용했다.
|
|
@@ -26,18 +26,20 @@ Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등
|
|
|
26
26
|
| ContentTransition | 새 항목 등장과 바깥 높이 전환 | [내용 전환](../components/content-transition.md) |
|
|
27
27
|
| TextField | 제품이 항목 id별로 보존하는 메모 | [필드](../components/field.md) |
|
|
28
28
|
| Button · Text | 명시적 데이터 변경과 변경 결과 | [버튼](../components/button.md), [글자](../components/text.md) |
|
|
29
|
+
| EmptyState | 기록이 없을 때 목록 자리 가운데 안내 | [빈 상태](../components/empty-state.md) |
|
|
30
|
+
| 미리보기 도구(Showcase `PreviewTools`) | 움직임 멈추기 같은 예제 전용 조작. 제품 행동과 분리한 `small` ghost 버튼([선택 조립 규칙](../SELECTION.md#조립-규칙필수)) | — |
|
|
29
31
|
|
|
30
32
|
## 배치
|
|
31
33
|
|
|
32
34
|
```text
|
|
33
35
|
[안내]
|
|
34
|
-
[기록 추가] [3개 함께 추가]
|
|
35
|
-
|
|
36
|
-
[움직임 멈추기]
|
|
36
|
+
[기록 추가] [3개 함께 추가] [순서 뒤집기] [첫 기록 삭제] ← 한 inline 묶음, 좁으면 감김
|
|
37
|
+
미리보기 도구
|
|
38
|
+
[움직임 멈추기] ← small ghost, 예제 전용
|
|
37
39
|
[변경 결과]
|
|
38
40
|
┌ 기록 목록 ────────────┐
|
|
39
41
|
│ 제목·설명 │
|
|
40
|
-
│
|
|
42
|
+
│ 메모 입력 (행 글자 시작선에 맞춤) │
|
|
41
43
|
├───────────────────────┤
|
|
42
44
|
│ 다음 기록·메모 │
|
|
43
45
|
└───────────────────────┘
|
|
@@ -45,9 +47,11 @@ Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등
|
|
|
45
47
|
|
|
46
48
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
47
49
|
| --- | --- | --- | --- |
|
|
48
|
-
| 바깥 틀 | Web: Stack, Native: KeyboardAvoiding → ScrollView → Stack | 위에서 아래. Native canvas는 자체 스크롤이 없으므로 명시적 host 사용 | gap lg=20px, Native keyboardShouldPersistTaps="handled"·하단 spacing.md |
|
|
49
|
-
| 변경 행동 | inline Stack · Button | 목록 위, 좁으면 감김 |
|
|
50
|
-
|
|
|
50
|
+
| 바깥 틀 | Web: `Container size="reading"` → Stack, Native: KeyboardAvoiding → ScrollView → Stack | 위에서 아래. Native canvas는 자체 스크롤이 없으므로 명시적 host 사용 | gap lg=20px, Native keyboardShouldPersistTaps="handled"·하단 spacing.md |
|
|
51
|
+
| 변경 행동 | inline Stack 하나 · Button | 목록 위, 좁으면 감김 | `gap="sm"` 12. 주 행동은 "기록 추가" primary 하나, 나머지는 secondary |
|
|
52
|
+
| 예제 도구 | 미리보기 도구 캡션 + Button `size="small"` `tone="ghost"` | 변경 행동 아래 | 캡션과 버튼 사이 `spacing.xs` 8. 제품에는 복사하지 않는다 |
|
|
53
|
+
| 데이터 | List | 변경 결과 아래 | separator full, 공통 ListRow/TextField 크기. 메모 TextField는 `layoutStyle` 좌우 margin `listRowRecipe.density.comfortable.paddingHorizontal`(8)로 행 글자 시작선에 맞춘다 |
|
|
54
|
+
| 빈 상태 | EmptyState | 목록 자리 가운데 | EmptyState 기본 |
|
|
51
55
|
| 등장 | ContentTransition | 각 stable id 항목 | rise=12px, motion.normal, easing.enter |
|
|
52
56
|
| 높이 | ContentTransition animateHeight | 목록 바깥 | 같은 stateKey를 유지하며 실제 측정 높이만 전환 |
|
|
53
57
|
|
|
@@ -66,7 +70,7 @@ Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등
|
|
|
66
70
|
| 진행 중 | 새 행만 짧게 등장 | 기존 입력 노드와 초안 유지 |
|
|
67
71
|
| 실패 | 서버 실패가 있는 제품은 기존 목록·초안을 보존하고 오류를 별도 표시 | 이 로컬 예제에는 네트워크 요청 없음 |
|
|
68
72
|
| 삭제 | 해당 행 즉시 제거 | Web pointer 삭제는 먼저 삭제 버튼으로 초점을 옮긴다. 마지막 행은 삭제 버튼도 비활성되므로 추가 버튼으로 이동 |
|
|
69
|
-
| 비어 있음 |
|
|
73
|
+
| 비어 있음 | EmptyState(제목·설명)가 목록 자리 가운데 | 추가 버튼 유지 |
|
|
70
74
|
| 모션 감소·정지 | 같은 데이터, 공간 전환 없음 | 의미·입력 순서 동일 |
|
|
71
75
|
|
|
72
76
|
## 코드 골격
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
| --- | --- | --- |
|
|
23
23
|
| BottomNavigation | 목적지 2~6개, 선택 상태, 배지 | [BottomNavigation](../components/bottom-navigation.md) |
|
|
24
24
|
| IconButton `tone="primary" size="large" shape="circle"` | `primaryAction`: 목적지가 아닌 행동 | [IconButton](../components/icon-button.md) |
|
|
25
|
-
| HjmProvider / HjmNativeProvider `brandPalette` | 막대 범위의 제품 색(스토리 색은 예시) | [DesignSystemProvider](../components/design-system-provider.md) |
|
|
26
|
-
|
|
|
25
|
+
| HjmProvider / HjmNativeProvider `brandPalette` | 막대 범위의 제품 색(스토리 색은 예시). `accent`는 흰 글자를 올리는 채움색, `soft`는 옅은 표면색이다. Web 갤러리는 `host="contents"`로 Provider 자체는 칠하지 않고 Card가 표면을 칠한다 | [DesignSystemProvider](../components/design-system-provider.md) |
|
|
26
|
+
| Card `title`·`description` | 스토리에서 참고 막대 하나를 묶는 갤러리 틀(제품에는 필요 없음). 제목 단계는 페이지 Heading `level3` → 동작 묶음 Heading `level4` → Card 제목 순서로 내려간다 | [Card](../components/card.md) |
|
|
27
|
+
| SegmentedControl | 스토리에서 참고 표현을 고르는 단일 선택기(제품에는 필요 없음). `selected` Button 나열로 만들지 않는다([선택 조립 규칙](../SELECTION.md#조립-규칙필수)) | [SegmentedControl](../components/segmented-control.md) |
|
|
27
28
|
|
|
28
29
|
## 배치
|
|
29
30
|
|
|
@@ -142,7 +143,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
142
143
|
`route`·`items`·`routes`·`Link`·`renderGlyph`·`PlusGlyph`·`barRef`/`barHeight`(막대 높이 측정)·`navigation`은 제품 소유다.
|
|
143
144
|
탭 navigator를 쓰면 `View` 대신 navigator의 `tabBar`에 `BottomNavigation`을 넘기고 본문은 각 탭 화면이 그린다.
|
|
144
145
|
|
|
145
|
-
스토리의 참고 색(`accent`·`soft`), 아이콘 세트, 막대를 감싼
|
|
146
|
+
스토리의 참고 색(`accent`·`soft`), 아이콘 세트, 막대를 감싼 갤러리 Card와 읽기 폭(`Container size="reading"`) 틀은
|
|
146
147
|
제품에 가져오지 않는다. 제품 색은 앱 루트 Provider의 `brandPalette`로 준다.
|
|
147
148
|
|
|
148
149
|
## 플랫폼 차이
|
|
@@ -165,3 +166,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
165
166
|
2026-10-13 예제는 사각 선택 표현을 공개 tile 축으로 이관하고 내부 radius·padding CSS와 Native deprecated 스타일을 제거했다. Web 갤러리의 `style.position="relative"`는 fixed 막대를 문서 흐름에서 비교하기 위한 showcase 전용 배치다. 제품 하단 탐색에는 복사하지 않으며 `layoutStyle`은 position을 지원하지 않는다.
|
|
166
167
|
|
|
167
168
|
비교 예제는 목적지 이름과 현재 선택만 바꾸는 메모리 데모다. 주 행동은 작성 화면을 열지 않고 실행 횟수를 올린다. Web 링크는 인스턴스별 fragment를 사용해 현재 선택과 브라우저 뒤로·앞으로 이동을 동기화하고 다른 막대의 주소는 무시한다. 표현 preset을 바꾸면 해당 막대의 초기 선택과 실행 횟수로 새로 시작한다. 실제 router·작성 화면·저장 API는 제품이 연결한다.
|
|
169
|
+
|
|
170
|
+
## 근거
|
|
171
|
+
|
|
172
|
+
- 2026-10-17 구성 리뷰: 프로젝트 참고 색이 `accent`/`soft`를 뒤집어 선택 타일이 흰 바탕에 흰 글자로 보였다. Web만 파일 내 CSS 테두리 틀을 써서 플랫폼 구조가 달랐고, 세 단계 제목이 같은 크기였으며, 데스크톱에서 선택 컨트롤이 1280 폭 전체로 늘어났다. 그래서 공개 Card와 읽기 폭 Container로 바꾸고 제목 단계를 나눴다.
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
|
|
45
45
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
46
46
|
| --- | --- | --- | --- |
|
|
47
|
-
| 바깥 틀 | 제품 화면의 `Container`(좌우
|
|
47
|
+
| 바깥 틀 | 제품 화면의 `Container size="reading"`(좌우 여백·Web 최대 폭 720) 안에 `Stack gap="md"` | 본문 흐름 안. 스크롤·안전 영역은 화면(문서 스크롤)이 소유하고, 텍스트 입력이 없어 키보드 처리는 없다 | 좌우 `Container` `gutter`(폭 600 미만 `compact` 16, 이상 `regular` 20). 요소 사이 `layout.contentGap` 16. 다른 구획과는 `layout.sectionGap` 24 |
|
|
48
48
|
| 지표 | AnimatedStatistic | 맨 위 | 값은 Statistic `comfortable`(`heading` 크기) |
|
|
49
49
|
| 값 갱신 | Button `secondary` | 지표 아래 | 높이 `medium` 44. 화면의 primary는 제품 주 행동에 남긴다 |
|
|
50
|
-
| 작업 메뉴 | MorphingMenu | 값 갱신 아래 | 트리거에서 펼쳐짐 |
|
|
50
|
+
| 작업 메뉴 | MorphingMenu | 값 갱신 아래 | 닫힌 트리거는 secondary Button과 같은 틀(라벨 + 끝의 `chevronDown`, 높이 44, 폭은 라벨 기준)이고 동작 줄이기·RTL의 `Menu` 트리거와 같은 모양이다. 트리거에서 펼쳐짐(열린 폭 240) |
|
|
51
51
|
| 결과 문구 | `Text as="p" role="status"` | 맨 아래 | — |
|
|
52
52
|
|
|
53
53
|
## 흐름과 상태
|