@hjmds/design-contracts 1.13.1 → 1.15.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 +4 -0
- package/dist/catalog.d.ts.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/design-profile-layout.d.ts +11 -0
- package/dist/design-profile-layout.d.ts.map +1 -0
- package/dist/design-profile-layout.js +18 -0
- package/dist/design-profile-layout.js.map +1 -0
- package/dist/design-profile.d.ts +89 -0
- package/dist/design-profile.d.ts.map +1 -0
- package/dist/design-profile.js +262 -0
- package/dist/design-profile.js.map +1 -0
- package/dist/design-system-provider.d.ts +6 -0
- package/dist/design-system-provider.d.ts.map +1 -1
- package/dist/design-system-provider.js +4 -2
- package/dist/design-system-provider.js.map +1 -1
- 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/gooey-navigation.d.ts +19 -1
- package/dist/gooey-navigation.d.ts.map +1 -1
- package/dist/gooey-navigation.js +37 -2
- package/dist/gooey-navigation.js.map +1 -1
- 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/palette-contrast.d.ts +6 -0
- package/dist/palette-contrast.d.ts.map +1 -1
- package/dist/palette-contrast.js +17 -0
- package/dist/palette-contrast.js.map +1 -1
- 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/screen-patterns.d.ts +16 -1
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +4 -0
- package/dist/screen-patterns.js.map +1 -1
- 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/toast-liquid.d.ts +3 -1
- package/dist/toast-liquid.d.ts.map +1 -1
- package/dist/toast-liquid.js +6 -2
- package/dist/toast-liquid.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/agreement.md +14 -0
- package/docs/asset.md +7 -0
- package/docs/brand-boundary.md +14 -5
- package/docs/code-block.md +12 -1
- package/docs/collapsible.md +6 -0
- package/docs/design-profile.md +263 -0
- package/docs/design-system-provider.md +7 -0
- package/docs/dialog.md +20 -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/gooey-navigation.md +37 -6
- package/docs/heading.md +15 -0
- 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/task-list.md +15 -1
- package/docs/text-annotation.md +85 -0
- package/docs/theming.md +18 -5
- package/docs/usage/README.md +23 -1
- package/docs/usage/components/activity-heatmap.md +3 -1
- package/docs/usage/components/agreement.md +15 -3
- package/docs/usage/components/alert-dialog.md +16 -1
- package/docs/usage/components/asset.md +9 -2
- package/docs/usage/components/avatar.md +33 -1
- package/docs/usage/components/badge.md +3 -1
- package/docs/usage/components/bottom-cta.md +3 -1
- package/docs/usage/components/bottom-navigation.md +5 -1
- package/docs/usage/components/button.md +7 -1
- package/docs/usage/components/calendar.md +3 -1
- package/docs/usage/components/card.md +26 -3
- package/docs/usage/components/carousel.md +10 -1
- package/docs/usage/components/chat-message.md +11 -1
- package/docs/usage/components/chip.md +5 -0
- package/docs/usage/components/code-block.md +14 -2
- package/docs/usage/components/collapsible.md +10 -2
- package/docs/usage/components/combobox.md +12 -1
- package/docs/usage/components/command-palette.md +11 -6
- package/docs/usage/components/content-transition.md +29 -5
- package/docs/usage/components/context-menu.md +7 -1
- package/docs/usage/components/date-picker.md +3 -1
- package/docs/usage/components/design-system-provider.md +7 -5
- package/docs/usage/components/dialog.md +70 -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 +26 -1
- package/docs/usage/components/form.md +13 -3
- package/docs/usage/components/heading.md +7 -1
- package/docs/usage/components/image-comparison.md +82 -0
- package/docs/usage/components/image.md +83 -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 +5 -1
- package/docs/usage/components/list.md +8 -1
- package/docs/usage/components/load-more.md +3 -1
- package/docs/usage/components/mentions.md +3 -1
- package/docs/usage/components/menu.md +3 -1
- package/docs/usage/components/menubar.md +7 -1
- package/docs/usage/components/message-composer.md +16 -5
- package/docs/usage/components/notice.md +9 -0
- package/docs/usage/components/number-field.md +3 -1
- package/docs/usage/components/onboarding-screen.md +16 -9
- package/docs/usage/components/overview-screen.md +73 -0
- package/docs/usage/components/password-field.md +5 -1
- package/docs/usage/components/popover.md +14 -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/saved-items-screen.md +3 -1
- package/docs/usage/components/screen-layout.md +9 -1
- package/docs/usage/components/search-field.md +13 -1
- package/docs/usage/components/search-screen.md +5 -0
- package/docs/usage/components/segmented-control.md +17 -0
- package/docs/usage/components/select.md +9 -0
- package/docs/usage/components/sheet.md +14 -1
- package/docs/usage/components/skeleton.md +9 -0
- package/docs/usage/components/slider.md +3 -1
- package/docs/usage/components/statistic.md +14 -1
- package/docs/usage/components/surface.md +8 -1
- package/docs/usage/components/tabs.md +11 -2
- package/docs/usage/components/tag.md +3 -1
- package/docs/usage/components/tags-input.md +11 -1
- package/docs/usage/components/text-area.md +3 -1
- package/docs/usage/components/text-transition.md +8 -2
- package/docs/usage/components/text.md +3 -1
- package/docs/usage/components/toast.md +23 -1
- package/docs/usage/components/top-bar.md +3 -1
- 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/command-records.md +106 -0
- package/docs/usage/compositions/content-transition-comparison.md +108 -0
- package/docs/usage/compositions/context-toolbar.md +85 -0
- package/docs/usage/compositions/date-entry.md +108 -0
- package/docs/usage/compositions/date-time-selection.md +110 -0
- package/docs/usage/compositions/design-profile-comparison.md +114 -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/flow-onboarding.md +5 -3
- package/docs/usage/screens/product-bento.md +117 -0
- package/docs/usage/tokens/color.md +9 -2
- package/docs/usage/tokens/elevation-opacity.md +8 -1
- package/docs/usage/tokens/motion.md +10 -1
- package/docs/usage/tokens/radius.md +8 -1
- package/docs/usage/tokens/typography.md +15 -1
- package/package.json +49 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# 그림과 시작 안내
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: `showcase/web/src/patterns/illustrated-outcome-preview.tsx`, `showcase/native/src/illustrated-outcome-preview.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/그림과 시작 안내`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
빈 목록에서 시작을 안내하고 짧은 온보딩을 거쳐 결과를 보여 줄 때 쓴다. 3D 그림은 제품 자산이며
|
|
16
|
+
공통 컴포넌트의 의미·초점·버튼을 대체하지 않는다. 별도 wrapper 없이 기존 공개 API를 사용한다.
|
|
17
|
+
|
|
18
|
+
## 구성 요소
|
|
19
|
+
|
|
20
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| EmptyState | 채울 수 있는 빈 목록과 첫 행동 | [빈 상태](../components/empty-state.md) |
|
|
23
|
+
| OnboardingScreen | 단계·이전·다음·완료 배치 | [시작 안내](../screens/flow-onboarding.md) |
|
|
24
|
+
| Result | 흐름이 끝난 결과와 다음 행동 | [결과](../components/result.md) |
|
|
25
|
+
| TextField | 제품이 소유하는 입력 초안 | [텍스트 필드](../components/field.md) |
|
|
26
|
+
|
|
27
|
+
## 배치
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
[장식 그림]
|
|
31
|
+
[제목 + 설명]
|
|
32
|
+
[단계 본문: 그림 또는 입력]
|
|
33
|
+
[다음 / 완료]
|
|
34
|
+
[이전: 두 번째 단계부터]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| 바깥 틀 | Stack | 본문 전체 | gap lg=20px |
|
|
40
|
+
| 빈 상태 그림 | EmptyState 제품 슬롯 | 제목 위 | 120×120, Asset xlarge와 같은 크기 |
|
|
41
|
+
| 단계 본문 | OnboardingScreen | 제목과 하단 행동 사이 | 그림 120×120 또는 TextField |
|
|
42
|
+
| 완료 | Result | 흐름 종료 자리 | 체크 32×32, 공통 56px 아이콘 영역 안 |
|
|
43
|
+
| 버튼 | 각 공개 컴포넌트 | 화면의 기존 footer 또는 action 영역 | 기존 recipe 최소 터치 크기 유지 |
|
|
44
|
+
|
|
45
|
+
## 흐름과 상태
|
|
46
|
+
|
|
47
|
+
1. EmptyState 행동으로 시작 안내를 연다. 화면의 현재 단계는 제품이 제어한다.
|
|
48
|
+
2. 다음→입력→이전→다음에서도 초안은 상위 제품 상태에 남긴다.
|
|
49
|
+
3. 예제는 제목이 공백이면 완료를 비활성화한다. 실제 앱의 저장 결과는 서버 응답으로만 확정한다.
|
|
50
|
+
4. 그림 로딩 실패는 그림만 제거한다. 설명·입력·버튼은 유지하며 오류를 성공으로 바꾸지 않는다.
|
|
51
|
+
5. 완료 후 다시 시작해도 초안을 유지한다. 초안 삭제는 제품의 명시적인 정책이 있을 때만 한다.
|
|
52
|
+
|
|
53
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| 기본 | 빈 상태 제목·장식 그림·첫 행동 | 제목으로 의미를 전달하고 그림은 장식으로 숨김 |
|
|
56
|
+
| 진행 중 | 단계 안내·입력·이전/다음 행동 | 초안을 유지하며 실제 저장 중에는 제품 pending 연결 |
|
|
57
|
+
| 실패 | 그림 실패 시 본문·행동 유지 | 장식 실패를 저장 실패와 혼동하지 않음 |
|
|
58
|
+
| 완료 | 결과 제목·입력 내용·다시 시작 | Result의 기본 status 의미 유지 |
|
|
59
|
+
|
|
60
|
+
## 코드 골격
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Web
|
|
64
|
+
import { EmptyState, Result } from "@hjmds/react/feedback";
|
|
65
|
+
import { OnboardingScreen } from "@hjmds/react/screen-flows";
|
|
66
|
+
<EmptyState title={title} description={description} icon={decorativeArtwork} action={startButton} />
|
|
67
|
+
<Result status="success" title={completedTitle} icon={decorativeCheck} actions={actions} />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// Native
|
|
72
|
+
import { EmptyState, Result } from "@hjmds/react-native/feedback";
|
|
73
|
+
import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
|
|
74
|
+
<EmptyState title={title} description={description} illustration={decorativeArtwork} action={startButton} />
|
|
75
|
+
<Result status="success" title={completedTitle} renderIcon={() => decorativeCheck} actions={actions} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
OnboardingScreen에는 `steps`, `index`, `onIndexChange`, `nextLabel`, `backLabel`, `complete`,
|
|
79
|
+
`progressLabel`을 전달한다. 단계 구조·버튼 위치는 기존 화면 지침을 따른다. 제품 코드에서
|
|
80
|
+
Showcase를 import하지 않는다. 그림 호스트는 Web img의 빈 alt, Native Image의 accessible=false로 장식임을 명시한다.
|
|
81
|
+
|
|
82
|
+
## 플랫폼 차이
|
|
83
|
+
|
|
84
|
+
| 항목 | Web | Native |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| 파일 | 로컬 파일 URL | Metro 정적 require |
|
|
87
|
+
| 그림 접근성 | 빈 alt | accessible=false |
|
|
88
|
+
| 장식 슬롯 | EmptyState.icon / Result.icon | EmptyState.illustration / Result.renderIcon |
|
|
89
|
+
| 실패 | 그림만 숨김 | 그림만 숨김 |
|
|
90
|
+
|
|
91
|
+
실제 기기의 자산 디코딩·큰 글자·접근성 검증은 타입 검사와 별도다.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 추가해도 유지되는 목록
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: `showcase/web/src/patterns/live-list-preview.tsx`, `showcase/native/src/live-list-preview.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/정보 표시/추가해도 유지되는 목록`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
입력 중인 목록에 새 데이터가 추가되거나 순서가 바뀌어도 기존 초안과 항목의 정체성을 유지할 때 쓴다.
|
|
16
|
+
Magic UI Animated List의 소개 페이지 순차 노출에서 새 항목의 등장 표현만 흡수한다. 이미 도착한
|
|
17
|
+
데이터를 타이머로 늦추지 않고, 실제 알림 수신과 데모 재생을 혼동하지 않는다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| List · ListRow | stable key 항목의 목록 의미·제목·구분선 | [목록](../components/list.md), [행](../components/list-row.md) |
|
|
24
|
+
| ContentTransition | 새 항목 등장과 바깥 높이 전환 | [내용 전환](../components/content-transition.md) |
|
|
25
|
+
| TextField | 제품이 항목 id별로 보존하는 메모 | [필드](../components/field.md) |
|
|
26
|
+
| Button · Text | 명시적 데이터 변경과 변경 결과 | [버튼](../components/button.md), [글자](../components/text.md) |
|
|
27
|
+
|
|
28
|
+
## 배치
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
[안내]
|
|
32
|
+
[기록 추가] [3개 함께 추가]
|
|
33
|
+
[순서 뒤집기] [첫 기록 삭제]
|
|
34
|
+
[움직임 멈추기]
|
|
35
|
+
[변경 결과]
|
|
36
|
+
┌ 기록 목록 ────────────┐
|
|
37
|
+
│ 제목·설명 │
|
|
38
|
+
│ 메모 입력 │
|
|
39
|
+
├───────────────────────┤
|
|
40
|
+
│ 다음 기록·메모 │
|
|
41
|
+
└───────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| 바깥 틀 | Stack | 위에서 아래 | gap lg=20px |
|
|
47
|
+
| 변경 행동 | inline Stack · Button | 목록 위, 좁으면 감김 | 기본 Stack/Button recipe |
|
|
48
|
+
| 데이터 | List | 변경 결과 아래 | separator full, 공통 ListRow/TextField 크기 |
|
|
49
|
+
| 등장 | ContentTransition | 각 stable id 항목 | rise=12px, motion.normal, easing.enter |
|
|
50
|
+
| 높이 | ContentTransition animateHeight | 목록 바깥 | 같은 stateKey를 유지하며 실제 측정 높이만 전환 |
|
|
51
|
+
|
|
52
|
+
## 흐름과 상태
|
|
53
|
+
|
|
54
|
+
1. 초기 데이터는 바로 표시하고 최초 등장 모션은 끈다.
|
|
55
|
+
2. 추가된 항목은 고유 id를 부여해 즉시 배열에 반영한다. 해당 항목만 enterOnMount를 켠다.
|
|
56
|
+
3. 기존 id의 React key와 stateKey를 유지한다. 전체 목록 stateKey를 항목 개수로 바꾸지 않는다.
|
|
57
|
+
4. 순서 변경은 데이터 배열 순서만 바꾸며 초안은 id에 연결한다. 순서 변경 자체의 이동 애니메이션은 제공하지 않는다.
|
|
58
|
+
5. 삭제는 즉시 반영한다. 삭제된 입력·버튼을 담은 퇴장 사본을 남기지 않는다.
|
|
59
|
+
6. 움직임 멈추기는 데이터 처리를 멈추지 않는다. 다시 켜도 기존 항목은 재생하지 않는다.
|
|
60
|
+
|
|
61
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| 기본 | 기존 목록과 입력 | 초기 데이터 즉시 표시 |
|
|
64
|
+
| 진행 중 | 새 행만 짧게 등장 | 기존 입력 노드와 초안 유지 |
|
|
65
|
+
| 실패 | 서버 실패가 있는 제품은 기존 목록·초안을 보존하고 오류를 별도 표시 | 이 로컬 실험에는 네트워크 요청 없음 |
|
|
66
|
+
| 삭제 | 해당 행 즉시 제거 | 삭제 행동은 목록 밖에 두어 초점 유실 방지 |
|
|
67
|
+
| 비어 있음 | 기록 없음과 추가 안내 | 추가 버튼 유지 |
|
|
68
|
+
| 모션 감소·정지 | 같은 데이터, 공간 전환 없음 | 의미·입력 순서 동일 |
|
|
69
|
+
|
|
70
|
+
## 코드 골격
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// Web
|
|
74
|
+
import { ContentTransition } from "@hjmds/react/content-transition";
|
|
75
|
+
<ContentTransition stateKey="records" animateHeight motion={paused ? 'none' : 'system'}>
|
|
76
|
+
<List label={listLabel}>
|
|
77
|
+
{items.map(item => <ContentTransition key={item.id} stateKey={item.id}
|
|
78
|
+
enterOnMount={item.justAdded} preset="rise" motion={paused ? 'none' : 'system'}>
|
|
79
|
+
<RecordEditor item={item} onChange={changeById} />
|
|
80
|
+
</ContentTransition>)}
|
|
81
|
+
</List>
|
|
82
|
+
</ContentTransition>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Native
|
|
87
|
+
import { ContentTransition } from "@hjmds/react-native/content-transition";
|
|
88
|
+
|
|
89
|
+
<ContentTransition stateKey={item.id} enterOnMount={item.justAdded} preset="rise">
|
|
90
|
+
<RecordEditor item={item} onChange={changeById} />
|
|
91
|
+
</ContentTransition>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 플랫폼 차이
|
|
95
|
+
|
|
96
|
+
| 항목 | Web | Native |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| 등장 | 기존 framer-motion optional peer | 기존 RN Animated native driver |
|
|
99
|
+
| 높이 | ResizeObserver + WAAPI | onLayout + JS driver |
|
|
100
|
+
| 변경 알림 | status | accessibilityLiveRegion polite |
|
|
101
|
+
| 접근성 | 기존 입력 초점·list 의미 | 기존 입력과 Native list 의미 |
|
|
102
|
+
|
|
103
|
+
데이터·중복 제거·정렬·서버 확정은 제품 소유다. 긴 목록은 VirtualList 선택을 먼저 검토하고
|
|
104
|
+
재활용 mount를 새 데이터로 오인해 enterOnMount를 켜지 않는다. 큰 글자·제품 팔레트·성능은 승격 전 검증한다.
|
|
@@ -165,5 +165,5 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
|
|
|
165
165
|
- KeyboardDock은 window 기준 좌표를 쓴다. Storybook 캔버스처럼 아래에 다른 영역이 남는 host에서는 위치가 어긋나 스토리가 전체 화면 `Modal`로 띄운다. 제품도 앱 크기 host에서 쓴다.
|
|
166
166
|
- NativeContextMenu 패치(Zeego 3.0.6 관련)는 HJM 설치로 적용되지 않는다. `@hjmds/react-native/docs/patches/`를 제품에 복사해 등록한다.
|
|
167
167
|
- 필요한 optional peer(`react-native-zoom-toolkit` 5.1.1, `react-native-keyboard-controller` 1.22.5, `@gorhom/bottom-sheet` 5.2.14, `zeego` 3.0.6 등)가 없으면 기기 Metro 번들이 실패한다.
|
|
168
|
-
-
|
|
168
|
+
- `ImageViewer` 실패는 Android assertive live region과 iOS announceForAccessibility로 알린다. 제품이 같은 오류를 다시 낭독하지 않는다. 제품 이미지 host와 상태 이벤트 연결은 [Image 사용 지침](../components/image.md)의 Native ImageViewer 절을 따른다.
|
|
169
169
|
- 2026-10-06 예제 검수에서 머리·본문·하단의 좌우 여백을 Container compact로 맞추고 본문 간격은 Stack md로 옮겼다. 안전 영역·키보드 좌표는 바깥 host가 유지한다. 메뉴 결과는 상태 문구와 iOS/Android 알림을 함께 갱신한다.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# 버튼에서 이어지는 편집
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: `showcase/web/src/patterns/origin-dialog-preview.tsx`, `showcase/native/src/origin-dialog-preview.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/버튼에서 이어지는 편집`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
현재 화면의 항목을 짧게 편집하고 돌아올 때 출발 위치를 시각적으로 연결한다. 별도 상태
|
|
16
|
+
엔진 없이 Dialog의 선택적 motionOrigin과 제품 소유 초안을 사용한다. 원본의 닫기 후
|
|
17
|
+
초점·초안 손실을 그대로 가져오지 않기 위한 구성이다. 긴 편집은 전용 화면을 사용한다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Button | 측정할 열기 버튼과 저장 | [버튼](../components/button.md) |
|
|
24
|
+
| Popover | Web에서 페이지를 계속 볼 수 있는 비모달 편집 변형 | [팝오버](../components/popover.md) |
|
|
25
|
+
| Dialog | 기존 모달 상태·닫기·초점 복귀와 선택적인 공간 전환 | [대화상자](../components/dialog.md) |
|
|
26
|
+
| TextField | 제품 상태에 보존하는 초안 | [필드](../components/field.md) |
|
|
27
|
+
| Text | 확정한 결과 표시 | [텍스트](../components/text.md) |
|
|
28
|
+
|
|
29
|
+
## 배치
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
[안내]
|
|
33
|
+
[메모 편집] ──→ [여행 메모 닫기]
|
|
34
|
+
[저장 결과] [메모 입력]
|
|
35
|
+
[저장]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| 바깥 틀 | Stack | 원래 화면 | gap lg=20px |
|
|
41
|
+
| 진행 중 | Dialog | 기본 overlay 중앙 | 기본 Dialog recipe |
|
|
42
|
+
| 입력 | TextField | Dialog 본문 | 공통 Field 크기·간격 |
|
|
43
|
+
| 확정 | Button | Web footer / Native primaryAction | 공통 Dialog 행동 영역 |
|
|
44
|
+
|
|
45
|
+
## 흐름과 상태
|
|
46
|
+
|
|
47
|
+
1. 열기 직전에 트리거를 실제 viewport/window 좌표로 측정한다. 측정 불가 시 일반 Dialog를 연다.
|
|
48
|
+
2. 제품이 open·draft·saved를 각각 소유한다. 저장 전 닫기는 draft를 지우지 않는다.
|
|
49
|
+
3. 저장 시 draft를 결과로 확정하고 닫는다. 실패 체험을 예약하면 첫 저장만 실패하고
|
|
50
|
+
오류·초안을 유지한다. 다시 저장은 성공한다. 실제 서버가 있으면 busy와 확정 응답을 연결한다.
|
|
51
|
+
4. 닫기 전환이 끝난 뒤 트리거로 초점을 돌린다. 빠른 재열기는 이전 종료 결과를 무시한다.
|
|
52
|
+
5. 모션 감소·잘못된 측정에서는 공간 전환을 생략한다.
|
|
53
|
+
|
|
54
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| 기본 | 열기 버튼과 마지막 저장 결과 | draft 유지 |
|
|
57
|
+
| 진행 중 | 제목·닫기·입력·저장 | 제품 상태로 갱신 |
|
|
58
|
+
| 결과 | 결과 갱신 후 닫기 | 트리거로 복귀 |
|
|
59
|
+
| 실패 | 오류 설명·입력·다시 저장 | 오류 알림, 초안 유지 |
|
|
60
|
+
| 측정 없음 | 일반 Dialog | 같음 |
|
|
61
|
+
|
|
62
|
+
## 코드 골격
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Web: origin은 trigger.getBoundingClientRect()의 측정값
|
|
66
|
+
<Dialog open={open} onOpenChange={setOpen} title={title} closeLabel={closeLabel}
|
|
67
|
+
motionOrigin={origin} returnFocusRef={triggerRef} footer={saveButton}>
|
|
68
|
+
<TextField label={label} value={draft} onValueChange={setDraft} />
|
|
69
|
+
</Dialog>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
// Native: origin은 trigger.measureInWindow 콜백의 측정값
|
|
74
|
+
<Dialog open={open} onOpenChange={setOpen} title={title} closeLabel={closeLabel}
|
|
75
|
+
motionOrigin={origin} returnFocusRef={triggerRef} primaryAction={saveAction}>
|
|
76
|
+
<TextField label={label} value={draft} onValueChange={setDraft} />
|
|
77
|
+
</Dialog>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
문구·초안·저장 로직·트리거 측정은 제품 소유다. Showcase를 앱에서 import하지 않는다.
|
|
81
|
+
|
|
82
|
+
## 플랫폼 차이
|
|
83
|
+
|
|
84
|
+
| 항목 | Web | Native |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| 원점 | getBoundingClientRect | measureInWindow |
|
|
87
|
+
| 전환 | WAAPI, 종료까지 한 subtree 유지 | 실제 modal 측정 + 기존 Animated progress |
|
|
88
|
+
| 모션 감소 | 공간 전환 없음 | 공간 전환 없음, 기존 opacity |
|
|
89
|
+
| 닫기 | Escape·바깥·닫기 | Back·바깥·닫기 |
|
|
90
|
+
|
|
91
|
+
키보드·회전·스크롤에 의한 목적지 변화, 실제 기기와 제품 테마 비교는 승격 전 검증한다.
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### Web의 페이지 안에서 편집 변형
|
|
95
|
+
|
|
96
|
+
`Contextual`·`ContextualDark`·`ContextualLargeText`는 기존 Popover의 선택적인 `motionOrigin`을
|
|
97
|
+
사용한다. 기본 360px 표면이 버튼 아래 8px 간격으로 열리고 viewport 충돌에 따라 배치가 바뀐다.
|
|
98
|
+
본문은 Stack gap md=16px 안에 오류 → TextField → 저장 순서다. Native는 모달 편집 변형을 쓴다.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Popover title={title} closeLabel={closeLabel} motionOrigin={origin}
|
|
102
|
+
trigger={<Button ref={triggerRef} onClick={() => setOrigin(triggerRef.current?.getBoundingClientRect())}>{openLabel}</Button>}>
|
|
103
|
+
{({ close }) => <Editor draft={draft} onDraftChange={setDraft} onSave={() => save(draft).then(close)} />}
|
|
104
|
+
</Popover>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
비모달에서는 Escape/닫기 직후 기존 규칙대로 초점을 복귀한다. 바깥 클릭이나 Tab으로 이동한
|
|
108
|
+
초점은 되가져오지 않는다. 저장 실패는 초안·열림을 유지하고, 닫았다 다시 열어도 제품 draft는 남는다.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# 선택 배경 이동
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/직접 조작과 모션/선택 배경 이동`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
짧은 단일 선택의 현재 항목을 이어지는 배경으로 보여 줄 때 쓴다. 선택 값·키보드·초점은 기존 SegmentedControl이 소유한다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
|
|
16
|
+
|
|
17
|
+
## 구성 요소
|
|
18
|
+
|
|
19
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| SegmentedControl · Text · TextField | 입력·표현·행동의 역할 분리 | [입력/동작](../components/segmented-control.md), [상태/전환](../components/content-transition.md) |
|
|
22
|
+
|
|
23
|
+
## 배치
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
[제목 또는 현재 입력]
|
|
27
|
+
[상태/내용 영역]
|
|
28
|
+
[관련 행동과 결과]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| 바깥 틀 | Stack | 화면의 본문 흐름 | gap lg=20px, 전체 폭 |
|
|
34
|
+
| 선택 | SegmentedControl 2개 | 제목 아래, 연결형 다음 필터형 | 기본 44px 터치 영역, 실제 글자에 따른 높이 |
|
|
35
|
+
| 결과·메모 | Text · TextField | 선택 컨트롤 아래 | 선택 변경 시 필드 재생성 없음 |
|
|
36
|
+
| Native 화면 host | ScreenLayout · KeyboardAvoiding | 본문 스크롤과 바깥 키보드 여백 | flex 1, 끝 여백 spacing.md=16 |
|
|
37
|
+
|
|
38
|
+
2026-10-07 iOS 2배 글자에서 Stack만 사용한 예제의 메모가 키보드 뒤에 가려졌다.
|
|
39
|
+
글자나 선택 항목을 줄이지 않고 위 화면 host를 연결해 입력·선택을 스크롤로 오가게 한다.
|
|
40
|
+
선택 배경 이동은 입력 초점·스크롤을 소유하지 않으므로 제품 화면도 이 경계를 연결해야 한다.
|
|
41
|
+
|
|
42
|
+
## 흐름과 상태
|
|
43
|
+
|
|
44
|
+
1. 현재 선택 측정→사용자가 선택→배경만 이동. 메모 필드는 유지한다.
|
|
45
|
+
2. 서버 응답·파일 권한·문구·브랜드는 제품이 전달한다. Showcase의 예제 응답과 고정 데이터를 가져오지 않는다.
|
|
46
|
+
|
|
47
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 기본 | 입력과 사용 가능한 행동 | 조작 이름은 제품 언어 |
|
|
50
|
+
| 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
|
|
51
|
+
| 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
|
|
52
|
+
|
|
53
|
+
## 코드 골격
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// Web
|
|
57
|
+
<SegmentedControl label={t("period")} items={items} value={value} onValueChange={setValue} selectionMotion="slide" />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// Native
|
|
62
|
+
<SegmentedControl label={t("period")} items={items} value={value} onValueChange={setValue} selectionMotion="slide" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
|
|
66
|
+
선택 콜백은 두 플랫폼 모두 `onValueChange`다. 위 골격의 번역·항목·값은 제품이 제공한다.
|
|
67
|
+
|
|
68
|
+
## 플랫폼 차이
|
|
69
|
+
|
|
70
|
+
| 항목 | Web | Native |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
|
|
73
|
+
| 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 질감 비교
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [EffectSurface 계약](../../effect-surface.md), `showcase/*/texture-comparison-preview.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/정보 표시/질감 비교`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
기존 반복 점 grain과 불규칙한 정적 noise를 같은 배경·강도로 비교할 때 쓴다.
|
|
16
|
+
독립 NoiseTexture API를 추가하지 않고 기존 EffectSurface의 선택 레이어를 사용한다.
|
|
17
|
+
일반 입력·데이터 패널은 불투명 Surface, 작업 진행·성공은 Progress·Result를 쓴다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| EffectSurface | 질감·테마·장식 수명주기 | [배경 효과](../components/effect-surface.md) |
|
|
24
|
+
| SegmentedControl | 두 영역의 강도를 함께 선택 | [단일 선택](../components/segmented-control.md) |
|
|
25
|
+
| TextField·Button·Text | 읽기·입력·행동·결과 확인 | [입력](../components/field.md) |
|
|
26
|
+
|
|
27
|
+
noise는 생성된64×64 PNG alpha mask이며 자체 런타임 픽셀 생성·외부 요청이 없다.
|
|
28
|
+
기본 intensity0.22, 실험의 비교 강도0.6은 가독성 스트레스 조건이며 제품 기본 권장이 아니다.
|
|
29
|
+
seed는 고정 타일의 위상만 이동한다. 새 타일 픽셀을 생성하지 않는다.
|
|
30
|
+
|
|
31
|
+
## 배치
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
[강도 선택: 두 영역에 함께 적용]
|
|
35
|
+
[grain: 제목 → 설명 → 입력 → 버튼]
|
|
36
|
+
[noise: 제목 → 설명 → 입력 → 버튼]
|
|
37
|
+
[선택 결과와 비교 한계]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| 바깥 틀 | Stack | 화면 본문 세로 | gap lg20 |
|
|
43
|
+
| 비교 영역 | EffectSurface | grain 다음 noise | 내부 padding spacing.lg20, Stack gap md16 |
|
|
44
|
+
| 질감 | SVG mask | absolute 배경 | viewBox 밖64×64 CSS pixel/Native logical point 타일 반복 |
|
|
45
|
+
| 본문 | TextField·Button | 장식 앞 일반 레이아웃 | 기존 recipe |
|
|
46
|
+
| Native 화면 | ScreenLayout·KeyboardAvoiding | 본문 스크롤과 바깥 키보드 여백 | 긴 내용·큰 글자 접근용 |
|
|
47
|
+
|
|
48
|
+
## 흐름과 상태
|
|
49
|
+
|
|
50
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| 기본 | 두 질감과 기본22% 강도 | 입력·버튼 활성 |
|
|
53
|
+
| 진행 중 | 강도 선택을 두 질감에 함께 반영 | 입력 유지, 인위 대기 없음 |
|
|
54
|
+
| 실패 | 장식 host 실패는 기존 EffectSurface fallback | 제품 입력·행동은 유지 |
|
|
55
|
+
|
|
56
|
+
1. 강도를 바꾸고 두 영역의 문장을 읽고 타이핑한다.
|
|
57
|
+
2. 영역별 버튼을 눌러 결과 안내를 비교한다. 결과는 예제 내부 선택일 뿐 저장하지 않는다.
|
|
58
|
+
|
|
59
|
+
## 코드 골격
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Web
|
|
63
|
+
import { EffectSurface } from "@hjmds/react/effect-surface";
|
|
64
|
+
<EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Native
|
|
69
|
+
import { EffectSurface } from "@hjmds/react-native/effect-surface";
|
|
70
|
+
<EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 플랫폼 차이
|
|
74
|
+
|
|
75
|
+
| 항목 | Web | Native |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| 장식 | SVG image·mask | react-native-svg Image·Mask |
|
|
78
|
+
| peer | 없음 | 기존 react-native-svg15.15.5 필요 |
|
|
79
|
+
| 동작 | 가시성·document visibility | visible·AppState |
|
|
80
|
+
|
|
81
|
+
Native FeTurbulence는 미구현이라 공유 PNG mask를 택했다. 새 GPU peer나 Native 무표시
|
|
82
|
+
fallback을 동일 구현으로 세지 않는다. noise는 theme.text 색의 alpha mask다. 임의 브랜드/강도에서
|
|
83
|
+
대비를 보장하지 않는다. 기본은 정적이며 active를 켜면 기존 모션 감소·숨김 수명주기를 유지한다.
|
|
84
|
+
Web/RN rasterization·비율 차이로 픽셀 일치를 보장하지 않는다. 원본 SVG Perlin noise와 달리
|
|
85
|
+
자체 생성한6 octave value noise이며 원본 자산을 복사하지 않았다. 실제 기기 표시·비용·VoiceOver
|
|
86
|
+
검증은 따로 한다.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 선택과 오류 복구
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: 공개 API를 사용하는 `showcase/*/reference-adoption-previews.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/선택과 오류 복구`
|
|
10
|
+
|
|
11
|
+
2026-10-07 사용자 승인으로 승급. [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md).
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
파일 선택과 전송 상태의 취소·재시도를 연결할 때 쓴다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
|
|
16
|
+
|
|
17
|
+
## 구성 요소
|
|
18
|
+
|
|
19
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| FilePicker · UploadItem · Button | 입력·표현·행동의 역할 분리 | [입력/동작](../components/file-picker.md), [상태/전환](../components/upload-item.md) |
|
|
22
|
+
|
|
23
|
+
## 배치
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
[제목 또는 현재 입력]
|
|
27
|
+
[상태/내용 영역]
|
|
28
|
+
[관련 행동과 결과]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| 바깥 틀 | Stack | 화면의 본문 흐름 | gap md=16px, 전체 폭 |
|
|
34
|
+
| 내용 | 해당 공개 API | 입력과 행동 사이 | 자체 recipe 크기, 줄바꿈 허용 |
|
|
35
|
+
| 행동 | Button/기존 컨트롤 | 관련 항목 바로 뒤 | inline일 때 wrap, md=16px |
|
|
36
|
+
|
|
37
|
+
## 흐름과 상태
|
|
38
|
+
|
|
39
|
+
1. 선택→중복 제거→전송→성공 또는 실패→취소/재시도
|
|
40
|
+
2. 서버 응답·파일 권한·문구·브랜드는 제품이 전달한다. Showcase의 예제 응답과 고정 데이터를 가져오지 않는다.
|
|
41
|
+
|
|
42
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| 기본 | 입력과 사용 가능한 행동 | 조작 이름은 제품 언어 |
|
|
45
|
+
| 진행 중 | 실제 작업 상태에 맞는 loading 또는 열린 도구 | 중복 요청 차단, 입력 유지 |
|
|
46
|
+
| 실패 | 문구와 가능한 재시도 | 원래 입력/파일 유지, 결과 안내 |
|
|
47
|
+
|
|
48
|
+
## 코드 골격
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
// Web
|
|
52
|
+
<UploadItem descriptor={item} labels={labels} onCancel={cancel} onRetry={retry} />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// Native
|
|
57
|
+
<UploadItem descriptor={item} labels={labels} onCancel={cancel} onRetry={retry} />
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
|
|
61
|
+
Button의 실행 콜백은 Web `onClick`, Native `onPress`로 연결한다. 위 골격의 도메인 함수·변수는 제품이 제공한다.
|
|
62
|
+
|
|
63
|
+
## 플랫폼 차이
|
|
64
|
+
|
|
65
|
+
| 항목 | Web | Native |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
|
|
68
|
+
| 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
|
|
69
|
+
|
|
70
|
+
### 예제의 제한과 초점 복구
|
|
71
|
+
|
|
72
|
+
2026-10-07 실제 복구 흐름에서 상태 버튼이 사라질 때 Web 초점이 body로 빠졌다.
|
|
73
|
+
예제는 상태 변경 전에 해당 파일의 이름 있는 영역으로 초점을 옮기고, 제거 후에는 예제 추가
|
|
74
|
+
버튼으로 복귀한다. 이후 Tab으로 취소·재시도 등 현재 가능한 행동을 선택한다.
|
|
75
|
+
제품도 비동기 상태 전환으로 사라지는 행동의 초점 목적지를 정한다.
|
|
76
|
+
|
|
77
|
+
양쪽 예제는 이미지3개·파일당5MB 제한을 선언한다. Web은 실제 file chooser를 사용하며,
|
|
78
|
+
Native onPick은 예제 후보를 반환하는 fixture다. Native 시스템 사진 선택기 검증으로 세지 않는다.
|
|
79
|
+
서버 비전송 안내는 성공·거부 상태와 별도로 항상 표시한다. 실제 제품은 서버 확정·취소 시
|
|
80
|
+
늦게 도착하는 응답·전송 ticket의 소유권을 제품 업로드 계층에 연결해야 한다.
|
|
@@ -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
|
+
| 설명 | 화면의 동등한 텍스트 | 화면의 동등한 텍스트 |
|