@hjmds/design-contracts 1.13.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agreement.d.ts +7 -1
- package/dist/agreement.d.ts.map +1 -1
- package/dist/agreement.js +14 -3
- package/dist/agreement.js.map +1 -1
- package/dist/behaviors.d.ts +1 -1
- package/dist/catalog.d.ts +5 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-recipes.d.ts +14 -0
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +16 -1
- package/dist/component-recipes.js.map +1 -1
- package/dist/content-transition.d.ts +17 -0
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +21 -0
- package/dist/content-transition.js.map +1 -1
- package/dist/date-entry.d.ts +71 -0
- package/dist/date-entry.d.ts.map +1 -0
- package/dist/date-entry.js +78 -0
- package/dist/date-entry.js.map +1 -0
- package/dist/document-resource.d.ts +104 -0
- package/dist/document-resource.d.ts.map +1 -0
- package/dist/document-resource.js +77 -0
- package/dist/document-resource.js.map +1 -0
- package/dist/effect-surface.d.ts +6 -1
- package/dist/effect-surface.d.ts.map +1 -1
- package/dist/effect-surface.js +5 -2
- package/dist/effect-surface.js.map +1 -1
- package/dist/field-group.d.ts +47 -0
- package/dist/field-group.d.ts.map +1 -0
- package/dist/field-group.js +92 -0
- package/dist/field-group.js.map +1 -0
- package/dist/image.d.ts +17 -0
- package/dist/image.d.ts.map +1 -1
- package/dist/image.js +19 -0
- package/dist/image.js.map +1 -1
- package/dist/internal/effect-noise.d.ts +2 -0
- package/dist/internal/effect-noise.d.ts.map +1 -0
- package/dist/internal/effect-noise.js +4 -0
- package/dist/internal/effect-noise.js.map +1 -0
- package/dist/progressive-blur.d.ts +32 -0
- package/dist/progressive-blur.d.ts.map +1 -0
- package/dist/progressive-blur.js +28 -0
- package/dist/progressive-blur.js.map +1 -0
- package/dist/reference-controls.d.ts +32 -0
- package/dist/reference-controls.d.ts.map +1 -0
- package/dist/reference-controls.js +28 -0
- package/dist/reference-controls.js.map +1 -0
- package/dist/scroll-progress.d.ts +7 -1
- package/dist/scroll-progress.d.ts.map +1 -1
- package/dist/scroll-progress.js +21 -2
- package/dist/scroll-progress.js.map +1 -1
- package/dist/text-annotation.d.ts +43 -0
- package/dist/text-annotation.d.ts.map +1 -0
- package/dist/text-annotation.js +137 -0
- package/dist/text-annotation.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/agreement.md +14 -0
- package/docs/dialog.md +16 -1
- package/docs/effect-surface.md +19 -3
- 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/image.md +17 -0
- package/docs/optional-adapters.md +25 -0
- package/docs/popover.md +15 -0
- package/docs/rating.md +6 -1
- package/docs/reference-controls.md +40 -0
- package/docs/screen-patterns.md +2 -0
- package/docs/sheet.md +9 -0
- package/docs/task-list.md +15 -1
- package/docs/text-annotation.md +85 -0
- package/docs/toggle-group.md +6 -0
- package/docs/usage/README.md +18 -1
- package/docs/usage/components/agreement.md +13 -3
- package/docs/usage/components/alert-dialog.md +7 -1
- package/docs/usage/components/avatar.md +33 -1
- package/docs/usage/components/card.md +7 -1
- package/docs/usage/components/carousel.md +5 -1
- package/docs/usage/components/chat-message.md +11 -1
- package/docs/usage/components/chip.md +10 -2
- package/docs/usage/components/content-transition.md +28 -4
- package/docs/usage/components/dialog.md +18 -1
- package/docs/usage/components/effect-surface.md +4 -2
- package/docs/usage/components/empty-state.md +10 -2
- package/docs/usage/components/field.md +23 -0
- package/docs/usage/components/form.md +6 -1
- package/docs/usage/components/image-comparison.md +82 -0
- package/docs/usage/components/image.md +81 -2
- package/docs/usage/components/keyboard-avoiding.md +6 -1
- package/docs/usage/components/link.md +3 -1
- package/docs/usage/components/list-row.md +3 -1
- package/docs/usage/components/list.md +6 -1
- package/docs/usage/components/message-composer.md +5 -0
- package/docs/usage/components/popover.md +7 -1
- package/docs/usage/components/progress.md +28 -0
- package/docs/usage/components/progressive-blur.md +113 -0
- package/docs/usage/components/rating.md +74 -0
- package/docs/usage/components/search-field.md +10 -0
- package/docs/usage/components/search-screen.md +16 -5
- package/docs/usage/components/segmented-control.md +29 -4
- package/docs/usage/components/sheet.md +7 -1
- package/docs/usage/components/statistic.md +11 -0
- package/docs/usage/components/tags-input.md +5 -0
- package/docs/usage/components/toast.md +5 -0
- package/docs/usage/components/upload-item.md +3 -1
- package/docs/usage/compositions/action-feedback.md +77 -0
- package/docs/usage/compositions/adaptive-content.md +81 -0
- package/docs/usage/compositions/context-toolbar.md +85 -0
- package/docs/usage/compositions/date-entry.md +108 -0
- package/docs/usage/compositions/document-resource.md +124 -0
- package/docs/usage/compositions/field-group.md +104 -0
- package/docs/usage/compositions/illustrated-outcome.md +91 -0
- package/docs/usage/compositions/live-list.md +104 -0
- package/docs/usage/compositions/optional-adapters.md +1 -1
- package/docs/usage/compositions/origin-dialog.md +108 -0
- package/docs/usage/compositions/selection-motion.md +73 -0
- package/docs/usage/compositions/texture-comparison.md +86 -0
- package/docs/usage/compositions/upload-recovery.md +80 -0
- package/docs/usage/compositions/video-dialog.md +100 -0
- package/docs/usage/screens/common-search.md +7 -2
- package/docs/usage/screens/flow-onboarding.md +5 -3
- package/docs/usage/screens/product-bento.md +117 -0
- package/package.json +37 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# 영상 미리보기
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: `showcase/web/src/patterns/video-dialog-preview.tsx`, `showcase/native/src/video-dialog-preview.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/정보 표시/영상 미리보기`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
현재 입력을 유지하면서 짧은 영상 설명을 확인할 때 쓴다. HJM Dialog가 모달·닫기·초점 복귀를
|
|
16
|
+
소유하고 제품이 플레이어·자막·미디어 권한·재시도를 공급한다. 전체 화면 감상과 재생 목록은
|
|
17
|
+
제품의 전용 미디어 화면을 사용한다. 기존 Dialog.children으로 충분하므로 새 wrapper를 만들지 않는다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Dialog | 제목·닫기·모달 접근성·초점 복귀 | [대화상자](../components/dialog.md) |
|
|
24
|
+
| Button | 미리보기·재생·재시도 | [버튼](../components/button.md) |
|
|
25
|
+
| TextField | 바깥 초안 보존 | [텍스트 필드](../components/field.md) |
|
|
26
|
+
| 제품 영상 호스트 | 디코딩·재생·미디어 해제 | Web video / Native Expo Video 예시 |
|
|
27
|
+
|
|
28
|
+
## 배치
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
바깥: [영상 메모] [영상 미리보기]
|
|
32
|
+
Dialog: [제목 닫기]
|
|
33
|
+
[설명]
|
|
34
|
+
[16:9 플레이어 + 미디어 컨트롤]
|
|
35
|
+
[동등한 내용의 텍스트 설명]
|
|
36
|
+
[재생/일시 정지 또는 다시 시도]
|
|
37
|
+
[실제 재생 상태]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| 바깥 틀 | Stack | 모달 아래의 원래 화면 | gap lg=20px |
|
|
43
|
+
| 모달 | Dialog | 공통 overlay 위치 | size large, renderer recipe를 그대로 사용 |
|
|
44
|
+
| 영상 | 제품 Player | 제목·설명 아래 본문 | 폭 100%, aspectRatio 16/9 |
|
|
45
|
+
| 설명·복구 | Text / Button | 플레이어 아래 | Stack gap md=16px, 긴 문구는 줄바꿈 |
|
|
46
|
+
|
|
47
|
+
## 흐름과 상태
|
|
48
|
+
|
|
49
|
+
1. 제품이 open과 입력 초안을 소유한다. 닫기 후 초안을 지우지 않는다.
|
|
50
|
+
2. open일 때만 플레이어를 mount한다. 자동 재생하지 않고 사용자 행동으로 시작한다.
|
|
51
|
+
3. 브라우저/Native 미디어 이벤트에서 재생·정지·실패를 읽는다. 요청 성공으로 재생을 추정하지 않는다.
|
|
52
|
+
4. 실패 시 설명과 재시도를 유지한다. 재시도는 정상 source로 호스트를 새로 만들며 Web은 재생 버튼으로 초점을 옮긴다.
|
|
53
|
+
5. open=false 즉시 호스트를 unmount한다. 모달 퇴장 애니메이션이 끝날 때까지 기다리지 않는다.
|
|
54
|
+
6. Web은 pause 후 src를 제거하고 load하여 자원을 해제한다. Native useVideoPlayer는 unmount 시 player를 release한다.
|
|
55
|
+
7. 앱/문서가 비활성화되면 일시 정지한다. 복귀 시 자동 재생하지 않는다.
|
|
56
|
+
|
|
57
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 기본 | 열기 전 플레이어 없음, 메모 유지 | 열기 버튼 |
|
|
60
|
+
| 진행 중 | 실제 이벤트에 따른 일시 정지와 상태 | 재생 상태 알림 |
|
|
61
|
+
| 실패 | 설명·다시 시도, 원래 메모 유지 | 오류 알림, Web 재시도 후 재생 버튼 |
|
|
62
|
+
| 호스트 없음 | Native 지원 모듈 누락 안내, 닫기 가능 | 제목과 안내 |
|
|
63
|
+
| 닫힘 | 영상 호스트 제거 | 열기 버튼으로 초점 복귀 |
|
|
64
|
+
|
|
65
|
+
## 코드 골격
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Web
|
|
69
|
+
import { Dialog } from "@hjmds/react/overlays";
|
|
70
|
+
<Dialog open={open} onOpenChange={setOpen} title={title} description={description}
|
|
71
|
+
closeLabel={closeLabel} size="large" returnFocusRef={triggerRef}>
|
|
72
|
+
{open ? <ProductPlayer source={source} /> : null}
|
|
73
|
+
</Dialog>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// Native
|
|
78
|
+
import { Dialog } from "@hjmds/react-native/overlays";
|
|
79
|
+
<Dialog open={open} onOpenChange={setOpen} title={title} closeLabel={closeLabel}
|
|
80
|
+
size="large" returnFocusRef={triggerRef}>
|
|
81
|
+
{open ? <ProductPlayer source={source} /> : null}
|
|
82
|
+
</Dialog>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
ProductPlayer는 제품 소유 코드의 자리이며 HJM export가 아니다. Showcase를 앱에서 import하지 않는다.
|
|
86
|
+
제품은 미디어 자산·번역 문구·자막/대본·재생 권한을 공급한다. 무음 테스트 패턴 설명은 실제 영상 자막 검증을 대신하지 않는다.
|
|
87
|
+
|
|
88
|
+
## 플랫폼 차이
|
|
89
|
+
|
|
90
|
+
Web은 HTML video controls와 playsInline을 사용한다. Native 예제는 Expo SDK 57의 expo-video
|
|
91
|
+
호스트를 선택하며 설치된 앱에 모듈이 없으면 명시적으로 안내한다. Native fullscreen/PiP는 이 짧은
|
|
92
|
+
모달 미리보기에서 사용하지 않는다. 제품이 다른 플레이어를 사용해도 Dialog 계약을 유지하며
|
|
93
|
+
닫기·백그라운드·실패·초점 복귀를 실제 호스트에서 검증한다.
|
|
94
|
+
|
|
95
|
+
| 항목 | Web | Native |
|
|
96
|
+
| --- | --- | --- |
|
|
97
|
+
| 호스트 | HTML video | 제품 제공 player, Showcase는 Expo Video |
|
|
98
|
+
| 정리 | pause·src 제거·load | hook 소유 player 해제 |
|
|
99
|
+
| 지원 누락 | 미디어 오류와 재시도 | 모듈 누락 안내 |
|
|
100
|
+
| 설명 | 화면의 동등한 텍스트 | 화면의 동등한 텍스트 |
|
|
@@ -68,7 +68,7 @@ Native · Web < 960: 아래 시트(size large) Web ≥ 960(expanded): 옆
|
|
|
68
68
|
|
|
69
69
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
70
70
|
| --- | --- | --- | --- |
|
|
71
|
-
| 바깥 틀 | SearchScreen(ScreenLayout) | route 본문, host가 남은 높이·safe area·키보드 처리 | 폭 최대 `layout.readingMaxWidth` 720, 좌우 `spacing.md` 16. Native 본문 `scrollProps={{ keyboardDismissMode: "on-drag" }}` |
|
|
71
|
+
| 바깥 틀 | SearchScreen(ScreenLayout) | route 본문, host가 남은 높이·safe area·키보드 처리 | 폭 최대 `layout.readingMaxWidth` 720, 좌우 `spacing.md` 16. host가 여백을 주면 `contentInset="none"` + 그 여백 이름 `hostGutter`(Container `gutter`, Sheet 안은 `regular`). Native 본문 `scrollProps={{ keyboardDismissMode: "on-drag" }}` |
|
|
72
72
|
| 머리 | `title` + `leading`(IconButton 뒤로) | 맨 위 | `description`은 비운다(고정 영역 한 줄 절약) |
|
|
73
73
|
| 검색 입력 | 기본 SearchField(`queryLabel`·`queryLabelVisibility="hidden"`·`queryClearLabel`·`onSubmit`·`searching`) | 머리 아래, 고정 | `medium` 44 |
|
|
74
74
|
| 필터 칩 줄 | HJM `filterSheet.trigger` Chip + 제품 `filters`(Stack inline gap `xs` > Chip `small`) | 검색 입력 아래, 고정, 결과 단계에만 | 입력과 `spacing.sm` 12, 칩 36(+Native hitSlop 4), 칩 사이 `spacing.xs` 8. `filtersOverflow="scroll"` |
|
|
@@ -247,13 +247,16 @@ import { SearchScreen } from "@hjmds/react-native/screen-flows";
|
|
|
247
247
|
| 다크 | semantic token만 쓰므로 따로 처리하지 않는다. 일치 강조는 색이 아니라 굵기라 다크·색각 차이에서도 남는다. 예제 색을 제품 기본값으로 복사하지 않는다 |
|
|
248
248
|
| 좁은 폭 | 320에서 칩 줄은 화면 끝까지 스크롤, 적용 조건 칩은 줄바꿈, 시트는 아래 전폭 |
|
|
249
249
|
| 넓은 폭(Web ≥ 960) | 한 열(최대 720) 유지, 필터 시트는 `placement="end"` 옆 시트로 결과를 가리지 않는다. 상시 왼쪽 필터 패널은 ScreenLayout 폭 계약 밖이라 두지 않는다 |
|
|
250
|
-
| 키보드 | Native 본문 ScrollView가 `keyboardShouldPersistTaps="handled"`라 키보드가 떠 있어도 제안 행이 한 번에 눌린다. 검색
|
|
250
|
+
| 키보드 | Native 본문 ScrollView가 `keyboardShouldPersistTaps="handled"`라 키보드가 떠 있어도 제안 행이 한 번에 눌린다. 검색 키뿐 아니라 제안·최근·추천을 골라 확정해도 키보드가 닫힌다(Native `Keyboard.dismiss()`, Web은 결과 영역으로 포커스, 미게시(1.13.1 이후)) |
|
|
251
|
+
| 큰 글자 · 칩 줄 안 pills | 칩 줄에 SegmentedControl `presentation="pills"`를 넣어도 세로로 쌓이지 않고 한 줄 가로 스크롤로 남는다(미게시(1.13.1 이후). 1.13.0 이하는 약 440pt 기둥이 됐다). 칩 높이는 라벨에 맞춰 늘어난다(Native Chip `minHeight`) |
|
|
251
252
|
|
|
252
253
|
## 플랫폼 차이
|
|
253
254
|
|
|
254
255
|
| 항목 | Web | Native |
|
|
255
256
|
| --- | --- | --- |
|
|
256
257
|
| 확정 신호 | Enter(IME 조합 중 Enter 무시) | 키보드 검색 키 |
|
|
258
|
+
| 고른 검색어로 확정한 뒤 | 결과 영역(`.hjm-search-screen__results`)으로 포커스. Enter 확정은 입력에 남는다 | 키보드를 닫고 포커스는 옮기지 않는다 |
|
|
259
|
+
| `contentInset="none"` host 안 칩 줄 | `hostGutter`만큼 host 가장자리까지 | 같음 |
|
|
257
260
|
| 일치 강조 | `suggestions.items[].match` 범위를 굵게. 결과 행은 제품이 ListRow `title`에 `Text emphasis="strong"` | ListRow `title`이 문자열이라 강조하지 않는다(`match`는 무시) |
|
|
258
261
|
| 개수 알림(SearchScreen) | 숨긴 `role="status"` 영역 하나 | `AccessibilityInfo.announceForAccessibilityWithOptions(…, { queue: true })` |
|
|
259
262
|
| 조건·최근 검색 삭제 뒤 포커스 | 다음 칩·× → 없으면 `필터` 칩·검색 입력 | 이동하지 않는다(스크린 리더 커서 유지) |
|
|
@@ -272,3 +275,5 @@ import { SearchScreen } from "@hjmds/react-native/screen-flows";
|
|
|
272
275
|
- `committedQuery`를 주고 `children`에 제안·로딩·0건을 다시 그리면 SearchScreen이 그린 것과 겹친다. 두 단계 검색에서 `children`은 결과 목록만 담는다.
|
|
273
276
|
- 최근 검색 저장소를 `onSearch`에 연결하지 않는다. SearchScreen은 확정만 `onSubmit`으로 보내므로 거기서만 저장한다.
|
|
274
277
|
- Storybook은 실제 검색 서버·라우터 연동 증거가 아니다. Native 화면은 2026-10-06 개편에서 시뮬레이터로 확인하지 않았다(구조는 렌더 테스트로만 확인).
|
|
278
|
+
- 시트 안 검색(채팅 도구 고르기 등)은 Sheet `size`를 `medium`·`large`로 주고 `scrollable` 없이 SearchScreen을 넣는다. SearchScreen은 `contentInset="none"`·`hostGutter="regular"`.
|
|
279
|
+
1.13.0 이하 Native는 이 조합에서 화면이 0pt가 되고 칩 줄이 시트 여백에서 잘렸다(2026-10-06 utilverse, [Sheet 함정](../components/sheet.md#함정)).
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## 목적
|
|
12
12
|
|
|
13
13
|
첫 실행 사용자를 몇 단계(소개 → 관심 주제 → 시작)로 안내하고 마지막 단계에서 완료를 저장하는 화면을 OnboardingScreen 하나로 구성한다.
|
|
14
|
-
단계 제목·설명은 화면
|
|
14
|
+
단계 제목·설명은 화면 맨 위, 진행 문구는 그 아래에 둔다. Web은 머리를 고정하고 Native는 제목·설명·진행을 본문과 함께 스크롤한다. 이동 버튼은 footer에 고정한다. 권한 요청·로그인·가입은
|
|
15
15
|
이 화면에 없다([권한 안내](flow-permission.md), [로그인](common-login.md)).
|
|
16
16
|
스토리는 `기본`(1단계부터 직접 넘기기), `관심 주제 고르기`(2단계를 바로 연 상태), `실패와 복구`(다음 완료 저장 실패 → 다시 시작하기)다.
|
|
17
17
|
2026-10-06 배포 직접 조립 온보딩(Steps·ContentTransition·여러 개 고르는 주제 버튼)을 이 항목으로 합쳤고, 그 고유 상태인
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
45
45
|
| --- | --- | --- | --- |
|
|
46
46
|
| 바깥 틀 | OnboardingScreen(내부 ScreenLayout `scroll="screen"`) | route 본문. host가 남은 높이·safe area·키보드를 준다 | 폭 최대 720, 바깥 padding `spacing.md` 16([ScreenLayout 배치](../components/screen-layout.md#배치)). 화면 props는 `layoutStyle`만 받는다 |
|
|
47
|
-
| 머리 | `steps[index].title`·`description` + `skip`(ghost Button) | 맨 위,
|
|
48
|
-
| 진행 문구 | `progressLabel(current, total)` → Text `variant="caption" tone="muted"` | 머리
|
|
47
|
+
| 머리 | `steps[index].title`·`description` + `skip`(ghost Button) | 맨 위, Web 고정·Native 본문 스크롤 | 제목 열 최소 120 × 글자 배율, 모자라면 건너뛰기가 다음 줄로 내려간다 |
|
|
48
|
+
| 진행 문구 | `progressLabel(current, total)` → Text `variant="caption" tone="muted"` | 머리 아래, Web notice 고정·Native 본문 스크롤 | 좌우 16 |
|
|
49
49
|
| 단계 본문 | `steps[index].content` | 진행 문구 아래, 본문 스크롤 | 본문 안 간격은 제품 소유. 예제는 Stack `gap="lg"` 20 |
|
|
50
50
|
| 관심 주제 | Stack `axis="inline" wrap gap="xs"`(Web `role="group"` + 이름) > [Chip](../components/chip.md) `selectionMode="multiple"` | 2단계 content 안 | 칩 높이 `small` 36(Native hitSlop으로 터치 44), 사이 `spacing.xs` 8 |
|
|
51
51
|
| 저장 실패 | [Notice](../components/notice.md) `tone="danger"` + `action` | 마지막 단계 content 맨 아래 | 본문 Stack 간격을 따른다 |
|
|
@@ -191,3 +191,5 @@ const topicPicker = <Stack axis="inline" wrap gap="xs">
|
|
|
191
191
|
- 완료 `pending` 동안에도 이전 버튼은 막히지 않는다. 저장 중 단계 이동이 문제가 되면 `onIndexChange`에서 무시한다.
|
|
192
192
|
- 현재 스토리의 진행 문구는 `` `${index} / ${total}` `` 고정 문자열이다. 제품은 보간 키 하나(`onboarding.progress`)를 쓴다.
|
|
193
193
|
- Storybook은 실제 서버·라우터 연동 증거가 아니다. 기본·어두운 테마·큰 글자와 실패와 복구를 각각 확인한다.
|
|
194
|
+
|
|
195
|
+
2026-10-07 iOS 26.5 / 200% 글자에서 키보드가 열린 시작 안내의 고정 머리가 본문 높이를 모두 소비했다. Native는 기존 ScreenLayout의 본문 스크롤 안으로 단계 안내를 옮겨 입력에 도달하게 하고 footer의 완료·이전 버튼은 유지한다. [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 기능 카드와 주 행동
|
|
2
|
+
|
|
3
|
+
- 단계: 화면
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: `reference-adoption-previews.tsx`, `reference-adoption.css`
|
|
9
|
+
- 스토리북: `배포/화면/소개/기능 카드와 주 행동`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 목적
|
|
14
|
+
|
|
15
|
+
기능을 실제 미리보기로 보여 주고 첫 행동으로 이어지는 소개 화면이다. 기존 Surface/Stack/Button/ImageComparison을 조합한다.
|
|
16
|
+
|
|
17
|
+
## 영역 구조
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
[브랜드]
|
|
21
|
+
[제목 · 가치 설명]
|
|
22
|
+
[비교 미리보기] [기능 설명 두 장] ← 넓은 Web 2:1
|
|
23
|
+
[첫 입력(시작한 뒤)]
|
|
24
|
+
[사용 방법(선택)]
|
|
25
|
+
[보조 행동] [주 행동]
|
|
26
|
+
[항상 보이는 체험·저장 범위]
|
|
27
|
+
[처리 결과 · 저장된 제목]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
좁은 Web과 Native는 비교→설명 두 장→첫 행동 순서로 한 열에 놓는다.
|
|
31
|
+
|
|
32
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| 바깥 틀 | Web Stack / Native KeyboardAvoiding → ScreenLayout → Stack | Web 문서 스크롤 / Native 고정 제목 아래 본문 스크롤 | 본문 lg=20px, Native 제목 간격 md=16px, contentInset none |
|
|
35
|
+
| 기능 | Surface/Stack | 설명 다음 | padding lg=20px, 카드 gap md=16px |
|
|
36
|
+
| 제목/본문 | Text/Stack | 각 카드 안 | sm=12px |
|
|
37
|
+
| 첫 행동 | BottomCTA | 기능·입력 뒤 | description → 보조/주 행동, flow 배치 |
|
|
38
|
+
| 조건 | BottomInfo | 주 행동 바로 아래 | 항상 유지, 예제의 로컬 저장 범위 설명 |
|
|
39
|
+
|
|
40
|
+
## 버튼과 행동 위치
|
|
41
|
+
|
|
42
|
+
| 행동 | 컴포넌트·tone | 위치 | 개수·순서 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| 주 행동 | BottomCTA primaryAction | 기능·입력 아래 | 시작 → 미리보기에 저장, 1개 |
|
|
45
|
+
| 보조 행동 | BottomCTA secondaryAction | 같은 행동 영역 | 사용 방법 보기/접기 → 소개로 돌아가기 |
|
|
46
|
+
| 비교 보조 | Button·secondary | 이미지 슬라이더 아래 | 전→후 전체 보기 2개, wrap |
|
|
47
|
+
| 파괴 행동 | 없음 | 없음 | 0개 |
|
|
48
|
+
|
|
49
|
+
## 상태
|
|
50
|
+
|
|
51
|
+
| 상태 | 화면 모습 | 행동 |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| 기본 | 설명과 비교·주/보조 행동·조건 안내 | 첫 입력 또는 사용 방법으로 이동 |
|
|
54
|
+
| 로딩 | 실제 이미지 로드 상태 | 주 행동은 제품 가용성에 맞춤 |
|
|
55
|
+
| 빈 | 제품 자료가 없으면 비교 영역 생략 | 문구와 첫 행동 유지 |
|
|
56
|
+
| 오류 | 이미지 실패 대체 또는 저장 실패 문구·제목 유지 | 현재 제목으로 다시 저장 |
|
|
57
|
+
| 진행 중 | 주 행동 spinner, 보조 행동 비활성 | 중복 실행 방지, 입력 내용은 제품 상태에 유지 |
|
|
58
|
+
| 완료 | 확정한 제목과 성공 문구 | 다음 입력 가능 |
|
|
59
|
+
|
|
60
|
+
## 사용하는 지침
|
|
61
|
+
|
|
62
|
+
| 지침 | 쓰는 곳 |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| [ImageComparison](../components/image-comparison.md) | 기능 미리보기 |
|
|
65
|
+
| [Stack](../components/stack.md) | 읽기 순서와 간격 |
|
|
66
|
+
| [BottomCTA](../components/bottom-cta.md) · [BottomInfo](../components/bottom-info.md) | 주·보조 행동과 항상 보이는 조건 |
|
|
67
|
+
|
|
68
|
+
## 코드 골격
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Web
|
|
72
|
+
<Stack gap="lg">
|
|
73
|
+
<Text variant="heading">{title}</Text>
|
|
74
|
+
<Surface padding="lg"><ImageComparison {...comparison} /></Surface>
|
|
75
|
+
<BottomCTA primaryAction={{ label: startLabel, onClick: start }} secondaryAction={{ label: helpLabel, onClick: showHelp }} />
|
|
76
|
+
<BottomInfo items={conditions} />
|
|
77
|
+
</Stack>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Native: screen-owned scrolling keeps the CTA reachable below long previews.
|
|
82
|
+
<KeyboardAvoiding style={{ flex: 1 }}>
|
|
83
|
+
<ScreenLayout title={title} contentInset="none">
|
|
84
|
+
<Stack gap="lg">
|
|
85
|
+
<Surface padding="lg"><ImageComparison {...comparison} decrementLabel={decrementLabel} incrementLabel={incrementLabel} /></Surface>
|
|
86
|
+
<BottomCTA primaryAction={{ label: startLabel, onPress: start }} secondaryAction={{ label: helpLabel, onPress: showHelp }} />
|
|
87
|
+
<BottomInfo items={conditions} />
|
|
88
|
+
</Stack>
|
|
89
|
+
</ScreenLayout>
|
|
90
|
+
</KeyboardAvoiding>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Native는 같은 public component entry를 `@hjmds/react-native`로 바꾸고 Button에 `onPress`를 쓴다.
|
|
94
|
+
Native ImageComparison에는 증감 행동 label을 추가한다. 제품의 계정/저장 안내와 데이터는 제품이 공급한다.
|
|
95
|
+
|
|
96
|
+
## 큰 글자·다크·좁은 폭
|
|
97
|
+
|
|
98
|
+
| 조건 | 바뀌는 것 |
|
|
99
|
+
| --- | --- |
|
|
100
|
+
| 큰 글자 | 제목·본문·버튼이 자연스럽게 줄바꿈 |
|
|
101
|
+
| 다크 | provider semantic palette 사용 |
|
|
102
|
+
| 좁은 폭 | 예제 Web 48rem 이하에서 1열. Native는 항상 단일 순서 |
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
### CTA 레퍼런스 흡수와 저장 소유권
|
|
106
|
+
|
|
107
|
+
2026-10-07 CTA Gallery의 Webflow 데스크톱·모바일 캡처에서 주/보조 행동의 위계를,
|
|
108
|
+
Ente 캡처에서 플랫폼 조건에 맞춘 행동 묶음을 확인했다. HJM에는 기존 BottomCTA/BottomInfo로
|
|
109
|
+
역할을 분리하며 외부 브랜드 색·스토어 배지·가격을 복제하지 않는다. 별도 CTA 엔진은 추가하지 않는다.
|
|
110
|
+
|
|
111
|
+
제목은 화면의 controlled state에 둔다. 소개로 돌아가 입력을 숨겨도 제목은 남고 다시 시작하면
|
|
112
|
+
복구된다. 실제 서버가 없는 데모임을 상시 표시한다. 기존 useDemoAction/ActionSession으로
|
|
113
|
+
중복 요청·pending·실패 후 재시도를 검증하며 350ms 지연은 데모 전용이다. 실패 시 현재 제목은
|
|
114
|
+
유지하고 다시 저장은 현재 입력 snapshot을 제출한다. 저장된 제목은 마지막으로 확정한 값이다.
|
|
115
|
+
|
|
116
|
+
Web 큰 글자에서는 BottomCTA 계약대로 주 행동이 위, 보조 행동이 아래로 쌓인다. 모바일 Native도
|
|
117
|
+
같은 계약을 따르며 화면 host가 스크롤·키보드·safe area를 제공한다. Native 예제에는 기존 ScreenLayout과 KeyboardAvoiding을 연결한다. 스크롤 없는 Canvas에서 CTA가 화면 밖으로 잘렸던 2026-10-07 실제 확인에 따른 수정이다. 제품은 host safe area/keyboard offset을 실제 내비게이션에 맞춘다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hjmds/design-contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -589,6 +589,12 @@
|
|
|
589
589
|
"import": "./dist/content-transition.js",
|
|
590
590
|
"default": "./dist/content-transition.js"
|
|
591
591
|
},
|
|
592
|
+
"./reference-controls": {
|
|
593
|
+
"types": "./dist/reference-controls.d.ts",
|
|
594
|
+
"react-native": "./dist/reference-controls.js",
|
|
595
|
+
"import": "./dist/reference-controls.js",
|
|
596
|
+
"default": "./dist/reference-controls.js"
|
|
597
|
+
},
|
|
592
598
|
"./duration-field": {
|
|
593
599
|
"types": "./dist/duration-field.d.ts",
|
|
594
600
|
"react-native": "./dist/duration-field.js",
|
|
@@ -666,6 +672,36 @@
|
|
|
666
672
|
"react-native": "./dist/screen-patterns.js",
|
|
667
673
|
"import": "./dist/screen-patterns.js",
|
|
668
674
|
"default": "./dist/screen-patterns.js"
|
|
675
|
+
},
|
|
676
|
+
"./progressive-blur": {
|
|
677
|
+
"types": "./dist/progressive-blur.d.ts",
|
|
678
|
+
"react-native": "./dist/progressive-blur.js",
|
|
679
|
+
"import": "./dist/progressive-blur.js",
|
|
680
|
+
"default": "./dist/progressive-blur.js"
|
|
681
|
+
},
|
|
682
|
+
"./text-annotation": {
|
|
683
|
+
"types": "./dist/text-annotation.d.ts",
|
|
684
|
+
"react-native": "./dist/text-annotation.js",
|
|
685
|
+
"import": "./dist/text-annotation.js",
|
|
686
|
+
"default": "./dist/text-annotation.js"
|
|
687
|
+
},
|
|
688
|
+
"./date-entry": {
|
|
689
|
+
"types": "./dist/date-entry.d.ts",
|
|
690
|
+
"react-native": "./dist/date-entry.js",
|
|
691
|
+
"import": "./dist/date-entry.js",
|
|
692
|
+
"default": "./dist/date-entry.js"
|
|
693
|
+
},
|
|
694
|
+
"./field-group": {
|
|
695
|
+
"types": "./dist/field-group.d.ts",
|
|
696
|
+
"react-native": "./dist/field-group.js",
|
|
697
|
+
"import": "./dist/field-group.js",
|
|
698
|
+
"default": "./dist/field-group.js"
|
|
699
|
+
},
|
|
700
|
+
"./document-resource": {
|
|
701
|
+
"types": "./dist/document-resource.d.ts",
|
|
702
|
+
"react-native": "./dist/document-resource.js",
|
|
703
|
+
"import": "./dist/document-resource.js",
|
|
704
|
+
"default": "./dist/document-resource.js"
|
|
669
705
|
}
|
|
670
706
|
},
|
|
671
707
|
"files": [
|