@hjmds/design-contracts 1.12.0 → 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,126 @@
|
|
|
1
|
+
# Asset
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [Asset contract](../../asset.md), [VoiceNote](../../voice-note.md), recipe `assetRecipe`(`src/asset.ts`)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/이미지·영상 표시` · `배포/컴포넌트/데이터 표시/음성 메모`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
아이콘·이미지·Lottie·비디오를 같은 크기·모서리 규칙의 액자에 넣을 때 쓴다. 종류가 다른 그림이
|
|
14
|
+
한 줄에 섞여 나오는 자리(캐릭터 그림, 첨부 썸네일 줄)가 대표적이다. 재생기는 제품이 슬롯으로 넣는다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 사람·계정 얼굴 | [Avatar](avatar.md) |
|
|
21
|
+
| 비율이 정해진 큰 이미지·영상 | [AspectRatio](aspect-ratio.md), [Image](image.md) |
|
|
22
|
+
| 버튼·행 안의 단일 아이콘 | [Icon](icon.md) |
|
|
23
|
+
| 업로드 진행 중인 첨부 | [UploadItem](upload-item.md) |
|
|
24
|
+
|
|
25
|
+
## 공개 이름과 import
|
|
26
|
+
|
|
27
|
+
| 이름 | 역할 | Web | Native |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `Asset` | 기본(액자) | `@hjmds/react`, `/asset` | `@hjmds/react-native`, `/asset` |
|
|
30
|
+
| `AssetGroup` | 동반(겹쳐 쌓는 묶음) | `@hjmds/react`, `/asset` | `@hjmds/react-native`, `/asset` |
|
|
31
|
+
| `VoiceNote` | 확장(음성 메모 재생 UI, optional-extension) | `/voice-note`만 | `/voice-note`만 |
|
|
32
|
+
|
|
33
|
+
`VoiceNote`는 root에서 export되지 않는다. Asset·Slider·Button·Stack·Surface·Text만 합성하며
|
|
34
|
+
optional peer를 요구하지 않는다(소스 import 확인).
|
|
35
|
+
|
|
36
|
+
## 최소 사용 예
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
// Web
|
|
40
|
+
import { Asset } from "@hjmds/react/asset";
|
|
41
|
+
|
|
42
|
+
<Asset descriptor={{ kind: "lottie", size: "large", accessibilityLabel: t("pet.fox") }}>
|
|
43
|
+
{({ animate }) => <FoxLottie autoplay={animate} />}
|
|
44
|
+
</Asset>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
// Native
|
|
49
|
+
import { Image } from "react-native";
|
|
50
|
+
import { Asset, AssetGroup } from "@hjmds/react-native/asset";
|
|
51
|
+
|
|
52
|
+
<AssetGroup label={t("letter.carriers")} size="small">
|
|
53
|
+
{carriers.map((c) => (
|
|
54
|
+
<Asset key={c.id} descriptor={{ kind: "image", size: "small", shape: "circle", decorative: true }}>
|
|
55
|
+
<Image source={c.art} style={{ width: 32, height: 32 }} />
|
|
56
|
+
</Asset>
|
|
57
|
+
))}
|
|
58
|
+
</AssetGroup>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Native
|
|
63
|
+
// VoiceNote. Web(`@hjmds/react/voice-note`)도 같은 props에 `layoutStyle`만 더 받는다.
|
|
64
|
+
import { VoiceNote } from "@hjmds/react-native/voice-note";
|
|
65
|
+
|
|
66
|
+
<VoiceNote
|
|
67
|
+
descriptor={{ title: note.title, state: player.state, duration: player.duration, position: player.position }}
|
|
68
|
+
labels={{ play: t("voice.play"), pause: t("voice.pause"), seek: t("voice.seek"), loading: t("voice.loading"),
|
|
69
|
+
error: t("voice.error"), retry: t("voice.retry"), backward: t("voice.backward"), forward: t("voice.forward") }}
|
|
70
|
+
formatTime={formatSeconds}
|
|
71
|
+
onPlayingChange={(playing) => (playing ? player.play() : player.pause())}
|
|
72
|
+
onSeek={player.seekTo}
|
|
73
|
+
onRetry={player.reload}
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 축과 기본값
|
|
78
|
+
|
|
79
|
+
| prop | 값 | 기본값 | 설명 |
|
|
80
|
+
| --- | --- | --- | --- |
|
|
81
|
+
| `descriptor` | `{ kind, size?, shape?, decorative?, accessibilityLabel? }`(`AssetDescriptor`) | 필수 | 이름 또는 `decorative: true` 둘 중 하나만 |
|
|
82
|
+
| `descriptor.kind` | `icon` · `image` · `lottie` · `video` | 필수 | — |
|
|
83
|
+
| `descriptor.size` | `small`(32) · `medium`(48) · `large`(72) · `xlarge`(120) | `medium` | — |
|
|
84
|
+
| `descriptor.shape` | `square` · `rounded` · `circle` | `rounded` | — |
|
|
85
|
+
| `children` | `ReactNode` 또는 `(state: { animate: boolean }) => ReactNode` | 필수 | `animate`는 `lottie`·`video`이고 reduced motion이 아닐 때만 `true`다 |
|
|
86
|
+
| `accessory` | `ReactNode` | — | 액자 바깥 모서리에 붙는 작은 표식(재생 아이콘, 상태 점) |
|
|
87
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | Asset·AssetGroup 바깥 배치 전용 |
|
|
88
|
+
| Native `style`(Asset·AssetGroup) | — | — | deprecated — `layoutStyle` 또는 descriptor `size`/`shape`(개발 모드 1회 경고, 다음 major 제거) |
|
|
89
|
+
| `AssetGroup` `label` · `size` | `string` · `AssetSize` | `label` 필수 · `size` `medium` | 겹침은 크기의 30%로 Avatar와 같다 |
|
|
90
|
+
| `VoiceNote` `descriptor` | `{ title, state: "paused" \| "playing" \| "loading" \| "error", duration: number \| null, position: number, disabled? }` | 필수 | `duration: null`(메타데이터 미확인)과 `0`이면 재생·탐색이 막힌다 |
|
|
91
|
+
| `VoiceNote` `labels` | `{ play, pause, seek, loading, error, retry, backward, forward }`(모두 `string`) | 필수 | i18n 문구 |
|
|
92
|
+
| `VoiceNote` `formatTime` | `(seconds: number) => string` | 필수 | 경과/전체 시간 문구와 Slider 접근성 값 |
|
|
93
|
+
| `VoiceNote` `onPlayingChange` · `onSeek` · `onRetry` | `(playing: boolean) => void` · `(seconds: number) => void` · `() => void` | 앞의 둘 필수 | 플레이어 제어는 제품 소유 |
|
|
94
|
+
| `VoiceNote` `artwork` | `ReactNode` | — | 장식 원형 Asset으로 그린다 |
|
|
95
|
+
|
|
96
|
+
## 배치
|
|
97
|
+
|
|
98
|
+
| 항목 | 값 | 근거 |
|
|
99
|
+
| --- | --- | --- |
|
|
100
|
+
| 크기 | 정사각 액자. 한 변 `small` 32 · `medium` 48 · `large` 72 · `xlarge` 120. 모서리 `square` 0 · `rounded` `radius.md` 12 · `circle` `radius.full`. 터치 대상이 아니므로 누를 수 있게 하려면 감싸는 버튼·행이 최소 44를 확보한다 | `assetRecipe.sizes`·`shapes`, `.hjm-asset__frame` |
|
|
101
|
+
| 간격 | `accessory`는 액자 끝·아래 모서리 바깥으로 `spacing.xxs` 4 띄워 붙으므로 옆 요소와 `spacing.xs` 8 이상 띄운다. `AssetGroup` 겹침은 크기의 30%(48이면 −14) | `assetRecipe.accessory`·`overlapRatio`, `react/src/asset.tsx` |
|
|
102
|
+
| 순서·정렬 | 늘어나지 않는 인라인 요소(Web `inline-flex`, `flex: 0 0 auto`). 행 안에서는 시작 쪽에 둔다. 한 줄에 종류가 다른 그림을 섞을 때 모두 같은 `size`·`shape`를 준다 | `.hjm-asset`, `assetBehavior.scenarios` |
|
|
103
|
+
| 고정·스크롤 | 고정 영역이 없다 | — |
|
|
104
|
+
| 좁은 폭·큰 글자 | 액자 크기는 그대로이고 옆 문구가 줄바꿈된다 | `assetRecipe.sizes`(고정 수치) |
|
|
105
|
+
|
|
106
|
+
## 꼭 지킬 것
|
|
107
|
+
|
|
108
|
+
- 뜻이 있는 그림은 `accessibilityLabel`을, 장식은 `decorative: true`만 준다. 이름 없는 비장식과
|
|
109
|
+
둘 다 준 경우 모두 렌더 중 `TypeError`가 난다.
|
|
110
|
+
- reduced motion에서는 숨기지 말고 `animate`로 재생을 멈춘다. 판단을 제품에서 다시 만들지 않는다.
|
|
111
|
+
- Lottie·비디오·오디오 엔진, 그림 자산은 제품 소유다. HJM은 액자·크기·겹침·표식 위치만 소유한다.
|
|
112
|
+
- `VoiceNote`의 재생 위치는 실제 플레이어 값을 넘긴다. 내부 타이머로 진행을 꾸미지 않는다.
|
|
113
|
+
|
|
114
|
+
## 플랫폼 차이
|
|
115
|
+
|
|
116
|
+
| 항목 | Web | Native |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| 배치 prop | `layoutStyle`(+`className`) | `layoutStyle`(`style`은 deprecated). `VoiceNote`는 Native에 `layoutStyle` 없음 |
|
|
119
|
+
| 매체 크기 | CSS가 자식을 액자 안으로 줄인다(`max-*: 100%`) | 가운데 정렬만 한다. 자식 크기를 직접 준다 |
|
|
120
|
+
| `AssetGroup` 겹침 | CSS로 `.hjm-asset` 형제에 적용 | `children`이 배열일 때만 각 자식을 감싸 적용 |
|
|
121
|
+
| `VoiceNote` 앞뒤 이동 문구 | `labels.backward/forward`를 받지만 쓰지 않는다 | Slider 접근성 동작 문구로 쓴다 |
|
|
122
|
+
|
|
123
|
+
## 함정
|
|
124
|
+
|
|
125
|
+
- `AssetGroup`의 `size`는 겹침 폭만 정한다. 안의 각 `Asset` descriptor에 같은 `size`를 직접 준다.
|
|
126
|
+
- Native `AssetGroup`에 Fragment 하나나 단일 자식을 넘기면 겹치지 않는다. `map` 결과 배열을 넘긴다.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# AuthProviderButton
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [AuthProviderButton](../../provider-button.md), recipe `authProviderButtonRecipe`(`src/provider-button.ts`), 포트폴리오 상위 기준 루트 `docs/LOGIN_SCREEN_STANDARD.md`(LS-02·LS-05·LS-09)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/동작/소셜 로그인 버튼`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
Google·Kakao·Naver·Apple 소셜 로그인 버튼에 쓴다. 지원 제공자는 이 네 개뿐이며
|
|
14
|
+
(`AuthProviderId`), 다른 값은 렌더 시 `TypeError`가 난다. 로그인 화면에서는
|
|
15
|
+
[AuthScreenLayout](auth-screen-layout.md)의 `main` 슬롯에 세로로 쌓는다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 제공자가 아닌 일반 행동, 개발 로그인·QA 슬롯 버튼 | [Button](button.md) (`tone="secondary"`, LS-05) |
|
|
22
|
+
| 로그인 화면 전체 배치 | [AuthScreenLayout](auth-screen-layout.md) |
|
|
23
|
+
| 로그인 진행 표시 | AuthScreenLayout의 `pendingLabel` (버튼별 `busy` 아님) |
|
|
24
|
+
|
|
25
|
+
## 공개 이름과 import
|
|
26
|
+
|
|
27
|
+
| 이름 | 역할 | Web | Native |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| `AuthProviderButton` | 기본 | `@hjmds/react`, `/provider-button` | `@hjmds/react-native`, `/provider-button` |
|
|
30
|
+
|
|
31
|
+
## 최소 사용 예
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// Web
|
|
35
|
+
import { AuthProviderButton } from "@hjmds/react/provider-button";
|
|
36
|
+
|
|
37
|
+
<AuthProviderButton
|
|
38
|
+
descriptor={{ provider: "kakao", label: t("auth.provider.kakao") }}
|
|
39
|
+
logo={<ProviderLogo provider="kakao" />} // 제품 컴포넌트
|
|
40
|
+
onClick={() => start("kakao")}
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Native
|
|
46
|
+
import { AuthProviderButton } from "@hjmds/react-native/provider-button";
|
|
47
|
+
|
|
48
|
+
<AuthProviderButton
|
|
49
|
+
descriptor={{ provider: "apple", label: t("auth.provider.apple") }}
|
|
50
|
+
logo={<ProviderLogo provider="apple" />} // 제품 컴포넌트
|
|
51
|
+
onPress={() => start("apple")}
|
|
52
|
+
/>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 축과 기본값
|
|
56
|
+
|
|
57
|
+
| prop | 값 | 기본값 | 설명 |
|
|
58
|
+
| --- | --- | --- | --- |
|
|
59
|
+
| `descriptor.provider` | `google` · `kakao` · `naver` · `apple` | 필수 | 색은 `authProviderPalettes`가 정하고 테마는 제공자가 규정한 light/dark 변형 중 하나를 고를 뿐이다. 다크에서 Google·Apple은 변형이 바뀌고 Kakao·Naver는 같다 |
|
|
60
|
+
| `descriptor.label` | 문자열 | 필수 | 공백만 있으면 `TypeError` |
|
|
61
|
+
| `descriptor.busy` · `disabled` | `boolean` | `false` | — |
|
|
62
|
+
| `logo` | `ReactNode` | 필수 | 제품이 공급하는 제공자 로고 |
|
|
63
|
+
| Web `onClick` · Native `onPress` | `(event: MouseEvent<HTMLButtonElement>) => void` · `() => void` | Native 필수 | 로그인 시작 |
|
|
64
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용 |
|
|
65
|
+
| Native `style` | — | — | deprecated — `layoutStyle`(외형은 `authProviderButtonRecipe` 소유, 개발 모드 1회 경고, 다음 major 제거) |
|
|
66
|
+
| — | — | HJM 소유 | 크기: 높이 `buttonRecipe` medium(Native는 최소 터치 타깃과 큰 쪽), radius `md`, 로고 상자 20, 테두리 1(테두리를 규정한 변형만), 포커스 링은 fill 바깥(offset 2) |
|
|
67
|
+
| Web `type` | HTML button type | `"button"` | — |
|
|
68
|
+
|
|
69
|
+
## 배치
|
|
70
|
+
|
|
71
|
+
| 항목 | 값 | 근거 |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| 크기 | 폭을 꽉 채운다(Web `inline-size: 100%`). 최소 높이 44(`buttonRecipe` medium과 `control.minTouchTarget` 중 큰 쪽), 모서리 `radius.md` 12, 로고 20×20, 라벨 `typography.body` 14/20(제공자 가이드가 라벨·색 표현을 소유한다. 네이버 녹색 대비는 [제공자 색 예외](../../provider-button.md#제공자-색과-글자-대비)) | `authProviderButtonRecipe`, `.hjm-auth-provider-button` |
|
|
74
|
+
| 간격 | 좌우 여백 `spacing.md` 16, 로고↔이름 `spacing.sm` 12. 버튼 사이는 스토리 기준 `Stack gap="sm"`(12) | `authProviderButtonRecipe`, `showcase/web/src/patterns/Agreement.stories.tsx`(`AuthScreenLayoutPreview`) |
|
|
75
|
+
| 순서·정렬 | [AuthScreenLayout](auth-screen-layout.md) `main` 슬롯에 세로로 쌓는다. 안쪽은 [로고]+[제공자 이름]이 가운데 정렬. 순서는 제품이 정하고, 일반 Button(개발 로그인 등)은 제공자 사이가 아니라 목록 아래에 `tone="secondary"`로 둔다 | `.hjm-auth-provider-button`(`justify-content: center`) |
|
|
76
|
+
| 고정·스크롤 | 고정되지 않는다. 진행 중에는 버튼 목록을 그대로 두고 카드 가운데에 로딩 하나만 보인다(`pendingLabel`). 버튼 자리·크기는 바뀌지 않는다 | `react/src/auth-screen.tsx`, `react-native/src/auth-screen.tsx` |
|
|
77
|
+
| 좁은 폭·큰 글자 | 이름이 줄바꿈되고 버튼 높이가 늘어난다. 자르지 않는다 | `.hjm-auth-provider-button__label`(`overflow-wrap: anywhere`) |
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
main 카드(mainCard)
|
|
81
|
+
┌──────────────────────────┐
|
|
82
|
+
│ [ (G) Google ] │
|
|
83
|
+
│ [ (K) 카카오 ] │ ← 간격 spacing.sm 12
|
|
84
|
+
│ [ (N) 네이버 ] │
|
|
85
|
+
│ [ () Apple ] │ ← iOS 필수
|
|
86
|
+
└──────────────────────────┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 꼭 지킬 것
|
|
90
|
+
|
|
91
|
+
- `label`에는 제공자 이름만 넣는다(`카카오`, `네이버`, `Google`, `Apple`). "Google로 계속하기" 같은 문장을
|
|
92
|
+
만들지 않는다. 문구는 제품 i18n 카탈로그가 소유하며 renderer가 만들거나 자르지 않는다.
|
|
93
|
+
- `logo`는 제품이 공급한다. HJM에는 제공자 로고가 없고 넣지도 않는다(공개 MIT 배포라 상표를 담을 수 없다).
|
|
94
|
+
로고 원본은 포트폴리오 루트 `assets/auth-providers/`이고 `node scripts/sync-auth-provider-logos.mjs --write`로
|
|
95
|
+
앱 checkout에 투사한 복사본만 쓴다. 앱에서 따로 내려받지 않는다. 비율을 유지하고 정사각으로 늘리지 않는다.
|
|
96
|
+
- 배치는 `layoutStyle`로만 한다. 제공자 색을 `className`·`style`로 다시 칠하지 않는다. 제품 테마·브랜드 색은 이 버튼에 닿지 않는다.
|
|
97
|
+
- 로그인 화면의 진행 상태는 AuthScreenLayout `mainCard` + `pendingLabel`로 표시한다. 버튼마다 `busy`를 켜지
|
|
98
|
+
않고, 진행 중에 버튼 목록을 제거하거나 바꾸지 않는다. `busy`는 단독 버튼 호환용이며 카드 진행 상태와 함께 쓰지 않는다.
|
|
99
|
+
- 제공자 목록과 순서는 제품(서버 `GET /auth/providers` 등)이 정한다. 설정되지 않은 제공자는 그리지 않는다.
|
|
100
|
+
iOS 앱에서는 Apple이 필수다(LS-02). 웹·Android·iOS의 제공자 집합은 같게 둔다(iOS에 Apple 추가만 허용).
|
|
101
|
+
|
|
102
|
+
## 플랫폼 차이
|
|
103
|
+
|
|
104
|
+
| 항목 | Web | Native |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| 이벤트 | `onClick` 등 `<button>` 속성(선택) | `onPress`(필수) |
|
|
107
|
+
| 비활성 | `descriptor.disabled`(`disabled` 속성은 받지 않음) | `descriptor.disabled` |
|
|
108
|
+
| 배치 입력 | `layoutStyle`(+`className`, `style` 없음) | `layoutStyle`(`style`은 deprecated) |
|
|
109
|
+
| 접근성 이름 | `aria-label={label}` | `accessibilityLabel={label}` |
|
|
110
|
+
| `testID`·ref | ref 전달 | 둘 다 없음 |
|
|
111
|
+
|
|
112
|
+
## 함정
|
|
113
|
+
|
|
114
|
+
- `busy`이면 로고와 라벨은 자리를 유지한 채 숨고 가운데 스피너 하나만 보인다. 버튼 폭은 줄지 않는다.
|
|
115
|
+
`disabled`는 불투명도 0.5로 흐려지지만 `busy`는 흐려지지 않는다(두 renderer 같음).
|
|
116
|
+
- descriptor 검증은 렌더마다 실행된다. 빈 번역 키 결과(빈 문자열)가 그대로 들어가면 화면이 아니라 렌더가 실패한다.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# AuthScreenLayout
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [로그인 화면 골격](../../auth-screen.md), [1.4 제품 채택 가이드](../../product-adoption-1.4.md), recipe `authScreenRecipe`(`src/auth-screen.ts`), 포트폴리오 상위 기준 루트 `docs/LOGIN_SCREEN_STANDARD.md`(LS)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/레이아웃/로그인 화면`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
로그인·가입 진입 화면의 배치에 쓴다. 위 영역(`hero` + `main`)은 남는 세로 공간에서 가운데 정렬되고,
|
|
14
|
+
`footer`(동의 고지·정책 링크)는 바닥에 붙는다. 내용이 화면보다 길면 스크롤로 바뀐다.
|
|
15
|
+
이 컴포넌트는 배치만 소유하며 슬롯 안의 내용은 그리지 않는다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 제공자 버튼 하나하나 | [AuthProviderButton](auth-provider-button.md) (`main` 슬롯에 넣는다) |
|
|
22
|
+
| 동의 체크 항목 | [Agreement](agreement.md) (`footer`나 가입 단계에 넣는다) |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `AuthScreenLayout` | 기본 | `@hjmds/react`, `/auth-screen` | `@hjmds/react-native`, `/auth-screen` |
|
|
29
|
+
|
|
30
|
+
## 최소 사용 예
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// Web
|
|
34
|
+
import { AuthScreenLayout } from "@hjmds/react/auth-screen";
|
|
35
|
+
|
|
36
|
+
<AuthScreenLayout
|
|
37
|
+
mainCard
|
|
38
|
+
{...(pending ? { pendingLabel: t("auth.pending") } : {})}
|
|
39
|
+
hero={<ProductHero />} // 제품 마크·제목·설명
|
|
40
|
+
main={<ProviderButtons />} // pending이어도 같은 목록
|
|
41
|
+
footer={<ConsentAndPolicyLinks />}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// Native
|
|
47
|
+
import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
|
|
48
|
+
|
|
49
|
+
<AuthScreenLayout
|
|
50
|
+
mainCard
|
|
51
|
+
{...(pending ? { pendingLabel: t("auth.pending") } : {})}
|
|
52
|
+
hero={<ProductHero />}
|
|
53
|
+
main={<ProviderButtons />}
|
|
54
|
+
footer={<ConsentAndPolicyLinks />}
|
|
55
|
+
testID="login-screen"
|
|
56
|
+
/>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 축과 기본값
|
|
60
|
+
|
|
61
|
+
| prop | 값 | 기본값 | 설명 |
|
|
62
|
+
| --- | --- | --- | --- |
|
|
63
|
+
| `hero` · `main` | `ReactNode` | 필수 | 제품 마크·제목·설명 / 제공자 버튼 목록(진행 중에도 같은 목록) |
|
|
64
|
+
| `footer` | `ReactNode` | — | 동의 고지·정책 링크 |
|
|
65
|
+
| `density` | `regular` · `compact` | `regular` | 제품이 화면 높이를 보고 고른다. 간격·padding만 바뀐다 |
|
|
66
|
+
| `hasFooter` | `boolean` | `true` | `false`이거나 `footer`가 없으면 아래 영역을 그리지 않는다 |
|
|
67
|
+
| `mainCard` | `boolean` | `false` | 기존 제품 카드와 중첩 방지. 새 조합은 켠다. 카드 배경은 테마 `bg`, radius `lg`, padding `md` |
|
|
68
|
+
| `pendingLabel` | 문자열 | — | 주면 진행 상태다. 공백만 있으면 `TypeError`. 제거하면 원래 상태로 돌아온다 |
|
|
69
|
+
| Web `as` | `main` · `section` | `main` | 최대 폭은 416이다 |
|
|
70
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용(Web·Native 모두) |
|
|
71
|
+
| Native `testID` | `string` | — | 뿌리 `ScrollView` 식별 |
|
|
72
|
+
|
|
73
|
+
## 배치
|
|
74
|
+
|
|
75
|
+
| 항목 | 값 | 근거 |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| 크기 | 화면 전체(Web `min-block-size: 100dvh`, Native `ScrollView flex: 1`). 위 블록과 footer는 최대 폭 416, 마크 72(`markSize`, `radius.lg`), 정책 링크 최소 44(`footerMinTouchTarget`), mainCard 모서리 `radius.lg` 16 | `authScreenRecipe`, `.hjm-auth-screen*` |
|
|
78
|
+
| 간격 | `regular` / `compact`: 바깥 좌우 `layout.pagePadding.regular` 20 / `.compact` 16, 바깥 위아래 `spacing.xxxl` 40 / `spacing.lg` 20, hero 안 `spacing.md` 16 / `spacing.xs` 8, hero↔main `spacing.xl` 24 / `spacing.md` 16, 위 블록↔footer 최소 `spacing.xl` 24 / `spacing.md` 16. mainCard 안쪽 `spacing.md` 16 | `authScreenRecipe.densities`·`mainCard` |
|
|
79
|
+
| 순서·정렬 | 위→아래 hero(가운데 정렬 문구) → main(폭 꽉 채움) → footer(동의 고지·정책 링크). 위 블록은 남는 세로 공간에서 가운데, footer는 바닥에 붙는다. 진행 중 로딩은 main 카드 가운데에 겹친다 | `.hjm-auth-screen__block`(`justify-content: center`), `.hjm-auth-screen__pending` |
|
|
80
|
+
| 고정·스크롤 | 고정 영역이 없다. 넘치면 화면 전체가 스크롤한다. 안전 영역은 Native `contentInsetAdjustmentBehavior="automatic"`과 키보드 inset 자동 조정이 처리하고, Web은 safe-area inset을 더하지 않는다 | `react-native/src/auth-screen.tsx`, `.hjm-auth-screen` |
|
|
81
|
+
| 좁은 폭·큰 글자 | 키 작은 화면·큰 글자에서는 `compact`를 고른다. 그래도 넘치면 스크롤되며 footer는 맨 아래로 밀린다 | `authScreenRecipe.densities` |
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
┌──────────────────────────┐
|
|
85
|
+
│ (바깥 위 여백 40) │
|
|
86
|
+
│ [마크 72] │ ← hero(가운데)
|
|
87
|
+
│ 어떤 계정으로… │
|
|
88
|
+
│ 설명 한 줄 │
|
|
89
|
+
│ ↕ spacing.xl 24 │
|
|
90
|
+
│ ┌──────────────────────┐ │
|
|
91
|
+
│ │ [ Google ] │ │ ← main(mainCard), 최대 폭 416
|
|
92
|
+
│ │ [ 카카오 ] … │ │
|
|
93
|
+
│ └──────────────────────┘ │
|
|
94
|
+
│ (남는 공간) │
|
|
95
|
+
│ 계속하면 동의 · 정책 링크 │ ← footer(바닥)
|
|
96
|
+
│ (바깥 아래 여백 40) │
|
|
97
|
+
└──────────────────────────┘
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## 꼭 지킬 것
|
|
101
|
+
|
|
102
|
+
- 로그인 진행 중에는 `pendingLabel` 하나로 표시한다. `main`의 버튼 목록은 그대로 두고(조건부 제거·교체 금지)
|
|
103
|
+
카드 크기를 유지한 채 중앙 로딩 하나만 보인다. 버튼별 `busy`를 함께 켜지 않는다.
|
|
104
|
+
- `pendingLabel`은 스크린리더 안내로만 쓰인다. 로딩 아래에 보이는 문구를 따로 두지 않는다.
|
|
105
|
+
완료·취소·실패 시 prop을 제거한다. 취소는 실패 문장 없이 버튼만 복구한다(LS-09).
|
|
106
|
+
- `pendingLabel`은 기존 카드에서 전환하는 상태다. 제공자 목록 최초 조회의 로딩을 대신하지 않는다.
|
|
107
|
+
- 슬롯 내용은 전부 제품 소유다: 마크 자산, 모든 문구(i18n 키), 제공자 목록과 순서, 정책 링크 목적지,
|
|
108
|
+
심사자 폼 노출 조건. 제공자 로고를 HJM에 넣지 않는다([AuthProviderButton](auth-provider-button.md)).
|
|
109
|
+
- iOS 앱의 제공자 목록에는 Apple이 있어야 한다(LS-02). 심사자 이메일 폼은 서버 env와 `review=1` 두 조건이
|
|
110
|
+
모두 맞을 때만 제품이 `main` 카드 안에 그린다(LS-07).
|
|
111
|
+
- `hasFooter: false`는 필수 동의 고지·정책 링크를 생략해도 된다는 뜻이 아니다.
|
|
112
|
+
- 외부 배치는 `layoutStyle`(정렬·flex·margin·폭 키만 허용)로 한다. Native에는 `style` prop이 없고, Web `style`로 배치하지 않는다.
|
|
113
|
+
|
|
114
|
+
## 플랫폼 차이
|
|
115
|
+
|
|
116
|
+
| 항목 | Web | Native |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| 뿌리 요소 | `<main>` 또는 `as="section"`, ref 전달 | `ScrollView` |
|
|
119
|
+
| 배치·식별 | `layoutStyle`, `className`, HTML 속성 | `layoutStyle`, `testID` |
|
|
120
|
+
| 진행 중 숨김 | `inert` + `aria-hidden` + `visibility: hidden`, 로딩은 `role="status"` | 터치·접근성 제외 + 불투명도 0, 로딩은 `progressbar` + live region |
|
|
121
|
+
| 키보드 | 브라우저 기본 | 키보드 inset 자동 조정, 탭 유지(`handled`), iOS `interactive`·Android `on-drag` 내리기 |
|
|
122
|
+
|
|
123
|
+
## 함정
|
|
124
|
+
|
|
125
|
+
- Native는 자체로 키보드 스크롤 여백을 준다. 부모가 다시 keyboard avoidance를 하면 여백이 중복될 수 있으니
|
|
126
|
+
심사자 폼처럼 입력칸이 있는 화면은 소비 화면에서 확인한다.
|
|
127
|
+
- 앱 셸이 이미 `<main>`을 가진 Web 화면에서 기본값을 쓰면 main landmark가 중첩된다. `as="section"`을 쓴다.
|
|
128
|
+
- 기존 제품 카드 wrapper 안에서 `mainCard`를 켜면 카드가 이중이 된다. wrapper를 걷어내고 `mainCard`로 옮긴다.
|
|
129
|
+
- 현재 Web `style`은 recipe 크기 변수(`--hjm-auth-screen-*`) 뒤에 펼쳐져 최대 폭·간격 변수를 덮을 수 있다. 배치는 `layoutStyle`로만 준다.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Avatar
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: [Avatar fallback and Blobatar](../../avatar-fallback.md), recipe `avatarRecipe`(`src/component-recipes.ts`)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/아바타` · `배포/컴포넌트/데이터 표시/블로바타 캐릭터` · `배포/컴포넌트/데이터 표시/움직이는 블로바타 캐릭터`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
사람·계정을 사진 또는 이니셜로 나타낼 때 쓴다. 사진이 없거나 로드에 실패하면 자동으로 대체 표시로
|
|
14
|
+
바뀌고, 사진 주소가 바뀌면 다시 시도한다. Web은 여러 명을 겹쳐 보이는 `AvatarGroup`이 있다.
|
|
15
|
+
|
|
16
|
+
## 쓰지 않을 때
|
|
17
|
+
|
|
18
|
+
| 상황 | 대신 쓸 것 |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| 사람이 아닌 그림·캐릭터·Lottie | [Asset](asset.md) |
|
|
21
|
+
| 비율이 있는 큰 사진 | [Image](image.md), [AspectRatio](aspect-ratio.md) |
|
|
22
|
+
| 이름 옆 숫자·상태 표시 | [CounterBadge](counter-badge.md), [Badge](badge.md) |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `Avatar` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
29
|
+
| `AvatarGroup` | 동반(겹친 묶음) | `@hjmds/react`, `/display` | 없음 |
|
|
30
|
+
| `createBlobatarFallback` | 확장(선택 대체 그림, 정적) | `/avatar-blobatar` | `/avatar-blobatar` |
|
|
31
|
+
| `createAnimatedBlobatarFallback` | 확장(선택 대체 그림, 움직임) | `/avatar-blobatar-motion` | `/avatar-blobatar-motion` |
|
|
32
|
+
|
|
33
|
+
Blobatar 어댑터는 root에서 export되지 않는다. optional peer `blobatar@2.7.0`과
|
|
34
|
+
`@blobatar/react@2.7.0`(Web) 또는 `@blobatar/react-native@2.7.0`(Native)을 앱에 설치해야 하고,
|
|
35
|
+
Native는 `react-native-svg`도 필요하다. motion Native entry는 `@blobatar/react-native/animated`를 써서
|
|
36
|
+
호환 Reanimated·Worklets가 추가로 필요하다([계약](../../avatar-fallback.md)). 설치하지 않은 채 import하면
|
|
37
|
+
tsc·테스트는 통과해도 기기 Metro에서 깨질 수 있다.
|
|
38
|
+
|
|
39
|
+
## 최소 사용 예
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
// Web
|
|
43
|
+
import { Avatar, AvatarGroup } from "@hjmds/react/display";
|
|
44
|
+
|
|
45
|
+
// 사진이 없으면 src를 빼야 한다(exactOptionalPropertyTypes에서 undefined를 넘기면 타입 오류).
|
|
46
|
+
const profile = (
|
|
47
|
+
<Avatar name={user.displayName} {...(user.photoUrl ? { src: user.photoUrl } : {})} size="large" />
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const roomMembers = (
|
|
51
|
+
<AvatarGroup label={t("room.members", { count: members.length })} size="small"
|
|
52
|
+
overflow={hidden > 0 ? t("room.moreMembers", { count: hidden }) : null}>
|
|
53
|
+
{visible.map((m) => <Avatar key={m.id} name={m.displayName} src={m.photoUrl} size="small" />)}
|
|
54
|
+
</AvatarGroup>
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// Native
|
|
60
|
+
import { Avatar } from "@hjmds/react-native/data-display";
|
|
61
|
+
|
|
62
|
+
<Avatar name={user.displayName} {...(user.photoUrl ? { source: { uri: user.photoUrl } } : {})}
|
|
63
|
+
accessibilityLabel={user.displayName} size={48} />
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 축과 기본값
|
|
67
|
+
|
|
68
|
+
| prop | 값 | 기본값 | 설명 |
|
|
69
|
+
| --- | --- | --- | --- |
|
|
70
|
+
| `name` | `string` | 필수 | 이니셜과 기본 접근성 이름의 원천. 공백뿐이면 `TypeError` |
|
|
71
|
+
| Web `src` · Native `source` | `string` · `ImageSourcePropType` | — | 사진이 없으면 prop을 뺀다. 주소(Native는 source 내용)가 바뀌면 실패 상태를 지우고 다시 시도한다 |
|
|
72
|
+
| Web `size` | `small`(32) · `medium`(40) · `large`(48) · `xlarge`(64) | `medium` | — |
|
|
73
|
+
| Web `shape` | `circle` · `rounded` | `circle` | — |
|
|
74
|
+
| Native `size` | 숫자(pt) | 44 | 24 미만이면 `RangeError`. 모양은 항상 원이다 |
|
|
75
|
+
| `renderFallback` | `(context: { size: number; decorative: true }) => ReactNode` | — | 사진이 없거나 실패했을 때만 불린다. `size`는 px/pt 값. null·undefined를 돌려주면 기본 대체 표시(Web `fallback`→이니셜, Native 이니셜)를 쓴다 |
|
|
76
|
+
| Web `alt` | `string` | `name` | `""`이면 보조기기에서 숨긴다 |
|
|
77
|
+
| Web `imageProps` | `img` 속성(`alt`·`src` 제외) | — | `onError`는 `(event: SyntheticEvent<HTMLImageElement>) => void`이며 대체 표시로 바꾼 뒤 불린다 |
|
|
78
|
+
| Native `accessibilityLabel` · `decorative` | `{ accessibilityLabel: string }` 또는 `{ decorative: true }` | — | 둘 중 하나가 타입으로 강제된다 |
|
|
79
|
+
| Native `initials` | `string` | 이름에서 계산 | 앞뒤 공백을 지우고 최대 3자, 대문자 |
|
|
80
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`imageStyle`은 deprecated — layoutStyle 또는 `size`/`renderFallback` |
|
|
81
|
+
| `AvatarGroup`(Web) `label` · `size` · `overflow` | `string` · Avatar 크기 · `ReactNode` | `label` 필수 · `size` `medium` | 빈 `label`은 `TypeError`. `overflow`는 제품이 만든 "+3" 같은 문구이며 `aria-hidden`이다(남은 인원은 `label`에 담는다). 겹침은 크기의 30%다 |
|
|
82
|
+
|
|
83
|
+
## 배치
|
|
84
|
+
|
|
85
|
+
| 항목 | 값 | 근거 |
|
|
86
|
+
| --- | --- | --- |
|
|
87
|
+
| 크기 | Web `small` 32 · `medium` 40 · `large` 48 · `xlarge` 64, Native 숫자(기본 44). 목록 행에는 Web `medium`/Native 기본, 프로필 머리에는 `xlarge`. Avatar 자체는 터치 대상이 아니므로 누르는 프로필은 감싸는 행·버튼이 최소 44를 확보한다 | `.hjm-avatar[data-size]`, `react-native/src/data-display.tsx` |
|
|
88
|
+
| 간격 | 자체 바깥 여백이 없다. `AvatarGroup`은 크기의 30%만큼 겹치고 각 아바타에 `bg` 색 2px 테두리를 둘러 경계를 만든다 | `.hjm-avatar-group` |
|
|
89
|
+
| 순서·정렬 | 행·카드·댓글 머리의 시작 쪽에 두고 이름·본문이 끝 쪽으로 이어진다. `AvatarGroup`의 넘친 수 표시는 맨 끝에 온다 | `.hjm-avatar-group__overflow` |
|
|
90
|
+
| 고정·스크롤 | 고정 영역이 없다 | — |
|
|
91
|
+
| 좁은 폭·큰 글자 | 아바타 크기는 그대로이고 옆 문구가 줄바꿈된다. 이니셜 글자는 Web `small`에서 label, `xlarge`에서 title 크기다 | `.hjm-avatar[data-size="small"]`·`[data-size="xlarge"]` |
|
|
92
|
+
|
|
93
|
+
## 꼭 지킬 것
|
|
94
|
+
|
|
95
|
+
- `name`은 빈 문자열이면 `TypeError`다. 접근성 이름은 Avatar가 소유하고, 대체 그림은 장식으로만 그린다(버튼 등 상호작용 금지).
|
|
96
|
+
- Blobatar `seed`에는 공개 제품 식별자를 쓴다. 이름·이메일·자격증명은 seed로 쓰지 않는다.
|
|
97
|
+
- 사진·표시 이름·남은 인원 문구는 제품 소유다. `overflow`는 i18n 키로 만든다. `overflow`는 보조기기에서 숨겨지므로 전체 인원은 `label` 문구에 넣는다.
|
|
98
|
+
- 배치는 `layoutStyle`로만 한다. Native `style`·`imageStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
|
|
99
|
+
|
|
100
|
+
## 플랫폼 차이
|
|
101
|
+
|
|
102
|
+
| 항목 | Web | Native |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| 사진 | `src`(문자열), `imageProps`로 `img` 속성·`onError` 전달 | `source`(`ImageSourcePropType`). `imageStyle`은 deprecated — layoutStyle 또는 `size` |
|
|
105
|
+
| 접근성 이름 | `alt`(기본 `name`), `alt=""`이면 숨김 | `accessibilityLabel` 필수, 또는 `decorative` |
|
|
106
|
+
| 대체 표시 | `fallback` 노드 또는 이니셜 | `initials`(최대 3자) 또는 이니셜 |
|
|
107
|
+
| 크기·모양 축 | 4단 이름 · `circle`/`rounded` | 숫자 · 원만 |
|
|
108
|
+
| 묶음 | `AvatarGroup` | 없음 |
|
|
109
|
+
|
|
110
|
+
## 함정
|
|
111
|
+
|
|
112
|
+
- `src`/`source`에 `undefined`를 직접 넘기면 `exactOptionalPropertyTypes`에서 타입 오류다. 사진이 없으면 prop을 빼거나 spread로 조건부로 넣는다.
|
|
113
|
+
- Web `AvatarGroup`은 이제 `style`을 버리지 않고 `layoutStyle`과 합친다. 겹침 변수(`--hjm-avatar-overlap`)는 마지막에 덮이므로 `style`로 겹침을 바꿀 수 없다.
|
|
114
|
+
- 이니셜은 두 플랫폼 모두 `resolveAvatarInitials`(`@hjmds/design-contracts/avatar-fallback`)로 첫 단어와 마지막 단어의 첫 글자(code point)다. 1.12.1까지 Web은 앞 두 단어를 써서 "Kim Min Jun"이 Web "KM", Native "KJ"였다(미게시 변경). 1.12.1에서 일치가 필요하면 Native `initials`·Web `fallback`으로 같은 값을 준다.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.12.1
|
|
7
|
+
- 검토일: 2026-10-06
|
|
8
|
+
- 근거: recipe `badgeRecipe`(`src/component-recipes.ts`)
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/배지`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
항목의 상태나 분류를 짧은 글자 하나로 붙일 때 쓴다. "진행 중", "완료", "시즌 3"처럼 누를 수 없는 표시다.
|
|
14
|
+
|
|
15
|
+
## 쓰지 않을 때
|
|
16
|
+
|
|
17
|
+
| 상황 | 대신 쓸 것 |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| 읽지 않은 수·알림 점 | [CounterBadge](counter-badge.md) |
|
|
20
|
+
| 사용자가 붙인 키워드·카테고리 | [Tag](tag.md) |
|
|
21
|
+
| 누르거나 고르는 필터 | [Chip](chip.md) |
|
|
22
|
+
| 문장으로 설명해야 하는 상태 | [Notice](notice.md) |
|
|
23
|
+
|
|
24
|
+
## 공개 이름과 import
|
|
25
|
+
|
|
26
|
+
| 이름 | 역할 | Web | Native |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| `Badge` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
29
|
+
|
|
30
|
+
## 최소 사용 예
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// Web
|
|
34
|
+
import { Badge } from "@hjmds/react/display";
|
|
35
|
+
|
|
36
|
+
<Badge tone="success" size="small">{t("order.status.done")}</Badge>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// Native
|
|
41
|
+
import { Badge } from "@hjmds/react-native/data-display";
|
|
42
|
+
|
|
43
|
+
<Badge tone="success" size="small" label={t("order.status.done")} />
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 축과 기본값
|
|
47
|
+
|
|
48
|
+
| prop | 값 | 기본값 | 설명 |
|
|
49
|
+
| --- | --- | --- | --- |
|
|
50
|
+
| `tone` | `neutral` · `strong` · `brand` · `info` · `success` · `warning` · `attention` · `danger` | `neutral` | `brand`는 판이 중립색이고 글자만 브랜드색이다. 브랜드 틴트는 "선택됨"을 뜻하므로 정적 표시에 쓰지 않는다. `strong`은 `neutral`보다 한 단계 강한 잉크 판이며 승패처럼 한눈에 갈려야 하는 사실에 쓴다 |
|
|
51
|
+
| `variant` | `filled` · `outline` | `filled` | — |
|
|
52
|
+
| `size` | `small` · `medium` | `medium` | — |
|
|
53
|
+
| `leading` | `ReactNode` | — | 라벨 앞 아이콘 자리이며 보조기기에서 숨겨진다 |
|
|
54
|
+
| Web `children` · Native `label` | `ReactNode` · `string \| number`(필수) | — | 표시 라벨 |
|
|
55
|
+
| Native `accessibilityLabel` | `string` | `String(label)` | 숫자 라벨에 단위를 붙여 읽힐 때 쓴다 |
|
|
56
|
+
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`·`labelStyle`은 deprecated — layoutStyle 또는 tone/토큰 |
|
|
57
|
+
|
|
58
|
+
콜백이 없다. 누를 수 없는 표시다.
|
|
59
|
+
|
|
60
|
+
## 배치
|
|
61
|
+
|
|
62
|
+
| 항목 | 값 | 근거 |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| 크기 | 내용 폭만 차지한다. 최소 높이 `medium` 24 · `small` 20, 모서리 `radius.full`. 누를 수 없는 표시라 44 터치 영역이 필요 없다(누르게 하려면 [Chip](chip.md)) | `badgeRecipe.sizes`, `.hjm-badge` |
|
|
65
|
+
| 간격 | 좌우 여백 `medium` `spacing.xs` 8 · `small` `spacing.xxs` 4, 아이콘↔라벨 `spacing.xxs` 4. 여러 개를 나란히 둘 때는 `spacing.xxs` 4~`spacing.xs` 8 | `badgeRecipe.sizes` |
|
|
66
|
+
| 순서·정렬 | 제목·행 이름 옆(끝 쪽) 또는 카드 머리 위에 인라인으로 붙인다. 한 행에 2~3개를 넘기지 않는다 | `.hjm-badge`(`inline-flex`) |
|
|
67
|
+
| 고정·스크롤 | 고정 영역이 없다 | — |
|
|
68
|
+
| 좁은 폭·큰 글자 | 라벨이 줄바꿈된다(`overflow-wrap: anywhere`). 말줄임하지 않는다 | `.hjm-badge` |
|
|
69
|
+
|
|
70
|
+
## 꼭 지킬 것
|
|
71
|
+
|
|
72
|
+
- 라벨은 i18n 키로 넣고 짧게 둔다. 상태 뜻을 색에만 싣지 않는다(글자가 뜻을 말해야 한다).
|
|
73
|
+
- 색은 `tone`으로만 고른다. 브랜드 색을 `style`·`className`·`labelStyle`로 덮지 않는다.
|
|
74
|
+
- 배치는 Web·Native 모두 `layoutStyle`로만 한다. Native `style`·`labelStyle`은 deprecated(개발 모드 1회 경고, 다음 major 제거)다.
|
|
75
|
+
|
|
76
|
+
## 플랫폼 차이
|
|
77
|
+
|
|
78
|
+
| 항목 | Web | Native |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| 라벨 | `children` | `label`(문자열·숫자, 필수) |
|
|
81
|
+
| 접근성 이름 | 라벨 텍스트 | `accessibilityLabel`(기본 `String(label)`) |
|
|
82
|
+
| 배치 | `layoutStyle` | `layoutStyle`(`style`은 deprecated) |
|
|
83
|
+
| 라벨 스타일 훅 | 없음 | `labelStyle` deprecated — tone/토큰 |
|
|
84
|
+
| 기본 폭 | inline | `alignSelf: "flex-start"` |
|