@hjmds/design-contracts 1.12.1 → 1.13.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/avatar-fallback.d.ts +11 -0
- package/dist/avatar-fallback.d.ts.map +1 -1
- package/dist/avatar-fallback.js +21 -0
- package/dist/avatar-fallback.js.map +1 -1
- package/dist/base-recipes.d.ts +17 -0
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +17 -0
- package/dist/base-recipes.js.map +1 -1
- package/dist/catalog.d.ts +25 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/command-palette.d.ts +14 -9
- package/dist/command-palette.d.ts.map +1 -1
- package/dist/command-palette.js +8 -9
- package/dist/command-palette.js.map +1 -1
- package/dist/component-recipes.d.ts +17 -0
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +5 -0
- package/dist/component-recipes.js.map +1 -1
- package/dist/provider-button.d.ts.map +1 -1
- package/dist/provider-button.js +3 -0
- package/dist/provider-button.js.map +1 -1
- package/dist/reactions.d.ts +10 -0
- package/dist/reactions.d.ts.map +1 -1
- package/dist/reactions.js +7 -0
- package/dist/reactions.js.map +1 -1
- package/dist/screen-patterns.d.ts +147 -0
- package/dist/screen-patterns.d.ts.map +1 -0
- package/dist/screen-patterns.js +149 -0
- package/dist/screen-patterns.js.map +1 -0
- package/dist/slider.d.ts +8 -0
- package/dist/slider.d.ts.map +1 -1
- package/dist/slider.js +6 -1
- package/dist/slider.js.map +1 -1
- package/dist/upload-item.d.ts +5 -0
- package/dist/upload-item.d.ts.map +1 -1
- package/dist/upload-item.js +5 -0
- 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/action-session.md +3 -3
- package/docs/agreement.md +5 -0
- package/docs/avatar-fallback.md +7 -0
- package/docs/bottom-navigation.md +6 -0
- package/docs/brand-boundary.md +1 -1
- package/docs/button-label.md +6 -0
- package/docs/clipboard.md +3 -0
- package/docs/command-palette.md +45 -2
- package/docs/consumer-policy.md +5 -1
- package/docs/data-table.md +6 -4
- package/docs/dialog.md +8 -2
- package/docs/form.md +51 -0
- 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/link.md +8 -0
- package/docs/migration-native-legacy-removal.md +45 -1
- package/docs/optional-adapters.md +1 -1
- package/docs/password-field.md +5 -0
- package/docs/product-composition-adoption.md +40 -0
- package/docs/progress.md +19 -1
- package/docs/provider-button.md +13 -0
- package/docs/result.md +3 -0
- package/docs/screen-chrome.md +10 -0
- package/docs/screen-patterns.md +376 -0
- package/docs/sheet.md +12 -0
- package/docs/splitter.md +8 -2
- package/docs/theming.md +36 -29
- package/docs/toggle-group.md +7 -0
- package/docs/tour.md +7 -1
- package/docs/tree.md +5 -2
- package/docs/upload-item.md +7 -0
- package/docs/usage/README.md +236 -0
- package/docs/usage/STANDARD.md +108 -0
- package/docs/usage/components/accordion.md +107 -0
- package/docs/usage/components/activity-heatmap.md +104 -0
- package/docs/usage/components/affix.md +86 -0
- package/docs/usage/components/agreement.md +129 -0
- package/docs/usage/components/alert-dialog.md +130 -0
- package/docs/usage/components/anchor.md +96 -0
- package/docs/usage/components/aspect-ratio.md +89 -0
- package/docs/usage/components/asset.md +126 -0
- package/docs/usage/components/auth-provider-button.md +116 -0
- package/docs/usage/components/auth-screen-layout.md +129 -0
- package/docs/usage/components/avatar.md +114 -0
- package/docs/usage/components/badge.md +84 -0
- package/docs/usage/components/bottom-cta.md +125 -0
- package/docs/usage/components/bottom-info.md +99 -0
- package/docs/usage/components/bottom-navigation.md +136 -0
- package/docs/usage/components/breadcrumb.md +81 -0
- package/docs/usage/components/button.md +118 -0
- package/docs/usage/components/calendar.md +122 -0
- package/docs/usage/components/card.md +110 -0
- package/docs/usage/components/carousel.md +113 -0
- package/docs/usage/components/celebration.md +96 -0
- package/docs/usage/components/chat-message.md +122 -0
- package/docs/usage/components/chat-screen.md +112 -0
- package/docs/usage/components/checkbox-group.md +104 -0
- package/docs/usage/components/checkbox.md +103 -0
- package/docs/usage/components/chip.md +104 -0
- package/docs/usage/components/code-block.md +111 -0
- package/docs/usage/components/collapsible.md +112 -0
- package/docs/usage/components/color-picker.md +86 -0
- package/docs/usage/components/combobox.md +137 -0
- package/docs/usage/components/command-palette.md +125 -0
- package/docs/usage/components/comment-thread-screen.md +125 -0
- package/docs/usage/components/container.md +98 -0
- package/docs/usage/components/content-transition.md +101 -0
- package/docs/usage/components/context-menu.md +136 -0
- package/docs/usage/components/counter-badge.md +107 -0
- package/docs/usage/components/data-table.md +122 -0
- package/docs/usage/components/date-picker.md +142 -0
- package/docs/usage/components/date-range-picker.md +111 -0
- package/docs/usage/components/description-list.md +103 -0
- package/docs/usage/components/design-system-provider.md +124 -0
- package/docs/usage/components/dialog.md +176 -0
- package/docs/usage/components/divider.md +89 -0
- package/docs/usage/components/editor-screen.md +126 -0
- package/docs/usage/components/effect-surface.md +120 -0
- package/docs/usage/components/empty-state.md +114 -0
- package/docs/usage/components/field.md +129 -0
- package/docs/usage/components/file-picker.md +114 -0
- package/docs/usage/components/floating-action-button.md +138 -0
- package/docs/usage/components/form.md +162 -0
- package/docs/usage/components/grid.md +99 -0
- package/docs/usage/components/heading.md +87 -0
- package/docs/usage/components/icon-button.md +126 -0
- package/docs/usage/components/icon.md +105 -0
- package/docs/usage/components/image.md +122 -0
- package/docs/usage/components/keyboard-avoiding.md +93 -0
- package/docs/usage/components/keyboard-dock.md +110 -0
- package/docs/usage/components/keyboard-form-scroll-view.md +95 -0
- package/docs/usage/components/keyboard-motion-provider.md +86 -0
- package/docs/usage/components/layout.md +117 -0
- package/docs/usage/components/link.md +121 -0
- package/docs/usage/components/list-detail-screen.md +103 -0
- package/docs/usage/components/list-row.md +124 -0
- package/docs/usage/components/list.md +119 -0
- package/docs/usage/components/load-more.md +115 -0
- package/docs/usage/components/masonry.md +109 -0
- package/docs/usage/components/media-selection-screen.md +119 -0
- package/docs/usage/components/mentions.md +119 -0
- package/docs/usage/components/menu.md +129 -0
- package/docs/usage/components/menubar.md +93 -0
- package/docs/usage/components/message-composer.md +124 -0
- package/docs/usage/components/moderation-screen.md +113 -0
- package/docs/usage/components/notice.md +106 -0
- package/docs/usage/components/notification-inbox-screen.md +97 -0
- package/docs/usage/components/notification-item.md +98 -0
- package/docs/usage/components/number-field.md +131 -0
- package/docs/usage/components/onboarding-screen.md +106 -0
- package/docs/usage/components/otp-field.md +101 -0
- package/docs/usage/components/pagination.md +82 -0
- package/docs/usage/components/password-field.md +137 -0
- package/docs/usage/components/permission-screen.md +107 -0
- package/docs/usage/components/photo-source-sheet.md +119 -0
- package/docs/usage/components/popover.md +108 -0
- package/docs/usage/components/profile-screen.md +89 -0
- package/docs/usage/components/progress.md +122 -0
- package/docs/usage/components/qr-code.md +122 -0
- package/docs/usage/components/radio-group.md +124 -0
- package/docs/usage/components/radio.md +104 -0
- package/docs/usage/components/result.md +116 -0
- package/docs/usage/components/saved-items-screen.md +126 -0
- package/docs/usage/components/screen-layout.md +119 -0
- package/docs/usage/components/search-field.md +120 -0
- package/docs/usage/components/search-screen.md +215 -0
- package/docs/usage/components/section.md +111 -0
- package/docs/usage/components/segmented-control.md +138 -0
- package/docs/usage/components/select.md +142 -0
- package/docs/usage/components/settings-screen.md +126 -0
- package/docs/usage/components/shared-transition-element.md +111 -0
- package/docs/usage/components/shared-transition-screen.md +86 -0
- package/docs/usage/components/sheet.md +151 -0
- package/docs/usage/components/side-panel.md +104 -0
- package/docs/usage/components/sidebar.md +107 -0
- package/docs/usage/components/skeleton.md +105 -0
- package/docs/usage/components/skip-nav.md +76 -0
- package/docs/usage/components/slider.md +121 -0
- package/docs/usage/components/sortable-collection.md +127 -0
- package/docs/usage/components/spinner.md +86 -0
- package/docs/usage/components/splitter.md +103 -0
- package/docs/usage/components/stack.md +93 -0
- package/docs/usage/components/statistic.md +123 -0
- package/docs/usage/components/steps.md +110 -0
- package/docs/usage/components/surface.md +91 -0
- package/docs/usage/components/swipe-actions.md +124 -0
- package/docs/usage/components/switch.md +120 -0
- package/docs/usage/components/tabs.md +134 -0
- package/docs/usage/components/tag.md +84 -0
- package/docs/usage/components/tags-input.md +111 -0
- package/docs/usage/components/text-area.md +112 -0
- package/docs/usage/components/text-format.md +75 -0
- package/docs/usage/components/text-transition.md +104 -0
- package/docs/usage/components/text.md +101 -0
- package/docs/usage/components/thinking-orb.md +105 -0
- package/docs/usage/components/timeline.md +105 -0
- package/docs/usage/components/toast.md +145 -0
- package/docs/usage/components/toggle-group.md +95 -0
- package/docs/usage/components/tooltip.md +103 -0
- package/docs/usage/components/top-bar.md +124 -0
- package/docs/usage/components/top.md +89 -0
- package/docs/usage/components/tour.md +118 -0
- package/docs/usage/components/transfer-list.md +115 -0
- package/docs/usage/components/tree.md +91 -0
- package/docs/usage/components/upload-item.md +99 -0
- package/docs/usage/components/virtual-list.md +105 -0
- package/docs/usage/components/visually-hidden.md +72 -0
- package/docs/usage/components/watermark.md +78 -0
- package/docs/usage/compositions/action-recovery-optimistic.md +180 -0
- package/docs/usage/compositions/action-recovery-save.md +235 -0
- package/docs/usage/compositions/action-recovery-undo.md +193 -0
- package/docs/usage/compositions/common-message.md +132 -0
- package/docs/usage/compositions/common-notification.md +101 -0
- package/docs/usage/compositions/compound-controls.md +186 -0
- package/docs/usage/compositions/data-layouts.md +157 -0
- package/docs/usage/compositions/disclosure.md +144 -0
- package/docs/usage/compositions/environment-matrix.md +139 -0
- package/docs/usage/compositions/expo-interactions.md +149 -0
- package/docs/usage/compositions/family-drawer.md +201 -0
- package/docs/usage/compositions/floating-action-button.md +197 -0
- package/docs/usage/compositions/input-sheet.md +148 -0
- package/docs/usage/compositions/interaction-adapters.md +190 -0
- package/docs/usage/compositions/interaction-flow-apply.md +205 -0
- package/docs/usage/compositions/interaction-flow-draft.md +188 -0
- package/docs/usage/compositions/interaction-flow-search.md +171 -0
- package/docs/usage/compositions/native-renderers.md +106 -0
- package/docs/usage/compositions/navigation-bar-collection.md +164 -0
- package/docs/usage/compositions/optional-adapters.md +169 -0
- package/docs/usage/compositions/optional-motion.md +109 -0
- package/docs/usage/compositions/photo-source.md +104 -0
- package/docs/usage/compositions/purpose-input-comment.md +110 -0
- package/docs/usage/compositions/purpose-input-message.md +119 -0
- package/docs/usage/compositions/reference-first.md +96 -0
- package/docs/usage/compositions/reference-review.md +107 -0
- package/docs/usage/compositions/reference-settings.md +107 -0
- package/docs/usage/compositions/selection-scope.md +174 -0
- package/docs/usage/compositions/stea-event-ticket.md +166 -0
- package/docs/usage/compositions/stea-flip-card.md +162 -0
- package/docs/usage/compositions/stea-order-progress.md +184 -0
- package/docs/usage/compositions/stea-otp-verify.md +215 -0
- package/docs/usage/compositions/stea-pixel-empty.md +140 -0
- package/docs/usage/compositions/stea-schedule-card.md +169 -0
- package/docs/usage/compositions/stea-stat-summary.md +154 -0
- package/docs/usage/compositions/time-selection.md +174 -0
- package/docs/usage/compositions/toast-layout.md +128 -0
- package/docs/usage/compositions/visual-foundations.md +185 -0
- package/docs/usage/compositions/web-additions.md +146 -0
- package/docs/usage/compositions/web-navigation.md +143 -0
- package/docs/usage/screens/common-chat.md +127 -0
- package/docs/usage/screens/common-comments.md +108 -0
- package/docs/usage/screens/common-inbox.md +110 -0
- package/docs/usage/screens/common-login.md +98 -0
- package/docs/usage/screens/common-profile.md +221 -0
- package/docs/usage/screens/common-saved.md +127 -0
- package/docs/usage/screens/common-search.md +274 -0
- package/docs/usage/screens/common-settings.md +126 -0
- package/docs/usage/screens/common-shell.md +108 -0
- package/docs/usage/screens/dashboard.md +245 -0
- package/docs/usage/screens/discovery-gallery.md +306 -0
- package/docs/usage/screens/flow-collection.md +96 -0
- package/docs/usage/screens/flow-editor.md +120 -0
- package/docs/usage/screens/flow-media.md +111 -0
- package/docs/usage/screens/flow-moderation.md +120 -0
- package/docs/usage/screens/flow-onboarding.md +193 -0
- package/docs/usage/screens/flow-permission.md +103 -0
- package/docs/usage/screens/landing.md +347 -0
- package/docs/usage/screens/mockup-studio.md +190 -0
- package/docs/usage/screens/notification-settings.md +206 -0
- package/docs/usage/screens/reference-comparison.md +159 -0
- package/docs/usage/templates/component.md +61 -0
- package/docs/usage/templates/composition.md +47 -0
- package/docs/usage/templates/screen.md +56 -0
- package/docs/usage/templates/token.md +32 -0
- package/docs/usage/tokens/color.md +142 -0
- package/docs/usage/tokens/elevation-opacity.md +86 -0
- package/docs/usage/tokens/layers.md +98 -0
- package/docs/usage/tokens/layout.md +114 -0
- package/docs/usage/tokens/motion.md +88 -0
- package/docs/usage/tokens/radius.md +53 -0
- package/docs/usage/tokens/size.md +74 -0
- package/docs/usage/tokens/spacing.md +73 -0
- package/docs/usage/tokens/stroke.md +50 -0
- package/docs/usage/tokens/theme-studio.md +70 -0
- package/docs/usage/tokens/typography-studio.md +70 -0
- package/docs/usage/tokens/typography.md +89 -0
- package/package.json +7 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# ChatScreen
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 미게시(1.12.1 이후)
|
|
7
|
+
- 검토일: 2026-10-06
|
|
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
|
+
- 스토리북: `배포/화면/소통/채팅`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
DM·대화방처럼 헤더, 메시지 타임라인, 하단 작성창으로 이루어진 화면 한 장에 쓴다. `ScreenLayout`에
|
|
14
|
+
`composer`를 footer로 고정하고 기본 `scroll="content"`로 제품 타임라인이 스크롤을 갖게 한다.
|
|
15
|
+
별도 보조 기능(supplemental)이라 `/screens` subpath로만 import 한다. 새 채팅 데이터 모델이나 전송 엔진은 없다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 메시지 한 개 | [ChatMessage](chat-message.md) |
|
|
22
|
+
| 부모/답글 댓글 화면 | [CommentThreadScreen](comment-thread-screen.md) |
|
|
23
|
+
| 작성창이 없는 일반 화면 | [ScreenLayout](screen-layout.md) |
|
|
24
|
+
| 알림 목록 화면 | [NotificationInboxScreen](notification-inbox-screen.md) |
|
|
25
|
+
|
|
26
|
+
## 공개 이름과 import
|
|
27
|
+
|
|
28
|
+
| 이름 | 역할 | Web | Native |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `ChatScreen` | 화면 조합 | `/screens` | `/screens` |
|
|
31
|
+
| `ChatMessage`, `MessageComposer`, `ScreenLayout` | 함께 쓰는 조각 | `/screens` | `/screens` |
|
|
32
|
+
|
|
33
|
+
루트 barrel에는 없다. `/screens`는 optional native peer를 요구하지 않는다.
|
|
34
|
+
|
|
35
|
+
## 최소 사용 예
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// Web — host가 실제 남은 높이를 준다(예: height: 100dvh)
|
|
39
|
+
import { ChatMessage, ChatScreen, MessageComposer } from "@hjmds/react/screens";
|
|
40
|
+
|
|
41
|
+
<ChatScreen
|
|
42
|
+
title={t("chat.title")}
|
|
43
|
+
state={query.isPending ? { kind: "loading", title: t("chat.loading") } : { kind: "ready" }}
|
|
44
|
+
composer={<MessageComposer value={draft} label={t("chat.input")} sendLabel={t("chat.send")}
|
|
45
|
+
pending={sending} onValueChange={setDraft} onSend={send} />}
|
|
46
|
+
>
|
|
47
|
+
<Timeline messages={messages} renderItem={m => <ChatMessage {...toMessageProps(m)}>{m.text}</ChatMessage>} />
|
|
48
|
+
</ChatScreen>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
// Native — 키보드 adapter는 하나만
|
|
53
|
+
import { FlatList } from "react-native";
|
|
54
|
+
import { KeyboardAvoiding } from "@hjmds/react-native/keyboard";
|
|
55
|
+
import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
|
|
56
|
+
|
|
57
|
+
<KeyboardAvoiding>
|
|
58
|
+
<ChatScreen title={t("chat.title")} composer={<MessageComposer value={draft} label={t("chat.input")}
|
|
59
|
+
sendLabel={t("chat.send")} onValueChange={setDraft} onSend={send} />}>
|
|
60
|
+
<FlatList data={messages} renderItem={renderMessage} />
|
|
61
|
+
</ChatScreen>
|
|
62
|
+
</KeyboardAvoiding>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 제품이 공급하는 것
|
|
66
|
+
|
|
67
|
+
| 슬롯·prop | 내용 | 비고 |
|
|
68
|
+
| --- | --- | --- |
|
|
69
|
+
| `title`(필수) | 지역화한 대화 제목 | `header`를 주면 Web은 `aria-label`로만 쓴다 |
|
|
70
|
+
| `header` / `leading` / `actions` / `description` | 기존 navigation 헤더, 또는 뒤로 가기·도구 슬롯 | `header`를 주면 기본 헤더를 그리지 않는다 |
|
|
71
|
+
| `children` | 타임라인(가상화 목록 + `ChatMessage`) | 자동 스크롤·새 메시지 배지·이전 메시지 위치는 제품 |
|
|
72
|
+
| `composer`(필수) | `MessageComposer` 등 작성창 | `state.kind === "ready"`일 때만 보인다 |
|
|
73
|
+
| `state` / `stateAction` | `loading`·`empty`·`error`·`restricted` + 지역화 `title` | ready 외에는 본문을 교체한다 |
|
|
74
|
+
| `notice` | 새로고침 실패 같은 비차단 안내 | 초안 유지가 필요하면 `state` 대신 이것 |
|
|
75
|
+
|
|
76
|
+
## 축과 기본값
|
|
77
|
+
|
|
78
|
+
| prop | 값 | 기본값 | 설명 |
|
|
79
|
+
| --- | --- | --- | --- |
|
|
80
|
+
| `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `state.kind === "ready"`일 때만 보인다 |
|
|
81
|
+
| `scroll` | `"content"` · `"screen"` | `"content"` | 가상화 목록이 스크롤을 소유하므로 작은 예제 외에는 바꾸지 않는다 |
|
|
82
|
+
| `contentInset` | `"default"` · `"none"` | `"default"` | 이미 gutter를 주는 route 안에서는 `"none"`으로 이중 여백을 막는다 |
|
|
83
|
+
| `state` | `ScreenContentState`(`{ kind: "ready" }` 또는 `{ kind, title, description? }`) | `{ kind: "ready" }` | ready 외에는 본문을 교체하고 composer를 숨긴다 |
|
|
84
|
+
| `as` (Web) | `"main"` · `"section"` | `"main"` | 제품 shell이 이미 `main`이면 `"section"` |
|
|
85
|
+
| 나머지 | `ScreenLayout`과 같음(`footer` 제외) | — | [ScreenLayout](screen-layout.md) |
|
|
86
|
+
|
|
87
|
+
## 배치
|
|
88
|
+
|
|
89
|
+
| 항목 | 값 | 근거 |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| 크기 | 폭 최대 `layout.readingMaxWidth` 720, 가운데 정렬; 높이는 host가 준 남은 높이(Web `block-size: 100%`, Native `flex: 1`) | `screenPatternRecipe.maxWidth`, Web `.hjm-screen`, Native `ScreenLayout` |
|
|
92
|
+
| 간격 | 헤더·본문·footer padding `spacing.md` 16; 헤더 안 간격 Web 16 / Native `spacing.sm` 12; 메시지 사이 간격은 타임라인(제품) 소유 | `screenPatternRecipe.padding`·`itemGap`, Web `.hjm-screen__header` |
|
|
93
|
+
| 순서·정렬 | 헤더 → notice → 타임라인 → composer(footer) | `ScreenLayout` 렌더 순서 |
|
|
94
|
+
| 고정·스크롤 | 헤더·composer 고정, 타임라인이 content 스크롤; footer 위 테두리 1, Web은 하단 safe area만큼 padding을 늘린다 | Web `.hjm-screen__footer`, Native footer `borderTopWidth` |
|
|
95
|
+
| 좁은 폭·큰 글자 | 제목 열 최소 폭 `headerMinWidth` 120 × 글자 배율, 모자라면 actions가 다음 줄로 내려간다; Native safe area·키보드는 host | `screenPatternRecipe.headerMinWidth` |
|
|
96
|
+
|
|
97
|
+
## 꼭 지킬 것
|
|
98
|
+
|
|
99
|
+
- 모든 문구·상태 title은 제품 i18n에서 넘긴다. ready 외 상태에 빈 title을 주면 `TypeError`가 난다.
|
|
100
|
+
- 전송 성공 판정과 초안 초기화는 서버 영수증을 받은 제품이 한다. `onSend`는 원문만 넘긴다.
|
|
101
|
+
- Native는 safe area와 탭/상단 navigation inset을 host가 먼저 뺀다. `KeyboardAvoiding`과 제품 keyboard adapter를
|
|
102
|
+
동시에 감싸지 않는다.
|
|
103
|
+
- 가상화 목록을 `scroll="screen"` 안에 넣어 스크롤 컨테이너를 중첩하지 않는다.
|
|
104
|
+
|
|
105
|
+
## 플랫폼 차이
|
|
106
|
+
|
|
107
|
+
| 항목 | Web | Native |
|
|
108
|
+
| --- | --- | --- |
|
|
109
|
+
| 배치 | `layoutStyle`, `className`(시각 override 금지) | `layoutStyle` |
|
|
110
|
+
| 높이 | host가 남은 높이 제공 | `flex: 1` |
|
|
111
|
+
| 스크롤 연결 | 없음 | `scrollRef`, `scrollProps`(`refreshControl`, `keyboardDismissMode` 등) — `scroll="screen"`이거나 상태 교체 중일 때만 ScrollView가 있다 |
|
|
112
|
+
| 테스트 id | 없음 | `testID` |
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# CheckboxGroup
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: recipe `selectionGroupRecipe`·`selectionControlRecipe`(`src/component-recipes.ts`), behavior `checkboxGroup`
|
|
9
|
+
- 스토리북: `배포/컴포넌트/입력/체크박스 그룹`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
한 질문에 대한 여러 선택지 중 0개 이상을 고르게 할 때 쓴다. 관심사·알림 종류·필터 조건처럼
|
|
14
|
+
각 선택지가 자기 줄과 설명을 가질 만한 목록이 여기에 속한다. 선택 상태는 `Set`으로 다룬다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 독립된 항목 하나 | [Checkbox](checkbox.md) |
|
|
21
|
+
| 하나만 고름 | [RadioGroup](radio-group.md) |
|
|
22
|
+
| 가로 버튼 줄로 짧은 옵션을 켜고 끔 | [ToggleGroup](toggle-group.md) ([경계](../../toggle-group.md)) |
|
|
23
|
+
| 약관 동의(필수가 제출 가능 여부를 정함) | [Agreement](agreement.md) ([이유](../../agreement.md)) |
|
|
24
|
+
| 칩 모양의 필터 줄 | [Chip](chip.md) `selectionMode="multiple"` |
|
|
25
|
+
| 목록 밖 값을 직접 입력 | [TagsInput](tags-input.md) |
|
|
26
|
+
|
|
27
|
+
## 공개 이름과 import
|
|
28
|
+
|
|
29
|
+
| 이름 | 역할 | Web | Native |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| `CheckboxGroup` | 기본 | `@hjmds/react`, `/selection` | `@hjmds/react-native`, `/inputs` |
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { CheckboxGroup } from "@hjmds/react/selection";
|
|
38
|
+
|
|
39
|
+
<CheckboxGroup
|
|
40
|
+
label={t("settings.notify.title")}
|
|
41
|
+
items={[
|
|
42
|
+
{ id: "comment", label: t("settings.notify.comment") },
|
|
43
|
+
{ id: "like", label: t("settings.notify.like"), description: t("settings.notify.likeHint") },
|
|
44
|
+
]}
|
|
45
|
+
value={channels}
|
|
46
|
+
onValueChange={setChannels}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Native
|
|
52
|
+
import { CheckboxGroup } from "@hjmds/react-native/inputs";
|
|
53
|
+
|
|
54
|
+
<CheckboxGroup
|
|
55
|
+
label={t("settings.notify.title")}
|
|
56
|
+
items={items}
|
|
57
|
+
value={channels}
|
|
58
|
+
onValueChange={setChannels}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 축과 기본값
|
|
63
|
+
|
|
64
|
+
| prop | 값 | 기본값 | 설명 |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `items` | `readonly { id: Key; label: string; description?: string; disabled?: boolean }[]` | 필수 | `label`·`description`은 `string`이다 |
|
|
67
|
+
| `value` + `onValueChange` | `ReadonlySet<Key>` + `(value: ReadonlySet<Key>) => void` | — | 제어. `value`를 주면 `onValueChange`가 필수다(타입이 `defaultValue`와 함께 쓰지 못하게 막는다) |
|
|
68
|
+
| `defaultValue` | `ReadonlySet<Key>` | 빈 `Set` | 비제어. 항목에서 빠진 id는 선택에서 자동으로 지워진다 |
|
|
69
|
+
| `label` · `accessibilityLabel` | `string` | — | 둘 중 하나는 필수 |
|
|
70
|
+
| `orientation` | `vertical` · `horizontal` | `vertical` | — |
|
|
71
|
+
| `presentation` | `card` · `plain` · `grouped` | `card` | — |
|
|
72
|
+
| `size` | `medium` · `small` | `medium` | — |
|
|
73
|
+
| `renderLeading` | Web `(item, appearance: { selected, color: "currentColor", size }) => ReactNode` · Native `(item, props: { checked, selected, disabled, readOnly, color, size }) => ReactNode` | — | 항목 아이콘 |
|
|
74
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 그룹 외곽(Web `fieldset`) 배치 |
|
|
75
|
+
|
|
76
|
+
## 배치
|
|
77
|
+
|
|
78
|
+
| 항목 | 값 | 근거 |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| 크기 | 폭을 채우는 묶음. 각 행은 [Checkbox](checkbox.md) 규격(최소 높이 `medium` 56 · `small` 44, 표시 24 · 20) | `selectionControlRecipe.sizes`, `.hjm-choice` |
|
|
81
|
+
| 간격 | 그룹 이름(legend)↔항목 `spacing.xs` 8. 항목 사이 세로 `card` `spacing.xs` 8 · `plain` `spacing.xxs` 4 · `grouped` 0, 가로 `card` `spacing.md` 16 · `plain` `spacing.sm` 12 · `grouped` 0. 설명·오류는 `formSupportContract.gap` `spacing.xs` 8 | `selectionGroupRecipe.orientations`, `.hjm-checkbox-group*` |
|
|
82
|
+
| 순서·정렬 | 위→아래 [그룹 이름(semibold)] → [설명] → [항목들] → [오류]. 항목은 시작 쪽 정렬. `grouped`는 항목들을 테두리 1px·`radius.lg` 16 카드 하나에 붙여 담는다 | `selectionGroupRecipe.slots`, `.hjm-checkbox-group[data-presentation="grouped"]` |
|
|
83
|
+
| 고정·스크롤 | 고정 영역이 없다. 목록이 길어도 그룹 안에서 스크롤 영역을 만들지 않는다 | `.hjm-checkbox-group__items` |
|
|
84
|
+
| 좁은 폭·큰 글자 | `horizontal`은 줄바꿈된다(`flex-wrap: wrap`). 좁은 폭·큰 글자에서는 `vertical`을 쓴다 | `.hjm-checkbox-group[data-orientation="horizontal"]` |
|
|
85
|
+
|
|
86
|
+
## 꼭 지킬 것
|
|
87
|
+
|
|
88
|
+
- `label` 또는 `accessibilityLabel` 중 하나는 반드시 준다. 둘 다 없거나 빈 문자열이면 실행 중 `TypeError`다.
|
|
89
|
+
- 항목 id는 비지 않고 중복되지 않아야 한다. 제어 `value`에 목록에 없는 id가 있으면 `RangeError`다.
|
|
90
|
+
항목이 바뀌면 제어하는 쪽이 `value`를 먼저 정리한다.
|
|
91
|
+
- 문구는 모두 i18n 키로 넣는다. 각 항목의 아이콘은 `renderLeading(item, appearance)`로 그린다.
|
|
92
|
+
- 배치는 `layoutStyle`로 한다. Native의 `style`과 행 슬롯 스타일(`controlStyle`·`labelStyle` 등)은 deprecated —
|
|
93
|
+
`layoutStyle` 또는 `presentation`·`size`·`renderIndicator`를 쓴다([이관 문서](../../migration-native-legacy-removal.md)).
|
|
94
|
+
|
|
95
|
+
## 플랫폼 차이
|
|
96
|
+
|
|
97
|
+
| 항목 | Web | Native |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `description`·`error` 타입 | `ReactNode` | `string` |
|
|
100
|
+
| 오류 표시 | `error` | `error` 또는 `invalid`(+`invalidLabel`) |
|
|
101
|
+
| 폼 제출 | `name`으로 체크박스마다 `value=id` 전송 | 없음 |
|
|
102
|
+
| 그룹 전체 비활성 | `disabled`(fieldset) | `disabled` |
|
|
103
|
+
| 필수·읽기 전용 안내 | `aria-required`·`aria-readonly` | `requiredLabel`·`readOnlyLabel`을 행 hint로 읽음 |
|
|
104
|
+
| 선택 표시 교체 | 없음 | `indicator="none"`, `renderIndicator(item, props)` |
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Checkbox
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: recipe `selectionControlRecipe`(`src/component-recipes.ts`), behavior `checkbox`
|
|
9
|
+
- 스토리북: `배포/컴포넌트/입력/체크박스`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
독립된 예/아니오 하나를 고르는 항목에 쓴다. "기억하기", 목록 전체 선택처럼 부분 선택(`mixed`)이
|
|
14
|
+
필요한 상위 항목도 여기에 속한다. 값은 제출이나 저장 때 반영되는 선택이다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 같은 질문의 여러 선택지를 묶어 고름 | [CheckboxGroup](checkbox-group.md) |
|
|
21
|
+
| 하나만 고름 | [RadioGroup](radio-group.md) |
|
|
22
|
+
| 누르는 즉시 적용되는 설정 켜기·끄기 | [Switch](switch.md) |
|
|
23
|
+
| 약관·개인정보 동의(필수/선택 구분, 전체 동의) | [Agreement](agreement.md) |
|
|
24
|
+
| 필터 줄의 작은 선택 | [Chip](chip.md) |
|
|
25
|
+
|
|
26
|
+
## 공개 이름과 import
|
|
27
|
+
|
|
28
|
+
| 이름 | 역할 | Web | Native |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `Checkbox` | 기본 | `@hjmds/react`, `/selection` | `@hjmds/react-native`, `/inputs` |
|
|
31
|
+
|
|
32
|
+
## 최소 사용 예
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
// Web
|
|
36
|
+
import { Checkbox } from "@hjmds/react/selection";
|
|
37
|
+
|
|
38
|
+
<Checkbox
|
|
39
|
+
label={t("signup.rememberMe")}
|
|
40
|
+
checked={remember}
|
|
41
|
+
onCheckedChange={setRemember}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Native
|
|
47
|
+
import { Checkbox } from "@hjmds/react-native/inputs";
|
|
48
|
+
|
|
49
|
+
<Checkbox
|
|
50
|
+
label={t("signup.rememberMe")}
|
|
51
|
+
checked={remember}
|
|
52
|
+
onCheckedChange={setRemember}
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 축과 기본값
|
|
57
|
+
|
|
58
|
+
| prop | 값 | 기본값 | 설명 |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| `presentation` | `card` · `plain` · `grouped` | `card` | — |
|
|
61
|
+
| `size` | `medium` · `small` | `medium` | — |
|
|
62
|
+
| `checked` · `defaultChecked` | Web `boolean` · Native `boolean \| "mixed"` | `defaultChecked` `false` | `checked`를 주면 제어, 없으면 비제어다 |
|
|
63
|
+
| 부분 선택 | Web `indeterminate: boolean` · Native `checked="mixed"` | Web `false` | Native에서 `mixed`를 누르면 `true`가 된다 |
|
|
64
|
+
| `onCheckedChange` | `(checked: boolean) => void` | — | 두 renderer 모두 `boolean`만 넘긴다(`"mixed"`는 오지 않는다) |
|
|
65
|
+
| Web `onChange` | `(event: ChangeEvent<HTMLInputElement>) => void` | — | 원시 이벤트가 필요할 때만. 값은 `onCheckedChange`로 받는다 |
|
|
66
|
+
| `readOnly` | `boolean` | `false` | 값을 바꾸지 않고 포커스·읽기는 유지한다 |
|
|
67
|
+
| `renderLeading` | Web `(appearance: { selected, color: "currentColor", size }) => ReactNode` · Native `(props: { checked, selected, disabled, readOnly, color, size }) => ReactNode` | — | 라벨 앞 아이콘 |
|
|
68
|
+
| Native `renderIndicator` · `indicator` | `(props: { checked, selected, disabled, readOnly, color, size }) => ReactNode` · `"default" \| "none"` | `indicator` `"default"` | 체크 표시 교체·숨김 |
|
|
69
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 행(Web 바깥 `label`, Native 행) 배치 |
|
|
70
|
+
|
|
71
|
+
## 배치
|
|
72
|
+
|
|
73
|
+
| 항목 | 값 | 근거 |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| 크기 | 행 최소 높이 `medium` 56(`layout.rowHeight.singleLine`) · `small` 44(`control.minTouchTarget`). 표시 `medium` 24(`control.selectionIndicator`) · `small` 20, Native hitSlop 10 · 12 | `selectionControlRecipe.sizes`, `.hjm-choice` |
|
|
76
|
+
| 간격 | `card`·`grouped` 안쪽 `medium` 위아래 `spacing.sm` 12 · 좌우 `spacing.md` 16, `small` `spacing.xs` 8 · `spacing.sm` 12. 표시↔라벨 `medium` `spacing.sm` 12 · `small` `spacing.xs` 8. `plain`은 안쪽 여백 0. 라벨↔설명 `spacing.xxs` 4 | `selectionControlRecipe.sizes`·`presentations`, `.hjm-choice*` |
|
|
77
|
+
| 순서·정렬 | 시작 쪽부터 [체크 표시] → [leading] → [라벨 / 설명]. 표시는 첫 줄에 맞춰 위쪽 정렬. 여러 개면 [CheckboxGroup](checkbox-group.md)으로 묶는다. `card`는 `canvas` 배경·테두리 1px·`radius.md` 12 | `selectionControlRecipe.slots`, `.hjm-choice__indicator` |
|
|
78
|
+
| 고정·스크롤 | 고정 영역이 없다 | — |
|
|
79
|
+
| 좁은 폭·큰 글자 | 라벨·설명이 줄바꿈되고 행 높이가 늘어난다. 표시 크기는 그대로다 | `.hjm-choice__copy` |
|
|
80
|
+
|
|
81
|
+
## 꼭 지킬 것
|
|
82
|
+
|
|
83
|
+
- `label`(필수)과 `description`은 i18n 키로 넣는다. Native는 둘 다 `string`만 받는다.
|
|
84
|
+
- 선택 아이콘을 바꾸려면 `renderLeading`(Native는 `renderIndicator`도)을 쓴다. 색·테두리를 직접 칠하지 않는다.
|
|
85
|
+
- 배치는 `layoutStyle`로 한다. Native의 `style`·`controlStyle`·`indicatorStyle`·`leadingStyle`·`contentStyle`·
|
|
86
|
+
`labelStyle`·`descriptionStyle`은 deprecated — `layoutStyle` 또는 `presentation`·`size`·`renderIndicator`를 쓴다
|
|
87
|
+
(개발 모드 1회 경고, 다음 major 제거. [이관 문서](../../migration-native-legacy-removal.md)).
|
|
88
|
+
|
|
89
|
+
## 플랫폼 차이
|
|
90
|
+
|
|
91
|
+
| 항목 | Web | Native |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| 부분 선택 | `indeterminate` | `checked`/`defaultChecked`에 `"mixed"` |
|
|
94
|
+
| `label`·`description` 타입 | `ReactNode` | `string` |
|
|
95
|
+
| 필수·오류 표시 | 없음(HTML `required`는 input에 전달) | `required`·`invalid` + `requiredLabel`·`invalidLabel`(접근성 hint로 합침) |
|
|
96
|
+
| 읽기 전용 안내 | `aria-readonly` | `readOnlyLabel`을 hint로 읽음 |
|
|
97
|
+
| 고정 leading 노드 | 없음 | `leading` |
|
|
98
|
+
| 원시 change 이벤트 | `onChange` | 없음 |
|
|
99
|
+
|
|
100
|
+
## 함정
|
|
101
|
+
|
|
102
|
+
- Web은 `className`·`layoutStyle`이 바깥 `label`에, 나머지 HTML 속성(`style`, `name`, `onFocus` 등)은 숨은
|
|
103
|
+
`input`에 붙는다. 배치용 `style`을 넘기면 보이는 행이 아니라 input에 적용되므로 `layoutStyle`을 쓴다.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Chip
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [Tag와의 경계](../../tag.md#chip과의-경계), recipe `chipRecipe`(`src/component-recipes.ts`), behavior `chip`
|
|
9
|
+
- 스토리북: `배포/컴포넌트/입력/선택 칩`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
누를 수 있는 작은 pill이다. 필터 줄(여러 개 켬), 정렬·범위처럼 하나만 고르는 칩 줄, 추천 검색어처럼
|
|
14
|
+
누르면 바로 행동하는 칩에 쓴다. 선택 상태는 항상 제품이 들고 있다(숨은 내부 상태가 없다).
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 누를 수 없는 분류·속성 라벨 | [Tag](tag.md) |
|
|
21
|
+
| 개수·상태 표시 | [Badge](badge.md) |
|
|
22
|
+
| 줄 단위 선택지와 설명 | [CheckboxGroup](checkbox-group.md), [RadioGroup](radio-group.md) |
|
|
23
|
+
| 같은 폭의 구간 전환 | [SegmentedControl](segmented-control.md) |
|
|
24
|
+
| 사용자가 값을 입력해 칩을 만듦 | [TagsInput](tags-input.md) |
|
|
25
|
+
| 일반 텍스트 행동 | [Button](button.md) |
|
|
26
|
+
|
|
27
|
+
## 공개 이름과 import
|
|
28
|
+
|
|
29
|
+
| 이름 | 역할 | Web | Native |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| `Chip` | 기본 | `@hjmds/react`, `/selection` | `@hjmds/react-native`, `/inputs` |
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { Chip } from "@hjmds/react/selection";
|
|
38
|
+
|
|
39
|
+
<Chip
|
|
40
|
+
label={t("feed.filter.photo")}
|
|
41
|
+
selectionMode="multiple"
|
|
42
|
+
selected={filters.has("photo")}
|
|
43
|
+
onSelectedChange={(next) => toggleFilter("photo", next)}
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// Native
|
|
49
|
+
import { Chip } from "@hjmds/react-native/inputs";
|
|
50
|
+
|
|
51
|
+
<Chip
|
|
52
|
+
label={t("feed.filter.photo")}
|
|
53
|
+
selectionMode="multiple"
|
|
54
|
+
selected={filters.has("photo")}
|
|
55
|
+
onPress={(next) => toggleFilter("photo", next)}
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 축과 기본값
|
|
60
|
+
|
|
61
|
+
| prop | 값 | 기본값 | 설명 |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `selectionMode` | `action`(버튼) · `single`(radio 역할) · `multiple`(checkbox 역할) | `action` | `single`·`multiple`이면 `selected`가 필수이고, `action`이면 `selected`를 줄 수 없다 |
|
|
64
|
+
| `selected` | `boolean` | — | 제어 전용. Chip은 내부 선택 상태를 두지 않는다 |
|
|
65
|
+
| Web `onSelectedChange` | `(selected: boolean) => void` | — | 선택 칩에서 필수. 다음 상태(`!selected`)를 받는다 |
|
|
66
|
+
| Web `onPress` | `(event: MouseEvent<HTMLButtonElement>) => void` | — | 선택 사항. `event.preventDefault()`면 `onSelectedChange`를 부르지 않는다 |
|
|
67
|
+
| Native `onPress` | 행동 칩 `(event: GestureResponderEvent) => void` · 선택 칩 `(selected: boolean, event: GestureResponderEvent) => void` | — | 필수 |
|
|
68
|
+
| `size` | `small` · `medium` | `small` | — |
|
|
69
|
+
| `renderSelectionIndicator` | `(props: { selected: boolean; color: string; size: number }) => ReactNode` (Web `color`는 `"currentColor"`) | 체크 표시 | 선택되면 붙는 체크 표시를 바꾼다 |
|
|
70
|
+
| `leading` · `trailing` | `ReactNode` | — | 장식이다(접근성 트리에서 숨는다). 의미는 `label`에 담는다 |
|
|
71
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 칩 배치. Native `leadingStyle`·`indicatorStyle`·`trailingStyle`도 배치 key만 받는다 |
|
|
72
|
+
|
|
73
|
+
## 배치
|
|
74
|
+
|
|
75
|
+
| 항목 | 값 | 근거 |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| 크기 | 내용 폭. 높이 `small` 36 · `medium` 44(`control.chipHeight`), 모서리 `radius.full`. `small`은 위아래 4를 넓혀(Native hitSlop, Web `::after`) 터치 영역을 44에 맞춘다 | `chipRecipe.sizes`, `react-native/src/inputs.tsx` |
|
|
78
|
+
| 간격 | 좌우 여백 `small` `spacing.sm` 12 · `medium` `spacing.md` 16, 아이콘↔라벨 `small` `spacing.xxs` 4 · `medium` `spacing.xs` 8(두 플랫폼). 칩 사이는 부모가 정한다(`spacing.xs` 8 권장) | `chipRecipe.sizes`, `.hjm-chip` |
|
|
79
|
+
| 순서·정렬 | 필터 줄·태그 묶음으로 가로로 나란히 둔다. 안쪽은 [선택 표시] → [leading] → [라벨] → [trailing]. 선택 상태는 브랜드 테두리·글자색 | `chipRecipe.slots`, `.hjm-chip[data-selected]` |
|
|
80
|
+
| 고정·스크롤 | 고정 영역이 없다. 칩이 많으면 부모가 줄바꿈하거나 가로 스크롤 영역을 둔다. 검색 화면의 필터 칩 줄은 [SearchScreen](search-screen.md) `filtersOverflow="scroll"`이 가로 스크롤을 소유한다 | `SearchScreen` |
|
|
81
|
+
| 좁은 폭·큰 글자 | 라벨이 줄바꿈된다(`overflow-wrap: anywhere`). 높이는 최소값이라 늘어난다 | `.hjm-chip__label` |
|
|
82
|
+
|
|
83
|
+
## 꼭 지킬 것
|
|
84
|
+
|
|
85
|
+
- `label`은 i18n 키로 넣는다. Native는 `string`만 받고, 다른 읽기 이름이 필요하면 `accessibilityLabel`을 준다.
|
|
86
|
+
- `single` 줄은 제품이 하나만 `selected`가 되도록 관리한다. Chip이 형제를 해제하지 않는다.
|
|
87
|
+
- 색·테두리를 덮지 않는다. 배치는 두 renderer 모두 `layoutStyle`로 한다. Native `labelStyle`은 deprecated —
|
|
88
|
+
`size`·`selected`로 글자 모양을 정한다([이관 문서](../../migration-native-legacy-removal.md)).
|
|
89
|
+
|
|
90
|
+
## 플랫폼 차이
|
|
91
|
+
|
|
92
|
+
| 항목 | Web | Native |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| 행동 칩 이벤트 | `onPress(event)`(선택 사항) | `onPress(event)`(필수) |
|
|
95
|
+
| 선택 칩 이벤트 | `onSelectedChange(next)`, `onPress(event)`는 선택 사항 | `onPress(next, event)` 하나 |
|
|
96
|
+
| 선택 변경 취소 | `onPress`에서 `event.preventDefault()` | 없음 |
|
|
97
|
+
| `label` 타입 | `ReactNode` | `string` |
|
|
98
|
+
| 배치 | `layoutStyle` | `layoutStyle`, 슬롯별 `leadingStyle`·`indicatorStyle`·`trailingStyle`(배치 key만) |
|
|
99
|
+
| 선택 표시 위치 | 표시 → leading → 라벨 | leading → 표시 → 라벨 |
|
|
100
|
+
|
|
101
|
+
## 함정
|
|
102
|
+
|
|
103
|
+
- 두 renderer의 선택 콜백 이름이 다르다. Web 코드를 옮기면서 `onSelectedChange`를 Native에 넘기면
|
|
104
|
+
타입 오류가 나고, Native의 `onPress(next, event)`를 Web에 넘기면 첫 인자가 이벤트다.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# CodeBlock
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [Code block](../../code-block.md), contract `src/code-block.ts`
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/코드 블록`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
코드·명령·설정 조각을 읽기 전용으로 보여 주고 사용자가 선택·복사하게 할 때 쓴다.
|
|
14
|
+
편집기나 HTML 실행기가 아니며 강조 색은 표현만 바꾸고 원문을 바꾸지 않는다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 사용자가 코드를 고침 | [TextArea](text-area.md) |
|
|
21
|
+
| 문장 안의 짧은 강조·서식 | [Text](text.md), [TextFormat](text-format.md) |
|
|
22
|
+
|
|
23
|
+
## 공개 이름과 import
|
|
24
|
+
|
|
25
|
+
| 이름 | 역할 | Web | Native |
|
|
26
|
+
| --- | --- | --- | --- |
|
|
27
|
+
| `CodeBlock` | 기본(별도 보조 기능, supplemental) | `/code-block` | `/code-block` |
|
|
28
|
+
| `ClipboardButton` | 보조(Web 복사 버튼, `copyAction` 슬롯에 넣는다) | `@hjmds/react`, `/clipboard` | 없음 |
|
|
29
|
+
|
|
30
|
+
`CodeBlock`은 root에서 export되지 않고 granular subpath로만 import 된다. 추가 peer는 없다.
|
|
31
|
+
구문 분석기·하이라이터·클립보드 엔진은 포함하지 않는다.
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { CodeBlock } from "@hjmds/react/code-block";
|
|
38
|
+
import { ClipboardButton } from "@hjmds/react/clipboard";
|
|
39
|
+
|
|
40
|
+
<CodeBlock
|
|
41
|
+
code={snippet}
|
|
42
|
+
label={t("docs.installSnippet")}
|
|
43
|
+
language="bash"
|
|
44
|
+
copyAction={
|
|
45
|
+
<ClipboardButton
|
|
46
|
+
tone="secondary"
|
|
47
|
+
size="small"
|
|
48
|
+
value={snippet}
|
|
49
|
+
labels={{ idle: t("common.copy"), copied: t("common.copied") }}
|
|
50
|
+
onCopyError={showCopyError}
|
|
51
|
+
/>
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
// Native
|
|
58
|
+
import { CodeBlock } from "@hjmds/react-native/code-block";
|
|
59
|
+
|
|
60
|
+
<CodeBlock code={snippet} label={t("docs.installSnippet")} language="bash" />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 축과 기본값
|
|
64
|
+
|
|
65
|
+
| prop | 값 | 기본값 | 설명 |
|
|
66
|
+
| --- | --- | --- | --- |
|
|
67
|
+
| `code` | `string` | 필수 | — |
|
|
68
|
+
| `label` | `string` | 필수 | 비면 `TypeError` |
|
|
69
|
+
| `language` | `string` | — | 헤더에 표시, 없으면 `label` 표시 |
|
|
70
|
+
| `wrap` | `boolean` | `false` | 긴 줄은 가로 스크롤한다. `true`면 줄바꿈한다 |
|
|
71
|
+
| `tokens` | `readonly { text: string; tone?: "plain" \| "keyword" \| "string" \| "comment" \| "number" }[]` | — | 없으면 전체가 plain 한 덩어리다 |
|
|
72
|
+
| `copyAction` | `ReactNode` | — | 머리 줄 끝 쪽 슬롯 |
|
|
73
|
+
| Web `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 `section` 배치. Native는 없다 |
|
|
74
|
+
| `ClipboardButton` `value` · `labels` | `string` · `{ idle: ReactNode; copied: ReactNode }` | 필수 | 복사할 원문과 두 상태 문구 |
|
|
75
|
+
| `ClipboardButton` `onCopy` · `onCopyError` | `(value: string) => void` · `(error: unknown) => void` | — | 실패(권한 거부 등)는 `onCopyError`로만 알 수 있다 |
|
|
76
|
+
| `ClipboardButton` `feedbackDuration` | ms | 2000 | "복사했어요" 상태 유지 시간 |
|
|
77
|
+
| `ClipboardButton` `tone` · `size` | [Button](button.md)과 같다 | `secondary` · `medium`(1.12.1은 `primary`) | 코드 블록 안에서는 `small`로 낮춘다 |
|
|
78
|
+
|
|
79
|
+
## 배치
|
|
80
|
+
|
|
81
|
+
| 항목 | 값 | 근거 |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| 크기 | 부모 폭을 채우고 높이는 코드 줄 수가 정한다. 모서리 `radius.lg` 16, 배경 `surface-alt` | `react/src/code-block.tsx`, `react-native/src/code-block.tsx` |
|
|
84
|
+
| 간격 | 머리 줄 안쪽 `spacing.md` 16, 언어 이름↔복사 버튼 `spacing.sm` 12, 코드 영역 안쪽 `spacing.md` 16 | 같은 파일 |
|
|
85
|
+
| 순서·정렬 | 위→아래 [언어(또는 label) — 시작 쪽 · `copyAction` — 끝 쪽] → [코드]. 본문 문단 사이에 블록으로 둔다 | 같은 파일 |
|
|
86
|
+
| 고정·스크롤 | `wrap` 기본 `false`면 코드 영역만 가로 스크롤한다(Web `pre` `overflow-x: auto`, Native `ScrollView horizontal`). 세로 스크롤 영역은 만들지 않는다 | 같은 파일 |
|
|
87
|
+
| 좁은 폭·큰 글자 | 좁은 폭·큰 글자에서 읽기를 우선하면 `wrap`을 켠다(Web `pre-wrap` + `overflow-wrap: anywhere`) | `react/src/code-block.tsx` |
|
|
88
|
+
|
|
89
|
+
## 꼭 지킬 것
|
|
90
|
+
|
|
91
|
+
- `tokens`의 `text`를 이어 붙인 결과는 `code`와 공백까지 같아야 한다. 다르면 렌더 중 `TypeError`를 던진다.
|
|
92
|
+
- 토큰은 제품이 고른 하이라이터로 만든다(제품 소유). 토큰 색은 HJM semantic 색이 정한다.
|
|
93
|
+
- `label`은 i18n 키로 넣는다. Native 접근성 이름에는 `label`과 원문이 함께 들어간다.
|
|
94
|
+
- 복사 버튼과 실패 응답은 제품이 `copyAction`으로 공급한다. `onCopyError`에서 "직접 선택해 복사" 같은 안내를 보인다.
|
|
95
|
+
- 복사 버튼은 화면의 주 행동이 아니다. 기본 tone은 `secondary`다(미게시(1.12.1 이후). 1.12.1은 Button 기본 `primary`를
|
|
96
|
+
물려받으므로 `tone="secondary"`를 명시한다). 코드 블록 안에서는 `size="small"`을 준다([Button](button.md)의 한 화면 primary 하나 규칙).
|
|
97
|
+
- 배치는 Web `layoutStyle`로 한다. Native CodeBlock은 배치 prop이 없으므로 감싸는 레이아웃(`Stack` 등)이 배치한다.
|
|
98
|
+
|
|
99
|
+
## 플랫폼 차이
|
|
100
|
+
|
|
101
|
+
| 항목 | Web | Native |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| 원문 요소 | 포커스 가능한 `pre`(`tabIndex=0`) | 선택 가능한 `Text`, `wrap=false`면 가로 `ScrollView` |
|
|
104
|
+
| 복사 | `ClipboardButton` 슬롯 | 시스템 텍스트 선택, 또는 제품의 복사 버튼 슬롯 |
|
|
105
|
+
| 글꼴 | stylesheet 기본 | iOS `Menlo`, 그 외 `monospace` |
|
|
106
|
+
|
|
107
|
+
## 함정
|
|
108
|
+
|
|
109
|
+
- 복사는 화면의 주 행동과 경쟁하지 않도록 `ClipboardButton tone="secondary" size="small"`을 쓴다. Web 예제도 이 구성을 따른다.
|
|
110
|
+
- `ClipboardButton`은 `navigator.clipboard`가 거부되면 상태를 바꾸지 않고 `onCopyError`만 부른다. 이 콜백을 비워 두면
|
|
111
|
+
사용자는 실패를 알 수 없다.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [Collapsible](../../collapsible.md), `FolderPreview`는 [Folder preview](../../folder-preview.md), recipe `collapsibleRecipe`(`src/collapsible.ts`)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/접기와 펼치기` · `배포/컴포넌트/데이터 표시/폴더 미리보기`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
이웃 없이 혼자 접었다 펴는 한 덩어리에 쓴다. "더 보기", 필터 패널, 접히는 본문이 여기에 속한다.
|
|
14
|
+
닫히면 내용은 트리에서 빠진다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 서로 연결된 여러 접이식 항목 | [Accordion](accordion.md) |
|
|
21
|
+
| 같은 자리의 보기 전환 | [Tabs](tabs.md) |
|
|
22
|
+
| 화면 위에 떠서 열리는 내용 | [Popover](popover.md), [Sheet](sheet.md) |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `Collapsible` | 기본 | `@hjmds/react`, `/collapsible` | `@hjmds/react-native`, `/collapsible` |
|
|
29
|
+
| `FolderPreview` | 확장(미리보기 표지가 달린 묶음, optional-extension) | `/folder-preview` | `/folder-preview` |
|
|
30
|
+
|
|
31
|
+
`FolderPreview`는 granular subpath로만 import 된다. 추가 peer는 없다.
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { Collapsible } from "@hjmds/react/collapsible";
|
|
38
|
+
|
|
39
|
+
<Collapsible trigger={t("filters.more")} defaultOpen={false}>
|
|
40
|
+
<FilterFields />
|
|
41
|
+
</Collapsible>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Native
|
|
46
|
+
import { Collapsible } from "@hjmds/react-native/collapsible";
|
|
47
|
+
|
|
48
|
+
<Collapsible trigger={t("post.showMore")} open={expanded} onOpenChange={setExpanded}>
|
|
49
|
+
<PostBody />
|
|
50
|
+
</Collapsible>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`FolderPreview`는 Web·Native가 같은 props다(Web만 `layoutStyle`을 더 받는다).
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// Native
|
|
57
|
+
import { FolderPreview } from "@hjmds/react-native/folder-preview";
|
|
58
|
+
|
|
59
|
+
<FolderPreview
|
|
60
|
+
label={t("album.folderLabel", { count: photos.length })}
|
|
61
|
+
open={open}
|
|
62
|
+
onOpenChange={setOpen}
|
|
63
|
+
previews={photos.slice(0, 3).map((photo) => <PhotoThumb key={photo.id} photo={photo} />)}
|
|
64
|
+
>
|
|
65
|
+
<PhotoList photos={photos} />
|
|
66
|
+
</FolderPreview>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 축과 기본값
|
|
70
|
+
|
|
71
|
+
| prop | 값 | 기본값 | 설명 |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| `open` + `onOpenChange` | `boolean` + `(open: boolean) => void` | — | 제어. `open`만 주고 `onOpenChange`를 빼면 `TypeError`를 던진다 |
|
|
74
|
+
| `defaultOpen` · `onOpenChange` | `boolean` · `(open: boolean) => void` | `defaultOpen` `false` | 비제어. `open`과 `defaultOpen`을 함께 주면 `TypeError`를 던진다 |
|
|
75
|
+
| `trigger` | `ReactNode` | 필수 | 버튼 문구. Native는 문자열이면 HJM `Text`로 감싼다 |
|
|
76
|
+
| `disabled` | `boolean` | `false` | trigger 옆의 ▸/▾ 표시는 HJM이 그리며 장식으로 숨겨진다 |
|
|
77
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 |
|
|
78
|
+
| `FolderPreview` `open` · `onOpenChange` | `boolean` · `(open: boolean) => void` | 필수(항상 controlled) | — |
|
|
79
|
+
| `FolderPreview` `previews` · `label` | `readonly ReactNode[]` · `string` | 필수 | `previews`는 앞의 세 개만 그리고, `label`이 비면 `TypeError`를 던진다 |
|
|
80
|
+
| `FolderPreview` `layoutStyle` | margin·width·flex·`alignSelf` | — | Web만 |
|
|
81
|
+
|
|
82
|
+
## 배치
|
|
83
|
+
|
|
84
|
+
| 항목 | 값 | 근거 |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| 크기 | 트리거는 폭을 꽉 채운다. 트리거 최소 높이 44(`control.minTouchTarget`, 두 플랫폼. Native는 미게시(1.12.1 이후)), Web 위아래 `spacing.xs` 8. `FolderPreview` 표지는 최대 260×160 고정 그림 | `.hjm-collapsible__trigger`, `react-native/src/collapsible.tsx`, `react/src/folder-preview.tsx` |
|
|
87
|
+
| 간격 | 트리거↔내용, 트리거 안 문구↔표시 `spacing.xs` 8(Web 트리거 안은 `spacing.sm` 12) | `collapsibleRecipe.gap`, `.hjm-collapsible__trigger` |
|
|
88
|
+
| 순서·정렬 | 위→아래 [트리거: 문구 시작 쪽 · ▸/▾ 끝 쪽] → [내용(열렸을 때만)]. `FolderPreview`는 트리거 안에 [표지 그림(가운데)] → [label]이 쌓인다 | `react/src/collapsible.tsx`, `react/src/folder-preview.tsx` |
|
|
89
|
+
| 고정·스크롤 | 고정 영역이 없다. 닫히면 내용이 트리에서 빠져 아래 내용이 올라온다 | `react-native/src/collapsible.tsx` 주석 |
|
|
90
|
+
| 좁은 폭·큰 글자 | 트리거 문구·내용이 줄바꿈된다(`overflow-wrap: anywhere`). `FolderPreview` 표지는 폭이 260보다 좁으면 줄어든다(`maxWidth: 260`) | `.hjm-collapsible__content`, `folder-preview.tsx` |
|
|
91
|
+
|
|
92
|
+
## 꼭 지킬 것
|
|
93
|
+
|
|
94
|
+
- `trigger`는 무엇이 열리는지 알 수 있는 i18n 문구로 넣는다. trigger 안에 다른 버튼·링크를 넣지 않는다(trigger 자체가 버튼이다).
|
|
95
|
+
- 닫힌 내용은 unmount된다. 닫혀도 유지해야 할 입력 상태는 바깥에서 들고 있는다.
|
|
96
|
+
- `FolderPreview`의 `previews`는 장식이다(접근성·터치에서 숨김). 항목 이름과 행동은 펼친 `children`에 다시 둔다.
|
|
97
|
+
미리보기 이미지·라벨 문구는 제품 소유다.
|
|
98
|
+
- 배치는 `layoutStyle`로 한다. Native `style`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
|
|
99
|
+
|
|
100
|
+
## 플랫폼 차이
|
|
101
|
+
|
|
102
|
+
| 항목 | Web | Native |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| 배치 | `layoutStyle`(`FolderPreview`도) | `layoutStyle`. `style`은 deprecated — `layoutStyle`을 쓴다. `FolderPreview`는 배치 prop이 없다 |
|
|
105
|
+
| 열린 영역 관계 | `aria-controls` + `role="region"` | 중첩 구조, `accessibilityState.expanded` |
|
|
106
|
+
| 문자열 trigger | 그대로 버튼 내용 | HJM `Text`로 감싼다 |
|
|
107
|
+
| `FolderPreview` 모션 | CSS transform, reduced motion이면 없음 | `ContentTransition`(`scale`) |
|
|
108
|
+
|
|
109
|
+
## 함정
|
|
110
|
+
|
|
111
|
+
- 현재 랜딩 스토리(Web·Native `Landing.stories.tsx`)는 FAQ 여러 항목을 `Collapsible` 반복으로 그린다. 서로 연결된
|
|
112
|
+
여러 항목은 [Accordion](accordion.md)이 규칙이다(한 번에 하나 펼침·키보드 이동·heading 위계를 Accordion이 소유한다).
|