@hjmds/design-contracts 1.26.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agreement.d.ts +1 -0
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +2 -1
- package/dist/agreement.js.map +1 -1
- package/dist/auth-screen.d.ts +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/calendar.d.ts +1 -0
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -1
- package/dist/calendar.js.map +1 -1
- package/dist/catalog.d.ts +78 -30
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +10 -5
- package/dist/catalog.js.map +1 -1
- package/dist/chart.d.ts +2 -0
- package/dist/chart.d.ts.map +1 -1
- package/dist/chart.js +1 -1
- package/dist/chart.js.map +1 -1
- package/dist/component-definitions.d.ts +1 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +1 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/component-recipes.d.ts +53 -6
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +64 -10
- package/dist/component-recipes.js.map +1 -1
- package/dist/data-table.d.ts +11 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +5 -0
- package/dist/data-table.js.map +1 -1
- package/dist/description-list.d.ts +3 -0
- package/dist/description-list.d.ts.map +1 -1
- package/dist/description-list.js +5 -1
- package/dist/description-list.js.map +1 -1
- package/dist/design-profile-layout.d.ts +28 -0
- package/dist/design-profile-layout.d.ts.map +1 -1
- package/dist/design-profile-layout.js +27 -3
- package/dist/design-profile-layout.js.map +1 -1
- package/dist/design-profile.d.ts.map +1 -1
- package/dist/design-profile.js +40 -8
- package/dist/design-profile.js.map +1 -1
- package/dist/field-group.d.ts +13 -0
- package/dist/field-group.d.ts.map +1 -1
- package/dist/field-group.js +17 -2
- package/dist/field-group.js.map +1 -1
- package/dist/foundations.d.ts +24 -11
- package/dist/foundations.d.ts.map +1 -1
- package/dist/foundations.js +40 -7
- package/dist/foundations.js.map +1 -1
- package/dist/heading.d.ts +5 -5
- package/dist/image-choice.d.ts +23 -0
- package/dist/image-choice.d.ts.map +1 -0
- package/dist/image-choice.js +26 -0
- package/dist/image-choice.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/masonry.d.ts +8 -0
- package/dist/masonry.d.ts.map +1 -1
- package/dist/masonry.js +12 -1
- package/dist/masonry.js.map +1 -1
- package/dist/number-field.d.ts +2 -2
- package/dist/number-field.d.ts.map +1 -1
- package/dist/number-field.js +4 -2
- package/dist/number-field.js.map +1 -1
- package/dist/qr-code-recipe.d.ts +1 -0
- package/dist/qr-code-recipe.d.ts.map +1 -1
- package/dist/qr-code-recipe.js +3 -1
- package/dist/qr-code-recipe.js.map +1 -1
- package/dist/recipes.d.ts +1 -1
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -1
- package/dist/recipes.js.map +1 -1
- package/dist/result.d.ts +9 -0
- package/dist/result.d.ts.map +1 -1
- package/dist/result.js +5 -0
- package/dist/result.js.map +1 -1
- package/dist/screen-patterns.d.ts +139 -2
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +140 -1
- package/dist/screen-patterns.js.map +1 -1
- package/dist/sheet.d.ts +15 -1
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +20 -1
- package/dist/sheet.js.map +1 -1
- package/dist/showcase.d.ts +27 -27
- package/dist/showcase.js +27 -27
- package/dist/showcase.js.map +1 -1
- package/dist/tag.d.ts +10 -2
- package/dist/tag.d.ts.map +1 -1
- package/dist/tag.js +3 -2
- package/dist/tag.js.map +1 -1
- package/dist/tags-input.d.ts +6 -0
- package/dist/tags-input.d.ts.map +1 -1
- package/dist/tags-input.js +3 -1
- package/dist/tags-input.js.map +1 -1
- package/dist/text-formats.d.ts +2 -2
- package/dist/top.d.ts +2 -2
- package/dist/transfer-list.d.ts +1 -1
- package/dist/transfer-list.d.ts.map +1 -1
- package/dist/transfer-list.js +2 -1
- package/dist/transfer-list.js.map +1 -1
- package/dist/upload-item.d.ts +1 -10
- package/dist/upload-item.d.ts.map +1 -1
- package/dist/upload-item.js +1 -2
- package/dist/upload-item.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/activity-chip.md +4 -3
- package/docs/catalog-freeze.json +5 -2
- package/docs/chart.md +1 -1
- package/docs/consumer-policy.md +13 -6
- package/docs/description-list.md +4 -0
- package/docs/design-profile.md +28 -2
- package/docs/font-roles.md +1 -1
- package/docs/generated/component-maturity.md +5 -4
- package/docs/generated/renderer-evidence.json +92 -6
- package/docs/generated/renderer-evidence.md +5 -3
- package/docs/generated/showcase-manifest.json +49 -1
- package/docs/gravity-letters.md +7 -2
- package/docs/masonry.md +4 -2
- package/docs/screen-patterns.md +5 -2
- package/docs/showcase.md +7 -9
- package/docs/stable-promotion.md +13 -1
- package/docs/usage/README.md +40 -10
- package/docs/usage/SELECTION.md +30 -4
- package/docs/usage/STANDARD.md +11 -2
- package/docs/usage/components/account-switcher.md +98 -0
- package/docs/usage/components/activity-chip.md +4 -4
- package/docs/usage/components/badge.md +1 -1
- package/docs/usage/components/bezier-editor.md +99 -0
- package/docs/usage/components/bottom-cta.md +2 -1
- package/docs/usage/components/button.md +26 -12
- package/docs/usage/components/carousel.md +4 -4
- package/docs/usage/components/chart.md +1 -0
- package/docs/usage/components/chat-message.md +18 -12
- package/docs/usage/components/chat-screen.md +6 -3
- package/docs/usage/components/cohort-retention-table.md +83 -0
- package/docs/usage/components/collapsible.md +7 -2
- package/docs/usage/components/comment-thread-row.md +102 -0
- package/docs/usage/components/comment-thread-screen.md +27 -13
- package/docs/usage/components/composition-layout.md +1 -0
- package/docs/usage/components/context-menu.md +32 -15
- package/docs/usage/components/conversation-row.md +11 -4
- package/docs/usage/components/conversion-funnel.md +85 -0
- package/docs/usage/components/correlation-matrix.md +80 -0
- package/docs/usage/components/data-table.md +4 -0
- package/docs/usage/components/description-list.md +2 -1
- package/docs/usage/components/editor-screen.md +9 -5
- package/docs/usage/components/effect-surface.md +4 -2
- package/docs/usage/components/empty-state.md +2 -2
- package/docs/usage/components/event-attendance-card.md +104 -0
- package/docs/usage/components/expandable-search.md +105 -0
- package/docs/usage/components/file-picker.md +5 -0
- package/docs/usage/components/filter-recovery.md +97 -0
- package/docs/usage/components/follow-profile-card.md +100 -0
- package/docs/usage/components/heading.md +6 -2
- package/docs/usage/components/histogram-explorer.md +89 -0
- package/docs/usage/components/icon-button.md +18 -2
- package/docs/usage/components/icon-tile.md +12 -4
- package/docs/usage/components/image-choice-group.md +81 -0
- package/docs/usage/components/import-field-mapping.md +94 -0
- package/docs/usage/components/inline-edit.md +105 -0
- package/docs/usage/components/keyboard-avoidance-context.md +60 -0
- package/docs/usage/components/keyboard-avoiding.md +5 -1
- package/docs/usage/components/list-detail-screen.md +4 -4
- package/docs/usage/components/list.md +1 -1
- package/docs/usage/components/masonry.md +6 -7
- package/docs/usage/components/media-selection-screen.md +7 -5
- package/docs/usage/components/media-transport.md +104 -0
- package/docs/usage/components/message-composer.md +13 -3
- package/docs/usage/components/moderation-screen.md +5 -2
- package/docs/usage/components/notification-inbox-screen.md +3 -3
- package/docs/usage/components/notification-row.md +64 -8
- package/docs/usage/components/onboarding-screen.md +1 -1
- package/docs/usage/components/overview-screen.md +9 -3
- package/docs/usage/components/pagination.md +18 -3
- package/docs/usage/components/password-rules-field.md +90 -0
- package/docs/usage/components/permission-screen.md +1 -1
- package/docs/usage/components/photo-source-sheet.md +1 -1
- package/docs/usage/components/popover.md +6 -3
- package/docs/usage/components/pricing-choice.md +85 -0
- package/docs/usage/components/profile-summary.md +56 -8
- package/docs/usage/components/progress.md +3 -3
- package/docs/usage/components/qr-code.md +5 -9
- package/docs/usage/components/repeatable-field-group.md +92 -0
- package/docs/usage/components/reply-composer.md +83 -0
- package/docs/usage/components/reply-message.md +85 -0
- package/docs/usage/components/result.md +3 -3
- package/docs/usage/components/saved-items-screen.md +7 -5
- package/docs/usage/components/screen-layout.md +21 -10
- package/docs/usage/components/search-field.md +1 -1
- package/docs/usage/components/section.md +2 -2
- package/docs/usage/components/selection-action-bar.md +89 -0
- package/docs/usage/components/settings-screen.md +13 -11
- package/docs/usage/components/sheet.md +19 -7
- package/docs/usage/components/shortcut-recorder.md +77 -0
- package/docs/usage/components/split-action.md +94 -0
- package/docs/usage/components/streaming-answer.md +94 -0
- package/docs/usage/components/switch.md +6 -2
- package/docs/usage/components/table-column-settings.md +88 -0
- package/docs/usage/components/target-summary.md +79 -0
- package/docs/usage/components/text.md +11 -5
- package/docs/usage/components/toggle-feedback-button.md +94 -0
- package/docs/usage/components/top-bar.md +8 -12
- package/docs/usage/components/transfer-task.md +87 -0
- package/docs/usage/compositions/action-recovery-optimistic.md +18 -14
- package/docs/usage/compositions/action-recovery-save.md +18 -14
- package/docs/usage/compositions/action-recovery-undo.md +18 -14
- package/docs/usage/compositions/activity-status.md +1 -1
- package/docs/usage/compositions/adaptive-content.md +2 -1
- package/docs/usage/compositions/collection-detail.md +6 -5
- package/docs/usage/compositions/collection-editing.md +13 -6
- package/docs/usage/compositions/command-records.md +5 -4
- package/docs/usage/compositions/communication.md +7 -5
- package/docs/usage/compositions/compound-controls.md +7 -3
- package/docs/usage/compositions/content-transition-comparison.md +10 -5
- package/docs/usage/compositions/data-layouts.md +12 -16
- package/docs/usage/compositions/data-workflows.md +7 -4
- package/docs/usage/compositions/date-time-selection.md +5 -5
- package/docs/usage/compositions/design-profile-comparison.md +22 -6
- package/docs/usage/compositions/disclosure.md +14 -9
- package/docs/usage/compositions/document-resource.md +1 -1
- package/docs/usage/compositions/environment-matrix.md +16 -12
- package/docs/usage/compositions/expo-interactions.md +9 -8
- package/docs/usage/compositions/family-drawer.md +9 -5
- package/docs/usage/compositions/field-group.md +7 -3
- package/docs/usage/compositions/interaction-adapters.md +11 -9
- package/docs/usage/compositions/interaction-flow-apply.md +17 -15
- package/docs/usage/compositions/interaction-flow-search.md +11 -7
- package/docs/usage/compositions/live-list.md +12 -8
- package/docs/usage/compositions/navigation-bar-collection.md +8 -3
- package/docs/usage/compositions/optional-motion.md +2 -2
- package/docs/usage/compositions/paper-surface.md +11 -6
- package/docs/usage/compositions/purpose-input-comment.md +10 -4
- package/docs/usage/compositions/purpose-input-message.md +10 -4
- package/docs/usage/compositions/reference-first.md +4 -4
- package/docs/usage/compositions/reference-review.md +4 -4
- package/docs/usage/compositions/reference-settings.md +4 -4
- package/docs/usage/compositions/selection-motion.md +2 -1
- package/docs/usage/compositions/social-compositions.md +7 -6
- package/docs/usage/compositions/stea-event-ticket.md +2 -4
- package/docs/usage/compositions/stea-order-progress.md +19 -21
- package/docs/usage/compositions/stea-pixel-empty.md +26 -26
- package/docs/usage/compositions/task-controls.md +7 -2
- package/docs/usage/compositions/texture-comparison.md +5 -5
- package/docs/usage/compositions/toast-layout.md +7 -3
- package/docs/usage/compositions/toggle-feedback.md +1 -1
- package/docs/usage/compositions/visual-foundations.md +10 -5
- package/docs/usage/compositions/web-additions.md +5 -1
- package/docs/usage/screens/common-chat.md +5 -3
- package/docs/usage/screens/common-comments.md +14 -9
- package/docs/usage/screens/common-conversation-list.md +109 -0
- package/docs/usage/screens/common-inbox.md +5 -3
- package/docs/usage/screens/common-login.md +7 -4
- package/docs/usage/screens/common-profile.md +16 -12
- package/docs/usage/screens/common-saved.md +12 -8
- package/docs/usage/screens/common-search.md +16 -8
- package/docs/usage/screens/common-settings.md +12 -11
- package/docs/usage/screens/common-shell.md +12 -10
- package/docs/usage/screens/dashboard.md +59 -56
- package/docs/usage/screens/discovery-gallery.md +18 -9
- package/docs/usage/screens/flow-collection.md +10 -6
- package/docs/usage/screens/flow-editor.md +9 -6
- package/docs/usage/screens/flow-media.md +12 -7
- package/docs/usage/screens/flow-moderation.md +9 -3
- package/docs/usage/screens/flow-onboarding.md +13 -8
- package/docs/usage/screens/flow-permission.md +5 -3
- package/docs/usage/screens/landing.md +20 -14
- package/docs/usage/screens/mockup-studio.md +100 -88
- package/docs/usage/screens/notification-settings.md +9 -7
- package/docs/usage/screens/product-bento.md +22 -10
- package/docs/usage/screens/reference-comparison.md +2 -2
- package/docs/usage/tokens/font-roles.md +18 -2
- package/docs/usage/tokens/typography.md +22 -11
- package/package.json +7 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: `src/component-recipes.ts`(`sectionRecipe`, Native renderer가 바인딩). 별도 계약 문서는 없다
|
|
9
9
|
- 스토리북: `배포/컴포넌트/레이아웃/섹션`
|
|
10
10
|
|
|
@@ -82,7 +82,7 @@ import { Section } from "@hjmds/react-native/primitives";
|
|
|
82
82
|
| 간격 | 머리와 본문 사이 `spacing.xs` 8, 글자 묶음과 행동 사이 `spacing.sm` 12, 제목과 설명 사이 `spacing.xxs` 4. Section끼리의 간격은 Section이 주지 않는다. 감싸는 [Stack](stack.md)이나 `layoutStyle`로 `layout.sectionGap`(`spacing.xl` 24)을 둔다 | `sectionRecipe.gap`·`headerGap`·`copyGap`, `layout.sectionGap` |
|
|
83
83
|
| 순서·정렬 | 머리 행동은 끝(LTR 오른쪽)에 하나, 글자 묶음과 세로 가운데 정렬. 보통 [Link](link.md) 또는 `tone="link"` [Button](button.md) | `.hjm-section__header`, Native `Section` |
|
|
84
84
|
| 고정·스크롤 | 고정되지 않는다. 화면 본문 스크롤 안에 둔다 | — |
|
|
85
|
-
| 좁은 폭·큰 글자 | 머리 행이 세로로 쌓이고 행동이 글자 아래로
|
|
85
|
+
| 좁은 폭·큰 글자 | 폭과 관계없이 한 행을 유지한다. 글자 크기 160% 이상(`largeTextThreshold`)에서만 머리 행이 세로로 쌓이고 행동이 글자 아래로 간다(두 플랫폼 같은 조건). 2026-10-17까지 Web은 폭 600 미만에서도 쌓여, 같은 휴대폰 폭에서 Native는 한 행인데 Web만 행동이 아래 줄로 늘어났다(화면 리뷰 S5#3) | Web `.hjm-root[data-large-text]`, Native `textScale >= 1.6` |
|
|
86
86
|
|
|
87
87
|
```text
|
|
88
88
|
최근 본 장소 [전체 보기] ← 머리: 제목 title 18, gap spacing.sm 12
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# SelectionActionBar
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-16
|
|
8
|
+
- 근거: [목록 편집과 복구 계약](../../collection-editing.md), `src/collection-editing.ts`의 `snapshotSelection`·`assertDistinctIds`, 양 renderer `src/collection-editing.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/선택과 필터/선택 항목 묶음 작업`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
목록에서 여러 항목을 고른 뒤 보관·삭제 같은 같은 행동을 한 번에 실행하고, 방금 확정된 작업만 되돌릴 때 쓴다.
|
|
14
|
+
선택 개수 요약, 묶음 행동 버튼, 선택 해제, 작업별 복구 버튼을 한 묶음으로 보여 준다. 실행 시점의 선택은 고정된 사본으로 넘긴다.
|
|
15
|
+
묶음 사용 기준은 [목록 편집과 복구](../compositions/collection-editing.md), 복구 흐름은 [보관과 실행 취소](../compositions/action-recovery-undo.md)다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 항목 하나의 행동 | 행 안 [Button](button.md)·[SwipeActions](swipe-actions.md) |
|
|
22
|
+
| 선택 항목 검토와 수정 | [ListRow](list-row.md) 목록 + 제품 확인 흐름 |
|
|
23
|
+
| 실행 직후 일시 안내 | [Toast](toast.md) |
|
|
24
|
+
|
|
25
|
+
## 공개 이름과 import
|
|
26
|
+
|
|
27
|
+
| 이름 | 역할 | Web | Native |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `SelectionActionBar` | 선택 요약·묶음 행동·해제·복구 | `@hjmds/react/collection-editing` | `@hjmds/react-native/collection-editing` |
|
|
30
|
+
|
|
31
|
+
루트 barrel에는 없다. `SelectionAction` 타입은 `@hjmds/design-contracts/collection-editing`에 있다.
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { SelectionActionBar } from "@hjmds/react/collection-editing";
|
|
38
|
+
|
|
39
|
+
<SelectionActionBar selectedIds={selected} selectionLabel={count => t("selection.count", { count })}
|
|
40
|
+
clearLabel={t("selection.clear")} onClear={() => setSelected([])}
|
|
41
|
+
actions={[{ id: "archive", label: t("selection.archive") }]} errorLabel={t("selection.failed")}
|
|
42
|
+
onAction={async (actionId, ids) => { await archive(ids); setSelected([]); setLastArchived(ids); }}
|
|
43
|
+
{...(lastArchived ? { recovery: { label: t("selection.undo", { count: lastArchived.length }), onUndo: () => restore(lastArchived) } } : {})} />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
// Native
|
|
48
|
+
import { SelectionActionBar } from "@hjmds/react-native/collection-editing";
|
|
49
|
+
|
|
50
|
+
<SelectionActionBar selectedIds={selected} selectionLabel={count => t("selection.count", { count })}
|
|
51
|
+
clearLabel={t("selection.clear")} onClear={() => setSelected([])}
|
|
52
|
+
actions={actions} errorLabel={t("selection.failed")} onAction={runConfirmed} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 축과 기본값
|
|
56
|
+
|
|
57
|
+
| prop | 값 | 기본값 | 설명 |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `selectedIds` | `string[]` | 필수 | 비어 있거나 겹치는 id는 `TypeError` |
|
|
60
|
+
| `selectionLabel` | `(count) => string` | 필수 | 요약 문구이자 Web group 이름 |
|
|
61
|
+
| `clearLabel` / `onClear` | `string` / `() => void` | 필수 | 선택 해제 |
|
|
62
|
+
| `actions` | `{ id, label, destructive? }[]` | 필수 | `destructive`면 `tone="danger"`, 아니면 `secondary`. id 중복은 `TypeError` |
|
|
63
|
+
| `onAction` | `(actionId, selectedIds) => void \| Promise<void>` | 필수 | 실행 시점 선택의 고정 사본을 받는다. reject하면 실패 표시 |
|
|
64
|
+
| `errorLabel` | `string` | 필수 | 실패 문구 |
|
|
65
|
+
| `recovery` | `{ label, onUndo() }` | 없음 | 제품이 확정한 그 작업만 되돌리는 버튼 |
|
|
66
|
+
| `disabled` | `boolean` | `false` | 모든 행동 잠금 |
|
|
67
|
+
|
|
68
|
+
선택이 없고 `recovery`도 없으며 진행·실패 상태가 아니면 아무것도 렌더하지 않는다(`null`).
|
|
69
|
+
|
|
70
|
+
## 배치
|
|
71
|
+
|
|
72
|
+
| 항목 | 값 | 근거 |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| 크기 | Button 기본 크기 | 양 renderer `SelectionActionBar` |
|
|
75
|
+
| 간격 | 요약·버튼 줄·실패 문구 사이 `Stack gap="sm"`(12), 버튼 사이 `sm` | 같은 구현 |
|
|
76
|
+
| 순서·정렬 | 선택 개수 요약 → [묶음 행동…] [선택 해제](ghost) [복구](ghost) → 실패 문구(가운데) | 렌더 순서 |
|
|
77
|
+
| 고정·스크롤 | 자체 고정 없음. 대상 목록 바로 아래 또는 제품 footer 슬롯에 둔다 | [목록 편집과 복구](../compositions/collection-editing.md) |
|
|
78
|
+
| 좁은 폭·큰 글자 | 버튼 줄은 `wrap`으로 다음 줄로 넘어간다 | `Stack axis="inline" wrap` |
|
|
79
|
+
|
|
80
|
+
## 꼭 지킬 것
|
|
81
|
+
|
|
82
|
+
- `recovery.onUndo`는 방금 확정된 작업만 되돌린다. 컬렉션 전체를 이전 상태로 복원하지 않는다.
|
|
83
|
+
- 성공 뒤 선택 해제·목록 갱신·복구 대상 보관은 제품이 한다. 이 구성은 선택을 바꾸지 않는다.
|
|
84
|
+
- 위험 행동은 `destructive: true`로 표시하고, 필요한 확인 대화상자는 제품이 `onAction` 안에서 연다.
|
|
85
|
+
|
|
86
|
+
## 상태와 상호작용
|
|
87
|
+
|
|
88
|
+
실행한 버튼만 로딩이 되고 다른 행동과 선택 해제는 잠긴다. 선택이 0개면 묶음 행동과 해제가 비활성이다(복구는 남는다).
|
|
89
|
+
요약은 Web `role="status"`, Native `accessibilityLiveRegion="polite"`로 개수 변화를 알린다. 실패 문구는 Web `role="alert"`, Native assertive live region(iOS는 `announceForAccessibility`)이다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.17.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [반복 화면 조합](../../screen-patterns.md), Web·Native `src/screens.tsx`·`src/screen-flows.tsx`; 기존 개별 지침을 새 규격으로 통합. 예제 스토리는 2026-10-06 사용자 승인으로 스토리북 배포([승인 기록](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-전체-승격과-규격-확정)). 스토리북 배포는 API 게시가 아니다(`적용` 참고)
|
|
9
9
|
- 스토리북: `배포/화면/설정/앱 설정`
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
[ScreenLayout](screen-layout.md) 안에 그룹으로 쌓는다(Web은 의미 있는 Section, Native는 Stack과 header 역할). 섹션 안의 행은
|
|
15
15
|
기존 ListRow·Switch·Select·Field를 그대로 넣는다.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
제목·섹션 틀·구분선·
|
|
18
18
|
상태 교체를 제품이 다시 만들지 않는다.
|
|
19
19
|
|
|
20
20
|
## 쓰지 않을 때
|
|
@@ -38,13 +38,11 @@
|
|
|
38
38
|
아래 preview의 메모리 상태를 서버 저장으로 취급하지 않는다. 예제는 읽기용이며 제품에서 showcase를 import하지 않는다.
|
|
39
39
|
|
|
40
40
|
<!-- usage-example: Web ../../../../../showcase/web/src/patterns/settings-page-preview.tsx -->
|
|
41
|
-
- [Web 실행 예제](
|
|
41
|
+
- [Web 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/web/src/patterns/settings-page-preview.tsx): 공개 SettingsScreen·행·Sheet 조합과 로컬 설정 상태.
|
|
42
42
|
<!-- usage-example: Native ../../../../../showcase/native/src/settings-page-preview.tsx -->
|
|
43
|
-
- [Native 실행 예제](
|
|
43
|
+
- [Native 실행 예제](https://github.com/jim1286/hjm-design-system/blob/9fccb921216a06bac2568ca8252bd0b4d7c48d95/showcase/native/src/settings-page-preview.tsx): 같은 흐름의 Native 공개 API와 이벤트 연결.
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
[Native 원본](https://github.com/jim1286/hjm-design-system/blob/v1.17.1/showcase/native/src/settings-page-preview.tsx)을 참고한다.
|
|
47
|
-
고정 버전 예제와 현재 checkout의 후속 변경은 구분하며 설치된 타입을 기준으로 사용한다.
|
|
45
|
+
고정 SHA의 예제는 설치 패키지에서도 열 수 있다. 해당 예제와 현재 checkout의 후속 변경을 구분하고 설치된 공개 타입을 기준으로 사용한다.
|
|
48
46
|
|
|
49
47
|
## 축과 기본값
|
|
50
48
|
|
|
@@ -52,9 +50,9 @@
|
|
|
52
50
|
| --- | --- | --- | --- |
|
|
53
51
|
| `sections` | `readonly { id: string; title: string; description?: string; children: ReactNode }[]` | 필수 | 각 항목을 설정 그룹 하나로 그린다. `id`는 안정적인 key |
|
|
54
52
|
| `profile` | `ReactNode` | 없음 | 섹션 위 프로필 영역 |
|
|
55
|
-
| 나머지 | `ScreenLayout`과 같음(`children`·`scroll` 제외) | — | `title` 필수, `state`·`stateAction`·`notice`·`footer`·`header`·`
|
|
53
|
+
| 나머지 | `ScreenLayout`과 같음(`children`·`scroll` 제외) | — | `title` 필수, `state`·`stateAction`·`notice`·`footer`·`header`·`exit`(헤더 시작 쪽 화면 나가기 아이콘)·`actions`·`contentInset`. 화면 전체가 스크롤한다 |
|
|
56
54
|
|
|
57
|
-
두 플랫폼 모두 그룹 위의 별도 구분선과 카드 배경을 두지 않는다. 행 구분은 List·Switch의 계약을
|
|
55
|
+
두 플랫폼 모두 그룹 위의 별도 구분선과 카드 배경을 두지 않는다. 행 구분은 List·Switch의 계약을 따른다.
|
|
58
56
|
|
|
59
57
|
## 배치
|
|
60
58
|
|
|
@@ -87,8 +85,12 @@
|
|
|
87
85
|
| Switch 기본 `presentation` | `inline` | `row` |
|
|
88
86
|
| 행 이동 이벤트 | ListRow `href`/`onClick` | ListRow `onPress` |
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
보였다. SettingsScreen은 화면 제목 아래의 그룹 이름을 `Text label / strong / muted`로 표시하고,
|
|
88
|
+
SettingsScreen은 화면 제목 아래의 그룹 이름을 `Text label / strong / muted`로 표시하고,
|
|
92
89
|
그룹 간격은 `md`, 제목과 내용 간격은 `xs`로 둔다. 그룹마다 추가하던 진한 선은 제거하며,
|
|
93
90
|
행 자체의 경계·터치 영역은 유지한다. 제품에서 내부 스타일을 덮거나 별도 설정 틀을 복제하지 않는다.
|
|
94
91
|
Web의 의미 있는 section heading과 Native의 header 역할은 유지한다.
|
|
92
|
+
|
|
93
|
+
## 근거
|
|
94
|
+
|
|
95
|
+
- SettingsScreen 도입: 2026-09-23 소비 감사에서 제품마다 같은 설정 화면을 직접 조립하고 있었다.
|
|
96
|
+
- 그룹 위 구분선·카드 배경 없음, 그룹 이름 `Text label / strong / muted`: 2026-10-08 실제 설정 화면 재검토(Spint 실화면) — 일반 Section의 큰 제목과 상단 구분선이 설정 행보다 강하게 보였다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [Sheet 입력 화면과 가용 영역](../../sheet.md), [optional adapters](../../optional-adapters.md)(Native 제스처 확장), `src/component-recipes.ts`(`sheetRecipe`), `src/sheet.ts`(`sheetBehaviorDefaults`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/오버레이/시트`
|
|
10
10
|
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
현재 화면 위에 모달로 띄우는 보조 작업 패널에 쓴다. 설정 한 항목 편집, 선택 목록(테마·언어),
|
|
14
14
|
짧은 입력 폼, 공유·사진 출처 선택처럼 끝나면 원래 화면으로 돌아오는 작업이다. 기본은 하단에서 올라온다.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
따로 보정하고 있었다. 이 보정은 Sheet가 소유한다. 제품 wrapper에서 Modal·키보드 listener·강제 maxHeight를
|
|
16
|
+
제목 정렬, 키보드 높이, top inset, 본문 maxHeight 보정은 Sheet가 소유한다. 제품 wrapper에서 Modal·키보드 listener·강제 maxHeight를
|
|
18
17
|
다시 만들지 않는다.
|
|
19
18
|
|
|
20
19
|
## 쓰지 않을 때
|
|
@@ -84,7 +83,9 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
84
83
|
| `open` · `defaultOpen` | `boolean` | 비제어 `false` | Web은 제어하면 `onOpenChange` 필수, 비제어면 `trigger` 필수 |
|
|
85
84
|
| `onOpenChange` | `(open: boolean, detail: { reason }) => void` | — | reason: `trigger` · `close-action` · `escape` · `back` · `outside` · `swipe` · `programmatic`. Android 하드웨어 back은 Native에서만 `back` |
|
|
86
85
|
| `onDismissComplete` | `(detail: { reason }) => void` | — | 표면이 실제로 사라진 뒤 한 번. 다음 modal·화면 이동은 여기서 |
|
|
87
|
-
| `dismissPolicy` | `{ dismissible?, dismissWhileBusy?, outsideDismiss?, escapeOrBackDismiss?, swipeDismiss? }` | `dismissible` true · `dismissWhileBusy` false · `outsideDismiss` true · `escapeOrBackDismiss` true · `swipeDismiss` false | 닫기 허용
|
|
86
|
+
| `dismissPolicy` | `{ dismissible?, dismissWhileBusy?, outsideDismiss?, escapeOrBackDismiss?, swipeDismiss? }` | `dismissible` true · `dismissWhileBusy` false · `outsideDismiss` 하단 false / 옆 true · `escapeOrBackDismiss` true · `swipeDismiss` true(미게시 변경, 이전 false) | 닫기 허용 범위. `resolveSheetDismissPolicy(placement, override)` |
|
|
87
|
+
| `keyboardAvoidance` (Native) | `boolean` | `true`(미게시 변경, 이전 `false`) | 모달이 키보드 높이만큼 비킨다. 안쪽 `ScreenLayout`은 중복 처리하지 않는다 |
|
|
88
|
+
| `showClose` | `boolean` | 하단 `false` · 옆 `true`(미게시) | 머리의 × 표시. 하단 시트는 드래그로 닫는 것이 기본이라 끈다 |
|
|
88
89
|
| `busy` | `boolean` | `false` | 사용자 닫기를 막는다 |
|
|
89
90
|
| `title` · `closeLabel` | 문구 | 필수 | Native `title`이 element면 `accessibilityTitle`(string) 필수 |
|
|
90
91
|
| `description` · `footer` | 노드(Native `description`은 `string`) | — | `footer`는 스크롤 밖 고정. 저장 행동은 여기 |
|
|
@@ -95,7 +96,6 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
95
96
|
|
|
96
97
|
### 디자인 프로필 상속
|
|
97
98
|
|
|
98
|
-
2026-10-07 테마 소비 경로 점검에서 고정 foundation/recipe 값이 남은 곳을 보완했다.
|
|
99
99
|
모서리의 recipe 역할은 유지하고 값은 가장 가까운 Provider의 `designProfile.tokens.radius`를
|
|
100
100
|
읽는다. Dialog/AlertDialog/Sheet/일반 Toast의 그림자는 `tokens.shadow.floating`을 읽으며
|
|
101
101
|
프로필 없는 소비자의 기본값은 유지한다. 상태·초안·선택·Modal teardown은 이 축의 소유가 아니다.
|
|
@@ -112,7 +112,7 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
112
112
|
| --- | --- | --- |
|
|
113
113
|
| 크기 | 하단 시트 높이: `auto` 내용 높이(최대 화면 높이 × `maxHeightRatio` 0.9), `medium` 60%, `large` 85%, `full` 위 safe area 안 전체 높이(0.9 상한 없음). Web 하단 시트 최대 폭 640. 위 두 모서리만 `radius.xl` 24. 옆 시트(`start`·`end`): Web 폭 `min(28rem, 100%)`·높이 화면 − 32, Native 폭 88%(최대 420)·화면 높이 | `sheetRecipe.sizes`·`content`·`web`, `.hjm-sheet`, Native `Sheet` |
|
|
114
114
|
| 간격 | 두 플랫폼 같은 값: 좌우 `content.paddingHorizontal`(`spacing.lg` 20), 위·아래 `content.paddingTop/Bottom`(`spacing.sm` 12, 하단 시트는 아래 safe area를 더함), 머리·본문·footer 사이 `body.gap`(`spacing.md` 16), footer 위 `footer.paddingTop`(`spacing.sm` 12), 제목–닫기 `header.gap`(`spacing.sm` 12), footer 버튼 사이 `footer.gap`(`spacing.sm` 12) | `.hjm-sheet__*`, `sheetRecipe.content`·`body`·`footer` |
|
|
115
|
-
| 순서·정렬 | 머리(제목·설명 + 닫기) → 본문 → `footer`. 머리 최소 높이 44(`control.minTouchTarget`), 닫기는 끝 쪽에서 제목 세로 중앙. Web footer는 오른쪽 정렬 가로 줄 [보조][주]. Native footer는 세로 열이라 꽉 찬 폭 버튼을 주 행동 먼저 둔다.
|
|
115
|
+
| 순서·정렬 | 머리(제목·설명 + 닫기) → 본문 → `footer`. 머리 최소 높이 44(`control.minTouchTarget`), 닫기는 끝 쪽에서 제목 세로 중앙. Web footer는 오른쪽 정렬 가로 줄 [보조][주]. Native footer는 세로 열이라 꽉 찬 폭 버튼을 주 행동 먼저 둔다. 하단 시트는 항상 위 가운데 36×4 손잡이(터치 최소 폭 44)를 둔다. `detents`가 있으면 높이 전환 버튼, 없으면 `closeLabel` 이름의 닫기 버튼이다. 손잡이·머리 영역을 아래로 끌어 `sheetDragRecipe.dismissDistanceRatio`(시트 높이의 25%) 또는 `dismissVelocity` 0.5px/ms를 넘기면 `swipe`로 닫고, 아니면 제자리로 돌아간다(미게시, Web·Native 동일) | `sheetRecipe.header`·`handle`, `.hjm-sheet__footer`, `.hjm-sheet__handle` |
|
|
116
116
|
| 고정·스크롤 | 본문만 스크롤, 머리·`footer` 고정(Native는 `scrollable`일 때 ScrollView). 고정 높이(`medium`·`large`·`full`, 옆 시트)에서는 본문이 머리·footer를 뺀 남은 높이를 차지하고 `footer`는 시트 아래에 붙는다. 그래서 본문의 `flex: 1` 자식(SearchScreen, 목록 화면)이 그 높이를 채운다(Web `.hjm-sheet__body` `flex: 1 1 auto`, Native 본문 `flexGrow: 1`·`minHeight: 0`, 1.13.1 변경). `auto`는 내용 높이 그대로다. 저장·확인은 `footer`에. 아래 안전 영역은 시트가 자기 여백에 더한다(`spacing.sm` 12 + 아래 inset, Web은 `env(safe-area-inset-bottom)`). 위 inset은 시트가 올라갈 높이를 줄인다. 제품이 inset을 다시 더하지 않는다 | `.hjm-sheet__body`, `sheetRecipe.safeArea`, Native `Sheet` |
|
|
117
117
|
| 좁은 폭·큰 글자 | 폭 < 640이면 하단 시트가 화면 폭을 채운다. Web footer는 줄을 바꾼다. 큰 글자로 내용이 길어지면 `auto`는 최대 90%에서 멈추고 본문이 스크롤된다(`full`은 위 safe area까지) | `.hjm-sheet[data-placement="bottom"]`, `maxHeightRatio` |
|
|
118
118
|
|
|
@@ -135,6 +135,8 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
135
135
|
|
|
136
136
|
## 꼭 지킬 것
|
|
137
137
|
|
|
138
|
+
- 하단 시트는 드래그로 내려 닫을 수 있어야 한다(필수). 배경 탭 닫기와 ×는 기본으로 끈다. 드래그를 쓸 수 없는 사용자를 위해 손잡이 닫기 버튼, Escape·Android back, VoiceOver 닫기 제스처는 유지한다. `swipeDismiss: false`는 결제 확인처럼 실수로 닫히면 안 되는 흐름에만 쓰고 이유를 제품 코드에 남긴다.
|
|
139
|
+
|
|
138
140
|
- 입력 폼 Native Sheet는 `keyboardAvoidance`와 `scrollable`을 함께 켜고, 제품의 키보드 listener·maxHeight·
|
|
139
141
|
중첩 ScrollView를 지운다. 본문이 FlatList 등 가상화 목록이면 `scrollable={false}`로 둔다.
|
|
140
142
|
- 저장 버튼은 `footer`에 둔다. 저장 중에는 `busy`로 닫기를 막는다.
|
|
@@ -146,6 +148,8 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
146
148
|
|
|
147
149
|
## 플랫폼 차이
|
|
148
150
|
|
|
151
|
+
Flutter 제품은 `showModalBottomSheet` 대신 `showHjmBottomSheet`(`hjm_flutter`)로 연다. 같은 기본값(드래그 닫기·손잡이 표시·배경 탭 닫기 끔·× 없음)을 적용하고 본문은 `HjmSheetLayout`을 쓴다(미게시).
|
|
152
|
+
|
|
149
153
|
| 항목 | Web | Native |
|
|
150
154
|
| --- | --- | --- |
|
|
151
155
|
| uncontrolled 트리거 | `trigger`(uncontrolled면 필수) | 없음 |
|
|
@@ -169,7 +173,15 @@ import { Sheet } from "@hjmds/react-native/overlays";
|
|
|
169
173
|
|
|
170
174
|
- `size="full"`(Web `detents`의 `full` 포함)은 위 안전 영역을 뺀 전체 높이를 사용한다. 0.9 상한은 `auto`에만 적용된다(1.13.0 변경).
|
|
171
175
|
- 1.13.0 이하 Native 고정 높이 Sheet는 본문이 내용 높이라서 `flex: 1` 자식이 0pt가 됐다. 시트 안 SearchScreen이 검색 입력만 그리고
|
|
172
|
-
필터 줄·목록이
|
|
176
|
+
필터 줄·목록이 사라졌다. 제품은 창 높이 `flexBasis`로 우회했다.
|
|
173
177
|
1.13.1부터는 그 우회 없이 채워진다. 1.13.0 이하에서도 footer는 본문 바로 아래에 있었고, 1.13.1부터 고정 높이 시트에서는 시트 아래에 붙는다.
|
|
174
178
|
- `scrollable` 본문 안에서는 자식이 `flex: 1`로 높이를 채울 수 없다(ScrollView 내용은 내용 높이다). 자기 스크롤을 가진 화면(SearchScreen 등)은
|
|
175
179
|
`scrollable` 없이 넣는다.
|
|
180
|
+
|
|
181
|
+
## 근거
|
|
182
|
+
|
|
183
|
+
- Sheet가 제목 정렬·키보드·top inset·maxHeight 보정 소유: 2026-09-23 소비 감사에서 제품마다 Sheet를 직접 조립하며 이 값을 따로 보정하고 있었다.
|
|
184
|
+
- Native `keyboardAvoidance` 기본 true: 2026-10-16 키보드 전수 조사 — 입력 시트가 옵션을 빠뜨려 키보드에 가려졌다.
|
|
185
|
+
- 디자인 프로필 상속(radius·floating shadow): 2026-10-07 테마 소비 경로 점검에서 고정 foundation/recipe 값이 남은 곳을 보완했다.
|
|
186
|
+
- 하단 시트 드래그 닫기(필수): 2026-10-16 사용자 결정.
|
|
187
|
+
- 1.13.0 이하 고정 높이 Sheet 함정: 2026-10-06 utilverse 채팅 도구 선택, iPhone 17 Pro · iOS 26.5에서 확인했다.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ShortcutRecorder
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-16
|
|
8
|
+
- 근거: [편집 도구 계약](../../authoring-tools.md), `src/authoring-tools.ts`의 `resolveShortcutCapture`·`normalizeShortcut`, Web `src/authoring-tools.tsx`. Native `src/authoring-tools.tsx`는 이식 가능한 브라우저 키보드 등록면이 없어 제공하지 않는다고 명시한다
|
|
9
|
+
- 스토리북: `배포/구성/직접 조작과 모션/단축키 조합 기록`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
Web 앱에서 사용자가 수정키+키 조합을 눌러 기록하고, 확인 뒤 실제 단축키로 등록하게 할 때 쓴다.
|
|
14
|
+
브라우저·OS가 쓰는 조합과 제품이 넘긴 충돌·예약 조합을 거르고, 상태를 문구로 알려 준다. 묶음 사용 기준은 [베지어·단축키 편집 도구](../compositions/authoring-tools.md)다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Native 앱 | 지원하지 않는다(제품 전용 설정 화면) |
|
|
21
|
+
| 고정 단축키 안내만 표시 | [Text](text.md)·[CodeBlock](code-block.md) |
|
|
22
|
+
| 명령 검색·실행 | [CommandPalette](command-palette.md) |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `ShortcutRecorder` | 단축키 조합 기록·등록 | `@hjmds/react/authoring-tools` | 없음 |
|
|
29
|
+
|
|
30
|
+
루트 barrel에는 없다. `Shortcut`·`ShortcutCapture` 타입과 `normalizeShortcut`은 `@hjmds/design-contracts/authoring-tools`에 있다.
|
|
31
|
+
|
|
32
|
+
## 최소 사용 예
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// Web
|
|
36
|
+
import { ShortcutRecorder } from "@hjmds/react/authoring-tools";
|
|
37
|
+
|
|
38
|
+
<ShortcutRecorder value={shortcut} onRegister={saveShortcut} platform={platform}
|
|
39
|
+
conflicts={usedShortcutKeys} formatShortcut={formatForKeyboardLayout}
|
|
40
|
+
labels={{ record: t("shortcut.record"), recording: t("shortcut.recording"), empty: t("shortcut.empty"),
|
|
41
|
+
register: t("shortcut.register"), cancel: t("cancel"), error: t("shortcut.failed"),
|
|
42
|
+
status: status => t(`shortcut.status.${status}`) }} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 축과 기본값
|
|
46
|
+
|
|
47
|
+
| prop | 값 | 기본값 | 설명 |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| `value` | `Shortcut \| null` | 필수 | 현재 등록된 조합 `{ code, ctrl, alt, shift, meta }` |
|
|
50
|
+
| `onRegister` | `(shortcut) => void \| Promise<void>` | 필수 | 실제 등록. resolve하면 초안을 비우고, reject하면 실패 표시 |
|
|
51
|
+
| `platform` | `"mac"` · `"windows"` · `"linux"` | 필수 | 주 수정키(mac은 Meta, 그 외 Ctrl)와 OS 예약 판정 |
|
|
52
|
+
| `conflicts` / `reserved` | `string[]` | 없음 | `normalizeShortcut` 형식(예: `Ctrl+Shift+KeyJ`). 각각 `conflict`·`reserved` 상태 |
|
|
53
|
+
| `formatShortcut` | `(shortcut) => string` | 필수 | 표시 문구. 키보드 배열에 맞는 이름은 제품 소유 |
|
|
54
|
+
| `labels` | `{ record, recording, empty, register, cancel, error, status(status) }` | 필수 | `status`는 `ignored`·`cancelled`·`invalid`·`reserved`·`conflict`·`ready`별 문구 |
|
|
55
|
+
| `disabled` | `boolean` | `false` | 켜지면 기록·초안을 중단한다 |
|
|
56
|
+
|
|
57
|
+
## 배치
|
|
58
|
+
|
|
59
|
+
| 항목 | 값 | 근거 |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| 크기 | 기록 영역 최소 높이 44, 기록 중 2px 외곽선 | Web `ShortcutRecorder` |
|
|
62
|
+
| 간격 | 영역·버튼 줄·상태 사이 `Stack gap="md"`(16), 버튼 사이 `sm`(12) | 같은 구현 |
|
|
63
|
+
| 순서·정렬 | 기록 영역(가운데 정렬: 기록 중 안내/초안/현재 값/빈 문구) → [기록] [등록] [취소] → 상태 문구 → 실패 문구(가운데) | 렌더 순서 |
|
|
64
|
+
| 고정·스크롤 | 자체 고정·스크롤 없음 | 별도 scroller 없음 |
|
|
65
|
+
| 좁은 폭·큰 글자 | 버튼 줄은 inline Stack(줄바꿈 미지정), 문구는 Text로 줄바꿈 | Web 구현 |
|
|
66
|
+
|
|
67
|
+
## 꼭 지킬 것
|
|
68
|
+
|
|
69
|
+
- 수정키(Ctrl·Alt·Meta) 없는 단독 키는 `invalid`다. 입력·포커스 이동을 가로채지 않기 위한 계약이므로 우회하지 않는다.
|
|
70
|
+
- 등록 직전에 같은 판정을 다시 실행한다. `conflicts`·`reserved`는 최신 값을 넘긴다.
|
|
71
|
+
- 표시 이름(`formatShortcut`)과 저장 식별자(`normalizeShortcut`의 물리 키 code)를 섞지 않는다.
|
|
72
|
+
|
|
73
|
+
## 상태와 상호작용
|
|
74
|
+
|
|
75
|
+
[기록]을 누르면 영역에 포커스가 가고 keydown을 판정한다. Tab은 가로채지 않으며 포커스가 떠나면 기록을 멈춘다.
|
|
76
|
+
반복 입력·IME 조합·AltGraph·수정키 단독은 무시(`ignored`), Escape는 취소(`cancelled`)다. 브라우저 소유 조합(주 수정키+Q·W·T·N·R·L·P·S·O·F·C·V·X·A·Z)과 OS 조합(Alt+Tab·Alt+F4·Ctrl+Alt+Delete, mac 외 Meta, mac Meta+Space)은 `reserved`다.
|
|
77
|
+
`ready`면 초안을 보여 주고 [등록]이 나타난다. 상태 문구는 `role="status"`, 실패는 `role="alert"`다.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# SplitAction
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-16
|
|
8
|
+
- 근거: [작업 선택 계약](../../task-controls.md), `src/task-controls.ts`의 `resolveTaskChoice`, 양 renderer `src/task-controls.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/분할 실행`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
실행 버튼 하나와 실행 방식 선택 메뉴를 나란히 둔다. 선택한 방식(`modeId`)으로 실제 작업을 실행하고, 메뉴에서는
|
|
14
|
+
방식만 바꾼다. 방식 선택과 실행을 한 버튼에 섞으면 메뉴를 고르는 순간 작업이 실행되므로 두 행동을 분리했다.
|
|
15
|
+
배치·진행·실패 표시의 흐름은 [선택과 실제 실행](../compositions/task-controls.md) 구성 지침을 함께 본다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 실행 방식이 하나뿐인 행동 | [Button](button.md) |
|
|
22
|
+
| 고르는 즉시 적용되는 항목 목록 | [Menu](menu.md) |
|
|
23
|
+
| 계정·세션 전환 | `AccountSwitcher`([선택과 실제 실행](../compositions/task-controls.md)) |
|
|
24
|
+
| 요금 주기·상품 선택 후 계속 | `PricingChoice`([선택과 실제 실행](../compositions/task-controls.md)) |
|
|
25
|
+
|
|
26
|
+
## 공개 이름과 import
|
|
27
|
+
|
|
28
|
+
| 이름 | 역할 | Web | Native |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `SplitAction` | 실행 버튼 + 방식 선택 메뉴 | `@hjmds/react/task-controls` | `@hjmds/react-native/task-controls` |
|
|
31
|
+
|
|
32
|
+
`TaskChoice` 타입은 `@hjmds/design-contracts/task-controls`에서 가져온다.
|
|
33
|
+
|
|
34
|
+
## 최소 사용 예
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// Web
|
|
38
|
+
import { SplitAction } from "@hjmds/react/task-controls";
|
|
39
|
+
|
|
40
|
+
<SplitAction modes={modes} modeId={modeId} onModeChange={setModeId}
|
|
41
|
+
onExecute={id => exportReport(id)} executeLabel={mode => t("export.run", { mode: mode.label })}
|
|
42
|
+
menuLabel={t("export.modes")} dismissLabel={t("common.close")} errorLabel={t("export.failed")} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Native
|
|
47
|
+
import { SplitAction } from "@hjmds/react-native/task-controls";
|
|
48
|
+
|
|
49
|
+
<SplitAction modes={modes} modeId={modeId} onModeChange={setModeId}
|
|
50
|
+
onExecute={id => exportReport(id)} executeLabel={mode => t("export.run", { mode: mode.label })}
|
|
51
|
+
menuLabel={t("export.modes")} dismissLabel={t("common.close")} errorLabel={t("export.failed")} />
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 축과 기본값
|
|
55
|
+
|
|
56
|
+
| prop | 값 | 기본값 | 설명 |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `modes` | `readonly TaskChoice[]`(`{ id, label, description?, disabled? }`) | 필수 | id는 비지 않고 서로 달라야 한다. 아니면 던진다 |
|
|
59
|
+
| `modeId` | string | 필수 | 제어값. `modes`에 없으면 던진다 |
|
|
60
|
+
| `onModeChange` | `(id) => void` | 필수 | 메뉴에서 고른 방식. 잠금 중이거나 비활성 방식이면 부르지 않는다 |
|
|
61
|
+
| `onExecute` | `(modeId) => void \| Promise<void>` | 필수 | 실제 작업. Promise가 끝날 때까지 진행 중이다 |
|
|
62
|
+
| `executeLabel` | `(mode: TaskChoice) => string` | 필수 | 실행 버튼 문구 |
|
|
63
|
+
| `menuLabel` · `dismissLabel` · `errorLabel` | string | 필수 | 메뉴 trigger·닫기(Native Menu)·실패 문구 |
|
|
64
|
+
| `disabled` | boolean | false | 실행과 메뉴를 함께 잠근다 |
|
|
65
|
+
|
|
66
|
+
## 배치
|
|
67
|
+
|
|
68
|
+
| 항목 | 값 | 근거 |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| 크기 | 두 버튼 모두 [Button](button.md) 기본 `medium`(높이 44) | `task-controls.tsx` |
|
|
71
|
+
| 간격 | 실행·메뉴 사이 `spacing.xs` 8, 버튼 줄과 실패 문구 사이 `spacing.sm` 12 | `Stack gap="xs"`, 바깥 `Stack gap="sm"` |
|
|
72
|
+
| 순서·정렬 | [실행(primary)] → [방식 메뉴(secondary)], 실패 문구는 아래 가운데 정렬 | 양 renderer |
|
|
73
|
+
| 고정·스크롤 | 자체 고정·스크롤 없음. 놓는 영역이 정한다 | — |
|
|
74
|
+
| 좁은 폭·큰 글자 | 버튼 줄이 `wrap`으로 다음 줄로 넘어간다 | `Stack axis="inline" wrap` |
|
|
75
|
+
|
|
76
|
+
모든 방식이 `disabled`이면 메뉴 대신 비활성 버튼 하나(`menuLabel`)만 그린다.
|
|
77
|
+
|
|
78
|
+
## 꼭 지킬 것
|
|
79
|
+
|
|
80
|
+
- 메뉴 선택은 방식만 바꾼다. 선택만으로 작업을 실행하지 않는다.
|
|
81
|
+
- 진행 중(`onExecute` 대기)에는 실행 버튼이 loading이고 메뉴가 잠긴다. 실패하면 `errorLabel`을 경고로 알리며, 재시도는 같은 실행 버튼이다.
|
|
82
|
+
- 모든 문구는 제품 i18n에서 만든다. 실행 결과·권한·서버 요청은 제품이 소유한다.
|
|
83
|
+
- 같은 배치를 Button·Menu로 다시 조립하지 않는다.
|
|
84
|
+
|
|
85
|
+
## 플랫폼 차이
|
|
86
|
+
|
|
87
|
+
| 항목 | Web | Native |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| 메뉴 | `Menu` trigger 버튼, 고른 뒤 닫히고 `onModeChange` | `Menu` + `dismissLabel` 닫기, 닫힌 뒤 `onModeChange` |
|
|
90
|
+
| 실패 알림 | `role="alert"` | `accessibilityRole="alert"`, `accessibilityLiveRegion="assertive"` |
|
|
91
|
+
|
|
92
|
+
## 관련 지침
|
|
93
|
+
|
|
94
|
+
[선택과 실제 실행](../compositions/task-controls.md), [Button](button.md), [Menu](menu.md).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# StreamingAnswer
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-16
|
|
8
|
+
- 근거: [스트림 응답 계약](../../streaming-answer.md), `src/streaming-answer.ts`의 `consumeAnswerStream`, 양 renderer `src/streaming-answer.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/스트림 응답과 중지`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
AI 응답처럼 제공자가 조각(`delta`)으로 보내는 답을 받는 대로 표시하고, 생성·다시 생성·중지를 한 묶음으로 제공한다.
|
|
14
|
+
표시하는 글자는 실제 제공자 이벤트만 따른다. 타이머로 글자를 흘려 쓰는 연출은 이 API에 없다.
|
|
15
|
+
상태 흐름은 [스트림 응답과 중지](../compositions/streaming-answer.md) 구성 지침을 함께 본다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 이미 완성된 답만 보여 줄 때 | [Text](text.md) 또는 제품 본문 렌더러 |
|
|
22
|
+
| 대화 말풍선 목록 | [ChatMessage](chat-message.md), [ChatScreen](chat-screen.md) |
|
|
23
|
+
| 파일 업로드·다운로드 진행 | [TransferTask](transfer-task.md) |
|
|
24
|
+
|
|
25
|
+
## 공개 이름과 import
|
|
26
|
+
|
|
27
|
+
| 이름 | 역할 | Web | Native |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `StreamingAnswer` | 스트림 답 표시와 생성·중지 | `@hjmds/react/streaming-answer` | `@hjmds/react-native/streaming-answer` |
|
|
30
|
+
|
|
31
|
+
`AnswerStreamEvent`·`AnswerGenerationContext` 타입은 `@hjmds/design-contracts/streaming-answer`에서 가져온다.
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { StreamingAnswer } from "@hjmds/react/streaming-answer";
|
|
38
|
+
|
|
39
|
+
<StreamingAnswer answerId={question.id} label={t("answer.title")}
|
|
40
|
+
onGenerate={({ signal, regenerate }) => streamAnswer(question.id, { signal, regenerate })}
|
|
41
|
+
labels={answerLabels} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Native
|
|
46
|
+
import { StreamingAnswer } from "@hjmds/react-native/streaming-answer";
|
|
47
|
+
|
|
48
|
+
<StreamingAnswer answerId={question.id} label={t("answer.title")}
|
|
49
|
+
onGenerate={({ signal, regenerate }) => streamAnswer(question.id, { signal, regenerate })}
|
|
50
|
+
labels={answerLabels} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`streamAnswer`는 `AsyncIterable<{ type: "delta"; text } | { type: "done" }>`를 돌려주고 `signal`을 실제 전송 계층(fetch 등)에 연결해야 한다.
|
|
54
|
+
|
|
55
|
+
## 축과 기본값
|
|
56
|
+
|
|
57
|
+
| prop | 값 | 기본값 | 설명 |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `answerId` | string | 필수 | 안정적인 id. 비면 던지고, 바뀌면 상태를 새로 시작한다 |
|
|
60
|
+
| `label` | string | 필수 | 답 영역 제목 |
|
|
61
|
+
| `onGenerate` | `(context) => AsyncIterable<AnswerStreamEvent> \| Promise<…>` | 필수 | `context`는 `{ signal, previousText, regenerate }` |
|
|
62
|
+
| `labels` | `{ generate, regenerate, stop, thinking, streaming, stopping, complete, stopped, error }` | 필수 | 버튼·상태 문구 |
|
|
63
|
+
| `initialText` | string | 없음 | 저장된 답. 있으면 `complete` 상태로 시작한다 |
|
|
64
|
+
| `thinkingIndicator` | ReactNode | `Progress`(`labels.thinking`) | 첫 조각 전 표시 |
|
|
65
|
+
| `renderText` | `(text) => ReactNode` | 기본 `Text` | 마크다운 등 제품 렌더러 |
|
|
66
|
+
| `disabled` | boolean | false | 생성·중지를 잠근다 |
|
|
67
|
+
|
|
68
|
+
## 배치
|
|
69
|
+
|
|
70
|
+
| 항목 | 값 | 근거 |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| 크기 | 버튼은 [Button](button.md) 기본 `medium`(높이 44), 본문 폭은 부모를 채운다 | `streaming-answer.tsx` |
|
|
73
|
+
| 간격 | 제목·본문·표시기·버튼 줄·상태 사이 `spacing.md` 16, 버튼 사이 `spacing.sm` 12 | `Stack gap="md"`, 버튼 줄 `gap="sm"` |
|
|
74
|
+
| 순서·정렬 | 제목 → 답 본문 → (생각 중 표시기, 가로·세로 가운데) → [생성/다시 생성] [중지] → 상태 문구(가운데) | 양 renderer |
|
|
75
|
+
| 고정·스크롤 | 자체 스크롤 없음. 긴 답의 스크롤은 놓는 영역이 소유한다 | — |
|
|
76
|
+
| 좁은 폭·큰 글자 | 버튼 줄 `wrap`. Web 본문은 `pre-wrap`·`overflowWrap: anywhere`로 줄바꿈 | 양 renderer |
|
|
77
|
+
|
|
78
|
+
## 꼭 지킬 것
|
|
79
|
+
|
|
80
|
+
- `onGenerate`는 실제 제공자 스트림을 넘긴다. 제공자 완료 이벤트(`done`) 없이 끝나면 실패로 처리한다.
|
|
81
|
+
- `signal`을 전송 계층에 연결한다. 중지 요청만으로 완료로 보지 않으며 `AbortError`로 끝나야 `stopped`다.
|
|
82
|
+
- 다시 생성 중에는 첫 조각이 오기 전까지 이전 답을 유지한다.
|
|
83
|
+
- 상태 문구는 `role="status"`, 실패는 경고로 알린다. 모든 문구는 제품 i18n에서 만든다.
|
|
84
|
+
|
|
85
|
+
## 플랫폼 차이
|
|
86
|
+
|
|
87
|
+
| 항목 | Web | Native |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| 기본 본문 | `Text` + `whiteSpace: pre-wrap` | `Text` |
|
|
90
|
+
| 상태 알림 | `role="status"` / 실패 `role="alert"` | `accessibilityLiveRegion="polite"`, 실패 `accessibilityRole="alert"` |
|
|
91
|
+
|
|
92
|
+
## 관련 지침
|
|
93
|
+
|
|
94
|
+
[스트림 응답과 중지](../compositions/streaming-answer.md), [Progress](progress.md), [Button](button.md).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [제품 채택 1.4 §설정 한 행](../../product-adoption-1.4.md), `src/component-recipes.ts`(`switchRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/스위치`
|
|
10
10
|
|
|
@@ -61,7 +61,7 @@ import { Switch } from "@hjmds/react-native/inputs";
|
|
|
61
61
|
|
|
62
62
|
| prop | 값 | 기본값 | 설명 |
|
|
63
63
|
| --- | --- | --- | --- |
|
|
64
|
-
| `presentation` | `inline` · `row` |
|
|
64
|
+
| `presentation` | `inline` · `row` | `row`(Web·Native, 미게시) | 이름·설명이 앞, 트랙이 끝. `inline`은 툴바처럼 좁은 자리에서만 명시한다 |
|
|
65
65
|
| `size` | `small`(44×26) · `medium`(52×32) | `medium` | — |
|
|
66
66
|
| `checked`/`defaultChecked` | `boolean` | `defaultChecked` `false` | `checked`를 생략하면 비제어로 동작한다 |
|
|
67
67
|
| `onCheckedChange` | `(checked: boolean) => void` | — | 다음 상태를 받는다 |
|
|
@@ -126,3 +126,7 @@ presentation="row" 큰 글자(≥1.6)
|
|
|
126
126
|
모두 `switchRecipe.rowLabelTextVariant`의 `body` 역할을 사용한다. 설명은 `caption`,
|
|
127
127
|
최소 터치 영역과 두 줄 행 높이는 그대로 유지한다. inline의 기존 글자 처리는 유지한다.
|
|
128
128
|
앱에서 Text로 이름을 다시 조립하거나 내부 글자 스타일을 덮지 않는다.
|
|
129
|
+
|
|
130
|
+
## 근거
|
|
131
|
+
|
|
132
|
+
- 기본 `row` 통일: 2026-10-17 웹·앱 비교 도구에서 같은 설정 스위치가 Web은 트랙 먼저(inline), Native는 이름 먼저(row)로 거울처럼 그려졌다.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# TableColumnSettings
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-16
|
|
8
|
+
- 근거: [목록 편집과 복구 계약](../../collection-editing.md), `src/collection-editing.ts`의 `changeColumnSettings`, 양 renderer `src/collection-editing.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/선택과 필터/표 열 설정`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
표의 열마다 보이기 체크박스와 앞으로·뒤로 이동 버튼을 제공해 열 표시와 순서를 바꾼다. 결과 목록은 제품 상태로
|
|
14
|
+
돌려주며 표 자체([DataTable](data-table.md))의 `columns`에 그대로 반영한다. 고정 열(`locked`)은 움직이지 않는
|
|
15
|
+
경계이고, 보이는 열이 하나만 남으면 그 열은 숨길 수 없다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 표의 열 기준 정렬 | [DataTable](data-table.md)의 정렬 |
|
|
22
|
+
| 여러 항목을 두 목록 사이로 옮기기 | [TransferList](transfer-list.md) |
|
|
23
|
+
| 끌어서 순서 바꾸기 | 제품 정렬 목록(이 API는 끌기 없이 버튼 이동만 제공) |
|
|
24
|
+
|
|
25
|
+
## 공개 이름과 import
|
|
26
|
+
|
|
27
|
+
| 이름 | 역할 | Web | Native |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `TableColumnSettings` | 열 표시·순서 설정 | `@hjmds/react/collection-editing` | `@hjmds/react-native/collection-editing` |
|
|
30
|
+
|
|
31
|
+
`ColumnSetting` 타입과 `changeColumnSettings`는 `@hjmds/design-contracts/collection-editing`에서 가져온다.
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { TableColumnSettings } from "@hjmds/react/collection-editing";
|
|
38
|
+
|
|
39
|
+
<TableColumnSettings label={t("table.columns")} columns={columns} onColumnsChange={setColumns}
|
|
40
|
+
earlierLabel={column => t("table.moveEarlier", { name: column.label })}
|
|
41
|
+
laterLabel={column => t("table.moveLater", { name: column.label })} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Native
|
|
46
|
+
import { TableColumnSettings } from "@hjmds/react-native/collection-editing";
|
|
47
|
+
|
|
48
|
+
<TableColumnSettings label={t("table.columns")} columns={columns} onColumnsChange={setColumns}
|
|
49
|
+
earlierLabel={column => t("table.moveEarlier", { name: column.label })}
|
|
50
|
+
laterLabel={column => t("table.moveLater", { name: column.label })} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 축과 기본값
|
|
54
|
+
|
|
55
|
+
| prop | 값 | 기본값 | 설명 |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| `label` | string | 필수 | 묶음 이름(Web `legend`) |
|
|
58
|
+
| `columns` | `readonly ColumnSetting[]`(`{ id, label, visible, locked? }`) | 필수 | 제어값. id가 비거나 겹치면 던진다 |
|
|
59
|
+
| `onColumnsChange` | `(columns) => void` | 필수 | 바뀐 경우에만 새 배열로 부른다 |
|
|
60
|
+
| `earlierLabel` · `laterLabel` | `(column) => string` | 필수 | 이동 버튼 문구. 열 이름을 넣어 구분한다 |
|
|
61
|
+
| `disabled` | boolean | false | 모든 체크박스·버튼을 잠근다 |
|
|
62
|
+
|
|
63
|
+
## 배치
|
|
64
|
+
|
|
65
|
+
| 항목 | 값 | 근거 |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 크기 | 체크박스는 [Checkbox](checkbox.md) 기본 `medium`, 이동 버튼은 `ghost` [Button](button.md) 기본 `medium`(높이 44) | `collection-editing.tsx` |
|
|
68
|
+
| 간격 | 열 사이·체크박스와 버튼 줄 사이·버튼 사이 모두 `spacing.sm` 12 | `Stack gap="sm"` |
|
|
69
|
+
| 순서·정렬 | 제목 → 열마다 [체크박스] 다음 줄 [앞으로] [뒤로], `columns` 순서대로 | 양 renderer |
|
|
70
|
+
| 고정·스크롤 | 자체 스크롤 없음. 시트·팝오버 등 놓는 영역이 소유한다 | — |
|
|
71
|
+
| 좁은 폭·큰 글자 | 이동 버튼 줄이 `wrap`으로 줄바꿈 | `Stack axis="inline" wrap` |
|
|
72
|
+
|
|
73
|
+
## 꼭 지킬 것
|
|
74
|
+
|
|
75
|
+
- `columns`는 제어값이다. `onColumnsChange`로 받은 배열을 저장하고 표에 반영한다.
|
|
76
|
+
- 고정 열은 숨기기·이동이 막히고, 고정 열을 넘어서는 이동도 막힌다. 이 규칙을 제품 쪽에서 다시 구현하지 않는다.
|
|
77
|
+
- 이동할 수 없는 방향의 버튼은 비활성이다. 마지막 보이는 열의 체크박스도 비활성이다.
|
|
78
|
+
- 설정 저장(사용자별 보존)은 제품이 한다.
|
|
79
|
+
|
|
80
|
+
## 플랫폼 차이
|
|
81
|
+
|
|
82
|
+
| 항목 | Web | Native |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| 묶음 | `fieldset` + `legend`, `disabled`는 fieldset이 전파 | 강조 `Text` 제목, `disabled`를 각 컨트롤에 전달 |
|
|
85
|
+
|
|
86
|
+
## 관련 지침
|
|
87
|
+
|
|
88
|
+
[목록 편집과 복구](../compositions/collection-editing.md), [DataTable](data-table.md), [Checkbox](checkbox.md).
|