@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,85 @@
|
|
|
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
|
+
작성 중인 입력을 보존한 채 선택적 도구를 펼쳐야 할 때 쓴다. 별도 신규 wrapper API가 아니라 아래 공개 컴포넌트의 조합 규격이다.
|
|
16
|
+
|
|
17
|
+
## 구성 요소
|
|
18
|
+
|
|
19
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| TextField · Collapsible · SegmentedControl | 입력·표현·행동의 역할 분리 | [입력](../components/field.md), [접기](../components/collapsible.md), [단일 선택](../components/segmented-control.md) |
|
|
22
|
+
|
|
23
|
+
## 배치
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
[작성 중인 입력: 항상 유지]
|
|
27
|
+
[도구 펼침/접힘 버튼 · 현재 선택]
|
|
28
|
+
[기본 | 인용 | 강조] 열린 동안만 표시
|
|
29
|
+
[선택한 표현 안내: 항상 유지]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| 바깥 틀 | Stack | 화면의 본문 흐름 | gap md=16px, 전체 폭 |
|
|
35
|
+
| 도구 | Collapsible | 입력 바로 아래 | trigger 최소 높이44px, 공개 recipe 사용 |
|
|
36
|
+
| 선택 | SegmentedControl presentation=pills | 열린 도구 내용 | 최소 높이44px, 항목 간8px, 좁으면 줄바꿈 |
|
|
37
|
+
| 결과 안내 | Text | 도구 아래 | Web status/Native polite |
|
|
38
|
+
|
|
39
|
+
## 흐름과 상태
|
|
40
|
+
|
|
41
|
+
1. 입력→도구 펼침→단일 표현 선택→접힘; 입력은 도구 바깥에 유지. 선택 값도 접히는 내용의 바깥에서 소유한다.
|
|
42
|
+
2026-10-07 실제 조작에서 개별 selected Button이 독립 토글로 안내되는 것을 확인해,
|
|
43
|
+
묶음 이름·radio 의미·방향키 이동을 제공하는 SegmentedControl을 사용한다.
|
|
44
|
+
여러 서식을 동시에 켜는 편집기는 ToggleGroup을 쓰며 이 단일 선택 예제를 복사하지 않는다.
|
|
45
|
+
2. 서버 응답·파일 권한·문구·브랜드는 제품이 전달한다. Showcase의 예제 응답과 고정 데이터를 가져오지 않는다.
|
|
46
|
+
|
|
47
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| 기본 | 접힌 도구: 입력·펼침 버튼·현재 선택 안내 | 숨긴 선택지는 탐색 대상에서 제거 |
|
|
50
|
+
| 진행 중 | 도구를 펼쳐 단일 선택; 비동기 pending 없음 | Web Tab으로 선택 진입, 방향키로 변경 |
|
|
51
|
+
| 선택 변경 | 정확히 하나 선택, 안내 갱신 | 초안 유지, 선택 값은 바깥 상태에 저장 |
|
|
52
|
+
| 다시 접힘/펼침 | 마지막 선택과 초안 복원 | Web 접기 버튼에 포커스 유지 |
|
|
53
|
+
| 실패 | 예제에 서버 작업 없음; 제품 저장 실패는 별도 연결 | 실제 저장 실패 시에도 초안·선택은 보존 |
|
|
54
|
+
|
|
55
|
+
## 코드 골격
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
// Web
|
|
59
|
+
<TextField label={draftLabel} value={draft} onValueChange={setDraft} />
|
|
60
|
+
<Collapsible open={open} onOpenChange={setOpen} trigger={toolsLabel}>
|
|
61
|
+
<SegmentedControl label={formatLabel} presentation="pills" items={formats}
|
|
62
|
+
value={format} onValueChange={setFormat} />
|
|
63
|
+
</Collapsible>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
// Native
|
|
68
|
+
<TextField label={draftLabel} value={draft} onValueChange={setDraft} />
|
|
69
|
+
<Collapsible open={open} onOpenChange={setOpen} trigger={toolsLabel}>
|
|
70
|
+
<SegmentedControl label={formatLabel} presentation="pills" items={formats}
|
|
71
|
+
value={format} onValueChange={setFormat} />
|
|
72
|
+
</Collapsible>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native` entry를 쓴다.
|
|
76
|
+
초안과 선택 값은 접히는 내용 바깥에서 소유한다. 예제는 선택·입력 보존을 보여 주며,
|
|
77
|
+
본문 서식 변환·영구 저장·실패 복구는 구현하지 않는다. 이 동작이 필요한 제품은
|
|
78
|
+
실제 편집 모델과 저장 상태를 연결하고 따로 검증한다.
|
|
79
|
+
|
|
80
|
+
## 플랫폼 차이
|
|
81
|
+
|
|
82
|
+
| 항목 | Web | Native |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
|
|
85
|
+
| 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# 날짜 직접 입력
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [날짜 입력 조사](../../../../../docs/qa/2026-10-07-date-entry-reference.md), `src/date-entry.ts`
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/날짜 직접 입력`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
사용자가 알고 있는 날짜를 직접 입력할 때 쓴다. DatePicker는 달력에서 날짜를 선택하는 API이므로
|
|
16
|
+
부분 연도·월 이름을 편집하는 초안을 담지 않는다. 새 DateEntry는 Field의 optional extension이며
|
|
17
|
+
기존 TextField를 합성한다. 달력·언어·시간대 계산은 제품이 맡는 기존 Calendar 경계를 유지한다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| DateEntry | 날짜 초안·필드 순서·오류 연결 | 이 문서 |
|
|
24
|
+
| TextField | 각 날짜 조각 입력·포커스·오류 | [Field](../components/field.md) |
|
|
25
|
+
| Button | 제품의 확인/저장 | [Button](../components/button.md) |
|
|
26
|
+
|
|
27
|
+
## 배치
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
그룹 이름
|
|
31
|
+
설명(선택)
|
|
32
|
+
[연도] [월] [일] ← 제품 order
|
|
33
|
+
오류 안내 한 번 ← 입력 위, 해당 칸 테두리로 연결
|
|
34
|
+
[확인] ← 제품 행동
|
|
35
|
+
결과/서버 상태 ← 제품 소유
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
39
|
+
| --- | --- | --- | --- |
|
|
40
|
+
| 바깥 틀 | Web fieldset / Native View | 제품 폼 안 | 최소 폭 0, 테두리 없는 그룹 |
|
|
41
|
+
| 그룹 이름 | Web legend / Native Text label | 맨 위 | 아래 spacing.sm 12 |
|
|
42
|
+
| 입력 | TextField 3개 | order 순서 | 간격 spacing.md 16, Web 최소 10ch 자동 줄바꿈, Native 기준 spacing.xxxl × 3 × textScale |
|
|
43
|
+
| 오류 | 그룹 안내 + TextField invalid | 입력 위 한 번 | 해당 필드만 오류 테두리, Web 설명 ID·Native hint 연결 |
|
|
44
|
+
| 확인 | 제품 Button | 그룹 다음 | DateEntry 내부에 저장 버튼을 넣지 않음 |
|
|
45
|
+
|
|
46
|
+
## 흐름과 상태
|
|
47
|
+
|
|
48
|
+
1. value는 `{year, month, day}` 원문 문자열이다. onValueChange는 한 필드만 바꾼 새 초안을 전달한다.
|
|
49
|
+
2. order는 세 필드가 중복 없이 한 번씩 나온 배열이다. RTL만으로 날짜 순서를 추측하지 않는다.
|
|
50
|
+
3. required 기본 false. 전체 공백은 optional이면 오류가 없고 일부만 비면 optional이어도 incomplete다.
|
|
51
|
+
4. 모든 조각이 있으면 parse가 valid/value 또는 incomplete·invalid/code/fields를 반환한다. 입력을
|
|
52
|
+
자동 정규화하거나 다음 필드로 자동 이동하지 않는다. parse는 동결된 복사본을 받는다.
|
|
53
|
+
5. showErrors 기본 false. 제품은 제출/blur 정책에 맞춰 켠다. 오류 문구는 formatIssue로 지역화한다.
|
|
54
|
+
6. 목적이 birthdate일 때만 날짜 조각 자동완성을 요청한다. 일반 date는 off다. 실제 자동완성은 OS/브라우저 소유다.
|
|
55
|
+
7. monthInput 기본 text는 월 이름 입력을 허용한다. 숫자만 받는 제품은 numeric을 명시한다.
|
|
56
|
+
|
|
57
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| 기본 | 세 입력과 설명 | 자동 초점 이동 없음 |
|
|
60
|
+
| 진행 중 | 미완성 원문 유지 | 일반 Tab/터치로 이동, validation 노출은 제품 제어 |
|
|
61
|
+
| 실패 | 그룹 오류 한 번과 해당 필드의 오류 테두리 | Web 연결된 오류 설명, Native 그룹+조각 이름·오류 hint, iOS 그룹 안내 한 번 |
|
|
62
|
+
| 확인 | 제품에 전달한 valid 값 | 확인 UI/서버 저장은 제품 소유 |
|
|
63
|
+
| 비활성·읽기 전용 | 편집 차단 | 호스트가 늦게 edit 이벤트를 보내도 callback 차단 |
|
|
64
|
+
|
|
65
|
+
## 코드 골격
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// Web
|
|
69
|
+
import { DateEntry } from "@hjmds/react/date-entry";
|
|
70
|
+
// Resolver: @hjmds/design-contracts/date-entry.
|
|
71
|
+
<DateEntry value={draft} onValueChange={setDraft}
|
|
72
|
+
order={["year", "month", "day"]}
|
|
73
|
+
labels={{ label: t("date.label"), year: t("date.year"), month: t("date.month"), day: t("date.day") }}
|
|
74
|
+
required showErrors={submitted} parse={parseProductDate} formatIssue={formatDateIssue}
|
|
75
|
+
onBlur={part => markTouched(part)} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Native
|
|
80
|
+
import { DateEntry } from "@hjmds/react-native/date-entry";
|
|
81
|
+
<DateEntry value={draft} onValueChange={setDraft} order={["year", "month", "day"]}
|
|
82
|
+
labels={localizedLabels} parse={parseProductDate} formatIssue={formatDateIssue}
|
|
83
|
+
required showErrors={submitted} />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
parse는 순수 함수다. 네트워크 요청·Date.now·초안 변경을 넣지 않는다. 날짜 체계·허용 범위·
|
|
87
|
+
로케일별 숫자와 월 이름은 제품이 결정한다. valid.value는 제품이 정한 날짜 문자열이며
|
|
88
|
+
resolveDateEntryDraft는 실제 날짜 유효성을 다시 계산하지 않는다. 저장 실패 시 draft를 유지한다.
|
|
89
|
+
|
|
90
|
+
## 플랫폼 차이
|
|
91
|
+
|
|
92
|
+
| 항목 | Web | Native |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| 그룹 | fieldset/legend | 각 필드의 접근성 이름에 그룹 포함 |
|
|
95
|
+
| 자동완성 | bday 조각 | Android birthdate 조각 + iOS 명시적 textContentType |
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
Web은 fieldset/legend와 필드별 label·aria-describedby를 사용한다. Native는 그룹 이름을 각 입력의
|
|
99
|
+
접근성 이름에 포함하며 세 입력을 하나의 접근성 노드로 합치지 않는다. Native 날짜 자동완성은
|
|
100
|
+
Android의 `birthdate-year/month/day`와 iOS의 명시적 `birthdateYear/Month/Day` content type을 연결한다. Web은 `bday-year/month/day`다. Web className, 양쪽 layoutStyle을 지원한다.
|
|
101
|
+
|
|
102
|
+
## 함정
|
|
103
|
+
|
|
104
|
+
- 서버 저장 완료와 valid 초안을 구분한다. 편집하면 이전 확인 결과를 무효화한다.
|
|
105
|
+
- Showcase의 Gregorian/영어 월 파서는 예제 정책이며 HJM 기본 파서가 아니다.
|
|
106
|
+
- Calendar로 부분 입력을 강제로 변환하거나 NumberField로 교체하면 원문 보존 계약이 깨진다.
|
|
107
|
+
- Web 390px 다크/RTL/2배 글자와 iOS 2배 글자에서 오류 복구를 확인했다. 제품 팔레트·스크린리더·자동완성 실제 검증은 남아 있다. 실험 등록은 승격·게시가 아니다.
|
|
108
|
+
- Native 화면 호스트는 키보드 inset을 처리하는 ScrollView 등으로 하단 행동에 접근할 수 있어야 한다. DateEntry 내부에 중첩 스크롤을 만들지 않는다.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# 날짜와 시각 선택
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 실험
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 미게시(1.14.0 이후)
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [Magic 조사](../../../../../docs/qa/2026-10-07-reference-parallel-b.md), 양 Showcase `date-time-selection-preview.tsx`, shared `date-time-selection.ts`
|
|
9
|
+
- 스토리북: `실험/구성/선택과 필터/날짜와 시각 선택`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
기록·알림의 날짜 하나와 하루 안의 시각을 함께 고를 때 쓴다. [기존 시간 선택](time-selection.md)에
|
|
14
|
+
날짜 선택을 붙이는 구성이다. 서버 예약·시간대 변환을 담당하는 새 DateTimePicker API가 아니다.
|
|
15
|
+
[Magic 글](https://magicui.design/blog/time-and-date-picker)은 공개 interface와 설명을 제공하며
|
|
16
|
+
동작하는 picker 구현은 제공하지 않는다. 설명의 구성 아이디어만 기존 API에 연결했다.
|
|
17
|
+
|
|
18
|
+
## 구성 요소
|
|
19
|
+
|
|
20
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Provider | 10개 테마 순회와 필드 표현 상속 | [프로필 계약](../../design-profile.md) |
|
|
23
|
+
| DatePicker | ISO 날짜 하나와 표시 달, 접근 가능한 달력 표면 | [날짜 선택](../components/date-picker.md) |
|
|
24
|
+
| Select ×2 | 시·분 선택 | [선택 목록](../components/select.md) |
|
|
25
|
+
| Section·Container·Stack | 제목·전체 흐름·폭 | [구역](../components/section.md) · [컨테이너](../components/container.md) · [스택](../components/stack.md) |
|
|
26
|
+
| Button | 확인·다시 선택 | [버튼](../components/button.md) |
|
|
27
|
+
| Text·Notice | 선택값·진행·실패·확인 결과 | [텍스트](../components/text.md) · [알림](../components/notice.md) |
|
|
28
|
+
| Collapsible | Storybook의 결정적 응답/실패 검증 도구 | [접기](../components/collapsible.md) |
|
|
29
|
+
|
|
30
|
+
## 배치
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Section 제목/설명 → 선택 테마/다음 테마
|
|
34
|
+
날짜 트리거 → 달력(선택/지우기/이전·다음 달/닫기)
|
|
35
|
+
시 Select → 분 Select
|
|
36
|
+
같은 선택값 → 시간대/서버는 제품 소유 안내
|
|
37
|
+
확인(주 행동) → 다시 고르기(보조)
|
|
38
|
+
실패/확인 결과
|
|
39
|
+
검증 도구: 미리보기 응답 받기 → 다음 확인 실패
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
43
|
+
| --- | --- | --- | --- |
|
|
44
|
+
| 바깥 틀 | Container·Section·Stack | 세로 문서 흐름; Native ScrollView | `gutter="compact"` 16, `spacing.md` 16. Native 위아래 `spacing.lg` 20 |
|
|
45
|
+
| 날짜 | DatePicker | 제목 아래 | 기존 [날짜 필드 배치](../components/date-picker.md#배치); Web popover/Native Sheet |
|
|
46
|
+
| 시각 | Select | 날짜 아래, 시→분 | `control.fieldHeight` 44, 사이 `spacing.md` 16 |
|
|
47
|
+
| 주·보조 행동 | Button | 선택 상태 아래 | 확인→다시 고르기, `control.buttonHeight.medium` 44 기본. 고정하지 않음 |
|
|
48
|
+
| 결과 | Notice | 행동 아래 | 실패는 danger, 확인은 success. 위 `spacing.md` 16 |
|
|
49
|
+
|
|
50
|
+
## 흐름과 상태
|
|
51
|
+
|
|
52
|
+
1. 날짜를 고른다. 테마를 순회해도 같은 값·표시 달·진행 요청을 유지한다. 달 이동은 표시 달만 바꾸며 이미 고른 날짜·시각을 지우지 않는다.
|
|
53
|
+
2. 시0–23와 분0–59를 고른다. 셋 중 하나라도 없으면 확인은 비활성이다. 어느 값을 바꾸면 이전 결과를 지운다.
|
|
54
|
+
3. 선택 확인 후 진행 상태 동안 날짜·시·분·재설정·실패 예약을 잠근다. 진행 상태는 제품 mutation이 소유한다.
|
|
55
|
+
4. 미리보기에서는 응답 받기로 현재 요청의 선택값을 확정한다. 실패를 예약했다면 값이 남은 실패 상태가 된다.
|
|
56
|
+
5. 실패 후 다시 확인하고 응답 받기로 복구한다. 다시 고르기는 모든 선택과 결과를 비운다.
|
|
57
|
+
|
|
58
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| 기본 | 세 선택값 없음·확인 비활성 | 날짜·시·분 모두 필수 이름. 셀 disabled는 API 계약으로 제어 |
|
|
61
|
+
| 진행 중 | 확인 버튼 loading·입력/재설정 잠김 | placeholder를 서버 성공으로 바꾸지 않음; 이미 고른 값 유지 |
|
|
62
|
+
| 실패 | danger Notice·다시 확인 | 같은 날짜·시·분 보존; Web alert·Native assertive; Web 응답 뒤 확인 버튼으로 포커스 복귀 |
|
|
63
|
+
| 성공 | success Notice·확정한 날짜와 시각 | 값 변경 시 이전 결과 제거. 실제 예약·서버 저장을 뜻하지 않음 |
|
|
64
|
+
| 비활성 | 선택 트리거/행동 비활성 | CSS pointer-events로만 차단하지 않음 |
|
|
65
|
+
|
|
66
|
+
## 코드 골격
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Web: 날짜·시각·시간대 정책과 서버 응답은 제품 소유다.
|
|
70
|
+
import { DatePicker } from "@hjmds/react/date-picker";
|
|
71
|
+
import { Select } from "@hjmds/react/forms";
|
|
72
|
+
<DatePicker descriptor={{ grid, label: dateLabel, placeholder, displayValue: date,
|
|
73
|
+
selectedDate: date, onSelectionChange: setDate, focusedMonth: month,
|
|
74
|
+
onFocusedMonthChange: setMonth, disabled: busy }} monthLabel={monthLabel}
|
|
75
|
+
composeAccessibleName={composeAccessibleName} clearLabel={clearLabel} closeLabel={closeLabel} />
|
|
76
|
+
<Select label={hourLabel} placeholder={hourPlaceholder} emptySelectionLabel={hourClearLabel}
|
|
77
|
+
items={hours} selectedKey={hour} onSelectionChange={setHour} disabled={busy} />
|
|
78
|
+
<Select label={minuteLabel} placeholder={minutePlaceholder} emptySelectionLabel={minuteClearLabel}
|
|
79
|
+
items={minutes} selectedKey={minute} onSelectionChange={setMinute} disabled={busy} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// Native: 같은 날짜 descriptor, 시간 목록은 닫기 문구도 공급한다.
|
|
84
|
+
import { DatePicker } from "@hjmds/react-native/date-picker";
|
|
85
|
+
import { Select } from "@hjmds/react-native/forms";
|
|
86
|
+
<DatePicker descriptor={descriptor} monthLabel={monthLabel} composeAccessibleName={composeAccessibleName}
|
|
87
|
+
clearLabel={clearLabel} closeLabel={closeLabel} />
|
|
88
|
+
<Select label={hourLabel} placeholder={hourPlaceholder} dismissLabel={hourDismissLabel}
|
|
89
|
+
items={hours} selectedKey={hour} onSelectionChange={setHour} disabled={busy} />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 플랫폼 차이
|
|
93
|
+
|
|
94
|
+
| 항목 | Web | Native |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| 날짜 표면 | 필드에 붙은 popover | Sheet |
|
|
97
|
+
| 시각 표면 | listbox popover | modal sheet |
|
|
98
|
+
| 상태 | Text role=status | Showcase PatternStatus의 Android live region/iOS announce 보완. 제품은 공개Text/Notice와 같은 정책을 연결 |
|
|
99
|
+
| 날짜 키보드 | Calendar roving focus, 달 경계 callback은 제품 설정 | touch/AT, 실기기 확인 별도 |
|
|
100
|
+
|
|
101
|
+
## 함정
|
|
102
|
+
|
|
103
|
+
제품 문구는 제품의 i18n으로 공급한다.
|
|
104
|
+
Showcase shared fixture/검증 도구를 제품에서 import하지 않는다. 이 화면은 날짜를 기기 시간대의
|
|
105
|
+
Date timestamp로 암묵 변환하지 않는다. 민간 날짜·시간대·DST·예약 허용 범위·로캘·전송 값은
|
|
106
|
+
제품 계약에서 결정한다. 직접 CSS 색/모서리나 새 날짜 라이브러리를 이 구성에 추가하지 않는다.
|
|
107
|
+
|
|
108
|
+
ISO 날짜와 시각을 한 줄에 표시할 때는 제품 display 문자열에 LTR isolate를 적용해 RTL에서도
|
|
109
|
+
날짜→시각 순서를 유지한다. 표시용 Unicode 제어 문자를 원래 civil/전송 값에 넣지 않는다.
|
|
110
|
+
이는 이 실험의 ISO 표시 선택이며 실제 제품은 자신의 locale format을 공급한다.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# 테마 조합
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 실험
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 미게시(1.14.0 이후)
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [프로필 계약](../../design-profile.md), [조사와 QA](../../../../../docs/qa/2026-10-07-design-profile-research.md), 두 Showcase `design-profile-preview.tsx`
|
|
9
|
+
- 스토리북: `실험/구성/비교와 검증/테마 조합`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
같은 기능에 10가지 표현을 적용하고, 앱 소유 테마 설정을 넣었을 때 네 단계의 전파와 상태 유지를 검토할 때 쓴다.
|
|
14
|
+
|
|
15
|
+
## 구성 요소
|
|
16
|
+
|
|
17
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
18
|
+
| --- | --- | --- |
|
|
19
|
+
| Provider | 한 번 선택한 프로필 상속 | [프로필 계약](../../design-profile.md) |
|
|
20
|
+
| SegmentedControl | 테마/기간 선택 | [선택 입력](../components/segmented-control.md) |
|
|
21
|
+
| OverviewScreen | 도구·목록·주 행동 | [목록 화면](../components/overview-screen.md) |
|
|
22
|
+
| Tabs | 프로필 상속/명시 표시와 방문한 패널의 초안 유지 | [탭](../components/tabs.md) |
|
|
23
|
+
| Card | 무늬 위의 표면 질감과 초안 | [카드](../components/card.md) |
|
|
24
|
+
| Asset | 같은 그림의 프로필 둥근 액자와 명시한 정사각·원형 비교 | [자산](../components/asset.md) |
|
|
25
|
+
| Heading | 선택 테마의 5단계 제목 크기 | [제목](../components/heading.md) |
|
|
26
|
+
| BottomCTA | 저장·실패 재현과 위쪽 그림자 | [하단 행동](../components/bottom-cta.md) |
|
|
27
|
+
| Popover(Web) | 비모달 초안과 같은 프로필 순회 | [팝오버](../components/popover.md) |
|
|
28
|
+
| Dialog·Sheet | 열린 초안과 같은 프로필 순회 | [대화상자](../components/dialog.md) · [패널](../components/sheet.md) |
|
|
29
|
+
| Notice·Skeleton·Toast | 알림·로딩·확정 후 피드백의 모서리/그림자 | [알림](../components/notice.md) · [로딩](../components/skeleton.md) · [토스트](../components/toast.md) |
|
|
30
|
+
| ContentTransition | 실제 저장 상태 전환 | [내용 전환](../components/content-transition.md) |
|
|
31
|
+
| Collapsible | 전체 비교 접기 | [접기](../components/collapsible.md) |
|
|
32
|
+
|
|
33
|
+
## 배치
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
설명 → 앱 테마 적용(참고 테마/산책 노트/문장 모음) → 표현 선택(기존 10종)
|
|
37
|
+
선택한 프로필: 헤더 → 저장 상태 → 이름/기간 → 같은 기록 3개 → 저장/실패 재현
|
|
38
|
+
선택한 프로필: 제목 크기 비교(5단계, 문서 단계 h3 유지)
|
|
39
|
+
표면 질감 비교: 장식 무늬 → Card 제목/설명 → 같은 초안 → 다음 테마
|
|
40
|
+
탭 선택 표시 비교: 테마 따르기/밑줄/이동/늘어남 → 기록/보관함 → 같은 초안 → 다음 테마
|
|
41
|
+
자산 액자 비교(기본 접힘): 같은 기존 Tick 그림 → 둥근/정사각/원형 → 다음 테마
|
|
42
|
+
입력·알림·오버레이 비교: Notice → Skeleton → Toast → Dialog/Sheet 열기
|
|
43
|
+
오버레이: 제목/닫기 → 같은 초안 → 다음 테마(현재 10종 순환)
|
|
44
|
+
10종 비교: 각 이름 → 같은 화면(현재 앱 설정도 함께 적용)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| 바깥 틀 | Stack | 세로 | `spacing.xl` 24 |
|
|
50
|
+
| 앱/참고 테마 | SegmentedControl | 선택 화면 위 | 앱 설정 3개 후 참고 테마 10개; `presentation="pills"`, 큰 선택 목록의 좁은 폭 배치는 공개 선택 계약을 따른다 |
|
|
51
|
+
| 이름/기간 | TextField·SegmentedControl | 도구 | `spacing.md` 16 |
|
|
52
|
+
| 목록 | OverviewScreen | 본문 | [목록 배치](../components/overview-screen.md#배치) |
|
|
53
|
+
| 저장/실패 | BottomCTA | footer | `spacing.sm` 12; 주 행동 후 ghost 실패 재현 |
|
|
54
|
+
| 자산 비교 | Asset·Stack | 탭 비교 아래 | `xlarge` 120px, `spacing.md` 16, 좁으면 줄바꿈. rounded는 프로필 radius.md, square 0, circle foundation full 유지 |
|
|
55
|
+
|
|
56
|
+
## 흐름과 상태
|
|
57
|
+
|
|
58
|
+
1. 기록 이름·기간을 바꾼다. 앱 설정과 표현을 바꿔도 같은 선택 화면의 초안/선택을 유지한다.
|
|
59
|
+
산책 노트는 녹색 잉크·cards/collapsible·landscape·slide/rise를, 문장 모음은 보라 잉크·rows/inline·editorial·none/fade를 지정한다.
|
|
60
|
+
나머지 표면/모서리/글자/질감은 고른 참고 테마를 상속한다. 두 설정은 제품 소유 설정 파일을 보여 주는 fixture이며 새 HJM 프리셋이 아니다.
|
|
61
|
+
2. 도구 접기/펼치기를 확인한다. 항상 펼친 테마로 가면 내용이 보인다.
|
|
62
|
+
3. 무늬 배경의 카드에 입력하고 다음 테마를 누른다. 유리·클레이 질감과 같은 초안 유지를 확인한다. Native 지원/접근성 설정에 따라 불투명 대체 경로도 확인한다.
|
|
63
|
+
4. 대화상자/패널을 열고 초안을 바꾼 뒤 다음 테마를 누른다. 열린 오버레이 안에서 프로필을 바꾸며 초안/문서 역할을 유지한다. 닫고 다시 열어도 제어 초안은 남는다.
|
|
64
|
+
5. 미리보기 저장 또는 실패 재현을 누른다. 실패 후 같은 입력을 재시도한다.
|
|
65
|
+
6. 자산 액자 비교를 펼쳐 다음 테마를 누른다. 같은 기존 CC0 그림과 120px 액자를 유지하며
|
|
66
|
+
rounded만 프로필을 따른다. 이 예제는 그림 재질·각도 자동 선택이나 Native 이미지 decode 검증의 완료 근거가 아니다.
|
|
67
|
+
|
|
68
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| 기본 | 저장 전 | 입력·선택 가능 |
|
|
71
|
+
| 진행 중 | 저장 버튼 pending | 입력을 제거하지 않음 |
|
|
72
|
+
| 실패 | 실패 문구·다시 저장 | 초안 유지·상태 알림 |
|
|
73
|
+
| 성공 | 미리보기 저장 문구 | 서버 저장으로 안내하지 않음 |
|
|
74
|
+
|
|
75
|
+
## 코드 골격
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Web
|
|
79
|
+
import { HjmProvider } from "@hjmds/react/provider";
|
|
80
|
+
import { OverviewScreen } from "@hjmds/react/design-profile";
|
|
81
|
+
import { defineHjmDesignProfile } from "@hjmds/design-contracts/design-profile";
|
|
82
|
+
const design = defineHjmDesignProfile({ extends: "paper", id: "my-app",
|
|
83
|
+
compositions: { collection: "cards", toolbar: "collapsible" }, screens: { overview: "landscape" } });
|
|
84
|
+
<HjmProvider designProfile={design}><OverviewScreen title={title} toolbarLabel={toolsLabel} toolbar={tools} items={items} footer={save} /></HjmProvider>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
// Native
|
|
89
|
+
import { HjmNativeProvider } from "@hjmds/react-native/provider";
|
|
90
|
+
import { OverviewScreen } from "@hjmds/react-native/design-profile";
|
|
91
|
+
import { defineHjmDesignProfile } from "@hjmds/design-contracts/design-profile";
|
|
92
|
+
const design = defineHjmDesignProfile({ extends: "paper", id: "my-app",
|
|
93
|
+
compositions: { collection: "cards", toolbar: "collapsible" }, screens: { overview: "landscape" } });
|
|
94
|
+
<HjmNativeProvider designProfile={design}><OverviewScreen title={title} toolbarLabel={toolsLabel} toolbar={tools} items={items} footer={save} /></HjmNativeProvider>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
제품은 Showcase를 import하지 않고 공개 API에 제품 문구/데이터를 넣는다. 유리 blur·클레이 inset shadow의 플랫폼 조건과 기기 미확인 범위는 [QA](../../../../../docs/qa/2026-10-07-design-profile-research.md)에 남긴다.
|
|
98
|
+
|
|
99
|
+
`showcase/shared/product-design.ts`는 순수 설정과 fixture 조합만 공유한다. 양 renderer가 자신의 workspace에서
|
|
100
|
+
공개 `defineHjmDesignProfile`을 주입한다. 루트에 renderer peer를 설치하거나 TypeScript alias로 소비 경계를 우회하지 않는다.
|
|
101
|
+
실제 앱은 이 fixture를 import하지 않고 자신의 `theme.ts`에서 같은 공개 helper를 사용한다. 테마 저장/URL/계정 동기화와 폰트/자산 로딩은 앱이 소유한다.
|
|
102
|
+
|
|
103
|
+
두 앱 변형은 같은 항목의 `ProductNotes`(앱 테마 · 산책)·`ProductReading`(앱 테마 · 문장) 스토리다.
|
|
104
|
+
새 테마마다 폴더나 상태 엔진을 만들지 않으며 Provider/RecordSample/입력/탭/오버레이에 product/preset key를 달아 교체하지 않는다.
|
|
105
|
+
OS 최대 글자와 최대값을 모사한 확대는 이번 추가의 설계·검증·후속·완료/릴리스 조건에서 제외한다.
|
|
106
|
+
|
|
107
|
+
코드 비교는 양 플랫폼의 공개 `CodeBlock`을 사용한다. 같은 원문에 프로필 code font·body metrics를 적용하며 RTL에서도 코드 본문은 LTR로 읽는다.
|
|
108
|
+
|
|
109
|
+
탭 비교는 공개 `Tabs`·`TextField`와 `mountPolicy="visited"`를 사용한다. 테마/표시 방식 변경은
|
|
110
|
+
선택한 탭과 같은 입력 호스트를 유지한다. forest·glass·aurora·clay는 생략한 appearance가
|
|
111
|
+
slide로 해석되고 나머지 6종은 standard다. 명시 값은 프로필보다 우선한다. 실제 제품은
|
|
112
|
+
이 샘플의 패널 수명을 기본값으로 복사하지 않고 초안과 탭의 사용 목적에 맞춰 선택한다.
|
|
113
|
+
|
|
114
|
+
Web의 팝오버는 비모달 편집 초안과 테마 순회를 추가 비교한다. Native의 같은 용도는 기존 Sheet 경로다. 저장/실패 샘플은 두 플랫폼 공개 BottomCTA이며 브랜드가 바뀌어도 같은 저장 상태·초안·재시도 callback을 유지한다.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# 문서와 파일
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [파일 원본 대조](../../../../../docs/qa/2026-10-07-file-reference.md), `src/document-resource.ts`
|
|
9
|
+
- 스토리북: `배포/구성/정보 표시/문서와 파일`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
이름·형식·크기와 미리보기·내보내기·별도 메뉴를 함께 제공하는 문서에 쓴다.
|
|
16
|
+
단순 다운로드 링크는 Link, 업로드 진행은 UploadItem을 사용한다. 파일 읽기·저장·권한·공유와
|
|
17
|
+
성공 영수증은 제품이 소유한다. DocumentResource는 controlled 상태의 배치와 버튼을 소유한다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| DocumentResource | metadata·상태·독립 행동 구성 | 이 문서 |
|
|
24
|
+
| Surface/Stack | 바깥 틀·세로 배치 | [Surface](../components/surface.md), [Stack](../components/stack.md) |
|
|
25
|
+
| Text | 파일명·형식·크기·오류 | [Text](../components/text.md) |
|
|
26
|
+
| Button | 미리보기·저장·재시도 | [Button](../components/button.md) |
|
|
27
|
+
|
|
28
|
+
## 배치
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
┌────────────────────────────┐
|
|
32
|
+
│ 파일명 │
|
|
33
|
+
│ 형식 / 크기(각 별도 줄) │
|
|
34
|
+
│ 설명 │
|
|
35
|
+
│ 미리보기 또는 상태 안내 │
|
|
36
|
+
│ [본문 보기] │
|
|
37
|
+
│ [미리보기 재시도] — 오류 시 │
|
|
38
|
+
│ [저장 / 재시도] │
|
|
39
|
+
│ 저장 상태·오류 │
|
|
40
|
+
│ moreAction(선택) │
|
|
41
|
+
└────────────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
45
|
+
| --- | --- | --- | --- |
|
|
46
|
+
| 바깥 틀 | Surface | 제품 목록/상세 안 | padding md, radius lg |
|
|
47
|
+
| 모든 영역 | Stack | 위→아래 | gap sm, spacing.sm 12 |
|
|
48
|
+
| 파일명 | Text | 최상단 | strong, heading 역할을 자동 부여하지 않음 |
|
|
49
|
+
| 형식·크기 | Text | 이름 아래 | caption/muted, 제품 문자열 그대로 |
|
|
50
|
+
| 행동 | Button | 미리보기 아래 | 세로 배치, Native growWithContent |
|
|
51
|
+
|
|
52
|
+
파일명은 줄바꿈한다. 미리보기 크기와 내용은 제품 host가 공급하며, 카드 전체를 링크/버튼으로
|
|
53
|
+
감싸지 않는다. `moreAction`도 독립 행동이다. 상위 화면이 스크롤을 소유한다.
|
|
54
|
+
|
|
55
|
+
Web에서 초점을 가진 미리보기 재시도 버튼이 제거되면 같은 문서의 사용 가능한 미리보기
|
|
56
|
+
버튼으로 복귀하며, 없으면 저장 버튼으로 옮긴다. 2026-10-07 실제 브라우저에서 제거된 버튼의
|
|
57
|
+
초점이 body로 떨어진 회귀에 따른 규칙이다. 제품이 다른 문서로 바꾸거나 사용자가 이미 다른
|
|
58
|
+
요소에 초점을 둔 경우에는 자동 이동하지 않는다. Native 스크린리더 복구는 별도 검증 대상이다.
|
|
59
|
+
|
|
60
|
+
Showcase에는 긴 파일명 전환과 미리보기 준비 중·실패·없음 조작이 있다. 이름을 전환하면
|
|
61
|
+
기존 action-session 결과를 reset한다. 미리보기 로딩은 저장을 자동으로 잠그지 않는다.
|
|
62
|
+
저장 잠금 토글은 미리보기와 그 재시도를 유지하며 저장·저장 재시도만 잠근다.
|
|
63
|
+
|
|
64
|
+
## 흐름과 상태
|
|
65
|
+
|
|
66
|
+
1. 양 renderer의 `/document-resource`에서 DocumentResource를 import한다. root export는 없다.
|
|
67
|
+
2. descriptor id는 파일 revision을 구분한다. name 필수, formatLabel/sizeLabel/description은 선택이다.
|
|
68
|
+
3. preview는 none/loading/ready/error이며 error에는 message와 retryable을 준다. retryable이면
|
|
69
|
+
onRetryPreview도 필수다. ready일 때만 preview 노드를 표시한다. onPreview는 선택이다.
|
|
70
|
+
4. save는 idle/pending/started/saved/cancelled/error다. error의 retryable이 true이면 onRetrySave가
|
|
71
|
+
필수이고 false면 일반 저장 버튼으로 우회하지 않는다. 새 시도 허용은 제품의 상태 판단이다.
|
|
72
|
+
5. labels의 열 문구를 모두 현지화한다. 비어 있는 문구·잘못된 상태는 TypeError다.
|
|
73
|
+
6. onSave/onRetrySave는 제품 action-session에 연결한다. 중복 실행은 session이 막으며 파일 교체·
|
|
74
|
+
unmount에서 reset으로 이전 결과를 분리한다. reset은 실제 OS/서버 작업 취소가 아니다.
|
|
75
|
+
7. 브라우저 anchor 시작이나 Native 공유 sheet 완료만으로 saved를 넣지 않는다. 실제 host가
|
|
76
|
+
확인한 저장 결과만 saved다. 예제 Web은 Blob 다운로드 시작, Native는 텍스트 공유 예제다.
|
|
77
|
+
|
|
78
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| 기본 | metadata·각 버튼 | 자동 초점 없음 |
|
|
81
|
+
| 진행 중 | 저장 버튼 pending/loading | 같은 버튼 위치 유지, 제품 세션이 재실행 차단 |
|
|
82
|
+
| 실패 | 해당 오류·허용된 재시도 | 미리보기 실패가 저장을 자동으로 막지 않음 |
|
|
83
|
+
| 시작/완료/취소 | 각각 다른 상태 문구 | Web status, Native live region; iOS 실제 알림 검증 대기 |
|
|
84
|
+
| 비활성 | 기본·재시도 행동 비활성 | 제품 moreAction도 같은 정책을 공급해야 함 |
|
|
85
|
+
|
|
86
|
+
`saveDisabled`는 기본 false이며 저장·저장 재시도만 막는다. 결과를 읽고 검토해야 내보낼 수
|
|
87
|
+
있는 제품에서 사용한다. `disabled`는 미리보기까지 막으므로 검토 대기 상태에 대신 쓰지 않는다.
|
|
88
|
+
2026-10-07 Utilverse PhotoOutputCard 조사에서 이 구분이 필요했다. 잠금은 진행 중인 OS 작업의
|
|
89
|
+
취소나 권한 검사를 대신하지 않는다. 제품은 onSave 실행 직전에도 유효한 권한·검토 상태를 확인하고,
|
|
90
|
+
`moreAction`에 별도 공유 행동을 넣었다면 그 행동에도 제품의 잠금 정책을 연결한다.
|
|
91
|
+
|
|
92
|
+
## 코드 골격
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
// Web
|
|
96
|
+
import { DocumentResource } from "@hjmds/react/document-resource";
|
|
97
|
+
<DocumentResource descriptor={documentState} labels={localizedLabels}
|
|
98
|
+
preview={previewHost} onPreview={openPreview} onSave={startExport}
|
|
99
|
+
onRetryPreview={retryPreview} onRetrySave={retryExport} />;
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// Native
|
|
104
|
+
import { DocumentResource } from "@hjmds/react-native/document-resource";
|
|
105
|
+
<DocumentResource descriptor={documentState} labels={localizedLabels}
|
|
106
|
+
preview={previewHost} onPreview={openPreview} onSave={startExport}
|
|
107
|
+
onRetryPreview={retryPreview} onRetrySave={retryExport} />;
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 플랫폼 차이
|
|
111
|
+
|
|
112
|
+
| 항목 | Web | Native |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| 바깥 의미 | group + 파일명 | accessible=false로 자식 조작 유지 |
|
|
115
|
+
| 내보내기 host | anchor/download, 파일 API 등 제품 선택 | OS 저장·공유·권한 adapter를 제품이 공급 |
|
|
116
|
+
| pending/error 알림 | status/alert | live region/alert, iOS 기기 낭독 미검증 |
|
|
117
|
+
|
|
118
|
+
## 함정
|
|
119
|
+
|
|
120
|
+
Showcase의 첫 실패는 합성 fixture이며 운영 서버 실패가 아니다. Native 텍스트 공유는 파일 저장
|
|
121
|
+
검증을 대신하지 않는다. HJM이 제품의 실패를 저장 성공으로 해석하지 않도록 action-session의
|
|
122
|
+
success와 host 결과 started/saved/cancelled를 따로 연결한다. 오류를 내부 상태로 기록하고
|
|
123
|
+
resolve하는 제품 세션은 Promise 완료만으로 성공을 판단하지 말고 세션 snapshot의 결과·오류를 읽는다. 제품 metadata에 URL·서버 오류 원문을
|
|
124
|
+
자동 노출하지 않는다. 아직 실험이며 전체 환경 검증·승격·npm 게시·소비 적용은 별도다.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# 관련 입력 묶음
|
|
2
|
+
|
|
3
|
+
- 단계: 구성
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.14.0
|
|
7
|
+
- 검토일: 2026-10-07
|
|
8
|
+
- 근거: [입력 그룹 조사](../../../../../docs/plans/field-group-experiment.md), `src/field-group.ts`
|
|
9
|
+
- 스토리북: `배포/구성/입력과 작성/관련 입력 묶음`
|
|
10
|
+
|
|
11
|
+
승급: 2026-10-07 사용자 승인, [검토 결과](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook 분류이며 제품 적용 증거는 별도다.
|
|
12
|
+
|
|
13
|
+
## 언제 쓰나
|
|
14
|
+
|
|
15
|
+
주소·연락처처럼 여러 입력이 하나의 질문에 답할 때 쓴다. FieldGroup은 관련성·도움말·오류·잠금을
|
|
16
|
+
소유하고 값·검증 시점·제출은 제품이 소유한다. Form 안에 여러 그룹을 둘 수 있다. 선택만 묶으면
|
|
17
|
+
CheckboxGroup/RadioGroup, 날짜 조각이면 DateEntry를 먼저 사용한다. 공통 제출 세션은 Form의 몫이다.
|
|
18
|
+
|
|
19
|
+
## 구성 요소
|
|
20
|
+
|
|
21
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| FieldGroup | 그룹 이름·설명·오류와 입력 연결 | 이 문서 |
|
|
24
|
+
| TextField 등 | renderField로 공급하는 개별 입력 | [Field](../components/field.md) |
|
|
25
|
+
| Form | 제품 제출 경계 | [Form](../components/form.md) |
|
|
26
|
+
|
|
27
|
+
## 배치
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
그룹 이름
|
|
31
|
+
그룹 설명(선택)
|
|
32
|
+
그룹 오류(선택, 한 번)
|
|
33
|
+
[입력 1]
|
|
34
|
+
필드 설명 / 필드 오류
|
|
35
|
+
[입력 2]
|
|
36
|
+
필드 설명 / 필드 오류
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| 바깥 틀 | Web fieldset / Native View | 기존 폼 안 | 테두리 없음, Web 최소 폭 0 |
|
|
42
|
+
| 그룹 이름 | Web legend / Native Text label | 맨 위 | Web 아래 spacing.sm 12, Native 그룹 간격 spacing.sm 12 |
|
|
43
|
+
| 입력 묶음 | renderField | descriptor.fields 순서 | 세로 간격 spacing.md 16 |
|
|
44
|
+
| 개별 도움말 | FieldGroup | 해당 입력 바로 아래 | Native 간격 spacing.xs 8, 글자 크기는 Text 기본 |
|
|
45
|
+
|
|
46
|
+
## 흐름과 상태
|
|
47
|
+
|
|
48
|
+
1. descriptor에 label과 fields를 넣는다. 필드 id는 그룹 안에서 고유하며 재정렬에도 유지한다.
|
|
49
|
+
2. description·error는 이미 현지화한 문구다. 빈 문자열은 허용하지 않는다. 표시할 오류가 없으면 생략한다.
|
|
50
|
+
3. 그룹 error는 message와 fieldIds를 받는다. 영향을 받는 필드만 invalid가 되고, 빈 배열은 그룹 전용 오류다.
|
|
51
|
+
4. renderField의 controlProps를 개별 입력에 전달한다. FieldGroup이 도움말·오류를 이미 그리므로 같은 내용을
|
|
52
|
+
TextField description/error에 다시 넣지 않는다. Web id·aria 연결을 덮어쓰지 않는다.
|
|
53
|
+
5. 값 변경에는 guardChange로 감싼 callback을 전달한다. 그룹 잠금·개별 잠금·필드 제거·언마운트 후
|
|
54
|
+
남은 callback을 차단한다. 잠금 해제 시 원래 disabled인 필드는 계속 잠긴다. 제거가 commit된
|
|
55
|
+
필드를 같은 id로 다시 추가해도 이전 callback은 복구하지 않는다. 새 renderField의 guardChange를
|
|
56
|
+
사용한다. 2026-10-07 회귀에서 enabled id만 검사하면 이전 이벤트가 새 초안을 바꾸는 것을 확인했다.
|
|
57
|
+
6. dynamic 필드 제거 시 해당 그룹 오류 대상도 함께 갱신한다. 사라진 id나 중복 오류 대상은 TypeError다.
|
|
58
|
+
7. 제품이 값과 검증을 유지한다. 입력 순서·그룹 잠금 변경은 값을 초기화하지 않는다.
|
|
59
|
+
|
|
60
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| 기본 | 그룹 설명과 독립 입력 | 자동 포커스 없음 |
|
|
63
|
+
| 진행 중 | 제품 값 편집 | 일반 Tab·터치 이동 |
|
|
64
|
+
| 실패 | 그룹 오류 한 번, 해당 입력 invalid, 개별 오류 보존 | Web 연결된 설명, Native hint에 그룹/개별 설명과 오류 |
|
|
65
|
+
| 비활성 | 각 입력 disabled | guardChange가 늦은 값 변경도 차단 |
|
|
66
|
+
|
|
67
|
+
## 코드 골격
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
// Web
|
|
71
|
+
import { FieldGroup } from "@hjmds/react/field-group";
|
|
72
|
+
import { TextField } from "@hjmds/react/forms";
|
|
73
|
+
<FieldGroup descriptor={group} renderField={({ id, controlProps, guardChange }) =>
|
|
74
|
+
<TextField {...controlProps} value={values[id] ?? ""}
|
|
75
|
+
onValueChange={guardChange((value: string) => updateField(id, value))} />} />
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// Native
|
|
80
|
+
import { FieldGroup } from "@hjmds/react-native/field-group";
|
|
81
|
+
import { TextField } from "@hjmds/react-native/inputs";
|
|
82
|
+
<FieldGroup descriptor={group} renderField={({ id, controlProps, guardChange }) =>
|
|
83
|
+
<TextField {...controlProps} value={values[id] ?? ""}
|
|
84
|
+
onValueChange={guardChange((value: string) => updateField(id, value))} />} />
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
공통 descriptor/resolver/edit session은 `@hjmds/design-contracts/field-group`에 있다.
|
|
88
|
+
사용자 정의 입력은 label·disabled·invalid·도움말을 자신의 실제 입력 host에 연결해야 한다.
|
|
89
|
+
controlProps는 TextField에 바로 연결되는 형태이며 Checkbox/Select 등 다른 공개 API와 호환되는지
|
|
90
|
+
확인 없이 그대로 펼치지 않는다. renderField의 반환값 안에 독립 제출 버튼을 넣지 않는다.
|
|
91
|
+
|
|
92
|
+
## 플랫폼 차이
|
|
93
|
+
|
|
94
|
+
| 항목 | Web | Native |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| 그룹 의미 | fieldset의 첫 legend | 개별 입력 accessibilityLabel에 그룹 이름 포함, 부모 accessible=false |
|
|
97
|
+
| 필드 연결 | id·aria-invalid·aria-describedby | label·invalid·accessibilityLabel·accessibilityHint |
|
|
98
|
+
| 그룹 오류 | role=alert | assertive live region, iOS 별도 announcement |
|
|
99
|
+
| 화면 스크롤 | 제품 host | 제품 ScrollView·키보드 회피 host |
|
|
100
|
+
|
|
101
|
+
## 함정
|
|
102
|
+
|
|
103
|
+
- 실제 스크린리더·Native 기기·제품 팔레트 검증은 아직 남았다. 자동 검사 통과를 승격 근거로 단독 사용하지 않는다.
|
|
104
|
+
- 국가·주소·연락처의 필드 순서와 autocomplete는 제품별로 정한다. 예제의 한국 주소 순서를 공통 규칙으로 복사하지 않는다.
|