@hjmds/design-contracts 1.17.2 → 1.19.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/action-session.d.ts +16 -0
- package/dist/action-session.d.ts.map +1 -1
- package/dist/action-session.js +18 -0
- package/dist/action-session.js.map +1 -1
- package/dist/activity-chip.d.ts +127 -0
- package/dist/activity-chip.d.ts.map +1 -0
- package/dist/activity-chip.js +65 -0
- package/dist/activity-chip.js.map +1 -0
- package/dist/base-recipes.d.ts +1 -1
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +2 -1
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +9 -6
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +5 -3
- package/dist/behaviors.js.map +1 -1
- package/dist/bottom-navigation.d.ts +16 -0
- package/dist/bottom-navigation.d.ts.map +1 -1
- package/dist/bottom-navigation.js +18 -0
- package/dist/bottom-navigation.js.map +1 -1
- package/dist/card.d.ts +4 -0
- package/dist/card.d.ts.map +1 -1
- package/dist/card.js +4 -1
- package/dist/card.js.map +1 -1
- package/dist/carousel.d.ts +3 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +80 -9
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-recipes.d.ts +74 -3
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +38 -6
- package/dist/component-recipes.js.map +1 -1
- package/dist/content-transition.d.ts +1 -1
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +2 -2
- package/dist/content-transition.js.map +1 -1
- package/dist/field-group.d.ts +14 -0
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +9 -0
- package/dist/field-group.js.map +1 -1
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +10 -3
- package/dist/form.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +1 -5
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +2 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/screen-patterns.d.ts +10 -0
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +17 -3
- package/dist/screen-patterns.js.map +1 -1
- package/dist/toast-liquid.d.ts +1 -1
- package/dist/toast-liquid.d.ts.map +1 -1
- package/dist/toast-liquid.js +3 -2
- package/dist/toast-liquid.js.map +1 -1
- package/dist/toast.d.ts +29 -2
- package/dist/toast.d.ts.map +1 -1
- package/dist/toast.js +59 -3
- package/dist/toast.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/action-session.md +2 -0
- package/docs/activity-chip.md +34 -0
- package/docs/asset.md +7 -0
- package/docs/bottom-navigation.md +16 -1
- package/docs/brand-boundary.md +3 -3
- package/docs/collection-rail.md +4 -0
- package/docs/compound-controls.md +8 -3
- package/docs/consumer-policy.md +39 -120
- package/docs/generated/component-maturity.md +1 -1
- package/docs/generated/renderer-evidence.json +3 -3
- package/docs/generated/renderer-evidence.md +1 -1
- package/docs/generated/showcase-manifest.json +1 -1
- package/docs/gravity-letters.md +2 -0
- package/docs/grid-reveal.md +2 -0
- package/docs/layout.md +2 -2
- package/docs/load-more.md +5 -0
- package/docs/migration-native-legacy-removal.md +30 -17
- package/docs/optional-adapters.md +4 -0
- package/docs/screen-patterns.md +23 -1
- package/docs/toast.md +64 -2
- package/docs/usage/README.md +248 -243
- package/docs/usage/SELECTION.md +43 -0
- package/docs/usage/STANDARD.md +22 -10
- package/docs/usage/components/accordion.md +5 -3
- package/docs/usage/components/activity-chip.md +109 -0
- package/docs/usage/components/agreement.md +6 -9
- package/docs/usage/components/alert-dialog.md +12 -6
- package/docs/usage/components/asset.md +10 -4
- package/docs/usage/components/auth-provider-button.md +2 -2
- package/docs/usage/components/avatar.md +8 -6
- package/docs/usage/components/badge.md +4 -4
- package/docs/usage/components/bottom-cta.md +4 -4
- package/docs/usage/components/bottom-info.md +2 -2
- package/docs/usage/components/bottom-navigation.md +18 -6
- package/docs/usage/components/button.md +6 -3
- package/docs/usage/components/card.md +28 -7
- package/docs/usage/components/carousel.md +12 -4
- package/docs/usage/components/chat-message.md +10 -2
- package/docs/usage/components/chat-screen.md +5 -5
- package/docs/usage/components/checkbox-group.md +1 -1
- package/docs/usage/components/checkbox.md +2 -2
- package/docs/usage/components/chip.md +2 -2
- package/docs/usage/components/code-block.md +3 -4
- package/docs/usage/components/collapsible.md +6 -4
- package/docs/usage/components/combobox.md +2 -2
- package/docs/usage/components/command-palette.md +1 -1
- package/docs/usage/components/comment-thread-screen.md +7 -4
- package/docs/usage/components/container.md +1 -1
- package/docs/usage/components/content-transition.md +3 -2
- package/docs/usage/components/context-menu.md +2 -2
- package/docs/usage/components/counter-badge.md +3 -3
- package/docs/usage/components/data-table.md +1 -1
- package/docs/usage/components/date-picker.md +4 -4
- package/docs/usage/components/description-list.md +3 -3
- package/docs/usage/components/design-system-provider.md +2 -2
- package/docs/usage/components/dialog.md +14 -3
- package/docs/usage/components/divider.md +2 -2
- package/docs/usage/components/editor-screen.md +5 -3
- package/docs/usage/components/effect-surface.md +1 -1
- package/docs/usage/components/empty-state.md +6 -6
- package/docs/usage/components/field.md +5 -3
- package/docs/usage/components/file-picker.md +6 -3
- package/docs/usage/components/form.md +1 -1
- package/docs/usage/components/heading.md +3 -3
- package/docs/usage/components/icon-button.md +6 -3
- package/docs/usage/components/icon.md +1 -1
- package/docs/usage/components/image.md +5 -4
- package/docs/usage/components/keyboard-dock.md +1 -1
- package/docs/usage/components/layout.md +1 -1
- package/docs/usage/components/link.md +2 -2
- package/docs/usage/components/list-detail-screen.md +6 -6
- package/docs/usage/components/list-row.md +13 -4
- package/docs/usage/components/list.md +5 -4
- package/docs/usage/components/masonry.md +2 -1
- package/docs/usage/components/media-selection-screen.md +11 -5
- package/docs/usage/components/mentions.md +2 -2
- package/docs/usage/components/menu.md +3 -2
- package/docs/usage/components/menubar.md +2 -2
- package/docs/usage/components/message-composer.md +3 -3
- package/docs/usage/components/moderation-screen.md +3 -2
- package/docs/usage/components/notice.md +2 -2
- package/docs/usage/components/notification-inbox-screen.md +1 -1
- package/docs/usage/components/notification-item.md +2 -2
- package/docs/usage/components/number-field.md +3 -3
- package/docs/usage/components/otp-field.md +1 -1
- package/docs/usage/components/overview-screen.md +1 -1
- 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 +2 -2
- package/docs/usage/components/profile-screen.md +1 -1
- package/docs/usage/components/progress.md +2 -2
- package/docs/usage/components/qr-code.md +2 -2
- package/docs/usage/components/radio-group.md +1 -2
- package/docs/usage/components/radio.md +1 -2
- package/docs/usage/components/result.md +1 -1
- package/docs/usage/components/saved-items-screen.md +4 -2
- package/docs/usage/components/screen-layout.md +4 -3
- package/docs/usage/components/search-field.md +1 -1
- package/docs/usage/components/search-screen.md +4 -4
- package/docs/usage/components/section.md +3 -3
- package/docs/usage/components/segmented-control.md +4 -4
- package/docs/usage/components/select.md +2 -3
- package/docs/usage/components/settings-screen.md +18 -56
- package/docs/usage/components/sheet.md +10 -5
- package/docs/usage/components/sidebar.md +1 -1
- package/docs/usage/components/skeleton.md +1 -1
- package/docs/usage/components/skip-nav.md +1 -1
- package/docs/usage/components/slider.md +2 -2
- package/docs/usage/components/spinner.md +2 -2
- package/docs/usage/components/splitter.md +1 -1
- package/docs/usage/components/statistic.md +5 -4
- package/docs/usage/components/steps.md +2 -2
- package/docs/usage/components/surface.md +2 -3
- package/docs/usage/components/switch.md +1 -1
- package/docs/usage/components/tabs.md +4 -4
- package/docs/usage/components/tag.md +2 -2
- package/docs/usage/components/tags-input.md +4 -4
- package/docs/usage/components/text-area.md +5 -3
- package/docs/usage/components/text.md +85 -4
- package/docs/usage/components/timeline.md +2 -2
- package/docs/usage/components/toast.md +41 -13
- package/docs/usage/components/toggle-group.md +3 -3
- package/docs/usage/components/tooltip.md +1 -1
- package/docs/usage/components/top-bar.md +5 -4
- package/docs/usage/components/top.md +3 -3
- package/docs/usage/components/transfer-list.md +5 -4
- package/docs/usage/components/upload-item.md +3 -3
- package/docs/usage/components/virtual-list.md +1 -0
- package/docs/usage/compositions/action-feedback.md +42 -6
- package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
- package/docs/usage/compositions/action-recovery-save.md +19 -23
- package/docs/usage/compositions/action-recovery-undo.md +6 -1
- package/docs/usage/compositions/activity-status.md +168 -0
- package/docs/usage/compositions/collection-detail.md +2 -0
- package/docs/usage/compositions/command-records.md +15 -5
- package/docs/usage/compositions/common-message.md +4 -2
- package/docs/usage/compositions/common-notification.md +5 -3
- package/docs/usage/compositions/compound-controls.md +13 -5
- package/docs/usage/compositions/content-transition-comparison.md +2 -2
- package/docs/usage/compositions/date-entry.md +10 -5
- package/docs/usage/compositions/date-time-selection.md +3 -3
- package/docs/usage/compositions/design-profile-comparison.md +7 -5
- package/docs/usage/compositions/disclosure.md +1 -1
- package/docs/usage/compositions/document-resource.md +10 -3
- package/docs/usage/compositions/expo-interactions.md +3 -2
- package/docs/usage/compositions/family-drawer.md +9 -4
- package/docs/usage/compositions/field-group.md +7 -1
- package/docs/usage/compositions/floating-action-button.md +18 -18
- package/docs/usage/compositions/illustrated-outcome.md +3 -2
- package/docs/usage/compositions/input-sheet.md +6 -2
- package/docs/usage/compositions/interaction-adapters.md +1 -1
- package/docs/usage/compositions/interaction-flow-apply.md +6 -3
- package/docs/usage/compositions/interaction-flow-draft.md +5 -2
- package/docs/usage/compositions/interaction-flow-search.md +5 -2
- package/docs/usage/compositions/live-list.md +7 -5
- package/docs/usage/compositions/navigation-bar-collection.md +8 -7
- package/docs/usage/compositions/optional-adapters.md +1 -1
- package/docs/usage/compositions/origin-dialog.md +5 -5
- package/docs/usage/compositions/paper-surface.md +1 -1
- package/docs/usage/compositions/photo-source.md +1 -1
- package/docs/usage/compositions/purpose-input-comment.md +5 -3
- package/docs/usage/compositions/purpose-input-message.md +5 -3
- package/docs/usage/compositions/reference-first.md +63 -42
- package/docs/usage/compositions/reference-review.md +60 -50
- package/docs/usage/compositions/reference-settings.md +60 -50
- package/docs/usage/compositions/selection-scope.md +48 -35
- package/docs/usage/compositions/stea-order-progress.md +1 -0
- package/docs/usage/compositions/stea-otp-verify.md +5 -3
- package/docs/usage/compositions/stea-pixel-empty.md +3 -3
- package/docs/usage/compositions/time-selection.md +6 -5
- package/docs/usage/compositions/upload-recovery.md +10 -1
- package/docs/usage/compositions/video-dialog.md +10 -3
- package/docs/usage/compositions/web-additions.md +5 -5
- package/docs/usage/compositions/web-navigation.md +1 -1
- package/docs/usage/screens/common-chat.md +21 -8
- package/docs/usage/screens/common-comments.md +13 -1
- package/docs/usage/screens/common-inbox.md +19 -6
- package/docs/usage/screens/common-login.md +19 -7
- package/docs/usage/screens/common-profile.md +22 -9
- package/docs/usage/screens/common-saved.md +16 -2
- package/docs/usage/screens/common-search.md +24 -4
- package/docs/usage/screens/common-settings.md +22 -50
- package/docs/usage/screens/common-shell.md +14 -2
- package/docs/usage/screens/dashboard.md +52 -109
- package/docs/usage/screens/discovery-gallery.md +57 -148
- package/docs/usage/screens/flow-collection.md +38 -9
- package/docs/usage/screens/flow-editor.md +25 -7
- package/docs/usage/screens/flow-media.md +23 -7
- package/docs/usage/screens/flow-moderation.md +15 -2
- package/docs/usage/screens/flow-onboarding.md +3 -3
- package/docs/usage/screens/flow-permission.md +17 -3
- package/docs/usage/screens/landing.md +37 -20
- package/docs/usage/screens/mockup-studio.md +22 -7
- package/docs/usage/screens/notification-settings.md +18 -4
- package/docs/usage/screens/product-bento.md +18 -9
- package/docs/usage/screens/reference-comparison.md +29 -10
- package/docs/usage/tokens/color.md +6 -3
- package/docs/usage/tokens/elevation-opacity.md +4 -2
- package/docs/usage/tokens/font-roles.md +2 -2
- package/docs/usage/tokens/layers.md +3 -3
- package/docs/usage/tokens/motion.md +2 -2
- package/docs/usage/tokens/radius.md +4 -2
- package/docs/usage/tokens/spacing.md +3 -3
- package/docs/usage/tokens/theme-studio.md +2 -2
- package/docs/usage/tokens/typography-studio.md +14 -8
- package/docs/usage/tokens/typography.md +15 -5
- package/package.json +7 -1
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-13
|
|
8
8
|
- 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
|
|
9
|
-
- 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드
|
|
9
|
+
- 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`, `실험/구성/피드백과 복구/백그라운드 작업 상태`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
| 화면 안에 계속 남아야 하는 안내·경고(점검 예정, 권한 없음, 폼 오류 요약) | [Notice](notice.md) |
|
|
21
21
|
| 사용자가 응답해야 진행되는 확인·삭제 확인·필수 선택 | [AlertDialog](alert-dialog.md) |
|
|
22
22
|
| 작업 결과를 화면 전체로 보여 줌 | [Result](result.md) |
|
|
23
|
+
| 헤더에 작업 상태를 상시 표시하고 눌러서 패널을 여닫음 | [ActivityChip](activity-chip.md) + 이 Toast 패널([구성](../compositions/activity-status.md)) |
|
|
23
24
|
|
|
24
25
|
선택 기준: 사용자가 놓쳐도 되고 자리를 차지하면 안 되면 Toast, 그 화면을 보는 동안 계속 보여야 하거나
|
|
25
26
|
읽지 않으면 다음 행동을 잘못할 수 있으면 Notice다. Toast에는 그 알림에서만 할 수 있는 행동을 두지 않는다.
|
|
@@ -78,20 +79,23 @@ function useProfileSavedToast() {
|
|
|
78
79
|
|
|
79
80
|
| prop | 값 | 기본값 | 설명 |
|
|
80
81
|
| --- | --- | --- | --- |
|
|
81
|
-
| descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`announcement`·`presentation` 선택 | — | 모두 `string`(ReactNode 아님) |
|
|
82
|
+
| descriptor | `id`·`description`·`closeLabel` 필수, `title`·`tone`·`priority`·`durationMs`·`action`·`press`·`announcement`·`presentation`·`media`·`progress` 선택 | — | 문구는 모두 `string`(ReactNode 아님). `media`만 예외 |
|
|
83
|
+
| `media` | `unknown`(Web·Native에서 `ReactNode`를 넘긴다) | — | 미게시(1.17.2 이후). 장식 시각물(마스코트·아바타). tone 배지 자리를 44(`toastRecipe.media.size`) 원으로 대신하고 보조기술에서 숨긴다 |
|
|
84
|
+
| `progress` | `{ steps: readonly string[]; current: number }` | — | 미게시(1.17.2 이후). 단계 2~5개, `current`는 0부터. 설명 아래 점+이름 줄로 그리고, 기본 낭독 끝에 현재 단계 이름을 붙인다. 범위 밖이면 throw |
|
|
82
85
|
| `action` | `{ label, accessibilityLabel?, onAction: () => void, dismissOnAction? }` | — | 누르면 `onAction`, 기본으로 알림이 닫힌다 |
|
|
86
|
+
| `press` | `{ accessibilityLabel: string; onPress: () => void }` | 없음 | 미게시. 카드 본문·미디어·여백을 누르면 콜백 후 `press` 사유로 닫힌다. 실행·닫기 버튼과 스와이프는 열기 동작을 실행하지 않는다. Web은 Tab/Enter/Space, Native는 독립 접근성 버튼 |
|
|
83
87
|
| `tone` | `neutral` · `info` · `success` · `warning` · `danger` | `neutral` | — |
|
|
84
88
|
| `priority` | `normal` · `high` | — | 낭독 순서, 색과 무관 |
|
|
85
|
-
| `durationMs` | ms · `null` | 3000 | 더 짧게 줘도 3000으로 올라간다. `null`은 수동으로 닫을 때까지 유지, `action
|
|
89
|
+
| `durationMs` | ms · `null` | 3000 | 더 짧게 줘도 3000으로 올라간다. `null`은 수동으로 닫을 때까지 유지, `action` 또는 `press`가 있으면 기본 유지 |
|
|
86
90
|
| `placement` | `bottom` · `top` · `top-start` · `top-end` · `bottom-start` · `bottom-end` | `bottom` | — |
|
|
87
91
|
| 큐 | `maxVisible` · `maxQueued` · `duplicatePolicy` · `timerUpdatePolicy` · `overflowPolicy` | 1 · 20 · `update` · `preserve` · `discard-oldest` | — |
|
|
88
92
|
| `publish` | Web `(descriptor, options?) => ToastPublishResult`, Native `(descriptor) => ToastPublishResult` | — | 결과는 `{ outcome: "added" \| "updated", id, position: "visible" \| "queued" }`, `{ outcome: "ignored", id, reason: "duplicate" \| "closing" }`, `{ outcome: "discarded", id, reason: "queue-overflow" }` 중 하나 |
|
|
89
93
|
| `dismiss` | Web `(id, reason) => boolean`, Native `(id, reason?) => boolean` | — | 닫았으면 `true`. Web은 `close(id)`도 있다. Native는 `pause`·`resume(id, reason?)`도 있다 |
|
|
90
|
-
| 닫힘 사유 `ToastDismissReason` | `timeout` · `action` · `close-action` · `escape` · `swipe` · `programmatic` · `queue-overflow` · `interrupted` | — | Web `Toast` `onDismissRequest`·Native `onDismiss`가 받는다 |
|
|
94
|
+
| 닫힘 사유 `ToastDismissReason` | `timeout` · `action` · `press` · `close-action` · `escape` · `swipe` · `programmatic` · `queue-overflow` · `interrupted` | — | Web `Toast` `onDismissRequest`·Native `onDismiss`가 받는다 |
|
|
91
95
|
| `toasts`/`defaultToasts` + `onToastsChange`(Native `ToastRegion`) | `readonly ToastDescriptor[]`, `(toasts) => void` | — | 큐를 제품 상태로 제어할 때 |
|
|
92
96
|
| `safeAreaInsets`(Native) | `{ top?, bottom?, left?, right?, start?, end? }` | `{}` | — |
|
|
93
97
|
| `layoutStyle`(Native `Toast`·`ToastRegion`) | `HjmCompositionStyleProp` | — | Web Toast·ToastProvider에는 없다(떠 있는 층이라 배치 대상이 아님) |
|
|
94
|
-
| `style`·`toastStyle`(Native) |
|
|
98
|
+
| `style`·`toastStyle`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `placement`·`safeAreaInsets`. 현재 공개 props에서 제거되었다 |
|
|
95
99
|
|
|
96
100
|
### 디자인 프로필 상속
|
|
97
101
|
|
|
@@ -101,10 +105,10 @@ function useProfileSavedToast() {
|
|
|
101
105
|
프로필 없는 소비자의 기본값은 유지한다. 상태·초안·선택·Modal teardown은 이 축의 소유가 아니다.
|
|
102
106
|
플랫폼 근사와 미검증 범위는 [프로필 계약](../../design-profile.md#오버레이선택-입력의-프로필-연결-보완)을 따른다.
|
|
103
107
|
|
|
104
|
-
Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg
|
|
108
|
+
Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`(패널 이전 `radius.lg`)과 `shadow.raised`를
|
|
105
109
|
읽는다. 일반 Toast의 `floating` 대신 `raised`를 유지하는 이유는 리퀴드 카드의 얕은 깊이를
|
|
106
110
|
정한 기존 검토다. RN 내용 clip과 Skia 카드 모서리를 함께 바꾸고 그림자의 위·아래·좌·우
|
|
107
|
-
여유를 확보한다. 프로필이 없으면
|
|
111
|
+
여유를 확보한다. 프로필이 없으면 모서리 24(패널, 미게시 1.17.2 이후. 그전 12)와 foundation `raised`를 쓴다.
|
|
108
112
|
원형 시작점·큐·행동은 유지하며 Web은 일반 Toast fallback이다. 기존
|
|
109
113
|
`실험/구성/비교와 검증/테마 조합`의 Native ‘물방울 알림의 테마 비교’에서 같은 알림을
|
|
110
114
|
유지한 채 다음 테마로 바꿀 수 있다. 기기 시각·음성·제스처 검증은 별도로 남는다.
|
|
@@ -114,9 +118,9 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
|
|
|
114
118
|
|
|
115
119
|
| 항목 | 값 | 근거 |
|
|
116
120
|
| --- | --- | --- |
|
|
117
|
-
| 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 열은 [아이콘 배지 32][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
|
|
121
|
+
| 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 모서리 `radius.xl` 24(패널, 미게시 1.17.2 이후. 그전 `lg` 16). 열은 [아이콘 배지 32 또는 `media` 44][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill. `progress` 점 6(현재 단계 1.4배) | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
|
|
118
122
|
| 간격 | 화면 가장자리에서 `spacing.md` 16 + 안전 영역(`safeAreaMode: "additive"`). 여러 장 사이 `spacing.sm` 12. 카드 안 열 간격 `spacing.sm` 12 | `toastRecipe.viewport`, `.hjm-toast-viewport` |
|
|
119
|
-
| 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. `action`은 둘째 줄 끝 | `toastRecipe.placements`, `.hjm-toast__action` |
|
|
123
|
+
| 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. 문구 열 안 순서는 제목 → 설명 → `progress` → `action`. `action`은 문구 아래 시작 쪽(`toastRecipe.action.align` `start`, 미게시 1.17.2 이후. 그전 둘째 줄 끝) | `toastRecipe.placements`, `.hjm-toast__action` |
|
|
120
124
|
| 고정·스크롤 | 화면 흐름 밖에 떠 있는 층이라 본문 레이아웃에 자리를 만들지 않는다. 하단 고정 바(BottomNavigation·BottomCTA)가 있으면 Web `bottomOffset`(px 또는 CSS 길이), Native `keyboardOffset`으로 그 위에 띄운다. Native 아래쪽 배치는 `avoidKeyboard`(기본 true)로 키보드 위에 선다 | `react/src/toast.tsx`, `react-native/src/feedback.tsx`(`ToastRegion`) |
|
|
121
125
|
| 좁은 폭·큰 글자 | 폭이 줄고 문구가 줄바꿈되어 카드가 세로로 자란다. 잘라 내지 않는다 | `.hjm-toast__content`(`overflow-wrap: anywhere`) |
|
|
122
126
|
|
|
@@ -126,13 +130,28 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
|
|
|
126
130
|
│ 본문(스크롤) │ │ 본문(스크롤) │
|
|
127
131
|
│ │ │ ┌──────────────────────────┐ │
|
|
128
132
|
│ ┌──────────────────────────┐ │ │ │(●) 저장했어요 [×] │ │
|
|
129
|
-
│ │(●) 저장했어요 [×] │ │ │ │
|
|
133
|
+
│ │(●) 저장했어요 [×] │ │ │ │ [되돌리기] │ │ ← action(선택), 문구 아래 시작 쪽
|
|
130
134
|
│ └──────────────────────────┘ │ │ └──────────────────────────┘ │
|
|
131
135
|
│ ↑ 16 + 안전 영역 │ │ ↑ bottomOffset/keyboardOffset
|
|
132
136
|
└──────────────────────────────┘ │ [홈] [검색] [내 정보] │ ← 고정 바
|
|
133
137
|
좌우 16, 최대 폭 420, 가운데 └──────────────────────────────┘
|
|
134
138
|
```
|
|
135
139
|
|
|
140
|
+
### 패널 — media·progress (미게시, 1.17.2 이후)
|
|
141
|
+
|
|
142
|
+
2026-10-10 사용자 요청으로 패널이 Toast의 모양이 됐다(기본·리퀴드 모두). 진행 중인 작업은 같은 `id`, `durationMs: null`로
|
|
143
|
+
열어 두고 단계가 바뀔 때마다 같은 `id`로 다시 `publish`해 제자리에서 갱신한다. 끝나면 `progress`를 빼고 결과 문구와
|
|
144
|
+
`action`으로 바꾼다. 여닫는 트리거는 [ActivityChip](activity-chip.md)이다.
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
┌──────────────────────────────────────┐
|
|
148
|
+
│ (🐥) 도우미 [×] │ ← media 44가 tone 배지 자리를 대신
|
|
149
|
+
│ 앱을 다듬고 있어요. │
|
|
150
|
+
│ ● 읽기 ◉ 다듬기 ○ 확인 │ ← progress: 완료·현재(굵게)·예정, 점 6
|
|
151
|
+
│ [결과 보기] │ ← action: 문구 아래 시작 쪽
|
|
152
|
+
└──────────────────────────────────────┘ 모서리 radius.xl 24
|
|
153
|
+
```
|
|
154
|
+
|
|
136
155
|
## 꼭 지킬 것
|
|
137
156
|
|
|
138
157
|
- Provider/Region은 앱에 하나만 두고 화면마다 새로 만들지 않는다. 진행 상태를 바꿀 때는 같은 `id`로 다시 `publish`한다.
|
|
@@ -159,14 +178,14 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
|
|
|
159
178
|
- 리퀴드는 `placement="top"`과 `maxVisible={1}`일 때만 동작하고, 아니면 `ToastRegion`이 `TypeError`를 던진다.
|
|
160
179
|
어댑터는 render 밖에서 만들거나 memo한다.
|
|
161
180
|
- ToastRegion 배치는 `layoutStyle={{ flex: 1 }}`로 쓴다. Native 예제도 이 경로를 사용한다.
|
|
162
|
-
- Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(
|
|
181
|
+
- Web `Toast` 단독 렌더는 나머지 HTML 속성(id·data-*·이벤트)을 루트에 전달한다(1.13.0 변경. 1.12.1은 `className`만 전달). `role`·`aria-labelledby`·`aria-describedby`·`data-tone`·`data-state`는 Toast가 정하므로 덮이지 않는다. 배치는 Provider를 쓴다.
|
|
163
182
|
|
|
164
183
|
|
|
165
184
|
### 고정 아이콘과 큰 글자
|
|
166
185
|
|
|
167
186
|
2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘린 재현에 따라 Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지한다. 주변 제목·라벨은 계속 확대한다. Chip의 체크와 Toast 닫기는 기존 비확대 경로를 유지하며 회귀 검사에 포함한다. 제품이 전달한 아이콘 슬롯은 제품이 같은 조건을 검증한다.
|
|
168
187
|
|
|
169
|
-
### 프로필 행간 소비 —
|
|
188
|
+
### 프로필 행간 소비 — 1.16.0 변경
|
|
170
189
|
|
|
171
190
|
2026-10-07 소비 감사에서 Web 제목/설명의 1.35/1.45 고정 행간이 `typography.body.lineHeight`를
|
|
172
191
|
우회하는 것을 확인했다. 두 slot은 기존 body 역할이므로 선택된 프로필의 `--hjm-type-body-line-height`를
|
|
@@ -174,3 +193,12 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.lg`
|
|
|
174
193
|
일괄 기본 행간 변경은 기존 알림 레이아웃까지 바꾸므로 채택하지 않았다. Native 본문 행간은 이미 body
|
|
175
194
|
토큰을 소비하며, 이번 Native 변경은 action 라벨의 ui 서체 역할만 명시한다. 알림 문구·큐·시간·닫기·
|
|
176
195
|
행동·초점 계약은 바꾸지 않는다.
|
|
196
|
+
|
|
197
|
+
### 카드에서 관련 화면 열기
|
|
198
|
+
|
|
199
|
+
댓글·답장·작업 완료 알림에서 `press: { accessibilityLabel: t("reply.open"), onPress: openReply }`를
|
|
200
|
+
같은 descriptor에 넣는다. `action`은 카드와 별개의 실행 버튼이 필요할 때만 함께 쓴다.
|
|
201
|
+
본문·이미지·카드 여백 전체가 열기 영역이며 실행·닫기 버튼은 각각 자기 동작만 수행한다.
|
|
202
|
+
Web은 전체 영역의 투명한 button과 두 제어 버튼을 형제로, Native는 뒤쪽 Pressable과 위쪽 제어 버튼을
|
|
203
|
+
독립적으로 배치한다. label은 동작 이름, 설명은 기존 알림 문구이며 기본 카드 배치와 크기는 바뀌지 않는다.
|
|
204
|
+
라우터는 HJM이 가져오지 않고 제품 콜백이 실행한다. 화면 이동 뒤 provider가 없어져도 중복 실행하지 않는다.
|
|
@@ -64,7 +64,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
|
|
|
64
64
|
| `size` | `small` · `medium` | `medium` | medium은 최소 높이 44 |
|
|
65
65
|
| `items[].disabled` | `boolean` | `false` | `disabled` 항목은 켜지지 않고 눌림 집합에도 들어가지 않는다. 사라진 id의 눌림 상태는 렌더 때 버려진다(`reconcileToggleGroupSelection`) |
|
|
66
66
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 묶음 컨테이너 배치. Web·Native 모두 |
|
|
67
|
-
| `style`(Native) |
|
|
67
|
+
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `size`. 현재 공개 props에서 제거되었다 |
|
|
68
68
|
|
|
69
69
|
## 배치
|
|
70
70
|
|
|
@@ -83,7 +83,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
|
|
|
83
83
|
- 라벨은 i18n 키로 넣고 짧게 둔다. 단일 선택 모드는 없다. 하나만 고르게 하려고 변환하지 않는다.
|
|
84
84
|
- 눌림 표시 색은 recipe(`idle`/`pressed`)가 소유한다. 상태는 Web `aria-pressed`, Native `selected`로
|
|
85
85
|
알리므로 색만으로 상태를 바꿔 그리지 않는다.
|
|
86
|
-
- 배치는 `layoutStyle`(묶음 컨테이너)로 한다. Web `className`·Native
|
|
86
|
+
- 배치는 `layoutStyle`(묶음 컨테이너)로 한다. Web `className`·Native에서 제거된 `style`로 항목 모양을 덮지 않는다.
|
|
87
87
|
|
|
88
88
|
## 플랫폼 차이
|
|
89
89
|
|
|
@@ -91,7 +91,7 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
|
|
|
91
91
|
| --- | --- | --- |
|
|
92
92
|
| 컨테이너 | `role="group"` + `aria-label` | `View` + `accessibilityLabel`(role 없음) |
|
|
93
93
|
| 배치 | CSS(`hjm-toggle-group`) | 가로 `flexWrap: "wrap"` |
|
|
94
|
-
| 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은
|
|
94
|
+
| 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
|
|
95
95
|
| 키보드 | 항목마다 tab stop(roving 없음) | 해당 없음 |
|
|
96
96
|
|
|
97
97
|
### 제품 테마의 모서리
|
|
@@ -72,7 +72,7 @@ Native: 없음. 계약상 Native는 `unsupported`이고 hover UI를 흉내 내
|
|
|
72
72
|
|
|
73
73
|
| 항목 | 값 | 근거 |
|
|
74
74
|
| --- | --- | --- |
|
|
75
|
-
| 크기 | 말풍선 안쪽 여백 사방 `spacing.xs` 8(`tooltipRecipe.surface.padding`; 2026-10-06까지 Web은 좌우 12였다, 1.
|
|
75
|
+
| 크기 | 말풍선 안쪽 여백 사방 `spacing.xs` 8(`tooltipRecipe.surface.padding`; 2026-10-06까지 Web은 좌우 12였다, 1.13.0 변경), 모서리 `radius.sm` 8, 최대 폭 280(`tooltipRecipe.content.maxWidth`, 화면 폭 − 24를 넘지 않음). trigger는 그 자체로 최소 터치 영역 44를 지켜야 한다(Tooltip은 영역을 넓히지 않는다). 보통 [IconButton](icon-button.md) | `tooltipRecipe.surface`, `.hjm-tooltip__content` |
|
|
76
76
|
| 간격 | trigger와 4(`positioning.sideOffset` = `spacing.xxs`), 뷰포트 가장자리와 최소 12(`positioning.collisionPadding` = `spacing.sm`) | `tooltipRecipe.positioning`, `react/src/overlays.tsx`(`Tooltip`) |
|
|
77
77
|
| 순서·정렬 | trigger 바로 옆에 붙는다. 한 줄에 Tooltip 달린 아이콘 버튼이 여럿이면 하나만 보이고, 300ms 안에 옆으로 옮기면 바로 열린다 | `react/src/overlays.tsx`(`Tooltip`) |
|
|
78
78
|
| 고정·스크롤 | 떠 있는 층(`position: fixed`)이라 문서 흐름에 자리를 만들지 않고 trigger 크기도 바꾸지 않는다. 공간이 모자라면 반대쪽으로 뒤집힌다 | `.hjm-tooltip__content`, `react/src/portal.tsx` |
|
|
@@ -75,10 +75,11 @@ import { TopBar, TopBarAction } from "@hjmds/react-native/top-bar";
|
|
|
75
75
|
| `titleLeading` | `ReactNode` | — | 제목 앞 작은 시각 요소 |
|
|
76
76
|
| `leading` / `actions`(별칭 `trailing`) | `ReactNode` | — | 둘 중 한 이름만 쓴다 |
|
|
77
77
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
|
|
78
|
-
| Native `style`·`leadingStyle`·`titleStyle`·`trailingStyle` |
|
|
78
|
+
| Native `style`·`leadingStyle`·`titleStyle`·`trailingStyle` | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `centered`. 현재 공개 props에서 제거되었다 |
|
|
79
79
|
| `TopBarAction` `intent` | `button` · `link` | `button` | `button`은 `onPress: (event) => void`, `link`는 `destination` + `onNavigate?: (destination: LinkDestination) => void \| Promise<void>` 또는 `renderLink` |
|
|
80
80
|
| `TopBarAction` `labelVisibility` | `visible` · `accessibility-only` | `visible` | — |
|
|
81
|
-
| `TopBarAction` `layoutStyle` | `HjmCompositionStyleProp` | — | `style`·`labelStyle`은
|
|
81
|
+
| `TopBarAction` `layoutStyle` | `HjmCompositionStyleProp` | — | `style`·`labelStyle`은 제거된 속성 |
|
|
82
|
+
| Native `TopBarAction` `renderAction`·`renderLink` | 렌더 콜백 | — | 전달받은 control props의 `style`·접근성·동작은 정식 host 입력이다. 이를 host에 그대로 연결하며 제거된 소비자 override prop과 구분한다 |
|
|
82
83
|
| `NavigationBar` | `label`·`brand`·`children` 필수, `actions` 선택 | — | Web만 `layoutStyle`도 받는다 |
|
|
83
84
|
|
|
84
85
|
## 배치
|
|
@@ -108,7 +109,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
|
|
|
108
109
|
- `title` 없이 `titleLeading`·`onTitleClick`/`onTitlePress`·`titleAccessibilityLabel`을 주면 `TypeError`다.
|
|
109
110
|
- 이동은 슬롯 안의 Link(Native는 `intent="link"`)로, 제목 클릭은 실제 버튼으로 표현한다.
|
|
110
111
|
- 화면 이름·행동 라벨은 i18n 키로, 아이콘은 제품 소유다. 높이·간격·제목 색은 recipe가 정한다.
|
|
111
|
-
배치는 `layoutStyle`로만 하고 Native
|
|
112
|
+
배치는 `layoutStyle`로만 하고 Native에서 제거된 슬롯 스타일로 덮지 않는다.
|
|
112
113
|
- 큰 글자에서 Native는 슬롯 구조를 다시 만들어 슬롯 안 로컬 상태·포커스가 초기화될 수 있다.
|
|
113
114
|
유지할 상태는 TopBar 밖에 둔다.
|
|
114
115
|
- `NavigationBar`는 `label`(빈 문자열이면 throw)·`brand`·`children` 필수, `actions` 선택이다.
|
|
@@ -120,7 +121,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
|
|
|
120
121
|
| --- | --- | --- |
|
|
121
122
|
| 루트 | `div`(Dialog 안에서도 landmark를 추가하지 않음), ref·HTML 속성 전달 | `accessibilityRole="toolbar"` `View`, ref 없음 |
|
|
122
123
|
| 제목 접근성 | `h1`~`h6` | `accessibilityRole="header"`, `titleAccessibilityHint`(`onTitlePress` 필수) |
|
|
123
|
-
| 슬롯 스타일 | `className`·`style`, 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`·`leadingStyle`·`titleStyle`·`trailingStyle`은
|
|
124
|
+
| 슬롯 스타일 | `className`·`style`, 배치는 `layoutStyle` | 배치는 `layoutStyle`(`style`·`leadingStyle`·`titleStyle`·`trailingStyle`은 제거된 속성) |
|
|
124
125
|
| 큰 글자 | CSS가 제목을 다음 행으로 | 글자 배율이 `largeTextThreshold` 이상이면 leading+제목 행 아래에 행동 행 |
|
|
125
126
|
| 행동 컴포넌트 | 일반 `IconButton`·`Link` | `TopBarAction`(아이콘 + 마이크로 라벨, 44pt) |
|
|
126
127
|
| `NavigationBar` 배경 | 지원 시 blur + 92% 불투명 | 불투명 surface(blur 없음) |
|
|
@@ -60,7 +60,7 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
60
60
|
| `descriptor.headingLevel` | `1` · `2` · `3` | `1` | 한 라우트가 여러 화면을 담을 때만 낮춘다(Web만 반영) |
|
|
61
61
|
| `trailing` | `ReactNode`(요소 하나) | — | 제목 줄을 같이 쓰는 보조 행동. 공간이 모자라거나 큰 글자면 아래로 내려간다 |
|
|
62
62
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 루트 배치. Web·Native 모두 |
|
|
63
|
-
| `style`(Native) |
|
|
63
|
+
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
|
|
64
64
|
|
|
65
65
|
## 배치
|
|
66
66
|
|
|
@@ -78,7 +78,7 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
78
78
|
- `eyebrow`에는 제목을 한정하는 짧은 말(카테고리·단계)만 넣고 제목에 없는 새 정보를 넣지 않는다.
|
|
79
79
|
- 주 행동을 `trailing`에 두지 않는다. 화면의 주 행동은 [BottomCTA](bottom-cta.md)나 본문 [Button](button.md)이다.
|
|
80
80
|
- 상태가 없다. 제목을 바꾸려면 다른 문자열을 넘긴다.
|
|
81
|
-
- 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은
|
|
81
|
+
- 배치는 `layoutStyle`로 한다(Web `className`도 받는다). Native `style`은 제거된 속성이다. 글자 크기·색은 recipe가 정하므로 덮지 않는다.
|
|
82
82
|
|
|
83
83
|
## 플랫폼 차이
|
|
84
84
|
|
|
@@ -92,4 +92,4 @@ import { Top } from "@hjmds/react-native/top";
|
|
|
92
92
|
|
|
93
93
|
2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level2`, `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
|
|
94
94
|
|
|
95
|
-
프로필 연결은 1.
|
|
95
|
+
프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
|
|
@@ -73,7 +73,7 @@ import { TransferList } from "@hjmds/react-native/transfer-list";
|
|
|
73
73
|
| `items` | `{ id, label, textValue, description?, disabled? }[]` | — | `disabled` 항목은 선택도 이동도 되지 않는다 |
|
|
74
74
|
| 패널별 체크 선택 | 내부 상태 | — | 이동 전 임시 상태라 prop으로 받지 않는다. 이동 버튼은 해당 패널에 선택이 있을 때만 활성화된다 |
|
|
75
75
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 최상위 컨테이너 배치. Web·Native 모두 |
|
|
76
|
-
| `style`(Native) |
|
|
76
|
+
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
|
|
77
77
|
|
|
78
78
|
## 배치
|
|
79
79
|
|
|
@@ -101,7 +101,8 @@ Web ≥ 480 Web < 480 · Native
|
|
|
101
101
|
|
|
102
102
|
- `labels`의 여섯 문구는 모두 제품 i18n으로 넣는다. HJM은 문장을 만들지 않는다.
|
|
103
103
|
- 이동 결과 낭독은 제품 몫이다. `onMove(movedIds, direction)`로 "2명 이동" 같은 문장을 만들어 알린다.
|
|
104
|
-
-
|
|
104
|
+
- 빈 패널의 안내는 해당 목록 내부에서 중앙 정렬한다. Native에서 패널에 남는 높이가 있으면 안내가 그 영역의 세로 중앙을 채운다.
|
|
105
|
+
- 배치는 `layoutStyle`(최상위 컨테이너)로 한다. Web `className`·Native에서 제거된 `style`로 패널·행 모양을 덮지 않는다.
|
|
105
106
|
|
|
106
107
|
## 플랫폼 차이
|
|
107
108
|
|
|
@@ -112,10 +113,10 @@ Web ≥ 480 Web < 480 · Native
|
|
|
112
113
|
| 키보드 | 화살표·Home/End 이동, Space 선택, Enter로 그 행만 이동 | 없음 |
|
|
113
114
|
| 이동 후 포커스 | 빈 자리로 올라온 행, 비면 빈 상태 문구 | 옮기지 않음 |
|
|
114
115
|
| 패널 비었을 때 전체 선택 | 활성 | `disabled` |
|
|
115
|
-
| 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은
|
|
116
|
+
| 외부 꾸밈 | `className`, `ref`, `layoutStyle` | `layoutStyle`(`style`은 제거된 속성) |
|
|
116
117
|
|
|
117
118
|
### 프로필 모서리·제목 소비
|
|
118
119
|
|
|
119
120
|
2026-10-07 소비 감사에서 Native 패널의 모서리 12가 프로필을 우회했다. Web 패널의 기존 `radius.md`와 같은 역할을 Native도 가까운 프로필에서 읽으며, 프로필이 없으면 기존 12를 유지한다. 선택 집합·이동·비활성 항목·초점 계약은 바꾸지 않는다.
|
|
120
121
|
|
|
121
|
-
프로필 연결은 1.
|
|
122
|
+
프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
|
|
@@ -70,7 +70,7 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
|
|
|
70
70
|
| `onCancel` / `onRetry` | `(id: string) => void` | — | descriptor `id`를 받는다. `uploading`·`error` 상태에서는 각각 필수 |
|
|
71
71
|
| `leading` | `ReactNode` | — | 아이콘·썸네일(장식) |
|
|
72
72
|
| `layoutStyle` | `HjmCompositionStyleProp` | — | 행 바깥 배치. Web·Native 모두 |
|
|
73
|
-
| `style`(Native) |
|
|
73
|
+
| `style`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle`. 현재 공개 props에서 제거되었다 |
|
|
74
74
|
|
|
75
75
|
## 배치
|
|
76
76
|
|
|
@@ -96,7 +96,7 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
|
|
|
96
96
|
|
|
97
97
|
| 항목 | Web | Native |
|
|
98
98
|
| --- | --- | --- |
|
|
99
|
-
| 루트 | `role="group"`, `aria-label`=파일명, `HTMLAttributes`·`ref`·`layoutStyle` 전달 | 일반 `View`, `layoutStyle`(`style`은
|
|
99
|
+
| 루트 | `role="group"`, `aria-label`=파일명, `HTMLAttributes`·`ref`·`layoutStyle` 전달 | 일반 `View`, `layoutStyle`(`style`은 제거된 속성) |
|
|
100
100
|
| 상태 낭독 | 상태 문장 live region | 파일 정보 묶음이 한 요소(`busy` state, value=상태 문장), 액션은 별도 버튼 |
|
|
101
101
|
| `leading` | `aria-hidden` | 접근성 트리에서 숨김 |
|
|
102
102
|
|
|
@@ -104,4 +104,4 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
|
|
|
104
104
|
|
|
105
105
|
2026-10-07 소비 감사에서 Native 행의 모서리 12가 프로필을 우회했다. Web의 기존 `radius.md`와 같은 역할을 Native도 가까운 프로필에서 읽으며, 프로필이 없으면 기존 12를 유지한다. 업로드 상태·취소/재시도·진행 알림은 바꾸지 않는다.
|
|
106
106
|
|
|
107
|
-
프로필 연결은 1.
|
|
107
|
+
프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
|
|
@@ -93,6 +93,7 @@ import { VirtualList } from "@hjmds/react-native/virtual-list";
|
|
|
93
93
|
- `rowHeight`는 현재 글자 배율에서 행 내용이 들어가는 값으로 제품이 정한다. 행 내용이 넘쳐도 잘리거나 겹칠 뿐
|
|
94
94
|
늘어나지 않는다. 높이를 확정할 수 없으면 List를 쓴다.
|
|
95
95
|
- 행 내용·데이터 가져오기는 제품 소유, 창 계산·키보드 탐색은 HJM 소유다.
|
|
96
|
+
- `empty` 안내는 `height`로 정한 목록 내부 영역의 가로·세로 중앙에 놓인다. Web에서 포인터나 프로그램으로 행에 초점을 옮긴 뒤에도 방향키는 그 행부터 이어진다.
|
|
96
97
|
|
|
97
98
|
## 플랫폼 차이
|
|
98
99
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.14.0
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-13
|
|
8
8
|
- 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
|
|
9
9
|
- 스토리북: `배포/구성/피드백과 복구/버튼 완료 피드백`
|
|
10
10
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
## 언제 쓰나
|
|
14
14
|
|
|
15
|
-
입력을 유지하며 저장의 진행·성공·재시도 가능 실패를 보여 줄 때 쓴다.
|
|
15
|
+
입력을 유지하며 저장의 진행·성공·재시도 가능 실패를 보여 줄 때 쓴다. 화면 wrapper 없이 아래 공개 컴포넌트와 `resolveSaveFeedback`을 조합한다.
|
|
16
16
|
|
|
17
17
|
## 구성 요소
|
|
18
18
|
|
|
@@ -50,16 +50,50 @@
|
|
|
50
50
|
| 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
|
|
51
51
|
| 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
|
|
52
52
|
|
|
53
|
+
## 현재 초안과 저장 응답 연결
|
|
54
|
+
|
|
55
|
+
`@hjmds/design-contracts/action-session`의 `resolveSaveFeedback`은 Web/Native 공통 순수 함수다.
|
|
56
|
+
알림 설정과 첫 기록 화면에서 반복하던 저장 응답·현재 초안 분기를 공유한다.
|
|
57
|
+
`phase`는 기존 action session 또는 제품 mutation의 상태, `dirty`는 **현재 초안과 마지막 확정 값**의 비교,
|
|
58
|
+
`valid`는 제품 검증 결과, `hasSaved`는 확정 값 존재 여부다. 낙관적 값과 비교해 저장 완료로 판단하지 않는다.
|
|
59
|
+
선택적 `disabled`는 권한 등 제품의 실행 잠금이며 기본값은 false다.
|
|
60
|
+
|
|
61
|
+
| 결과 | 의미 |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `pending` | 요청 진행 중. 입력을 유지하고 제출은 막음 |
|
|
64
|
+
| `saved` | 성공한 확정 값과 현재 초안이 같음 |
|
|
65
|
+
| `saved-with-changes` | 이전 제출은 성공했지만 현재 초안은 아직 미저장 |
|
|
66
|
+
| `error` | 요청 실패 후 변경이 남음. 현재 초안을 다시 저장할 수 있음 |
|
|
67
|
+
| `unsaved` | 아직 저장하지 않은 변경이 있음 |
|
|
68
|
+
| `unchanged` | 새 변경이 없음. 저장값으로 되돌린 뒤에는 이전 오류를 다시 표시하지 않음 |
|
|
69
|
+
|
|
70
|
+
`feedback`과 별도로 `currentSaved`는 현재 초안이 확정 값과 같은지 나타낸다.
|
|
71
|
+
`canSubmit`은 변경됨·유효함·진행 중 아님·잠금 아님을 모두 만족할 때만 true다.
|
|
72
|
+
이 함수는 요청 잠금이나 응답 무효화를 구현하지 않는다. 기존 `session.run`이 중복 실행을 막고
|
|
73
|
+
`session.reset`이 늦은 응답을 무효화한다. 버튼의 `disabled`만으로 요청 경합을 제어하지 않는다.
|
|
74
|
+
제품은 비교·문구·영구 저장을 소유하고, 실패 후 현재 초안을 저장할 때 새 제출값을 캡처해 `run`한다.
|
|
75
|
+
`retry`는 기존 실패 요청의 값 재시도이므로 현재 초안 저장과 구분한다.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { resolveSaveFeedback } from "@hjmds/design-contracts/action-session";
|
|
79
|
+
const feedback = resolveSaveFeedback({
|
|
80
|
+
phase: state.status,
|
|
81
|
+
dirty: !sameDraft(draft, confirmedValue),
|
|
82
|
+
valid: isValid(draft),
|
|
83
|
+
hasSaved,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
53
87
|
## 코드 골격
|
|
54
88
|
|
|
55
89
|
```tsx
|
|
56
90
|
// Web
|
|
57
|
-
<Button loading={
|
|
91
|
+
<Button loading={feedback.pending} disabled={!feedback.canSubmit} onClick={save}>{feedback.pending ? pendingLabel : saveLabel}</Button>
|
|
58
92
|
```
|
|
59
93
|
|
|
60
94
|
```tsx
|
|
61
95
|
// Native
|
|
62
|
-
<Button loading={
|
|
96
|
+
<Button loading={feedback.pending} disabled={!feedback.canSubmit} onPress={save}>{feedback.pending ? pendingLabel : saveLabel}</Button>
|
|
63
97
|
```
|
|
64
98
|
|
|
65
99
|
Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
|
|
@@ -67,11 +101,13 @@ Button의 실행 콜백은 Web `onClick`, Native `onPress`로 연결한다. 위
|
|
|
67
101
|
|
|
68
102
|
예제 응답은 useDemoAction의 인위 지연이며 실제 네트워크·영구 저장이 아니다.
|
|
69
103
|
완료 문구는 응답 확정 뒤 표시한다. 저장 중 입력 편집이 가능하므로, 저장 결과는 현재
|
|
70
|
-
초안으로 바꿔 쓰지 말고 응답 값으로 표시한다.
|
|
104
|
+
초안으로 바꿔 쓰지 말고 응답 값으로 표시한다. 현재 초안과 저장한 제목이 다르면 완료 체크를 제거하고
|
|
105
|
+
“현재 제목은 아직 저장하지 않았어요”와 마지막 저장 제목을 따로 보여 준다. 2026-10-13 실제 예제에서
|
|
106
|
+
진행 중 편집한 제목에도 이전 요청의 완료 체크가 남는 경로를 확인해 구분했다. 실패 체험 설정은 요청 중 바꾸지 않는다.
|
|
71
107
|
|
|
72
108
|
## 플랫폼 차이
|
|
73
109
|
|
|
74
110
|
| 항목 | Web | Native |
|
|
75
111
|
| --- | --- | --- |
|
|
76
112
|
| 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
|
|
77
|
-
|
|
|
113
|
+
| 좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
|
|
@@ -129,6 +129,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
|
|
|
129
129
|
|
|
130
130
|
```tsx
|
|
131
131
|
// Native
|
|
132
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
132
133
|
import { useState, useSyncExternalStore } from "react";
|
|
133
134
|
import { ScrollView, useWindowDimensions } from "react-native";
|
|
134
135
|
import { createActionSession } from "@hjmds/design-contracts/action-session";
|
|
@@ -163,7 +164,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
|
|
|
163
164
|
}
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
`setBookmark`(서버가 확정한 boolean을 돌려줌)
|
|
167
|
+
`setBookmark`(서버가 확정한 boolean을 돌려줌)는 제품 소유다. `StatusText`는 [공개 Text companion](../components/text.md#statustext)이다.
|
|
167
168
|
|
|
168
169
|
## 플랫폼 차이
|
|
169
170
|
|
|
@@ -178,3 +179,7 @@ function BookmarkToggle({ initial, setBookmark }: { initial: boolean; setBookmar
|
|
|
178
179
|
|
|
179
180
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
180
181
|
- 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
|
|
182
|
+
|
|
183
|
+
### 재시도 뒤 초점과 결과 구분
|
|
184
|
+
|
|
185
|
+
Web 예제는 사라질 재시도 버튼에서 기존 북마크 토글로 초점을 옮긴다. pending의 토글은 `aria-disabled`와 Button의 실행 차단을 사용해 키보드 위치를 유지한다. Native 예제의 `disabled`와 구분한다. 서버 새 상태로 reset한 뒤 이전 완료는 상태나 초점을 덮지 않는다. 예제는 메모리 요청이며 실제 서버 정합성을 검증하지 않는다.
|
|
@@ -145,6 +145,7 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
|
|
|
145
145
|
|
|
146
146
|
```tsx
|
|
147
147
|
// Native
|
|
148
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
148
149
|
import { useState, useSyncExternalStore } from "react";
|
|
149
150
|
import { ScrollView, useWindowDimensions } from "react-native";
|
|
150
151
|
import { createActionSession } from "@hjmds/design-contracts/action-session";
|
|
@@ -173,7 +174,7 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
|
|
|
173
174
|
{t("common.save")}
|
|
174
175
|
</Button>
|
|
175
176
|
{state.status === "error" && <Button tone="secondary" onPress={() => void session.retry()}>{t("save.retry")}</Button>}
|
|
176
|
-
<StatusText>{t(saveStatusKey[state.status])}</StatusText>{/*
|
|
177
|
+
<StatusText>{t(saveStatusKey[state.status])}</StatusText>{/* 공개 Text companion */}
|
|
177
178
|
{state.value ? <Text>{t("save.savedValue", { value: state.value })}</Text> : null}
|
|
178
179
|
</Stack>
|
|
179
180
|
</Container>
|
|
@@ -190,31 +191,20 @@ function SaveNote({ save }: { save(value: string): Promise<string> }) {
|
|
|
190
191
|
화면보다 오래 살아야 하면 제품의 작업 소유자(store)에 둔다. unmount 시 자동 reset은 기본으로 넣지 않는다.
|
|
191
192
|
- 구독은 Web·Native 모두 `useSyncExternalStore(session.subscribe, session.getSnapshot, session.getSnapshot)`다. SSR은 요청별 세션을 만든다.
|
|
192
193
|
- `reset(value)`는 세대를 바꿔 이전 응답과 재시도를 버린다. 서버 작업을 취소하지는 않는다.
|
|
193
|
-
- 상태
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
- 짧은 상태 문구는 공개 [StatusText](../components/text.md#statustext)를 쓴다. 문구와 실제 저장 결과는 제품이 소유하고,
|
|
195
|
+
Web live region·Native iOS 알림과 Android live region은 컴포넌트가 연결한다.
|
|
196
|
+
- 계속 표시되는 최초 안내는 iOS에서 알리지 않는다. 행동 결과를 조건부로 새로 마운트하면 Native의
|
|
197
|
+
`announceOnMount`를 명시한다. 빈 문구·같은 문구 재렌더·배경 변경은 iOS에서 발표하지 않으며 복귀 때 재생하지 않는다.
|
|
198
|
+
- Web은 동일 live node의 문구를 갱신한다. 조건부 마운트의 실제 낭독은 브라우저·보조기술에 따라 달라진다.
|
|
199
|
+
제목·설명·재시도 행동을 묶으면 Notice, 화면 위 일시 알림은 Toast를 고른다.
|
|
198
200
|
|
|
199
201
|
```tsx
|
|
200
202
|
// Native
|
|
201
|
-
import {
|
|
202
|
-
import {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
function StatusText({ children, ...props }: StatusTextProps) {
|
|
208
|
-
const previous = useRef(children); // 첫 렌더는 알리지 않는다
|
|
209
|
-
useEffect(() => {
|
|
210
|
-
const changed = previous.current !== children;
|
|
211
|
-
previous.current = children;
|
|
212
|
-
if (changed && children.trim() && Platform.OS === "ios" && AppState.currentState === "active") {
|
|
213
|
-
AccessibilityInfo.announceForAccessibilityWithOptions(children, { queue: true });
|
|
214
|
-
}
|
|
215
|
-
}, [children]);
|
|
216
|
-
return <Text {...props} accessibilityLiveRegion="polite">{children}</Text>;
|
|
217
|
-
}
|
|
203
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
204
|
+
// Web: import { StatusText } from "@hjmds/react/status-text";
|
|
205
|
+
|
|
206
|
+
<StatusText>{t(saveStatusKey[state.status])}</StatusText>
|
|
207
|
+
{saved ? <StatusText announceOnMount>{t("save.complete")}</StatusText> : null} // Native
|
|
218
208
|
```
|
|
219
209
|
|
|
220
210
|
## 플랫폼 차이
|
|
@@ -233,3 +223,9 @@ function StatusText({ children, ...props }: StatusTextProps) {
|
|
|
233
223
|
- 렌더 중 `createActionSession`을 부르면 매 렌더 새 세션이 생겨 중복 차단이 사라진다.
|
|
234
224
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
235
225
|
- 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
|
|
226
|
+
|
|
227
|
+
### 재시도 뒤 초점과 결과 구분
|
|
228
|
+
|
|
229
|
+
Web 예제는 재시도 버튼이 pending에서 사라지기 전에 저장 행동으로 초점을 옮긴다. 현재 입력이 비어 저장이 비활성화됐으면 입력칸으로 이동한다. 완료 시에는 추가 초점 이동을 하지 않아 사용자가 그동안 선택한 입력이나 다른 행동을 보존한다.
|
|
230
|
+
|
|
231
|
+
재시도는 실패한 제출 snapshot을 다시 실행하며, 새로 입력한 초안은 주 저장 행동으로 제출한다. 성공한 snapshot과 현재 초안이 다르면 현재 입력이 아직 저장되지 않았다고 별도로 안내한다. 확정 값으로 편집 중인 초안을 덮어쓰지 않는다.
|
|
@@ -138,6 +138,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
|
|
|
138
138
|
|
|
139
139
|
```tsx
|
|
140
140
|
// Native
|
|
141
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
141
142
|
import { useState, useSyncExternalStore } from "react";
|
|
142
143
|
import { ScrollView, useWindowDimensions } from "react-native";
|
|
143
144
|
import { createActionSession, type ActionSnapshot } from "@hjmds/design-contracts/action-session";
|
|
@@ -177,7 +178,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
|
|
|
177
178
|
}
|
|
178
179
|
```
|
|
179
180
|
|
|
180
|
-
`api.archive`·`api.unarchive`(서버가 확정한 boolean을 돌려줌)
|
|
181
|
+
`api.archive`·`api.unarchive`(서버가 확정한 boolean을 돌려줌)는 제품 소유다. `StatusText`는 [공개 Text companion](../components/text.md#statustext)이다.
|
|
181
182
|
|
|
182
183
|
## 플랫폼 차이
|
|
183
184
|
|
|
@@ -191,3 +192,7 @@ function ArchiveItem({ id, name, api }: { id: string; name: string; api: Archive
|
|
|
191
192
|
|
|
192
193
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
193
194
|
- 재시도는 secondary로 두어 저장과 경쟁하는 primary를 만들지 않는다(2026-10-06 예제 반영).
|
|
195
|
+
|
|
196
|
+
### 재시도 뒤 초점과 결과 구분
|
|
197
|
+
|
|
198
|
+
Web 예제는 사라질 재시도 버튼에서 기존 보관/실행 취소 행동으로 초점을 옮긴다. 해당 버튼은 pending에도 같은 노드로 남아 완료 후 다음 행동으로 이어진다. 역연산이 실패하면 마지막으로 확정된 보관 상태를 유지하고, 성공한 때에만 목록으로 돌아간다.
|