@hjmds/design-contracts 1.20.0 → 1.22.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/authoring-tools.d.ts +38 -0
- package/dist/authoring-tools.d.ts.map +1 -0
- package/dist/authoring-tools.js +35 -0
- package/dist/authoring-tools.js.map +1 -0
- package/dist/behaviors.d.ts +33 -3
- package/dist/behaviors.d.ts.map +1 -1
- package/dist/behaviors.js +2 -0
- package/dist/behaviors.js.map +1 -1
- package/dist/calendar.d.ts +9 -1
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -2
- package/dist/calendar.js.map +1 -1
- package/dist/catalog.d.ts +129 -2
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +6 -2
- package/dist/catalog.js.map +1 -1
- package/dist/chart.d.ts +398 -0
- package/dist/chart.d.ts.map +1 -0
- package/dist/chart.js +414 -0
- package/dist/chart.js.map +1 -0
- package/dist/collection-editing.d.ts +38 -0
- package/dist/collection-editing.d.ts.map +1 -0
- package/dist/collection-editing.js +53 -0
- package/dist/collection-editing.js.map +1 -0
- package/dist/communication.d.ts +29 -0
- package/dist/communication.d.ts.map +1 -0
- package/dist/communication.js +11 -0
- package/dist/communication.js.map +1 -0
- package/dist/component-definitions.d.ts +1 -0
- package/dist/component-definitions.d.ts.map +1 -1
- package/dist/component-definitions.js +1 -0
- package/dist/component-definitions.js.map +1 -1
- package/dist/data-table.d.ts +229 -5
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +242 -6
- package/dist/data-table.js.map +1 -1
- package/dist/data-workflows.d.ts +70 -0
- package/dist/data-workflows.d.ts.map +1 -0
- package/dist/data-workflows.js +109 -0
- package/dist/data-workflows.js.map +1 -0
- package/dist/dataviz.d.ts +7 -6
- package/dist/dataviz.d.ts.map +1 -1
- package/dist/dataviz.js +7 -6
- package/dist/dataviz.js.map +1 -1
- package/dist/gooey-navigation.d.ts +7 -0
- package/dist/gooey-navigation.d.ts.map +1 -1
- package/dist/gooey-navigation.js +10 -0
- package/dist/gooey-navigation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/media-transport.d.ts +16 -0
- package/dist/media-transport.d.ts.map +1 -0
- package/dist/media-transport.js +17 -0
- package/dist/media-transport.js.map +1 -0
- package/dist/recipes.d.ts +1 -0
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +1 -0
- package/dist/recipes.js.map +1 -1
- package/dist/showcase.d.ts.map +1 -1
- package/dist/showcase.js +3 -1
- package/dist/showcase.js.map +1 -1
- package/dist/social-compositions.d.ts +19 -0
- package/dist/social-compositions.d.ts.map +1 -0
- package/dist/social-compositions.js +33 -0
- package/dist/social-compositions.js.map +1 -0
- package/dist/streaming-answer.d.ts +18 -0
- package/dist/streaming-answer.d.ts.map +1 -0
- package/dist/streaming-answer.js +61 -0
- package/dist/streaming-answer.js.map +1 -0
- package/dist/task-controls.d.ts +31 -0
- package/dist/task-controls.d.ts.map +1 -0
- package/dist/task-controls.js +25 -0
- package/dist/task-controls.js.map +1 -0
- package/dist/toggle-feedback.d.ts +22 -0
- package/dist/toggle-feedback.d.ts.map +1 -0
- package/dist/toggle-feedback.js +32 -0
- package/dist/toggle-feedback.js.map +1 -0
- package/dist/transfer-task.d.ts +13 -0
- package/dist/transfer-task.d.ts.map +1 -0
- package/dist/transfer-task.js +13 -0
- package/dist/transfer-task.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/authoring-tools.md +31 -0
- package/docs/calendar.md +3 -1
- package/docs/catalog-freeze.json +5 -0
- package/docs/chart.md +206 -0
- package/docs/collection-editing.md +83 -0
- package/docs/communication.md +35 -0
- package/docs/data-table.md +56 -6
- package/docs/data-workflows.md +26 -0
- package/docs/generated/component-maturity.md +6 -5
- package/docs/generated/renderer-evidence.json +95 -5
- package/docs/generated/renderer-evidence.md +6 -3
- package/docs/generated/showcase-manifest.json +66 -3
- package/docs/gooey-navigation.md +20 -0
- package/docs/library-gap-analysis.md +4 -2
- package/docs/media-transport.md +37 -0
- package/docs/social-compositions.md +51 -0
- package/docs/streaming-answer.md +57 -0
- package/docs/task-controls.md +38 -0
- package/docs/toggle-feedback.md +56 -0
- package/docs/transfer-task.md +33 -0
- package/docs/usage/README.md +15 -4
- package/docs/usage/components/activity-chip.md +2 -2
- package/docs/usage/components/button.md +39 -0
- package/docs/usage/components/calendar.md +4 -2
- package/docs/usage/components/chart.md +277 -0
- package/docs/usage/components/data-table.md +96 -18
- package/docs/usage/components/screen-layout.md +3 -1
- package/docs/usage/components/search-screen.md +1 -0
- package/docs/usage/components/tabs.md +25 -0
- package/docs/usage/components/toast.md +1 -1
- package/docs/usage/compositions/action-feedback.md +18 -2
- package/docs/usage/compositions/activity-status.md +2 -2
- package/docs/usage/compositions/authoring-tools.md +90 -0
- package/docs/usage/compositions/collection-editing.md +110 -0
- package/docs/usage/compositions/communication.md +100 -0
- package/docs/usage/compositions/data-workflows.md +91 -0
- package/docs/usage/compositions/media-transport.md +87 -0
- package/docs/usage/compositions/social-compositions.md +91 -0
- package/docs/usage/compositions/streaming-answer.md +97 -0
- package/docs/usage/compositions/task-controls.md +93 -0
- package/docs/usage/compositions/toggle-feedback.md +84 -0
- package/docs/usage/compositions/transfer-task.md +86 -0
- package/docs/usage/compositions/web-additions.md +2 -2
- package/docs/usage/screens/common-shell.md +4 -0
- package/package.json +67 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# 스트림 응답과 중지
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-15
|
|
8
|
+
- 근거: [스트림 응답 계약](../../streaming-answer.md), `src/streaming-answer.ts`, 양 renderer `src/streaming-answer.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/스트림 응답과 중지`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
실제 provider가 보내는 응답 delta를 표시하고 중지·재생성·오류를 같은 영역에서 제공할 때 쓴다. 이미 받은 정적 본문은 Text로 보여 준다. 고정 문자열에 글자 재생 타이머를 붙이거나 HTTP 연결 종료만 보고 AI 생성 완료로 판정하는 용도로 쓰지 않는다.
|
|
14
|
+
|
|
15
|
+
## 구성 요소
|
|
16
|
+
|
|
17
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| StreamingAnswer | 실제 스트림과 요청 상태 연결 | [계약](../../streaming-answer.md) |
|
|
20
|
+
| Progress | 첫 delta 전 실제 대기 | [Progress](../components/progress.md) |
|
|
21
|
+
| Button | 생성·재생성·중지 | [Button](../components/button.md) |
|
|
22
|
+
| Text | 실제 본문·상태·오류 | 제품이 문구와 추가 본문 renderer 제공 |
|
|
23
|
+
|
|
24
|
+
## 배치
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
[응답 제목]
|
|
28
|
+
[이전 답변 또는 실제 새 delta 본문]
|
|
29
|
+
[첫 delta 전 대기 표시: 해당 영역 중앙]
|
|
30
|
+
[생성 / 다시 생성] [실제 요청 중 중지]
|
|
31
|
+
[중지 확인 중 / 완료 / 오류: 구성 내부 중앙]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| 바깥 틀 | Stack | 응답 카드·본문 패널 안 | md |
|
|
37
|
+
| 제목·본문 | Text 또는 renderText | 위에서 아래로 | 본문 폭, 실제 줄바꿈 보존 |
|
|
38
|
+
| 최초 대기 | Progress 또는 thinkingIndicator | 구성 내부 중앙 | 기존 Progress 계약 |
|
|
39
|
+
| 행동 | Button | 본문 아래 inline wrap | sm |
|
|
40
|
+
| 상태·오류 | Text | 행동 아래 해당 영역 | 가로·세로 중앙 |
|
|
41
|
+
|
|
42
|
+
## 흐름과 상태
|
|
43
|
+
|
|
44
|
+
1. 실제 answerId와 초기 본문·지역화 문구·`onGenerate` adapter를 제공한다.
|
|
45
|
+
2. adapter는 signal을 실제 요청에 연결하고 `{type:"delta",text}`를 실제 수신 순서로 보낸다. 첫 새 delta 전까지 기존 답변을 남긴다.
|
|
46
|
+
3. 명시적 `{type:"done"}`이 있어야 완료한다. delta 없는 done은 실제 빈 새 응답을 확정하며 이전 답변을 새 완료값으로 쓰지 않는다.
|
|
47
|
+
4. 중지는 abort 요청이다. iterator return과 finally-yield 정리 종료 또는 transport AbortError를 확인할 때까지 중지 확인 상태를 유지한다.
|
|
48
|
+
5. 실패·중지는 마지막 실제 부분 텍스트를 남긴다. 재생성은 시작 시점 previousText·regenerate 정보를 제품 adapter에 넘긴다.
|
|
49
|
+
|
|
50
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| 기본 | 제목·초기 텍스트·생성/재생성 버튼 | 생성은 명시적 버튼 행동 |
|
|
53
|
+
| 진행 중 | thinking→실제 delta·중지 행동 | 같은 생성 버튼 로딩, 중복 요청 차단 |
|
|
54
|
+
| 중지 확인 중 | 보존된 텍스트·확인 중 문구 | 중지 중복 클릭 차단 |
|
|
55
|
+
| 중지됨 | 실제 정리가 끝난 부분 본문 | 상태 안내, 재생성 가능 |
|
|
56
|
+
| 완료 | 명시적으로 완료된 실제 응답 | 빈 응답도 새 결과로 확정 |
|
|
57
|
+
| 실패 | 마지막 실제 부분 응답·중앙 오류 | Web alert / Native alert·live region |
|
|
58
|
+
|
|
59
|
+
## 코드 골격
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
// Web
|
|
63
|
+
import { StreamingAnswer } from "@hjmds/react/streaming-answer";
|
|
64
|
+
<StreamingAnswer answerId={message.id} initialText={message.text} label={answerTitle}
|
|
65
|
+
labels={answerLabels} onGenerate={generateActualProviderAnswer} />;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Native
|
|
70
|
+
import { StreamingAnswer } from "@hjmds/react-native/streaming-answer";
|
|
71
|
+
<StreamingAnswer answerId={message.id} initialText={message.text} label={answerTitle}
|
|
72
|
+
labels={answerLabels} onGenerate={generateActualProviderAnswer} />;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`generateActualProviderAnswer`는 `{signal,previousText,regenerate}`를 받아 실제 provider 연결에서 AsyncIterable을 반환한다. SSE 등의 delta·완료·오류를 제품에서 해석한다. provider의 명시적 완료 전에 iterator만 끝나면 성공으로 만들지 않는다.
|
|
76
|
+
|
|
77
|
+
실행 예제는 Web `showcase/web/src/patterns/StreamingAnswer.stories.tsx`, Native `showcase/native/src/StreamingAnswer.stories.tsx`다. 실제 text/plain URL을 직접 입력해 byte stream을 확인한다. 공유 `showcase/shared/streaming-answer-example.ts`는 plain UTF-8 transport adapter 예제다. AI 인증·모델·프롬프트·SSE protocol 구현·운영 저장은 제품 adapter가 소유한다.
|
|
78
|
+
|
|
79
|
+
## 플랫폼 차이
|
|
80
|
+
|
|
81
|
+
| 항목 | Web | Native |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| 기본 본문 | 줄바꿈·긴 문자열 줄바꿈 Text | Native Text |
|
|
84
|
+
| URL 실행 예제 | fetch body reader | expo/fetch body reader |
|
|
85
|
+
| 상태 전달 | status/alert role | alert·live region |
|
|
86
|
+
| provider 연동 | 제품 adapter가 실제 인증·protocol 해석 | 동일, 실제 기기 스트림 지원 별도 확인 |
|
|
87
|
+
|
|
88
|
+
## 함정
|
|
89
|
+
|
|
90
|
+
- `initialText`는 해당 ID의 초기값이다. 항목 전환은 실제 새 answerId로 표현한다.
|
|
91
|
+
- 재생성 중 새 delta 전 실패하면 기존 본문이 남고, 새 delta 후 실패하면 새 부분 본문이 남는다. 항상 이전 전체 답변으로 되돌리는 계약은 아니다.
|
|
92
|
+
- 중지 요청만으로 서버 작업·과금·영속 저장까지 취소되었다고 보고하지 않는다. 실제 provider adapter의 확인이 필요하다.
|
|
93
|
+
- plain-text 예제의 실제 EOF를 AI/SSE의 명시적 완료 이벤트로 대신 쓰지 않는다.
|
|
94
|
+
|
|
95
|
+
## 접근성과 검증
|
|
96
|
+
|
|
97
|
+
제품이 모든 제목·버튼·상태 문구를 제공한다. 첫 응답 대기는 현재 구성 영역 중앙에 표시하고 실패/중지 상태에서도 본문을 유지한다. 계약 검사는 명시적 완료·빈 재생성·finally-yield 정리를, renderer 검사는 실제 delta·실패 보존·요청 중복·ID 전환을 다룬다. Web UTF-8 stream 검사는 실제 byte decoding의 증거이며 실제 AI provider·Native 기기·게시·제품 적용 완료의 증거는 아니다.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# 선택과 실제 실행
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-15
|
|
8
|
+
- 근거: [작업 선택 계약](../../task-controls.md), `src/task-controls.ts`, 양 renderer의 `src/task-controls.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/분할 실행`, `배포/구성/입력과 작성/비밀번호 규칙 확인`, `배포/구성/탐색과 이동/계정 전환`, `배포/구성/선택과 필터/요금 주기 선택`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
선택만 하는 화면과 선택 뒤 실제 요청을 실행하는 화면의 상태를 혼동하지 않도록 도입했다.
|
|
14
|
+
분할 실행/계정 전환/규칙 확인/요금 선택의 비동기 복구나 해석이 필요할 때 사용한다.
|
|
15
|
+
단순 메뉴·필드·라디오 그룹이면 기존 Menu/PasswordField/RadioGroup을 직접 쓴다.
|
|
16
|
+
|
|
17
|
+
## 구성 요소
|
|
18
|
+
|
|
19
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| SplitAction | 모드 선택과 주요 실행 | [Menu](../components/menu.md) |
|
|
22
|
+
| AccountSwitcher | 확인된 계정과 전환 요청 | [Menu](../components/menu.md) |
|
|
23
|
+
| PasswordRulesField | controlled 비밀번호·규칙 충족 | [PasswordField](../components/password-field.md) |
|
|
24
|
+
| PricingChoice | 실제 요금/혜택·선택·계속 | [RadioGroup](../components/radio-group.md) |
|
|
25
|
+
|
|
26
|
+
## 배치
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
분할 실행: [현재 방식 실행] [방식 메뉴]
|
|
30
|
+
계정 전환: 확인된 현재 계정 → [계정 메뉴]
|
|
31
|
+
비밀번호: 기존 비밀번호 입력 → 규칙 충족 막대 → 규칙별 상태
|
|
32
|
+
요금: 주기별 라디오 카드 → 선택한 실제 가격·혜택 → [계속]
|
|
33
|
+
실패: 구성 내부 중앙 오류 → 기존 행동으로 재시도
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
37
|
+
| --- | --- | --- | --- |
|
|
38
|
+
| 바깥 틀 | Stack/Card | 기존 본문·패널 안 | sm 또는 md |
|
|
39
|
+
| 분할 행동 | Button/Menu | 같은 inline 행 | xs wrap |
|
|
40
|
+
| 비밀번호 | PasswordField/Progress/Text | 위→아래 | sm |
|
|
41
|
+
| 요금 선택 | RadioGroup | 카드 상단 | card presentation |
|
|
42
|
+
| 실패 | Text | 구성 내부 중앙 | 임의 상단 여백 없음 |
|
|
43
|
+
|
|
44
|
+
## 흐름과 상태
|
|
45
|
+
|
|
46
|
+
1. 제품이 현재 확인된 선택·계정, 옵션, 문구를 공급한다.
|
|
47
|
+
2. 모드·요금 선택은 controlled callback만 호출한다. 실제 실행은 주요 버튼에서만 시작한다.
|
|
48
|
+
3. 계정은 메뉴가 닫힌 뒤 실제 세션 callback을 기다리며 현재 accountId는 확정 전까지 유지한다.
|
|
49
|
+
4. 실패하면 선택·입력을 보존하고 다시 실행할 수 있다. 요금 계속은 선택 옵션 snapshot을 전달한다.
|
|
50
|
+
|
|
51
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| 기본 | 현재 선택과 실행 행동 | 기존 Menu/RadioGroup/PasswordField 키보드 계약 |
|
|
54
|
+
| 진행 중 | 실행 버튼 로딩·경쟁 선택 잠금 | 기존 메뉴 dismiss 뒤 실행, 중복 요청 차단 |
|
|
55
|
+
| 실패 | 선택 유지·중앙 오류 | Web alert / Native assertive, 같은 행동 재시도 |
|
|
56
|
+
|
|
57
|
+
## 코드 골격
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// Web
|
|
61
|
+
import { AccountSwitcher } from "@hjmds/react/task-controls";
|
|
62
|
+
<AccountSwitcher accounts={accounts} accountId={confirmedAccountId}
|
|
63
|
+
label={t("switch")} dismissLabel={t("close")} errorLabel={t("switchFailed")}
|
|
64
|
+
onSwitch={switchConfirmedSession} />;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Native
|
|
69
|
+
import { AccountSwitcher } from "@hjmds/react-native/task-controls";
|
|
70
|
+
<AccountSwitcher accounts={accounts} accountId={confirmedAccountId}
|
|
71
|
+
label={t("switch")} dismissLabel={t("close")} errorLabel={t("switchFailed")}
|
|
72
|
+
onSwitch={switchConfirmedSession} />;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
실행 예제: Web `showcase/web/src/patterns/TaskControls.previews.tsx`, Native `showcase/native/src/task-controls-preview.tsx`.
|
|
76
|
+
공개 API를 소비하며 제품이 showcase 소스를 import하지 않는다.
|
|
77
|
+
|
|
78
|
+
## 플랫폼 차이
|
|
79
|
+
|
|
80
|
+
| 항목 | Web | Native |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| 메뉴 | anchored popover | Modal, dismissLabel 필수 |
|
|
83
|
+
| 비밀번호 입력 | 기존 HTML input | 기존 TextInput |
|
|
84
|
+
| 계정 확정·요금 데이터 | 제품 controlled 상태 | 동일 |
|
|
85
|
+
|
|
86
|
+
## 함정
|
|
87
|
+
|
|
88
|
+
- 메뉴를 고른 것을 계정 전환 완료로 표시하지 않는다. 실제 세션 확정 뒤 accountId를 바꾼다.
|
|
89
|
+
- 충족 개수를 비밀번호 보안 강도라고 부르지 않는다. 규칙 test는 pure하고 예외를 던지지 않아야 한다.
|
|
90
|
+
- 가격 formatter가 실제 amountMinor/interval/intervalCount와 일치해야 한다. 할인·월 환산액은 추정하지 않는다.
|
|
91
|
+
- 옵션 선택으로 결제를 시작하지 않는다. 실제 결제·약관·상품 검증은 onContinue 뒤 제품 흐름 책임이다.
|
|
92
|
+
- 다른 엔티티/세션으로 전환할 때 host key를 바꾸고 늦은 제품 API 응답도 제품 상태 계층에서 검증한다.
|
|
93
|
+
- npm 1.21.0 API다. 기기·실제 로그인·결제 검증과 npm 게시를 구분한다.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# 선택 반응 버튼
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-15
|
|
8
|
+
- 근거: [선택 반응 계약](../../toggle-feedback.md), `src/toggle-feedback.ts`, 양 renderer의 `src/toggle-feedback.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/직접 조작과 모션/선택 반응 버튼`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
구독·북마크·좋아요에서 선택 요청과 실제 인원 수의 실패 복구, 국소 모션이 함께 필요할 때 쓴다.
|
|
14
|
+
단순 선택은 Button.selected, 프로필 카드가 필요하면 FollowProfileCard, 전체 축하는 Celebration을 고른다.
|
|
15
|
+
Web의 counter는 기존 AnimatedStatistic과 같은 `@number-flow/react`, Native의 도형은 기존 `react-native-svg`를 사용한다.
|
|
16
|
+
|
|
17
|
+
## 구성 요소
|
|
18
|
+
|
|
19
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| ToggleFeedbackButton | counted selection·실제 mutation·실패 복구 | [Button](../components/button.md) |
|
|
22
|
+
| AnimatedStatistic | 확인된 수 또는 pending 제안 수의 전환 | [Statistic](../components/statistic.md) |
|
|
23
|
+
| SVG/제품 아이콘 슬롯 | ripple/ribbon/burst의 국소 시각 반응 | [선택 반응 계약](../../toggle-feedback.md) |
|
|
24
|
+
|
|
25
|
+
## 배치
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
[아이콘 + 선택 행동] 인원 label / count
|
|
29
|
+
↑ AnimatedStatistic
|
|
30
|
+
오류: 이 구성의 안내 영역 중앙
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| 바깥 틀 | Stack | 카드·상세 본문 안 | gap=sm (`spacing.sm`, 12px) |
|
|
36
|
+
| 행동·수치 | Button · AnimatedStatistic | inline 행, 부족하면 wrap | gap=md (`spacing.md`, 16px), 수치 compact |
|
|
37
|
+
| 아이콘 | 기본 SVG / icon / renderIcon | 버튼 leading | glyph=24px, effect=40px, 중심 유지 |
|
|
38
|
+
| 오류 | Text | 안내 영역 가로·세로 중앙 | 임의 상단 여백 없음 |
|
|
39
|
+
|
|
40
|
+
## 흐름과 상태
|
|
41
|
+
|
|
42
|
+
1. `state={selected,count}`에 서버가 확인한 현재 값을 넣는다.
|
|
43
|
+
2. 선택할 때만 effect를 재생하고, 요청 중 제안 snapshot을 표시한다. 동시에 여러 요청을 보내지 않는다.
|
|
44
|
+
3. `onSelectedChange`는 실제 서버 snapshot으로 제품 state를 갱신한 뒤 resolve한다.
|
|
45
|
+
4. 실패하면 confirmed props로 복구한다. 같은 버튼으로 재시도한다. entityId를 바꾸면 이전 응답을 분리한다.
|
|
46
|
+
|
|
47
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 기본 | 실제 selected와 인원, 초기 선택에는 장식 없음 | 기존 Button 선택/키보드 계약 |
|
|
50
|
+
| 진행 중 | 제안 선택·수치, 버튼 내부 로딩·중복 잠금 | 선택 시만 국소 모션 |
|
|
51
|
+
| 실패 | confirmed 상태·중앙 오류 | Web alert / Native assertive, 같은 행동으로 재시도 |
|
|
52
|
+
| 해제 | selected 해제·인원 감소 | burst/ripple/ribbon 재생 없음 |
|
|
53
|
+
| 환경 정지 | 최종 아이콘과 실제 수치 | reducedMotion·active=false·백그라운드에서 장식 제거 |
|
|
54
|
+
|
|
55
|
+
모션은 `motion.slow` 320ms이며 Web/Native 모두 React 프레임 state를 만들지 않는다.
|
|
56
|
+
제품의 커스텀 아이콘은 `renderIcon({selected,pending})`으로 pending 선택까지 반영할 수 있다.
|
|
57
|
+
|
|
58
|
+
## 코드 골격
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// Web
|
|
62
|
+
import { ToggleFeedbackButton } from "@hjmds/react/toggle-feedback";
|
|
63
|
+
<ToggleFeedbackButton entityId={channel.id} state={confirmedSubscription} effect="ripple"
|
|
64
|
+
locale={locale} labels={subscriptionLabels} renderIcon={renderBrandBell}
|
|
65
|
+
onSelectedChange={async selected => {
|
|
66
|
+
const confirmed = await api.setSubscription(channel.id, selected);
|
|
67
|
+
updateChannelCache(channel.id, confirmed);
|
|
68
|
+
}} />;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// Native
|
|
73
|
+
import { ToggleFeedbackButton } from "@hjmds/react-native/toggle-feedback";
|
|
74
|
+
<ToggleFeedbackButton entityId={post.id} state={confirmedLike} effect="burst"
|
|
75
|
+
locale={locale} labels={likeLabels} renderIcon={renderProductHeart}
|
|
76
|
+
onSelectedChange={async selected => {
|
|
77
|
+
const confirmed = await api.setLike(post.id, selected);
|
|
78
|
+
updatePostCache(post.id, confirmed);
|
|
79
|
+
}} />;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
북마크는 같은 API에 `effect="ribbon"`을 준다. 실제 실행 예제는
|
|
83
|
+
Web `showcase/web/src/patterns/toggle-feedback-preview.tsx`, Native `showcase/native/src/toggle-feedback-preview.tsx`다.
|
|
84
|
+
예시 callback은 명시적 fixture이며 제품 서버나 기기 성능 검증이 아니다.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# 전송 진행과 취소
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.21.0
|
|
7
|
+
- 검토일: 2026-10-15
|
|
8
|
+
- 근거: [실제 전송 계약](../../transfer-task.md), `src/transfer-task.ts`, 양 renderer `src/transfer-task.tsx`
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/전송 진행과 취소`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
실제 다운로드·업로드·작업의 시작/대기/진행·취소·실패 복구를 한 구성으로 제공할 때 쓴다.
|
|
14
|
+
외부에서 관리하는 진행률만 보여 주려면 Progress를 직접 쓴다. TransferList는 두 목록 사이 항목 이동이며 이 작업과 다르다.
|
|
15
|
+
|
|
16
|
+
## 구성 요소
|
|
17
|
+
|
|
18
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
19
|
+
| --- | --- | --- |
|
|
20
|
+
| TransferTask | 실제 작업의 시작·취소·복구 | [계약](../../transfer-task.md) |
|
|
21
|
+
| Button | 원래 크기를 유지하는 실행/취소 | [Button](../components/button.md) |
|
|
22
|
+
| Progress | 같은 host의 미정→실제 분모 표시 | [Progress](../components/progress.md) |
|
|
23
|
+
|
|
24
|
+
## 배치
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
[작업 시작] [진행 중 취소]
|
|
28
|
+
같은 Progress: 대기 → 실제 완료량/알려진 전체량
|
|
29
|
+
원시 전송량
|
|
30
|
+
종료 상태: 구성 중앙 → 같은 시작 행동으로 재시도
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| 바깥 틀 | Stack | 기존 본문·패널 안 | md |
|
|
36
|
+
| 행동 | Button | inline 시작→취소 | sm wrap |
|
|
37
|
+
| 진행 | Progress/Text | 행동 아래 | 기존 Progress 규격 |
|
|
38
|
+
| 종료 상태 | Text | 구성 내부 중앙 | 임의 상단 margin 없음 |
|
|
39
|
+
|
|
40
|
+
## 흐름과 상태
|
|
41
|
+
|
|
42
|
+
1. onTransfer에서 실제 작업을 시작하고 signal을 실제 취소 경로로 전달한다.
|
|
43
|
+
2. 실제 transport event에서 report를 호출한다. 분모가 미정이면 total:null을 쓴다.
|
|
44
|
+
3. 실제 Promise 종료로 성공/실패를 표시한다. 100%만으로 성공을 만들지 않는다.
|
|
45
|
+
4. 취소 요청은 별도 상태다. AbortError로 확인한 뒤 취소 완료로 표시하고 실제 부분 데이터를 제품에서 정리한다.
|
|
46
|
+
|
|
47
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 기본 | 시작 버튼 | 기존 Button 계약 |
|
|
50
|
+
| 진행 중 | 같은 버튼 로딩·취소·실제 Progress | 중복 요청 차단·실제 값 접근성 |
|
|
51
|
+
| 실패 | 마지막 전송량 유지·중앙 오류 | Web alert / Native live region, 시작으로 재시도 |
|
|
52
|
+
|
|
53
|
+
## 코드 골격
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// Web
|
|
57
|
+
import { TransferTask } from "@hjmds/react/transfer-task";
|
|
58
|
+
<TransferTask labels={labels} formatProgress={formatBytes}
|
|
59
|
+
onTransfer={async ({ signal, report }) => sendActualFile({ signal, onProgress: report })} />;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// Native
|
|
64
|
+
import { TransferTask } from "@hjmds/react-native/transfer-task";
|
|
65
|
+
<TransferTask labels={labels} formatProgress={formatBytes}
|
|
66
|
+
onTransfer={async ({ signal, report }) => sendActualFile({ signal, onProgress: report })} />;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
실제 수신 예제는 각 플랫폼 TransferTask.stories.tsx와 shared/transfer-task-example.ts다. 제품은 showcase를 import하지 않는다.
|
|
70
|
+
|
|
71
|
+
## 플랫폼 차이
|
|
72
|
+
|
|
73
|
+
| 항목 | Web | Native |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| 예제 네트워크 | fetch body stream | 기존 Expo의 expo/fetch stream |
|
|
76
|
+
| 취소 | AbortSignal | 동일, 실제 adapter 지원 필수 |
|
|
77
|
+
| 접근성 | HTML progress·alert/status | 기존 Progress/live region |
|
|
78
|
+
|
|
79
|
+
## 함정
|
|
80
|
+
|
|
81
|
+
- setInterval로 완료량을 증가시키지 않는다. total을 모르면 임의 분모를 넣지 않는다.
|
|
82
|
+
- 실제 작업 Promise를 기다리지 않고 성공으로 바꾸지 않는다.
|
|
83
|
+
- 취소 클릭만으로 취소 완료를 표시하지 않는다. abort 요청과 실제 AbortError를 구분한다.
|
|
84
|
+
- 압축 Content-Length와 decoded byte 수를 같은 단위라고 가정하지 않는다.
|
|
85
|
+
- 부분 파일 정리·요청 멱등성·저장/서버 확정은 실제 adapter 소유다.
|
|
86
|
+
- npm 1.21.0 API다. 수신 성공을 OS 저장·운영 업로드·기기 검증 완료로 표현하지 않는다.
|
|
@@ -79,8 +79,8 @@ Native 화면에서 같은 일이 필요하면 이 구성을 옮기지 않는다
|
|
|
79
79
|
| 상태 | 모습 | 포커스·알림 |
|
|
80
80
|
| --- | --- | --- |
|
|
81
81
|
| 기본 | ColorPicker는 `value` 색, 워터마크 타일, Affix는 일반 위치(`data-affixed="false"`) | — |
|
|
82
|
-
| 진행 중(제품 연결)
|
|
83
|
-
| 실패(제품 연결)
|
|
82
|
+
| 진행 중 | (제품 비동기 저장 연결 시) 저장 버튼 `loading`(Watermark·Affix 안). ColorPicker는 요청이 없다 | 포커스는 버튼에 남는다 |
|
|
83
|
+
| 실패 | (제품 비동기 저장 연결 시) 저장 실패 Notice danger, 버튼 다시 활성. Affix 고정 중이면 버튼은 그대로 위에 붙어 있다 | Notice `danger`는 `role="alert"` |
|
|
84
84
|
| HEX 잘못 입력 | `labels.invalid` 문구가 오류로 보이고 외부 값은 그대로 | 오류 문구는 alert |
|
|
85
85
|
| 프리셋 선택됨 | 선택한 견본 테두리가 primary 색, `aria-pressed="true"` | — |
|
|
86
86
|
| `disabled` | fieldset 전체 흐림(opacity 0.5), 입력·프리셋 막힘 | 포커스 불가 |
|
|
@@ -118,3 +118,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
118
118
|
- 초기 로딩만 본문 교체; 갱신 실패는 notice로 본문 유지.
|
|
119
119
|
- 이 화면에는 전송 행동이 없어 `실패와 초안 복구` 스토리를 2026-10-06 뺐다. 갱신 실패 복구는 `연결 복구`로 확인한다.
|
|
120
120
|
- Storybook은 실제 서버·OS 권한·라우터 연동 증거가 아니다. 기본·다크·큰 글자와 실패/복구를 각각 확인한다.
|
|
121
|
+
|
|
122
|
+
### 숨긴 페이지 이름과 가운데 문맥
|
|
123
|
+
|
|
124
|
+
2026-10-14 야잘알에서 페이지 이름이 탭·필터와 중복되고 날짜가 왼쪽으로 밀린 사용자 피드백으로 `ScreenLayout`의 `titleVisibility="hidden"`과 `headerAlignment="center"`를 추가했다. 페이지 이름은 화면 식별값으로 유지하고 날짜·선수명 같은 실제 문맥만 표시할 수 있다. Native는 좌우 행동 영역의 큰 폭을 양쪽에 동일하게 확보해 중앙을 고정한다. `ListDetailScreen.detail`도 두 옵션을 전달한다. 기본값 visible/start는 기존 배치를 유지한다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hjmds/design-contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -253,6 +253,12 @@
|
|
|
253
253
|
"import": "./dist/data-table.js",
|
|
254
254
|
"default": "./dist/data-table.js"
|
|
255
255
|
},
|
|
256
|
+
"./components/chart": {
|
|
257
|
+
"types": "./dist/chart.d.ts",
|
|
258
|
+
"react-native": "./dist/chart.js",
|
|
259
|
+
"import": "./dist/chart.js",
|
|
260
|
+
"default": "./dist/chart.js"
|
|
261
|
+
},
|
|
256
262
|
"./components/date-picker": {
|
|
257
263
|
"types": "./dist/date-picker.d.ts",
|
|
258
264
|
"react-native": "./dist/date-picker.js",
|
|
@@ -726,6 +732,66 @@
|
|
|
726
732
|
"react-native": "./dist/activity-chip.js",
|
|
727
733
|
"import": "./dist/activity-chip.js",
|
|
728
734
|
"default": "./dist/activity-chip.js"
|
|
735
|
+
},
|
|
736
|
+
"./collection-editing": {
|
|
737
|
+
"types": "./dist/collection-editing.d.ts",
|
|
738
|
+
"react-native": "./dist/collection-editing.js",
|
|
739
|
+
"import": "./dist/collection-editing.js",
|
|
740
|
+
"default": "./dist/collection-editing.js"
|
|
741
|
+
},
|
|
742
|
+
"./data-workflows": {
|
|
743
|
+
"types": "./dist/data-workflows.d.ts",
|
|
744
|
+
"react-native": "./dist/data-workflows.js",
|
|
745
|
+
"import": "./dist/data-workflows.js",
|
|
746
|
+
"default": "./dist/data-workflows.js"
|
|
747
|
+
},
|
|
748
|
+
"./task-controls": {
|
|
749
|
+
"types": "./dist/task-controls.d.ts",
|
|
750
|
+
"react-native": "./dist/task-controls.js",
|
|
751
|
+
"import": "./dist/task-controls.js",
|
|
752
|
+
"default": "./dist/task-controls.js"
|
|
753
|
+
},
|
|
754
|
+
"./media-transport": {
|
|
755
|
+
"types": "./dist/media-transport.d.ts",
|
|
756
|
+
"react-native": "./dist/media-transport.js",
|
|
757
|
+
"import": "./dist/media-transport.js",
|
|
758
|
+
"default": "./dist/media-transport.js"
|
|
759
|
+
},
|
|
760
|
+
"./transfer-task": {
|
|
761
|
+
"types": "./dist/transfer-task.d.ts",
|
|
762
|
+
"react-native": "./dist/transfer-task.js",
|
|
763
|
+
"import": "./dist/transfer-task.js",
|
|
764
|
+
"default": "./dist/transfer-task.js"
|
|
765
|
+
},
|
|
766
|
+
"./authoring-tools": {
|
|
767
|
+
"types": "./dist/authoring-tools.d.ts",
|
|
768
|
+
"react-native": "./dist/authoring-tools.js",
|
|
769
|
+
"import": "./dist/authoring-tools.js",
|
|
770
|
+
"default": "./dist/authoring-tools.js"
|
|
771
|
+
},
|
|
772
|
+
"./social-compositions": {
|
|
773
|
+
"types": "./dist/social-compositions.d.ts",
|
|
774
|
+
"react-native": "./dist/social-compositions.js",
|
|
775
|
+
"import": "./dist/social-compositions.js",
|
|
776
|
+
"default": "./dist/social-compositions.js"
|
|
777
|
+
},
|
|
778
|
+
"./communication": {
|
|
779
|
+
"types": "./dist/communication.d.ts",
|
|
780
|
+
"react-native": "./dist/communication.js",
|
|
781
|
+
"import": "./dist/communication.js",
|
|
782
|
+
"default": "./dist/communication.js"
|
|
783
|
+
},
|
|
784
|
+
"./toggle-feedback": {
|
|
785
|
+
"types": "./dist/toggle-feedback.d.ts",
|
|
786
|
+
"react-native": "./dist/toggle-feedback.js",
|
|
787
|
+
"import": "./dist/toggle-feedback.js",
|
|
788
|
+
"default": "./dist/toggle-feedback.js"
|
|
789
|
+
},
|
|
790
|
+
"./streaming-answer": {
|
|
791
|
+
"types": "./dist/streaming-answer.d.ts",
|
|
792
|
+
"react-native": "./dist/streaming-answer.js",
|
|
793
|
+
"import": "./dist/streaming-answer.js",
|
|
794
|
+
"default": "./dist/streaming-answer.js"
|
|
729
795
|
}
|
|
730
796
|
},
|
|
731
797
|
"files": [
|