@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
|
@@ -70,20 +70,19 @@ Dialog (열림)
|
|
|
70
70
|
## 흐름과 상태
|
|
71
71
|
|
|
72
72
|
1. 목록을 아래로 스크롤하면 FAB가 원(`collapsed`)으로, 위로 스크롤하면 알약(`expanded`)으로 바뀐다. 같은 버튼 인스턴스라 포커스가 유지된다.
|
|
73
|
-
2. FAB를 누르면 Dialog가 열린다.
|
|
73
|
+
2. FAB를 누르면 Dialog가 열린다. 양 플랫폼 모두 FAB ref를 Dialog `returnFocusRef`에 연결해 포인터로 열었을 때도 닫기·저장 후 실제 FAB로 돌아오게 한다.
|
|
74
74
|
3. 내용을 쓰면 "기록 추가"가 활성화된다. 누르면 저장한다(비동기면 `busy`).
|
|
75
75
|
4. 성공하면 새 항목을 목록 맨 위에 넣고, 대화상자를 닫고, 입력을 비우고, 목록을 맨 위로 스크롤한다. 맨 위로 돌아오면 FAB 라벨이 다시 펼쳐진다.
|
|
76
76
|
5. 실패(네트워크·서버 오류)하면 입력을 비우지 않는다. Web은 대화상자를 연 채 본문 맨 위에 Notice를 보이고 "기록 추가"를 다시 누를 수 있다.
|
|
77
|
-
Native Dialog는
|
|
78
|
-
저장 중 닫기 요청을 무시한다(아래 코드 골격). 재시도도 실패하면 같은 Notice가 한 개만 남는다.
|
|
77
|
+
Native Dialog는 반환 Promise가 거절되면 열린 상태를 유지하고 `onActionError`를 호출한다. 저장 중 닫기는 Dialog가 차단한다. 재시도도 실패하면 같은 Notice가 한 개만 남는다.
|
|
79
78
|
|
|
80
79
|
| 상태 | 모습 | 포커스·알림 |
|
|
81
80
|
| --- | --- | --- |
|
|
82
81
|
| 기본 | 목록 + 펼친 FAB(아이콘 + "새 기록" 알약). 스크롤하면 접힌 원 52 | 접근성 이름은 접힘·펼침 모두 전체 `label` |
|
|
83
|
-
| 진행 중 | 저장 중. Dialog `busy`(닫기 막음), Web "기록 추가" `loading`, Native 행동 버튼 `
|
|
82
|
+
| 진행 중 | 저장 중. Dialog `busy`(닫기 막음), Web "기록 추가" `loading`, Native 내장 행동 버튼 `loading` | 포커스는 대화상자 안에 유지 |
|
|
84
83
|
| 실패 | 대화상자 열림 유지, 입력 유지, 본문 맨 위 `Notice tone="danger"` `t("notes.compose.failed")` | Web `danger` Notice는 `role="alert"`. Native는 `announcement="assertive"`를 줘야 알린다. 포커스 이동 없음 |
|
|
85
84
|
| 입력 비어 있음 | "기록 추가" `disabled` | — |
|
|
86
|
-
| 저장 후 | 새 항목이 맨 위, 대화상자 닫힘 |
|
|
85
|
+
| 저장 후 | 새 항목이 맨 위, 대화상자 닫힘 | 양 플랫폼 모두 FAB ref를 `returnFocusRef`로 지정한다. WebKit은 pointer 클릭이 FAB를 자동으로 focus하지 않으므로 명시 연결이 필요하다 |
|
|
87
86
|
| 큰 글자 | 라벨이 여러 줄이 되어 clearance가 커진다 | 측정값을 하단 padding에 그대로 반영 |
|
|
88
87
|
| 빈 목록 | 목록 자리에 [EmptyState](../components/empty-state.md), FAB는 그대로 | — |
|
|
89
88
|
| 목록 불러오기 실패 | 목록 자리에 [EmptyState](../components/empty-state.md)(실패 문구 + `action`에 다시 불러오기 버튼). FAB는 그대로 | EmptyState `announcement`(Native)로 알림 |
|
|
@@ -92,7 +91,7 @@ Dialog (열림)
|
|
|
92
91
|
|
|
93
92
|
```tsx
|
|
94
93
|
// Web
|
|
95
|
-
import { useState } from "react";
|
|
94
|
+
import { useRef, useState } from "react";
|
|
96
95
|
import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
|
|
97
96
|
import { Button } from "@hjmds/react/actions";
|
|
98
97
|
import { List, ListRow } from "@hjmds/react/display";
|
|
@@ -104,6 +103,7 @@ import { Dialog } from "@hjmds/react/overlays";
|
|
|
104
103
|
import { TopBar } from "@hjmds/react/top-bar";
|
|
105
104
|
|
|
106
105
|
function Notes() {
|
|
106
|
+
const composerTrigger = useRef<HTMLButtonElement>(null);
|
|
107
107
|
const [target, setTarget] = useState<HTMLDivElement | null>(null);
|
|
108
108
|
const layoutMode = useFloatingActionButtonScroll(target);
|
|
109
109
|
const [clearance, setClearance] = useState(resolveFloatingActionButtonContentClearance(0));
|
|
@@ -118,9 +118,9 @@ function Notes() {
|
|
|
118
118
|
</Container>
|
|
119
119
|
</div>
|
|
120
120
|
</div>
|
|
121
|
-
<FloatingActionButton descriptor={{ label: t("notes.new"), icon: { name: "add" }, layoutMode }}
|
|
121
|
+
<FloatingActionButton ref={composerTrigger} descriptor={{ label: t("notes.new"), icon: { name: "add" }, layoutMode }}
|
|
122
122
|
renderIcon={renderIcon} onContentClearanceChange={setClearance} onClick={() => setOpen(true)} />
|
|
123
|
-
<Dialog title={t("notes.compose.title")} open={open} onOpenChange={setOpen} closeLabel={t("common.close")} busy={saving}
|
|
123
|
+
<Dialog returnFocusRef={composerTrigger} title={t("notes.compose.title")} open={open} onOpenChange={setOpen} closeLabel={t("common.close")} busy={saving}
|
|
124
124
|
footer={<Button loading={saving} disabled={!draft.trim()} onClick={save}>{t("notes.compose.save")}</Button>}>
|
|
125
125
|
{failed && <Notice tone="danger" title={t("notes.compose.failed")} />}
|
|
126
126
|
<TextArea label={t("notes.compose.label")} value={draft} onChange={(event) => setDraft(event.currentTarget.value)} />
|
|
@@ -145,10 +145,10 @@ import { Container } from "@hjmds/react-native/primitives";
|
|
|
145
145
|
import { TopBar } from "@hjmds/react-native/top-bar";
|
|
146
146
|
|
|
147
147
|
function Notes() {
|
|
148
|
+
const composerTrigger = useRef<View>(null);
|
|
148
149
|
const insets = useSafeAreaInsets();
|
|
149
150
|
const { width } = useWindowDimensions();
|
|
150
151
|
const scroll = useRef<ScrollView>(null);
|
|
151
|
-
const submitting = useRef(false); // save()가 동기로 true로 둔다 — Dialog가 곧바로 보내는 close-action을 무시하기 위해
|
|
152
152
|
const { layoutMode, onScroll } = useFloatingActionButtonScroll();
|
|
153
153
|
const [clearance, setClearance] = useState(resolveFloatingActionButtonContentClearance(insets.bottom));
|
|
154
154
|
return (
|
|
@@ -159,11 +159,11 @@ function Notes() {
|
|
|
159
159
|
<List label={t("notes.recent")}>{notes.map((n) => <ListRow key={n.id} title={n.text} />)}</List>
|
|
160
160
|
</Container>
|
|
161
161
|
</ScrollView>
|
|
162
|
-
<FloatingActionButton descriptor={{ label: t("notes.new"), icon: { name: "add" }, layoutMode }}
|
|
162
|
+
<FloatingActionButton ref={composerTrigger} descriptor={{ label: t("notes.new"), icon: { name: "add" }, layoutMode }}
|
|
163
163
|
renderIcon={renderIcon} safeAreaBottomInset={insets.bottom}
|
|
164
164
|
onContentClearanceChange={setClearance} onPress={() => setOpen(true)} />
|
|
165
|
-
<Dialog title={t("notes.compose.title")} open={open} closeLabel={t("common.close")} busy={saving}
|
|
166
|
-
onOpenChange={
|
|
165
|
+
<Dialog returnFocusRef={composerTrigger} title={t("notes.compose.title")} open={open} closeLabel={t("common.close")} busy={saving}
|
|
166
|
+
onOpenChange={setOpen} onActionError={() => setFailed(true)}
|
|
167
167
|
primaryAction={{ label: t("notes.compose.save"), onPress: save, disabled: !draft.trim() }}>
|
|
168
168
|
{failed ? <Notice tone="danger" announcement="assertive" title={t("notes.compose.failed")} /> : null}
|
|
169
169
|
<TextArea label={t("notes.compose.label")} value={draft} onValueChange={setDraft} />
|
|
@@ -173,8 +173,8 @@ function Notes() {
|
|
|
173
173
|
}
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
`save`는 `saving
|
|
177
|
-
|
|
176
|
+
Web `save`는 `saving`을 켜고 성공 때 항목 추가·닫기·입력 비우기·맨 위 스크롤을 한다.
|
|
177
|
+
Native `primaryAction.onPress`는 저장 Promise를 반환한다. Dialog가 중복 실행·pending 표시·저장 중 닫기 차단을 소유하고, Promise가 이행될 때만 닫는다. 거절은 `onActionError`에서 제품 문구로 표시한다. 저장 오류를 삼켜 성공으로 반환하지 않는다. 스토리의 18개 예시 기록과 문구, `+` 글자 아이콘은 제품 데이터·아이콘으로 바꾼다.
|
|
178
178
|
|
|
179
179
|
## 플랫폼 차이
|
|
180
180
|
|
|
@@ -182,7 +182,7 @@ function Notes() {
|
|
|
182
182
|
| --- | --- | --- |
|
|
183
183
|
| FAB 위치 | CSS `position: fixed`, 안전 영역 `env()` 자동 | positioned 부모 안 `absolute`, `safeAreaBottomInset` 직접 전달 |
|
|
184
184
|
| 스크롤 hook | `useFloatingActionButtonScroll(target)` → `layoutMode` | `useFloatingActionButtonScroll()` → `{ layoutMode, onScroll }`, `scrollEventThrottle={16}` |
|
|
185
|
-
| 대화상자 주 행동 | `footer`에 Button(`loading` 가능) | `primaryAction` 객체.
|
|
185
|
+
| 대화상자 주 행동 | `footer`에 Button(`loading` 가능) | `primaryAction` 객체. 반환 Promise가 이행되면 `close-action`, 거절되면 열린 상태 유지. pending은 내장 버튼 loading으로 표시 |
|
|
186
186
|
| TextArea 값 | DOM `onChange`(Web TextArea에는 `onValueChange`가 없다, 1.12.1) | `onValueChange(value)` |
|
|
187
187
|
| 맨 위로 | `target.scrollTo({ top: 0 })` | `scroll.current?.scrollTo({ y: 0 })` |
|
|
188
188
|
|
|
@@ -190,8 +190,8 @@ function Notes() {
|
|
|
190
190
|
|
|
191
191
|
- clearance를 스크롤 콘텐츠 하단 padding에 넣지 않으면 마지막 항목이 FAB 아래에 가려진다. 지름만 예약하지 않는다.
|
|
192
192
|
- 한 화면에 FAB는 하나다. 하단 고정 결론 행동이 따로 있으면 [BottomCTA](../components/bottom-cta.md)를 쓴다.
|
|
193
|
-
- Native Dialog
|
|
193
|
+
- Native Dialog의 `onPress`에서 `void save()`로 Promise를 버리면 동기 성공으로 처리된다. `return save()` 또는 `async` 반환을 유지한다.
|
|
194
194
|
- 현재 Native 스토리는 `safeAreaBottomInset`·TopBar `safeAreaTop`을 넘기지 않는다. 실제 화면은 inset을 넘긴다.
|
|
195
|
-
-
|
|
195
|
+
- Native 스토리는 List를 Container 없이 화면 가장자리에 둔다(ListRow 좌우 8만 남는다). 제품은 Container gutter 안에 둔다. Web 스토리는 이미 Container를 사용한다.
|
|
196
196
|
- Web TextArea는 TextField와 달리 `onValueChange`가 없다(1.12.1). Web은 `onChange`로 값을 읽는다([TextArea](../components/text-area.md)).
|
|
197
|
-
- 현재 스토리는 저장이 동기라 진행 중·실패 상태가 없고 문구가 i18n 키 없는 리터럴이다.
|
|
197
|
+
- 현재 스토리는 저장이 동기라 진행 중·실패 상태가 없고 문구가 i18n 키 없는 리터럴이다. 작성 세션별 ref로 닫힘 렌더 전에 도착한 중복 저장을 막고, FAB로 새 작성창을 열 때 초기화한다. 비동기 저장은 제품의 실제 Promise와 실패 상태를 연결한다.
|
|
@@ -49,13 +49,14 @@
|
|
|
49
49
|
3. 예제는 제목이 공백이면 완료를 비활성화한다. 실제 앱의 저장 결과는 서버 응답으로만 확정한다.
|
|
50
50
|
4. 그림 로딩 실패는 그림만 제거한다. 설명·입력·버튼은 유지하며 오류를 성공으로 바꾸지 않는다.
|
|
51
51
|
5. 완료 후 다시 시작해도 초안을 유지한다. 초안 삭제는 제품의 명시적인 정책이 있을 때만 한다.
|
|
52
|
+
6. Web은 초점이 있던 전환 버튼을 실행할 때 새 단계 제목·입력·결과·다시 시작 행동으로 초점을 이어 준다. 외부 초점이나 명시적인 blur, 버튼에 초점을 주지 않은 pointer 활성화를 임의로 덮지 않는다.
|
|
52
53
|
|
|
53
54
|
| 상태 | 모습 | 포커스·알림 |
|
|
54
55
|
| --- | --- | --- |
|
|
55
56
|
| 기본 | 빈 상태 제목·장식 그림·첫 행동 | 제목으로 의미를 전달하고 그림은 장식으로 숨김 |
|
|
56
57
|
| 진행 중 | 단계 안내·입력·이전/다음 행동 | 초안을 유지하며 실제 저장 중에는 제품 pending 연결 |
|
|
57
58
|
| 실패 | 그림 실패 시 본문·행동 유지 | 장식 실패를 저장 실패와 혼동하지 않음 |
|
|
58
|
-
| 완료 | 결과 제목·입력 내용·다시 시작 | Result의 기본 status 의미
|
|
59
|
+
| 완료 | 결과 제목·입력 내용·다시 시작 | Result의 기본 status 의미 유지. Native 완료 진입은 Android live region과 iOS 안내로 알리고, 그림 토글만으로 같은 결과를 다시 안내하지 않음 |
|
|
59
60
|
|
|
60
61
|
## 코드 골격
|
|
61
62
|
|
|
@@ -88,4 +89,4 @@ Showcase를 import하지 않는다. 그림 호스트는 Web img의 빈 alt, Nati
|
|
|
88
89
|
| 장식 슬롯 | EmptyState.icon / Result.icon | EmptyState.illustration / Result.renderIcon |
|
|
89
90
|
| 실패 | 그림만 숨김 | 그림만 숨김 |
|
|
90
91
|
|
|
91
|
-
실제 기기의 자산
|
|
92
|
+
실제 기기의 자산 디코딩·접근성 검증은 타입 검사와 별도다. 예제는 로컬 체험이며 서버 저장이나 OS 음성 전달의 검증 근거가 아니다.
|
|
@@ -123,7 +123,7 @@ const insets = useSafeAreaInsets();
|
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
`save`는 `saving`을 켜고 저장한 뒤 성공이면 `setOpen(false)`, 실패면 `failed`를 켜고 시트를 연 채 둔다(다음 저장 시작 때 끈다).
|
|
126
|
-
|
|
126
|
+
스토리는 `createActionSession` 기반 데모 요청을 사용한다. `busy`·`loading`·실패 Notice·빈 값 차단·다시 저장이 연결되어 있고 성공에만 닫힌다. 설명 8줄은 스크롤 확인용이며 실제 서버 저장은 아니다.
|
|
127
127
|
|
|
128
128
|
## 플랫폼 차이
|
|
129
129
|
|
|
@@ -143,6 +143,10 @@ const insets = useSafeAreaInsets();
|
|
|
143
143
|
- 제품에서 키보드 listener·`maxHeight`·중첩 ScrollView를 다시 만들지 않는다. 키보드 여백은 Sheet의 Modal이 소유하고 내부 ScrollView는 `automaticallyAdjustKeyboardInsets={false}`로 두 번 더하지 않게 돼 있다.
|
|
144
144
|
- 완료 버튼을 본문 끝에 두면 키보드에 가려진다. `footer`에 둔다.
|
|
145
145
|
- 저장 실패에 시트를 닫고 Toast만 띄우면 입력이 사라진다. 시트를 연 채 Notice로 남긴다.
|
|
146
|
-
-
|
|
146
|
+
- 스토리의 한국어 리터럴과 실패 유도 버튼은 데모 전용이다. 제품은 i18n 키와 실제 요청을 연결한다. 초안은 닫기·다시 열기 동안 유지되며 화면 unmount·새로고침 복구는 보장하지 않는다.
|
|
147
147
|
|
|
148
148
|
- 2026-10-06 독립 재구현에서 기본 상태 표의 “완료 가능”과 코드의 빈 값 disabled가 충돌해 표를 수정했다. 닫기 후 초안 유지·폐기는 제품 정책이며, 저장 성공 전 초안을 잃지 않는 원칙을 유지한다.
|
|
149
|
+
|
|
150
|
+
## 실행 예제의 저장 경계
|
|
151
|
+
|
|
152
|
+
2026-10-13 정적 감사에서 동기 닫기만 있던 Web·Native 예제를 보강했다. 요청 시작 시 이름을 캡처하고 입력을 잠근다. 실패하면 시트와 입력을 유지하고 같은 위치의 주 버튼으로 다시 저장한다. 변경된 입력은 이전 실패를 해제한다. `session.getSnapshot()`은 같은 프레임의 중복 제출·닫기를 막고, `Sheet.busy`는 플랫폼 닫기 정책을 적용한다. 서버의 idempotency와 영속 초안은 제품 책임이다.
|
|
@@ -187,4 +187,4 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
187
187
|
- Native 화면 전환은 현재 포트폴리오 Expo 앱(expo-router, React Navigation 미사용)에서 바로 쓸 수 없다. exports patch를 제품 패키지 관리자에 등록해야 한다.
|
|
188
188
|
- 어댑터 peer 없이 subpath를 import하면 자동 대체 없이 기기 Metro 번들이 실패한다.
|
|
189
189
|
- 예제는 Heading·Container·Stack·ListRow를 사용한다. 플랫폼별 드래그/스와이프 host는 유지하고 동일한 배치 역할을 재사용한다.
|
|
190
|
-
- Native 결과 문구는 iOS 알림을 포함한 `
|
|
190
|
+
- Native 결과 문구는 iOS 알림을 포함한 공개 `StatusText`로 표시한다. 제품은 `@hjmds/react-native/status-text`를 import한다.
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
| 현재 값 | `Text` | 제목 아래, 트리거 위 | `spacing.md` 16 |
|
|
64
64
|
| 트리거 | `Button`(primary) | 현재 값 아래 | `medium` 44(`control.buttonHeight`) |
|
|
65
65
|
| 시트 머리 | `Sheet` header | 시트 위 고정 | Native 최소 높이 `control.minTouchTarget` 44, 제목–닫기 `spacing.sm` 12; Web 여백 `spacing.lg` 20, 제목–닫기 `spacing.md` 16 |
|
|
66
|
-
| 선택지 | `Button selected` × N | 시트 body, 스크롤 | 높이 44, 간격 `spacing.md` 16.
|
|
66
|
+
| 선택지 | `Button selected` × N | 시트 body, 스크롤 | 높이 44, 간격 `spacing.md` 16. 본문 안 선택지 묶음은 `Stack gap="md"`로 명시한다. 설치한 버전의 Sheet body gap과 중첩 여부를 확인하며 checkout 상태를 npm 게시 증거로 삼지 않는다 |
|
|
67
67
|
| 행동 | `Button` 적용·취소 | 시트 footer, 스크롤 밖 고정 | 간격 `spacing.sm` 12(`sheetRecipe.footer.gap`), Native 위 여백 `spacing.sm` 12 |
|
|
68
68
|
| 시트 틀 | `Sheet` | 하단, 모달 | Web 최대 폭 640(`sheetRecipe.web.maxWidth`)·최대 높이 90%, Native 가로 여백 `spacing.lg` 20·최대 높이 90%(`maxHeightRatio` 0.9), 하단 안전 영역 추가 |
|
|
69
69
|
|
|
@@ -126,7 +126,7 @@ const trigger = useRef<HTMLButtonElement>(null);
|
|
|
126
126
|
<Button tone="secondary" onClick={() => setOpen(false)}>{t("display.cancel")}</Button>
|
|
127
127
|
<Button onClick={() => { setApplied(draft); setOpen(false); }}>{t("display.apply")}</Button>
|
|
128
128
|
</>}>
|
|
129
|
-
<Stack gap="md">{/*
|
|
129
|
+
<Stack gap="md">{/* 선택지 묶음 내부 간격 */}
|
|
130
130
|
{options.map((id) => (
|
|
131
131
|
<Button key={id} tone="secondary" selected={draft === id} onClick={() => setDraft(id)}>{t(displayKey[id])}</Button>
|
|
132
132
|
))}
|
|
@@ -138,6 +138,7 @@ const trigger = useRef<HTMLButtonElement>(null);
|
|
|
138
138
|
|
|
139
139
|
```tsx
|
|
140
140
|
// Native
|
|
141
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
141
142
|
import { useRef, useState } from "react";
|
|
142
143
|
import { ScrollView, View, useWindowDimensions } from "react-native";
|
|
143
144
|
import { Button } from "@hjmds/react-native/actions";
|
|
@@ -175,7 +176,7 @@ const trigger = useRef<View>(null);
|
|
|
175
176
|
</Sheet>
|
|
176
177
|
```
|
|
177
178
|
|
|
178
|
-
`options`·`displayKey`·문구는 제품 소유다. `StatusText`는 [저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)
|
|
179
|
+
`options`·`displayKey`·문구는 제품 소유다. `StatusText`는 [저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)에서 사용하는 공개 Text companion이다.
|
|
179
180
|
Web `gutter`는 [화면 여백과 너비](../tokens/layout.md)처럼 폭 구간으로 고른다.
|
|
180
181
|
|
|
181
182
|
### 공통: 시트 흐름
|
|
@@ -203,3 +204,5 @@ Web `gutter`는 [화면 여백과 너비](../tokens/layout.md)처럼 폭 구간
|
|
|
203
204
|
## 함정
|
|
204
205
|
|
|
205
206
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
207
|
+
|
|
208
|
+
2026-10-13 실제 예제는 시트 열기마다 세대를 구분한다. 취소·닫기·적용·저장·폐기로 한 번 닫힌 세대의 입력/확정 콜백은 무시하며, 다시 연 뒤 도착한 이전 세대 콜백도 현재 초안이나 확정값을 바꾸지 않는다. 재열기에서는 마지막 적용값으로 임시 선택을 초기화한다.
|
|
@@ -132,6 +132,7 @@ const trigger = useRef<HTMLButtonElement>(null);
|
|
|
132
132
|
|
|
133
133
|
```tsx
|
|
134
134
|
// Native
|
|
135
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
135
136
|
import { ScrollView, View, useWindowDimensions } from "react-native";
|
|
136
137
|
import { Button } from "@hjmds/react-native/actions";
|
|
137
138
|
import { Heading } from "@hjmds/react-native/heading";
|
|
@@ -164,12 +165,12 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
164
165
|
<Button tone="secondary" disabled={saving} onPress={() => { setDraft(""); setOpen(false); }}>{t("memo.discard")}</Button>
|
|
165
166
|
</>}>
|
|
166
167
|
<TextField label={t("memo.body")} value={draft} onValueChange={setDraft} />
|
|
167
|
-
{saveFailed ? <StatusText>{t(draftStatusKey.failed)}</StatusText> : null}
|
|
168
|
+
{saveFailed ? <StatusText announceOnMount announcement="assertive">{t(draftStatusKey.failed)}</StatusText> : null}
|
|
168
169
|
</Sheet>
|
|
169
170
|
```
|
|
170
171
|
|
|
171
172
|
`save`(성공하면 초안을 비우고 닫고, 실패하면 `saveFailed`만 켠다)·`saving`·문구 키는 제품 소유다. `StatusText`는
|
|
172
|
-
[저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)
|
|
173
|
+
[저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)에서 사용하는 공개 Text companion이다.
|
|
173
174
|
|
|
174
175
|
## 플랫폼 차이
|
|
175
176
|
|
|
@@ -186,3 +187,5 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
186
187
|
|
|
187
188
|
- 초안 state를 시트 children 안 컴포넌트에 두지 않는다. 닫힌 시트의 내용이 계속 마운트돼 있다고 가정하지 않고, 시트를 여는 화면이 초안을 소유한다.
|
|
188
189
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
190
|
+
|
|
191
|
+
2026-10-13 실제 예제는 시트 열기마다 세대를 구분한다. 취소·닫기·적용·저장·폐기로 한 번 닫힌 세대의 입력/확정 콜백은 무시하며, 다시 연 뒤 도착한 이전 세대 콜백도 현재 초안이나 확정값을 바꾸지 않는다. 닫기는 메모리 초안을 유지하고, 명시적 폐기만 초안을 비운다.
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
| `Text` | 진행·결과·실패 문구 | [Text](../components/text.md) |
|
|
31
31
|
|
|
32
32
|
스토리의 "느린 응답으로 검색"(900ms)·"빠른 응답으로 검색"(150ms) 두 버튼은 역순 응답을 재현하는 데모다. 제품은 검색 버튼 하나(또는 입력 중 검색)로 둔다.
|
|
33
|
-
`다음 검색 실패시키기`는 실패 경로를 여는 데모 도구다. 스토리는 기본·어두운
|
|
33
|
+
`다음 검색 실패시키기`는 실패 경로를 여는 데모 도구다. 스토리는 기본·어두운 테마, `검색 중`, `실패와 다시 검색`, 제품 팔레트 두 가지(`보라 테마`, `초록 테마 · 어둡게`)다.
|
|
34
34
|
2026-10-06 같은 SearchField 진행 상태와 팔레트를 보이던 `입력과 작성/검색 입력`을 이 항목으로 합쳤다([옛 링크](../../../../../docs/STORYBOOK_NAVIGATION.md#실험-정리와-합친-항목-2026-10-06)).
|
|
35
35
|
검색 입력 칸·결과 목록이 필요한 화면이면 [SearchField](../components/search-field.md)·[List](../components/list.md)에 같은 세대 규칙을 연결한다.
|
|
36
36
|
|
|
@@ -123,6 +123,7 @@ const message = state.status === "pending" || state.status === "error" ? t(searc
|
|
|
123
123
|
|
|
124
124
|
```tsx
|
|
125
125
|
// Native
|
|
126
|
+
import { StatusText } from "@hjmds/react-native/status-text";
|
|
126
127
|
import { ScrollView, useWindowDimensions } from "react-native";
|
|
127
128
|
import { Button } from "@hjmds/react-native/actions";
|
|
128
129
|
import { Heading } from "@hjmds/react-native/heading";
|
|
@@ -150,7 +151,7 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
150
151
|
</ScrollView>
|
|
151
152
|
```
|
|
152
153
|
|
|
153
|
-
`api.search`·`renderResult`·`Result`·문구 키는 제품 소유다. `StatusText`는 [저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)
|
|
154
|
+
`api.search`·`renderResult`·`Result`·문구 키는 제품 소유다. `StatusText`는 [저장과 재시도의 공통 절](action-recovery-save.md#공통-세션과-상태-알림)에서 사용하는 공개 Text companion이다.
|
|
154
155
|
세션·`search`·`message`는 Web과 같다. 네트워크 요청을 실제로 끊어야 하면 제품이 `AbortController` 등을 따로 쓴다.
|
|
155
156
|
|
|
156
157
|
## 플랫폼 차이
|
|
@@ -169,3 +170,5 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
169
170
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
170
171
|
- 진행 표시를 결과 영역에 따로 두지 않는다. SearchField의 진행 표시와 상태 문구 하나면 된다(흡수한 `검색 입력` 예제의 규칙).
|
|
171
172
|
- 실패와 결과 없음은 다른 상태다. 실패는 `다시 검색`(`session.retry()`), 결과 없음은 검색어를 바꾸는 경로를 준다.
|
|
173
|
+
|
|
174
|
+
2026-10-13 실패 예약은 다음 요청이 **시작할 때** 그 요청에 귀속한다. 이미 진행 중인 응답이 이후에 켠 예약을 소비하지 않으며, 오래된 실패도 최신 성공을 덮지 않는다. 재시도는 기존 계약대로 실패한 제출 검색어를 다시 요청하고 작성 중인 검색어는 보존한다. 재시도 버튼이 사라질 때 입력 ref로 초점을 돌린다. unmount에서는 로컬 데모 지연 타이머를 정리하고 분리된 작업을 종료하지만 실제 네트워크 취소를 구현한 것은 아니다.
|
|
@@ -43,7 +43,7 @@ Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등
|
|
|
43
43
|
|
|
44
44
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
45
45
|
| --- | --- | --- | --- |
|
|
46
|
-
| 바깥 틀 | Stack | 위에서
|
|
46
|
+
| 바깥 틀 | Web: Stack, Native: KeyboardAvoiding → ScrollView → Stack | 위에서 아래. Native canvas는 자체 스크롤이 없으므로 명시적 host 사용 | gap lg=20px, Native keyboardShouldPersistTaps="handled"·하단 spacing.md |
|
|
47
47
|
| 변경 행동 | inline Stack · Button | 목록 위, 좁으면 감김 | 기본 Stack/Button recipe |
|
|
48
48
|
| 데이터 | List | 변경 결과 아래 | separator full, 공통 ListRow/TextField 크기 |
|
|
49
49
|
| 등장 | ContentTransition | 각 stable id 항목 | rise=12px, motion.normal, easing.enter |
|
|
@@ -62,8 +62,8 @@ Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등
|
|
|
62
62
|
| --- | --- | --- |
|
|
63
63
|
| 기본 | 기존 목록과 입력 | 초기 데이터 즉시 표시 |
|
|
64
64
|
| 진행 중 | 새 행만 짧게 등장 | 기존 입력 노드와 초안 유지 |
|
|
65
|
-
| 실패 | 서버 실패가 있는 제품은 기존 목록·초안을 보존하고 오류를 별도 표시 | 이 로컬
|
|
66
|
-
| 삭제 | 해당 행 즉시 제거 | 삭제
|
|
65
|
+
| 실패 | 서버 실패가 있는 제품은 기존 목록·초안을 보존하고 오류를 별도 표시 | 이 로컬 예제에는 네트워크 요청 없음 |
|
|
66
|
+
| 삭제 | 해당 행 즉시 제거 | Web pointer 삭제는 먼저 삭제 버튼으로 초점을 옮긴다. 마지막 행은 삭제 버튼도 비활성되므로 추가 버튼으로 이동 |
|
|
67
67
|
| 비어 있음 | 기록 없음과 추가 안내 | 추가 버튼 유지 |
|
|
68
68
|
| 모션 감소·정지 | 같은 데이터, 공간 전환 없음 | 의미·입력 순서 동일 |
|
|
69
69
|
|
|
@@ -97,8 +97,10 @@ import { ContentTransition } from "@hjmds/react-native/content-transition";
|
|
|
97
97
|
| --- | --- | --- |
|
|
98
98
|
| 등장 | 기존 framer-motion optional peer | 기존 RN Animated native driver |
|
|
99
99
|
| 높이 | ResizeObserver + WAAPI | onLayout + JS driver |
|
|
100
|
-
| 변경 알림 | status |
|
|
100
|
+
| 변경 알림 | status | 공개 StatusText: Android polite live region + iOS active 변경 알림 |
|
|
101
101
|
| 접근성 | 기존 입력 초점·list 의미 | 기존 입력과 Native list 의미 |
|
|
102
102
|
|
|
103
103
|
데이터·중복 제거·정렬·서버 확정은 제품 소유다. 긴 목록은 VirtualList 선택을 먼저 검토하고
|
|
104
|
-
재활용 mount를 새 데이터로 오인해 enterOnMount를 켜지 않는다.
|
|
104
|
+
재활용 mount를 새 데이터로 오인해 enterOnMount를 켜지 않는다. 제품 팔레트·성능은 제품 도입 시 검증한다. Storybook 배포 분류와 제품 검증·npm 게시는 구분한다.
|
|
105
|
+
|
|
106
|
+
2026-10-13 실제 예제 검사에서 WebKit의 pointer 삭제는 버튼을 자동 focus하지 않아 삭제되는 입력의 초점이 body로 사라졌다. 삭제 전에 후속 행동으로 이동하도록 보강했다. Native는 기본 글자에서도 추가된 행에 접근할 수 있도록 스크롤·키보드 host를 제공한다. 변경 문구는 추가 후 총 개수, 삭제한 기록, 역순 후 첫 기록을 포함해 연속 같은 행동도 구분하고 초기 안내·반복 렌더는 재발표하지 않는다. 이 예제는 추가·첫 행 삭제·역순·메모 편집·모션 정지만 제공하며 검색/filter/선택·서버 갱신 기능은 없다.
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
| 중앙에서 작업 실행 | `presentation: "floating"`, `distribution: "center-gap"` | 막대 정중앙(목적지 사이 빈칸 위) | 생성이 앱의 핵심 행동이고 목적지가 짝수(2·4·6)일 때 |
|
|
33
33
|
| 탐색 옆에서 작업 실행 | `presentation: "capsule"`, `distribution: "equal"` | 캡슐 밖 끝 쪽, 캡슐과 `spacing.xs` 8 간격 | 행동이 목적지와 섞여 보이면 안 될 때, 목적지가 홀수일 때 |
|
|
34
34
|
| 선택한 목적지 이름 표시 | `presentation: "capsule"`, 행동 없음 | — | 아이콘만으로 목적지가 모호해 선택 항목의 이름을 보여야 할 때 |
|
|
35
|
-
| 사각 영역으로 현재 위치 표시 | `
|
|
35
|
+
| 사각 영역으로 현재 위치 표시 | `floating` + `selection: "tile"` | — | 같은 폭의 목적지에서 현재 위치를 채운 사각 타일로 표시할 때 |
|
|
36
36
|
|
|
37
37
|
```text
|
|
38
38
|
중앙에서 작업 실행 (floating + center-gap) 탐색 옆에서 작업 실행 (capsule)
|
|
@@ -59,8 +59,7 @@
|
|
|
59
59
|
| 하단 안전 영역 | — | 막대 아래 | Web `env(safe-area-inset-bottom)` + `spacing.xs` 8, Native `safeAreaBottom` |
|
|
60
60
|
| 스크롤 본문 | 제품 화면 | 막대 위 | Web은 하단 여백을 막대 높이만큼 제품이 둔다. Native는 막대가 flex 형제라 겹치지 않는다 |
|
|
61
61
|
|
|
62
|
-
capsule은 기본 크기에서 선택되지 않은 항목의 이름을 접는다.
|
|
63
|
-
모든 이름을 세로로 보인다. 접힌 항목도 접근성 이름은 유지된다.
|
|
62
|
+
capsule은 기본 크기에서 선택되지 않은 항목의 이름을 접는다. 목적지가 5~6개면 모든 이름을 세로로 보인다. 접힌 항목도 접근성 이름은 유지된다.
|
|
64
63
|
|
|
65
64
|
## 흐름과 상태
|
|
66
65
|
|
|
@@ -76,7 +75,6 @@ capsule은 기본 크기에서 선택되지 않은 항목의 이름을 접는다
|
|
|
76
75
|
| 실패 | — (막대 자체는 실패하지 않는다. 목적지 화면의 불러오기 실패·재요청 실패는 그 화면의 [Result](../components/result.md)·[Notice](../components/notice.md)가 표시한다. `primaryAction`이 연 작성 화면의 저장 실패는 [저장과 재시도](action-recovery-save.md)를 따른다) | — |
|
|
77
76
|
| 배지 | 숫자 배지(`count`·`max`) | 배지 `accessibilityLabel`을 항목 이름과 함께 읽는다 |
|
|
78
77
|
| 키보드 열림 | 막대가 숨는다(`remain`이면 유지) | — |
|
|
79
|
-
| 큰 글자 | capsule의 모든 이름이 세로로 보인다 | 같다 |
|
|
80
78
|
|
|
81
79
|
## 코드 골격
|
|
82
80
|
|
|
@@ -151,14 +149,17 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
151
149
|
| --- | --- | --- |
|
|
152
150
|
| 배치 | CSS `position: fixed`(스토리는 갤러리용으로 `relative`로 덮음), 본문 하단 여백은 제품 | 화면 루트 세로 flex의 마지막 자식 또는 navigator `tabBar` |
|
|
153
151
|
| 목적지 | 링크(`getHref` 필수, `renderLink`) | tab 역할 + `onActivate` 필수 |
|
|
154
|
-
| 사각 선택 표현 |
|
|
152
|
+
| 사각 선택 표현 | `configuration.selection="tile"`와 floating/equal, 항상 라벨 표시 | Web과 같은 공개 configuration |
|
|
155
153
|
| 행동 아이콘 색 | `currentColor` | `colors.onPrimary`를 직접 넘김 |
|
|
156
154
|
|
|
157
155
|
## 함정
|
|
158
156
|
|
|
159
157
|
- `center-gap`은 홀수 목적지에서, `capsule`과 함께 쓰면 오류다.
|
|
160
158
|
- 생성 행동을 목적지 항목으로 넣지 않는다. 선택 상태가 생성 화면으로 옮겨 가 탭 의미가 깨진다.
|
|
161
|
-
- "사각 영역으로 현재 위치 표시"는 `
|
|
159
|
+
- "사각 영역으로 현재 위치 표시"는 `selection="tile"`을 쓴다. `presentation="floating"`, `distribution="equal"`, 보이는 라벨 계약을 유지하고 내부 CSS·Native listStyle로 외형을 우회하지 않는다.
|
|
162
160
|
- 구획 제목은 Heading으로 표시한다. 이전 Text heading 예제는 2026-10-06 제목 의미 구조를 맞추면서 수정했다.
|
|
163
|
-
- 현재 스토리는 Native 막대에 deprecated `style={{ paddingHorizontal: 0 }}`를 준다(320 폭 갤러리용). 제품은 바깥 좌우 `spacing.md` 16을 그대로 둔다.
|
|
164
161
|
- 현재 스토리는 바깥 틀(본문 스크롤·하단 여백)이 없고 막대를 갤러리 카드 안에 둔다. 제품은 바깥 틀 행대로 둔다.
|
|
162
|
+
|
|
163
|
+
2026-10-13 예제는 사각 선택 표현을 공개 tile 축으로 이관하고 내부 radius·padding CSS와 Native deprecated 스타일을 제거했다. Web 갤러리의 `style.position="relative"`는 fixed 막대를 문서 흐름에서 비교하기 위한 showcase 전용 배치다. 제품 하단 탐색에는 복사하지 않으며 `layoutStyle`은 position을 지원하지 않는다.
|
|
164
|
+
|
|
165
|
+
비교 예제는 목적지 이름과 현재 선택만 바꾸는 메모리 데모다. 주 행동은 작성 화면을 열지 않고 실행 횟수를 올린다. Web 링크는 인스턴스별 fragment를 사용해 현재 선택과 브라우저 뒤로·앞으로 이동을 동기화하고 다른 막대의 주소는 무시한다. 표현 preset을 바꾸면 해당 막대의 초기 선택과 실행 횟수로 새로 시작한다. 실제 router·작성 화면·저장 API는 제품이 연결한다.
|
|
@@ -81,7 +81,7 @@ Expo Go가 아니라 호환 개발 클라이언트에서만 확인된다. 이
|
|
|
81
81
|
| --- | --- | --- |
|
|
82
82
|
| 기본 | 트리거 두 개·메뉴 트리거·안내 결과 문구(`memo.menuHint`)·입력, 하단에 완료 | — |
|
|
83
83
|
| 진행 중 | 이미지: 이미지마다 `loadingLabel` 문구. 시트 `busy`: 끌어 닫기·배경 닫기·Android back이 막힌다(코드로는 닫을 수 있다) | 이미지 로딩 문구는 live region(polite) |
|
|
84
|
-
| 실패 | 이미지 로드 실패: `errorLabel` + `retryLabel` 버튼. 다시 시도는 같은 이미지를 새로 요청하고, 다시 실패하면 같은 실패 문구와 버튼으로 돌아온다. 시트·메뉴는 요청이 없다. 시트 안 저장처럼 제품 요청이 실패하면 `busy`를 풀고 시트를 연 채 실패 문구를 둔다 |
|
|
84
|
+
| 실패 | 이미지 로드 실패: `errorLabel` + `retryLabel` 버튼. 다시 시도는 같은 이미지를 새로 요청하고, 다시 실패하면 같은 실패 문구와 버튼으로 돌아온다. 시트·메뉴는 요청이 없다. 시트 안 저장처럼 제품 요청이 실패하면 `busy`를 풀고 시트를 연 채 실패 문구를 둔다 | Android assertive live region과 iOS 접근성 알림으로 실패를 알린다 |
|
|
85
85
|
| 시트 열림 | 하단 시트, 배경 눌러 닫기 | 열었던 트리거로 포커스 복귀는 제품이 처리 |
|
|
86
86
|
| 키보드 열림 | 완료 버튼이 키보드 위, 아래 안전 영역 0 | — |
|
|
87
87
|
| 메뉴 항목 disabled·danger | OS가 비활성·파괴 표시 | OS 메뉴 접근성 |
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
|
|
45
45
|
## 흐름과 상태
|
|
46
46
|
|
|
47
|
-
1. 열기 직전에 트리거를 실제 viewport/window 좌표로 측정한다. 측정 불가 시 일반 Dialog를 연다.
|
|
48
|
-
2. 제품이 open·draft·saved를 각각 소유한다. 저장 전 닫기는 draft를 지우지 않는다.
|
|
47
|
+
1. 열기 직전에 트리거를 실제 viewport/window 좌표로 측정한다. 측정 불가 시 일반 Dialog를 연다. Native의 비동기 측정은 새 열기·닫기·언마운트 때 이전 요청을 무효화한다. 늦은 측정이 닫힌 창을 다시 열거나 최신 위치를 덮지 않게 한다.
|
|
48
|
+
2. 제품이 open·draft·saved를 각각 소유한다. 저장 전 닫기는 draft를 지우지 않는다. 저장 전 상태는 `null`, 의도적으로 비운 메모를 저장한 결과는 빈 문자열로 구분해 저장 여부를 잘못 안내하지 않는다.
|
|
49
49
|
3. 저장 시 draft를 결과로 확정하고 닫는다. 실패 체험을 예약하면 첫 저장만 실패하고
|
|
50
|
-
오류·초안을 유지한다. 다시 저장은 성공한다. 실제 서버가 있으면 busy와 확정 응답을 연결한다.
|
|
50
|
+
오류·초안을 유지한다. 다시 저장은 성공한다. Web은 status/alert, Native는 StatusText의 Android live region·iOS 알림 경로로 결과와 실패를 알린다. 실제 서버가 있으면 busy와 확정 응답을 연결한다.
|
|
51
51
|
4. 닫기 전환이 끝난 뒤 트리거로 초점을 돌린다. 빠른 재열기는 이전 종료 결과를 무시한다.
|
|
52
52
|
5. 모션 감소·잘못된 측정에서는 공간 전환을 생략한다.
|
|
53
53
|
|
|
@@ -88,12 +88,12 @@
|
|
|
88
88
|
| 모션 감소 | 공간 전환 없음 | 공간 전환 없음, 기존 opacity |
|
|
89
89
|
| 닫기 | Escape·바깥·닫기 | Back·바깥·닫기 |
|
|
90
90
|
|
|
91
|
-
키보드·회전·스크롤에 의한 목적지 변화, 실제 기기와 제품 테마 비교는
|
|
91
|
+
키보드·회전·스크롤에 의한 목적지 변화, 실제 기기와 제품 테마 비교는 제품 도입 시 검증한다. Storybook 배포 분류는 이 검증의 증거가 아니다.
|
|
92
92
|
|
|
93
93
|
|
|
94
94
|
### Web의 페이지 안에서 편집 변형
|
|
95
95
|
|
|
96
|
-
`Contextual`·`ContextualDark
|
|
96
|
+
`Contextual`·`ContextualDark`는 기존 Popover의 선택적인 `motionOrigin`을
|
|
97
97
|
사용한다. 기본 360px 표면이 버튼 아래 8px 간격으로 열리고 viewport 충돌에 따라 배치가 바뀐다.
|
|
98
98
|
본문은 Stack gap md=16px 안에 오류 → TextField → 저장 순서다. Native는 모달 편집 변형을 쓴다.
|
|
99
99
|
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
| 실패 | 기존 EffectSurface의 장식 host 배경 대안 | 내용·입력과 행동 유지; peer 미설치는 번들 실패라 별도 확인 |
|
|
58
58
|
|
|
59
59
|
1. 초안을 입력한 뒤 표현·간격·테마를 바꾼다. 입력과 기록의 owner는 같은 컴포넌트에 남는다.
|
|
60
|
-
2. 기록 확인은
|
|
60
|
+
2. 기록 확인은 그 시점의 초안을 결과로 보관한다. 이후 입력이나 표현을 바꿔도 확인한 결과는 유지한다. Web status와 Native의 Android live region·iOS 안내로 변경을 알리며 서버 저장·자동 성공·운영 데이터 반영을 뜻하지 않는다.
|
|
61
61
|
3. 자세히 읽기에서 현재 초안을 확인하고 닫는다. 줄무늬를 접근성 이름/조작 요소로 노출하지 않는다.
|
|
62
62
|
|
|
63
63
|
## 코드 골격
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- 단계: 구성
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
|
-
- 적용:
|
|
6
|
+
- 적용: 구성 API 지침 참조
|
|
7
7
|
- 검토일: 2026-10-06
|
|
8
8
|
- 근거: [반복 화면 조합](../../screen-patterns.md); 공통 API와 실제 Web·Native 예제의 슬롯·상태를 대조해 중복 조립 방지. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/사진/촬영과 앨범 선택`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
|
|
9
9
|
- 스토리북: `배포/구성/선택과 필터/사진 촬영과 앨범 선택`
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- 단계: 구성
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
|
-
- 적용:
|
|
6
|
+
- 적용: 구성 API 지침 참조
|
|
7
7
|
- 검토일: 2026-10-06
|
|
8
8
|
- 근거: [MessageComposer](../components/message-composer.md), [공통 실행과 실패 복구](../../action-session.md), `showcase/web/src/patterns/conversation-previews.tsx`, `showcase/native/src/conversation-previews.tsx`. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/입력과 작성/댓글 작성`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
|
|
9
9
|
- 스토리북: `배포/구성/입력과 작성/댓글 작성`
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
| --- | --- | --- |
|
|
63
63
|
| 기본 | 빈 입력, 전송 비활성(글이 없을 때) | 입력 이름 `label` |
|
|
64
64
|
| 진행 중 | `pending`: 입력 잠금, 전송 자리 스피너 | 포커스 유지, 중복 요청 차단 |
|
|
65
|
-
| 실패 | 글·답글 대상 유지, `context`에 실패 문구 | 실패 문구는 입력
|
|
66
|
-
| 성공 | 초안 정리, 보낸 항목이 목록에 추가 |
|
|
65
|
+
| 실패 | 글·답글 대상 유지, `context`에 실패 문구 | 실패 문구는 입력 근처에서 알림(Web alert, Native StatusText), 초안 유지 |
|
|
66
|
+
| 성공 | 초안 정리, 보낸 항목이 목록에 추가 | Web은 전송을 시작할 때 입력으로 초점을 옮겨 유지. 양쪽은 전송한 항목 수를 알림 |
|
|
67
67
|
|
|
68
68
|
## 코드 골격
|
|
69
69
|
|
|
@@ -108,3 +108,5 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
108
108
|
- 실패 시 글·답글 대상을 지우지 않는다. 성공을 먼저 표시하고 지우면 재전송할 내용이 사라진다.
|
|
109
109
|
- Enter 전송을 덧붙이지 않는다. IME 조합 중 Enter를 전송으로 가로채면 한글 마지막 글자가 빠진다.
|
|
110
110
|
- 스토리의 "다음 전송 실패시키기"·350ms 지연·예제 사진은 로컬 시연이다. 업로드·서버 저장·권한은 제품이 연결한다.
|
|
111
|
+
|
|
112
|
+
2026-10-13 예제 검수: Web의 원형 전송 버튼이 잠기거나 없어질 때 초점이 body로 빠져나가던 경로를 `inputRef`로 보완했다. 전송 시작 때만 입력에 초점을 두므로 대기 중 사용자가 다른 곳으로 옮긴 초점을 완료 시 다시 가져오지 않는다. 실패 후 수정한 초안으로 재시도하며, 사진·답장 대상은 성공을 확인한 뒤에만 정리한다.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
- 단계: 구성
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
|
-
- 적용:
|
|
6
|
+
- 적용: 구성 API 지침 참조
|
|
7
7
|
- 검토일: 2026-10-06
|
|
8
8
|
- 근거: [MessageComposer](../components/message-composer.md), [공통 실행과 실패 복구](../../action-session.md), `showcase/web/src/patterns/conversation-previews.tsx`, `showcase/native/src/conversation-previews.tsx`. 2026-10-06 사용자 승인으로 스토리북 배포(이전 `실험/구성/입력과 작성/메시지 작성`, [승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정))
|
|
9
9
|
- 스토리북: `배포/구성/입력과 작성/메시지 작성`
|
|
@@ -65,8 +65,8 @@
|
|
|
65
65
|
| --- | --- | --- |
|
|
66
66
|
| 기본 | 빈 입력, 전송 비활성(글·사진이 없을 때) | 입력 이름 `label` |
|
|
67
67
|
| 진행 중 | `pending`: 입력 잠금, 전송 자리 스피너 | 포커스 유지, 중복 요청 차단 |
|
|
68
|
-
| 실패 | 글·사진·답장 대상 유지, `context`에 실패 문구 | 실패 문구는 입력
|
|
69
|
-
| 성공 | 초안 정리, 보낸 항목이 목록에 추가 |
|
|
68
|
+
| 실패 | 글·사진·답장 대상 유지, `context`에 실패 문구 | 실패 문구는 입력 근처에서 알림(Web alert, Native StatusText), 초안 유지 |
|
|
69
|
+
| 성공 | 초안 정리, 보낸 항목이 목록에 추가 | Web은 전송을 시작할 때 입력으로 초점을 옮겨 유지. 양쪽은 전송한 항목 수를 알림 |
|
|
70
70
|
|
|
71
71
|
## 코드 골격
|
|
72
72
|
|
|
@@ -117,3 +117,5 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
117
117
|
- 실패 시 글·사진·답장 대상을 지우지 않는다. 성공을 먼저 표시하고 지우면 재전송할 내용이 사라진다.
|
|
118
118
|
- Enter 전송을 덧붙이지 않는다. IME 조합 중 Enter를 전송으로 가로채면 한글 마지막 글자가 빠진다.
|
|
119
119
|
- 스토리의 "다음 전송 실패시키기"·350ms 지연·예제 사진은 로컬 시연이다. 업로드·서버 저장·권한은 제품이 연결한다.
|
|
120
|
+
|
|
121
|
+
2026-10-13 예제 검수: Web의 원형 전송 버튼이 잠기거나 없어질 때 초점이 body로 빠져나가던 경로를 `inputRef`로 보완했다. 전송 시작 때만 입력에 초점을 두므로 대기 중 사용자가 다른 곳으로 옮긴 초점을 완료 시 다시 가져오지 않는다. 실패 후 수정한 초안으로 재시도하며, 사진·답장 대상은 성공을 확인한 뒤에만 정리한다.
|