@hjmds/design-contracts 1.39.0 → 1.41.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 +2 -2
- package/dist/base-recipes.d.ts +11 -3
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +9 -4
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +4 -4
- package/dist/card.d.ts +1 -1
- package/dist/card.js +1 -1
- package/dist/card.js.map +1 -1
- package/dist/catalog.d.ts +119 -85
- package/dist/catalog.d.ts.map +1 -1
- package/dist/choice-row.d.ts +26 -0
- package/dist/choice-row.d.ts.map +1 -0
- package/dist/choice-row.js +24 -0
- package/dist/choice-row.js.map +1 -0
- package/dist/colors.d.ts +2 -0
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +3 -1
- package/dist/colors.js.map +1 -1
- package/dist/command-palette.d.ts +2 -2
- package/dist/component-contracts.d.ts +2 -2
- package/dist/component-contracts.js +2 -2
- package/dist/component-contracts.js.map +1 -1
- package/dist/component-recipes.d.ts +76 -64
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +18 -16
- package/dist/component-recipes.js.map +1 -1
- package/dist/data-table.d.ts +1 -1
- package/dist/floating-action-button.d.ts +14 -2
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +5 -3
- package/dist/floating-action-button.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +2 -2
- package/dist/icon-button-recipe.js +3 -3
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/menubar.d.ts +2 -2
- package/dist/palette-contrast.d.ts.map +1 -1
- package/dist/palette-contrast.js +2 -0
- package/dist/palette-contrast.js.map +1 -1
- package/dist/semantic-colors.d.ts +18 -1
- package/dist/semantic-colors.d.ts.map +1 -1
- package/dist/semantic-colors.js +7 -1
- package/dist/semantic-colors.js.map +1 -1
- package/dist/sidebar.d.ts +2 -2
- package/dist/time-picker.d.ts +16 -4
- package/dist/time-picker.d.ts.map +1 -1
- package/dist/time-picker.js +26 -5
- package/dist/time-picker.js.map +1 -1
- package/dist/toggle-group.d.ts +3 -3
- package/dist/toggle-group.js +3 -3
- package/dist/toggle-group.js.map +1 -1
- package/dist/transfer-list.d.ts +2 -2
- package/dist/tree.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/docs/ant-design-coverage.md +6 -1
- package/docs/brand-boundary.md +1 -0
- package/docs/floating-action-button.md +6 -4
- 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/optional-adapters.md +1 -1
- package/docs/theme-palette.md +60 -0
- package/docs/time-picker.md +21 -0
- package/docs/toast.md +11 -0
- package/docs/usage/FINDING.md +71 -0
- package/docs/usage/README.md +5 -3
- package/docs/usage/SELECTION.md +8 -2
- package/docs/usage/components/bottom-navigation.md +5 -0
- package/docs/usage/components/card.md +2 -2
- package/docs/usage/components/chart.md +3 -1
- package/docs/usage/components/chip.md +1 -1
- package/docs/usage/components/choice-row.md +77 -0
- package/docs/usage/components/floating-action-button.md +5 -4
- package/docs/usage/components/image.md +18 -3
- package/docs/usage/components/keyboard-avoidance-context.md +3 -3
- package/docs/usage/components/segmented-control.md +1 -1
- package/docs/usage/components/sheet.md +1 -0
- package/docs/usage/components/surface.md +7 -1
- package/docs/usage/components/tabs.md +5 -0
- package/docs/usage/components/time-picker.md +20 -2
- package/docs/usage/components/toggle-group.md +5 -0
- package/docs/usage/compositions/save-feedback.md +43 -21
- package/docs/usage/tokens/color.md +17 -3
- package/package.json +7 -1
|
@@ -79,12 +79,12 @@ Web·Native 모두 기존 `raised` Surface의 테두리·그림자를 재사용
|
|
|
79
79
|
|
|
80
80
|
| prop | 값 | 기본값 | 설명 |
|
|
81
81
|
| --- | --- | --- | --- |
|
|
82
|
-
| `tone` | `default` · `raised` · `accent` · `sunken` · `subtle`(Surface tone) | `raised` | 테두리와 공통 raised shadow로 입체감 표시. 평면 카드는 `tone="default"` 지정 |
|
|
82
|
+
| `tone` | `default` · `raised` · `accent` · `sunken` · `subtle` · `selected`(Surface tone) | `raised` | 테두리와 공통 raised shadow로 입체감 표시. 평면 카드는 `tone="default"` 지정 |
|
|
83
83
|
| `badgesAlign` | `start` · `center` | `start` | 미게시. 들판 대표 카드처럼 그림 아래의 배지 묶음을 가운데 배치할 때 `center` 사용. 줄바꿈된 행에도 적용 |
|
|
84
84
|
| `bordered` | `boolean` | `true` | Surface 기본 `false`와 다르다 |
|
|
85
85
|
| `padding` | spacing 이름 | `md`(16) | — |
|
|
86
86
|
| `radius` | radius 이름 | `lg`(foundation 16) | 선택한 designProfile의 같은 radius 역할로 frame과 media clip을 함께 변경 |
|
|
87
|
-
| `selected` | `boolean` | `false` | `true`면 tone이 `
|
|
87
|
+
| `selected` | `boolean` | `false` | `true`면 tone이 `selected`로 바뀐다(`cardRecipe.selectedTone`) |
|
|
88
88
|
| `headingLevel`(Web만) | `2` · `3` · `4` | `3` | 제목이 `h3`로 렌더되므로 문서 위계에 맞춰 고른다 |
|
|
89
89
|
| `title` · `description` · `leading` · `media` · `badges` · `actions` · `children` | `ReactNode` | — | 슬롯. 순서는 HJM이 고정한다 |
|
|
90
90
|
| `headerActions` | `ReactNode` | 없음 | 미게시. 제목 줄 끝의 배지·독립 명령. 카드 열기와 별도 대상으로 남으며 HJM이 위치와 제목 여유 폭을 소유한다 |
|
|
@@ -104,7 +104,7 @@ import { BarChart, Sparkline } from "@hjmds/react-native/chart";
|
|
|
104
104
|
| `missingLabel` | `string` | — (필수) | 텍스트 대안에서 빈 값을 읽는 문구 |
|
|
105
105
|
| `height` | `number` | `chartRecipe.heights.regular` 180 | 가로는 부모 폭을 채운다 |
|
|
106
106
|
| `curve` | `linear` · `monotone` · `step` | `linear` | `monotone`은 데이터 밖으로 넘지 않는 곡선 |
|
|
107
|
-
| `missingConnection` | `none` · `dotted` · `bridge` | `none` | `none`은 선을 끊어 둔다. `dotted`는 빈
|
|
107
|
+
| `missingConnection` | `none` · `dotted` · `bridge` | `none` | `none`은 선을 끊어 둔다. `dotted`는 앞·중간·뒤 빈 구간에 인접한 관측값에서 각자의 x 위치를 유지한 채 plot 하단까지 수직 점선을 내리며 양쪽 점선을 서로 연결하지 않는다. 하단은 결측 표시이며 0이 아니다. 값·포커스·영역 채움은 만들지 않으며 앞/뒤 결측을 외삽하지 않는다. Web·Native 지원. `bridge`는 관측 구간 끝과 다음 관측 시작을 옅은 점선으로 안내한다. 원본 null·포커스·영역 채움을 변경하지 않으며 제품은 점선이 미수집 구간이라는 설명을 표시한다. `bridge`는 1.39.0부터 |
|
|
108
108
|
| `area` | `boolean` | 계열이 하나면 `true` | 선 아래 그라데이션(0.32 → 0) |
|
|
109
109
|
| `stacked` | `boolean` | `false` | 쌓은 영역. 0을 항상 포함 |
|
|
110
110
|
| `invert` | `boolean` | `false` | 작을수록 위(순위·평균자책점) |
|
|
@@ -282,3 +282,5 @@ Native centerContent에는 Text 등 Native 노드를 쓴다. 모두 제외하거
|
|
|
282
282
|
원형 차트의 비율 라벨은 `chartRecipe.chrome.labelSurface`(`bg`) 판 위에 `labelContent`(`text`)로 표시한다. 제품이 계열색을 바꾸거나 선택하지 않은 조각을 흐리게 해도 라벨 대비가 유지된다. Native SVG 라벨은 프로필의 `caption` 크기와 `ui` 글꼴, Provider의 글자 배율을 따르며 범례 값과 커서 안내도 공통 Text를 사용한다.
|
|
283
283
|
|
|
284
284
|
2026-10-21 포트폴리오의 성긴 일별 관측이 끊겨 읽기 어렵다는 사용자 요청으로 `bridge`를 추가했다. 실제 관측선과 결측 연결 안내선을 구분하며 0 채움·수치 보간은 수행하지 않는다.
|
|
285
|
+
|
|
286
|
+
2026-10-23 오늘야구 선수 최근 7시즌 화면에서 앞쪽 결측의 경계 점선이 빠진 문제를 수정했다(개발 소스, 미게시). 앞·뒤 결측도 실제 관측값 위치에서 하단까지 표시하며, 관측값이 전혀 없으면 점선을 만들지 않는다.
|
|
@@ -76,7 +76,7 @@ import { Chip } from "@hjmds/react-native/inputs";
|
|
|
76
76
|
| --- | --- | --- |
|
|
77
77
|
| 크기 | 내용 폭. 최소 높이 `small` 36 · `medium` 44(`control.chipHeight`, 두 플랫폼 모두 최소값이라 라벨이 크면 늘어난다), 모서리 `radius.full`. `small`은 위아래 4를 넓혀(Native hitSlop, Web `::after`) 터치 영역을 44에 맞춘다 | `chipRecipe.sizes`, `react-native/src/inputs.tsx` |
|
|
78
78
|
| 간격 | 좌우 여백 `small` `spacing.sm` 12 · `medium` `spacing.md` 16, 아이콘↔라벨 `small` `spacing.xxs` 4 · `medium` `spacing.xs` 8(두 플랫폼). 칩 사이는 부모가 정한다(`spacing.xs` 8 권장) | `chipRecipe.sizes`, `.hjm-chip` |
|
|
79
|
-
| 순서·정렬 | 필터 줄·태그 묶음으로 가로로 나란히 둔다. 안쪽은 [선택 표시] → [leading] → [라벨] → [trailing]. 선택 상태는
|
|
79
|
+
| 순서·정렬 | 필터 줄·태그 묶음으로 가로로 나란히 둔다. 안쪽은 [선택 표시] → [leading] → [라벨] → [trailing]. 선택 상태는 `surfaceSelected` 회색 배경·`text` 글자·`borderControl` 테두리와 체크 표시 | `chipRecipe.slots`, `.hjm-chip[data-selected]` |
|
|
80
80
|
| 고정·스크롤 | 고정 영역이 없다. 칩이 많으면 부모가 줄바꿈하거나 가로 스크롤 영역을 둔다. 검색 화면의 필터 칩 줄은 [SearchScreen](search-screen.md) `filtersOverflow="scroll"`이 가로 스크롤을 소유한다 | `SearchScreen` |
|
|
81
81
|
| 좁은 폭·큰 글자 | 라벨이 줄바꿈된다(Web `overflow-wrap: anywhere`). 높이는 최소값이라 라벨 높이만큼 늘어난다(Web `min-block-size`, Native `minHeight`) | `.hjm-chip__label`, Native `Chip` |
|
|
82
82
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ChoiceRow
|
|
2
|
+
|
|
3
|
+
- 단계: 컴포넌트
|
|
4
|
+
- 상태: 배포
|
|
5
|
+
- 지원: Web · Native
|
|
6
|
+
- 적용: 1.41.0
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
|
+
- 근거: `src/choice-row.ts`(`resolveChoiceRow`). 2026-10-23 조사에서 언어 설정 행을 spint(`Select`)·utilverse(`ListRow`+`Sheet`+`RadioGroup`)·번뚝(자체 시트)이 각자 만들고, 테마 전환도 번뚝·야잘알이 따로 구현하고 있어 HJM으로 옮겼다.
|
|
9
|
+
- 스토리북: `배포/컴포넌트/입력/설정 값 선택`
|
|
10
|
+
|
|
11
|
+
## 언제 쓰나
|
|
12
|
+
|
|
13
|
+
설정 화면에서 언어·화면 테마·기본 정렬처럼 **여러 값 중 하나를 고르는 설정**에 쓴다. 행에는 현재 확인된 값을 보여 주고,
|
|
14
|
+
누르면 시트에서 고른다. 저장이 비동기면 시트가 저장 중으로 잠기고, 실패하면 시트를 연 채 오류를 보여 준다.
|
|
15
|
+
행에는 제품이 저장을 확인한 값만 보이므로, 실패한 선택이 저장된 것처럼 남지 않는다.
|
|
16
|
+
|
|
17
|
+
## 쓰지 않을 때
|
|
18
|
+
|
|
19
|
+
| 상황 | 대신 쓸 것 |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| 켜고 끄기 설정 | [Switch](switch.md)를 둔 [ListRow](list-row.md) |
|
|
22
|
+
| 폼 안에서 여러 필드와 함께 제출하는 선택 | [Select](select.md) 또는 [RadioGroup](radio-group.md) |
|
|
23
|
+
| 선택지가 화면에 펼쳐져 있어야 함(2~4개, 즉시 비교) | [SegmentedControl](segmented-control.md) 또는 [RadioGroup](radio-group.md) |
|
|
24
|
+
| 선택 외에 입력·설명이 많은 편집 패널 | [Sheet](sheet.md)를 직접 구성 |
|
|
25
|
+
|
|
26
|
+
## 공개 이름과 import
|
|
27
|
+
|
|
28
|
+
| 이름 | 역할 | Web | Native |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `ChoiceRow` | 독립 supplemental. root에서는 내보내지 않는다 | `@hjmds/react/choice-row` | `@hjmds/react-native/choice-row` |
|
|
31
|
+
| `ChoiceRowItem` | 선택지 타입 | 같은 경로 | 같은 경로 |
|
|
32
|
+
|
|
33
|
+
## 최소 사용 예
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
// Web
|
|
37
|
+
import { ChoiceRow } from "@hjmds/react/choice-row";
|
|
38
|
+
<ChoiceRow title={t("language")} items={languages} value={confirmedLanguage}
|
|
39
|
+
onValueChange={(next) => savePreference({ language: next })} closeLabel={t("close")} errorLabel={t("saveFailed")} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// Native
|
|
44
|
+
import { ChoiceRow } from "@hjmds/react-native/choice-row";
|
|
45
|
+
<ChoiceRow title={t("language")} items={languages} value={confirmedLanguage}
|
|
46
|
+
onValueChange={(next) => savePreference({ language: next })} closeLabel={t("close")} errorLabel={t("saveFailed")} />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`savePreference`가 성공한 뒤에 제품이 `confirmedLanguage`를 바꾼다. "기기 설정 따르기"는 `items`의 한 선택지로 넣는다.
|
|
50
|
+
|
|
51
|
+
## 축과 기본값
|
|
52
|
+
|
|
53
|
+
| prop | 값 | 기본값 | 설명 |
|
|
54
|
+
| --- | --- | --- | --- |
|
|
55
|
+
| `value` | 선택지 값 또는 null | 필수 | 제품이 확인한 값. null이면 `placeholder`가 필요하다 |
|
|
56
|
+
| `items` | `ChoiceRowItem[]` | 필수 | 2개 이상, 값 중복 금지. `description`·`disabled` 지원 |
|
|
57
|
+
| `onValueChange` | `(value) => void \| Promise<void>` | 필수 | Promise를 돌려주면 끝날 때까지 시트가 잠긴다. 거부되면 시트를 연 채 `errorLabel`을 보인다 |
|
|
58
|
+
| `sheetTitle`·`sheetDescription` | string | `title`·없음 | 시트 머리 |
|
|
59
|
+
| `leading`·`trailing` | ReactNode | 없음 | 제품 아이콘·꼬리 표시. ListRow 슬롯 그대로 |
|
|
60
|
+
| `disabled`·`loading` | boolean | false | ListRow의 비활성·자리 유지 로딩 |
|
|
61
|
+
|
|
62
|
+
## 배치
|
|
63
|
+
|
|
64
|
+
| 항목 | 값 | 근거 |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| 크기 | ListRow 기본 높이와 터치 영역. 시트는 내용 높이(`size="auto"`) | [ListRow](list-row.md#배치), [Sheet](sheet.md#배치) |
|
|
67
|
+
| 간격 | ListRow·RadioGroup recipe 간격을 그대로 쓴다 | 같은 recipe |
|
|
68
|
+
| 순서·정렬 | 행: leading → 제목·현재 값(description) → trailing. 시트: 제목 → 설명 → 세로 선택지 → 오류 | [설정 화면](../screens/common-settings.md#버튼과-행동-위치) |
|
|
69
|
+
| 고정·스크롤 | 행은 바깥 화면 스크롤을 따른다. 선택지가 길면 시트 본문이 스크롤된다 | Sheet |
|
|
70
|
+
| 좁은 폭·큰 글자 | 제목과 현재 값이 줄바꿈되며 행 높이가 늘어난다 | ListRow |
|
|
71
|
+
|
|
72
|
+
## 꼭 지킬 것
|
|
73
|
+
|
|
74
|
+
- `value`는 저장이 확인된 값만 넣는다. 선택 직후 낙관적으로 바꾸면 실패해도 행이 바뀐 값으로 남는다.
|
|
75
|
+
- 즉시 저장 설정에 쓴다. 하단 저장 버튼으로 모아 저장하는 설정은 [설정 화면의 일괄 저장](../screens/common-settings.md#즉시-저장과-일괄-저장-선택)대로 초안을 따로 둔다.
|
|
76
|
+
- 저장 중에는 시트를 닫거나 다른 값을 고를 수 없다. 다시 고르면 이전 오류는 지워진다.
|
|
77
|
+
- 문구·선택지·저장은 제품이 공급한다. 언어 목록이나 테마 적용 로직은 이 컴포넌트에 없다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [FloatingActionButton](../../floating-action-button.md), recipe `floatingActionButtonRecipe`(`src/floating-action-button.ts`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/동작/플로팅 실행 버튼`
|
|
10
10
|
|
|
@@ -74,6 +74,7 @@ const [clearance, setClearance] = useState(0);
|
|
|
74
74
|
|
|
75
75
|
| prop | 값 | 기본값 | 설명 |
|
|
76
76
|
| --- | --- | --- | --- |
|
|
77
|
+
| `size` | `medium` · `large` | `large` | 높이 44 / 52. 펼침·접힘 모두 같은 크기 티어를 사용한다(개발 소스, 미게시) |
|
|
77
78
|
| `descriptor.layoutMode` | `expanded` · `collapsed` | `expanded` | 직접 고르지 말고 `useFloatingActionButtonScroll` 결과를 넘긴다 |
|
|
78
79
|
| `descriptor.label` | 문구(필수) | — | `collapsed`에서도 접근성 이름은 항상 전체 `label`이다 |
|
|
79
80
|
| `descriptor.icon` | `{ name }` | — | `name`만 받는다(`size`·`tone`·접근성 이름은 넣을 수 없다). 아이콘은 항상 장식이다 |
|
|
@@ -82,14 +83,14 @@ const [clearance, setClearance] = useState(0);
|
|
|
82
83
|
| `onContentClearanceChange` | `(clearance: number) => void` | 필수 | 스크롤 콘텐츠 하단 padding으로 쓴다. 큰 글자·inset이 바뀌면 다시 불린다 |
|
|
83
84
|
| Web `onClick` · Native `onPress` | Button의 같은 이벤트 | — | — |
|
|
84
85
|
|
|
85
|
-
-
|
|
86
|
+
- 톤·모양은 `primary`·`pill`로 고정이다. 2026-10-23 번뜩의 버튼 축소 요청으로 기존 Button/IconButton의 `medium` 티어를 선택할 수 있게 했다(개발 소스, 미게시).
|
|
86
87
|
|
|
87
88
|
## 배치
|
|
88
89
|
|
|
89
90
|
| 항목 | 값 | 근거 |
|
|
90
91
|
| --- | --- | --- |
|
|
91
|
-
| 크기 | 높이·지름 52(IconButton `large`). `expanded`는 알약(아이콘 + 라벨, 라벨 좌우 여백 `spacing.lg` 20), `collapsed`는 지름 52 원이다. 최대 폭은 화면 폭 − 32(여백 양쪽)다 | `floating-action-button.ts` `floatingActionButtonRecipe`, `styles.css` `.hjm-fab` |
|
|
92
|
-
| 간격 | 가장자리 여백 `spacing.md`(16). 하단 거리는 16 + 하단 안전 영역이다(Web은 `safeAreaBottomInset`과 `env(safe-area-inset-bottom)` 중 큰 값, Native는 `safeAreaBottomInset`). 스크롤 콘텐츠 하단에 `onContentClearanceChange` 값을 padding으로 둔다. 기본값은 52 + 16×2 + 하단 inset(= 84 + inset)이다 | `floating-action-button.ts` `resolveFloatingActionButtonContentClearance`, `styles.css` `.hjm-fab`, `react-native/src/floating-action-button.tsx` |
|
|
92
|
+
| 크기 | 높이·지름 기본 52(IconButton `large`), `medium`은 44. `expanded`는 알약(아이콘 + 라벨, 라벨 좌우 여백 기본 `spacing.lg` 20, `medium`은 Button의 해당 티어), `collapsed`는 선택한 티어의 지름 44 / 52 원이다. 최대 폭은 화면 폭 − 32(여백 양쪽)다 | `floating-action-button.ts` `floatingActionButtonRecipe`, `styles.css` `.hjm-fab` |
|
|
93
|
+
| 간격 | 가장자리 여백 `spacing.md`(16). 하단 거리는 16 + 하단 안전 영역이다(Web은 `safeAreaBottomInset`과 `env(safe-area-inset-bottom)` 중 큰 값, Native는 `safeAreaBottomInset`). 스크롤 콘텐츠 하단에 `onContentClearanceChange` 값을 padding으로 둔다. 기본값은 52 + 16×2 + 하단 inset(= 84 + inset), `medium`은 76 + inset이다 | `floating-action-button.ts` `resolveFloatingActionButtonContentClearance`, `styles.css` `.hjm-fab`, `react-native/src/floating-action-button.tsx` |
|
|
93
94
|
| 순서·정렬 | 위치는 스크롤 영역의 **논리적 끝 쪽 하단 모서리**로 고정이다(RTL은 왼쪽) | `styles.css` `.hjm-fab`, `react-native/src/floating-action-button.tsx` |
|
|
94
95
|
| 고정·스크롤 | Web은 `position: fixed`(z-index `layer.sticky` 100)로 창에 붙는다. Native는 positioned 부모(`flex: 1` View) 안에서 `absolute`다. [BottomNavigation](bottom-navigation.md)·도구막대 위에 띄울 때는 그 높이를 `safeAreaBottomInset`에 더한다(Native는 제품이 더한다) | `styles.css` `.hjm-fab`, `react-native/src/floating-action-button.tsx` |
|
|
95
96
|
| 좁은 폭·큰 글자 | 큰 글자로 라벨이 여러 줄이 되면 clearance 값이 커진다 | `react-native/src/floating-action-button.tsx` |
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
| --- | --- | --- | --- |
|
|
32
32
|
| `Image` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
33
33
|
| `GridReveal` | 확장 — 로드 완료 시 4×4 마스크 연출(optional) | `/grid-reveal` | `/grid-reveal` |
|
|
34
|
-
| `ImageViewer` | 확장 — 전체 화면 넘겨 보기·확대
|
|
34
|
+
| `ImageViewer` | 확장 — 전체 화면 넘겨 보기·확대 | `/image-viewer` (개발 소스) | `/image-viewer` (optional) |
|
|
35
35
|
|
|
36
36
|
`GridReveal`은 추가 peer가 없다. `ImageViewer`는 granular subpath로만 import 되며 optional peer
|
|
37
37
|
`react-native-zoom-toolkit` 5.1.1, `react-native-gesture-handler` 2.32.0과 Reanimated·Worklets 설치가 필요하다.
|
|
@@ -195,7 +195,7 @@ const geometry = resolveImageInspectionGeometry(
|
|
|
195
195
|
| 정보 이미지 이름 | `accessibilityLabel` → `<img alt>`(`alt` prop은 받지 않는다) | `accessibilityLabel` |
|
|
196
196
|
| 이미지 host 스타일 | `imageProps.style`/`className` | `style`(ImageStyle) |
|
|
197
197
|
| 다른 이미지 엔진 | `renderImage`(받은 props를 `<img>`까지 전달) | `renderImage`(예: expo-image), `sourceAdapter`(헤더·캐시) |
|
|
198
|
-
| 확대 보기 |
|
|
198
|
+
| 확대 보기 | `ImageViewer` (개발 소스) | `ImageViewer` |
|
|
199
199
|
|
|
200
200
|
## 함정
|
|
201
201
|
|
|
@@ -206,6 +206,21 @@ const geometry = resolveImageInspectionGeometry(
|
|
|
206
206
|
|
|
207
207
|
### Native 확대 화면의 행동 배치
|
|
208
208
|
|
|
209
|
-
2026-10-20 번뚝 실제 첨부 사진 확인에서 큰 닫기/이전/다음 주 버튼이 사진 영역을 차지한 문제를 반영했다. ImageViewer는
|
|
209
|
+
2026-10-20 번뚝 실제 첨부 사진 확인에서 큰 닫기/이전/다음 주 버튼이 사진 영역을 차지한 문제를 반영했다. ImageViewer는 왼쪽 상단 닫기 IconButton과 하단의 이전·사진 이름/순서·다음 아이콘 행을 제공한다. 제품이 전달한 지역화 label을 접근성 이름으로 유지하고, 한 장이면 페이지 이동 버튼은 표시하지 않는다. 첫/마지막의 disabled, 전체 폭 사진·확대·오류 재시도·좌우 safe area는 유지한다. 현재 개발 소스의 표현 수정이며 공개 prop·플랫폼 지원 범위는 바뀌지 않는다.
|
|
210
210
|
|
|
211
211
|
2026-10-20 번뚝 사진 보기 확인: 고정 크기 뷰어 안에서는 원본 `width`·`height`와 `fit="contain"`을 유지하고 Image 루트의 공개 `style={{ inlineSize: "100%", blockSize: "100%" }}`로 주어진 슬롯을 채울 수 있다. 이 경우에도 실제 이미지의 fit은 HJM이 소유한다. `layoutStyle`은 배치용 `width`만 지원하며 `inlineSize`·`blockSize`를 받는 속성이 아니다.
|
|
212
|
+
|
|
213
|
+
### 앱·웹 공통 사진 확대 — 2026-10-23 개발 소스
|
|
214
|
+
|
|
215
|
+
사용자의 전사 사진 확대 요청에 따라 뷰어가 검은 전체 화면, 원본 비율 `contain`, 왼쪽 상단 닫기,
|
|
216
|
+
하단 사진 이름·여러 장일 때 이전/다음·순서를 제공한다. `actions` 슬롯은 신고 등 제품 행동을 오른쪽 상단에 둔다. 제품별 테마 덮어쓰기는 필요하지 않다.
|
|
217
|
+
사진·사진 선택 미리보기·도구 입력/결과가 대상이며 로고·장식·선택 행동을 가진 그림에는 자동 적용하지 않는다.
|
|
218
|
+
|
|
219
|
+
Web은 `@hjmds/react/image-viewer`에서 `ImageViewer`를 import한다. Native와 같은 `open`,
|
|
220
|
+
`items`(`id`, `uri`, `label`), `initialIndex`, `onClose`, `onIndexChange`, 라벨 6종을 받는다.
|
|
221
|
+
Native의 inset/inspection/이미지 상태 callback은 Web에 전달하지 않는다.
|
|
222
|
+
Web의 `renderImage(item)`은 현재 사진 하나만 마운트하며, 권한 있는 이미지 host가 조회·로딩·오류·재시도를 소유한다.
|
|
223
|
+
기본 host는 img 로드/오류와 재시도를 제공한다. Dialog와 같은 모달 focus·배경 격리·Escape·초점 복귀를 사용한다.
|
|
224
|
+
더블클릭은 맞춤↔2배, 두 손가락은 1~4배, 확대한 사진은 드래그한다. 사진 영역에서 +/− 키도 배율을 바꾼다.
|
|
225
|
+
Native는 기존 Gallery 확대와 제품별 결과 검사 계약을 유지한다.
|
|
226
|
+
새 Web 진입점과 Native 표현 변경은 미게시 개발 소스이며 설치 패키지·운영 배포 반영은 별도다.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# KeyboardAvoidanceContext
|
|
2
2
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
|
-
- 상태:
|
|
4
|
+
- 상태: 배포
|
|
5
5
|
- 지원: Native
|
|
6
|
-
- 적용:
|
|
7
|
-
- 검토일: 2026-10-
|
|
6
|
+
- 적용: 1.27.0
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: Native `src/keyboard.tsx`; 2026-10-16 키보드 전수 조사에서 바깥 KeyboardAvoiding과 안쪽 ScreenLayout이 같은 키보드 이벤트에 동시에 여백을 주지 않게 하려고 추가
|
|
9
9
|
- 스토리북: 없음
|
|
10
10
|
|
|
@@ -132,7 +132,7 @@ import { SegmentedControl } from "@hjmds/react-native/inputs";
|
|
|
132
132
|
|
|
133
133
|
- `presentation="pills"`: 카테고리·작성자·기간처럼 같은 목록의 범위를 좁힐 때 쓴다.
|
|
134
134
|
- 표면 높이·좌우 여백·글자는 같은 size의 Chip과 맞춘다. 터치 영역은 최소 `control.minTouchTarget` 44이며 안쪽 표면은 위아래 `spacing.xxs` 4만큼 들어간다. 2026-10-20 Utilverse 혼합 필터 행에서 작은 Chip36과 pill28의 불일치가 확인돼 공통 크기로 정렬했다(미게시). 누름 영역은 줄이지 않는다.
|
|
135
|
-
- 좌우 여백은 Chip과 같이 small12/medium16, 항목 사이 `spacing.xs` 8, 모서리 `radius.full`. 비선택은 `surface.raised`(`canvas`) 바탕과 `border.control` 1pt 테두리, 선택은 `
|
|
135
|
+
- 좌우 여백은 Chip과 같이 small12/medium16, 항목 사이 `spacing.xs` 8, 모서리 `radius.full`. 비선택은 `surface.raised`(`canvas`) 바탕과 `border.control` 1pt 테두리, 기본 선택은 `selection.background`(`surfaceSelected`)/`selection.content`(`text`) 조합이다. 2026-10-20 Utilverse의 파스텔 배경에서 비선택 버튼 경계가 묻힌 사용자 지적에 따라 inset 표면을 공통으로 보완했다(미게시). 색은 제품 provider에서 따라온다.
|
|
136
136
|
- 항목은 내용 폭이며 좁아지면 줄바꿈한다. 큰 글자에서도 세로로 쌓지 않는다(`pills.largeTextLayout` `wrap`): 블록 안에서는 줄바꿈하고, 가로 스크롤 줄 안에서는 한 줄로 남아 스크롤된다. 라벨 높이만큼 항목도 늘어난다. 선택 상태(radio)와 포커스 이동(Web 화살표, Native 접근성 순서)은 배치와 관계없이 같다.
|
|
137
137
|
- 그룹 이름은 `label`, 하나의 선택은 `value`/`onValueChange`. 복수 조건은 ToggleGroup, 화면 이동은 Tabs/Navigation을 쓴다.
|
|
138
138
|
- 예제의 전체 선택값도 실제 항목으로 넣는다. 선택된 항목을 다시 눌러도 선택을 해제하지 않는다.
|
|
@@ -189,6 +189,7 @@ Native 스크롤 본문은 viewport 높이만큼 content를 채운다(미게시)
|
|
|
189
189
|
|
|
190
190
|
## 근거
|
|
191
191
|
|
|
192
|
+
- 하단 시트의 아래 테두리 없음(개발 소스, 미게시): 2026-10-23 Utilverse 달력 화면에서 화면 끝의 가로선이 잘린 테두리처럼 보인다는 사용자 지적. 하단은 화면에 이어지는 면이므로 Web·Native 모두 아래 선을 그리지 않고, 위·좌우 테두리와 아래 safe area는 유지한다. `start`·`end` 옆 시트의 닫힌 테두리는 유지한다.
|
|
192
193
|
- 시트에 보이는 제목 없음: 2026-10-17 사용자 결정 — 시트 위쪽 제목 줄(예: SearchScreen 필터 시트의 "필터")을 모든 시트에서 없앤다. `title`은 대화상자 접근성 이름으로 남는다. Native Select·Combobox 선택 시트와 GestureSheet도 같다. 같은 작업에서 머리가 손잡이 영역(`.hjm-sheet__drag`, 2026-10-16) 안으로 들어가 `.hjm-sheet > .hjm-sheet__header` 선택자가 맞지 않아 Dialog 위 여백 20이 섞이던 Web 결함을 고쳤다.
|
|
193
194
|
- 가림막 누르면 닫힘: 2026-10-17 사용자 결정 — 하단 시트도 시트 밖을 누르면 닫힌다(2026-10-16의 드래그 전용 기본을 대체). 닫기 경로는 손잡이·드래그와 같은 `requestClose`라 `busy`·`dismissPolicy`·제어형 owner의 확인을 그대로 따른다.
|
|
194
195
|
- 스크롤 영역을 시트 여백 끝까지: 2026-10-17 1.27.2 채택 점검. utilverse 테마·언어 시트(Native `scrollable`)에서 여백 20이 ScrollView 바깥에 있어 ListRow 선택 표면의 양옆이 잘렸다(3x 측정: 선택 테두리 옆 선 없음, 위 선 21–381pt). Web은 본문 스크롤 상자가 이미 여백을 안쪽에 두어 잘림 0(402·1280, LTR·RTL)이었고 Native를 같은 구조로 맞췄다(수정 후 옆 선 13pt·끝 13pt).
|
|
@@ -62,7 +62,7 @@ import { Surface } from "@hjmds/react-native/primitives";
|
|
|
62
62
|
|
|
63
63
|
| prop | 값 | 기본값 | 설명 |
|
|
64
64
|
| --- | --- | --- | --- |
|
|
65
|
-
| `tone` | `default` · `raised` · `sunken` · `accent` · `subtle` | `default` | `raised`는 그림자, `accent`는 primary 색 테두리.
|
|
65
|
+
| `tone` | `default` · `raised` · `sunken` · `accent` · `subtle` · `selected` | `default` | `raised`는 그림자, `accent`는 primary 색 테두리. `selected`는 `surfaceSelected` 배경, 나머지는 테마 `bg` 바탕에 테두리·그림자로 위계를 나타낸다 |
|
|
66
66
|
| `padding` | `none` 또는 spacing 토큰(`xxs`~`xxxl`) | `none` | — |
|
|
67
67
|
| `radius` | `sm` · `md` · `lg` · `xl` · `full` | `lg`(16) | — |
|
|
68
68
|
| `bordered` | `boolean` | 생략 시 테두리 그림 | 모든 tone이 `borderAlways`. `bordered={false}`로만 끈다 |
|
|
@@ -104,3 +104,9 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
|
|
|
104
104
|
| 그림자(`raised`) | CSS | `elevation: 4`와 iOS shadow |
|
|
105
105
|
| ref | `forwardRef` | 없음 |
|
|
106
106
|
| import 경로 | `/layout` | `/primitives` |
|
|
107
|
+
|
|
108
|
+
### 선택 면
|
|
109
|
+
|
|
110
|
+
`tone="selected"`는 `surfaceSelected` 배경과 `borderControl` 경계를 사용한다.
|
|
111
|
+
선택 Card는 공통 `cardRecipe.selectedTone`을 통해 이 면을 사용한다.
|
|
112
|
+
브랜드 `accent`나 포커스 위치를 일반 선택 면으로 대신하지 않는다.
|
|
@@ -194,3 +194,8 @@ source 객체는 렌더마다 바꾸지 않도록 `useMemo` 또는 제품 어댑
|
|
|
194
194
|
### 진한 브랜드 선택 — 개발 소스, 미게시 (2026-10-21)
|
|
195
195
|
|
|
196
196
|
다에리·스핀트의 선택 배경이 너무 옅다는 사용자 피드백에 따라 `indicator="solid"`로 선택 탭을 `primary`로 채우고 글자·아이콘에 `onPrimary`를 쓴다. 기존 `filled`는 옅은 선택을 유지한다.
|
|
197
|
+
|
|
198
|
+
### 기본 선택 면
|
|
199
|
+
|
|
200
|
+
`indicator="filled"`는 neutral `selection.background`/`selection.content`를 쓴다.
|
|
201
|
+
정적·slide·gooey의 이동 배경도 같은 역할이며 `solid`는 명시적 `primary`/`onPrimary` 강조다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 미게시(1.34.0 이후)
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: [TimePicker](../../time-picker.md), `src/time-picker.ts`의 `timePickerRecipe`, 양 renderer `src/time-picker.tsx`
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/시각 선택`
|
|
10
10
|
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
Web Popover / Native Sheet를 열고 명시적으로 확정한다. 기존 날짜 시트 안에서는 `inline`을 쓴다.
|
|
15
15
|
현재 checkout의 개발 소스에서 제공하며 npm 게시 버전은 아직 없다.
|
|
16
16
|
|
|
17
|
+
기존 탭 목록은 `selectionStyle="list"`(기본), 관성 스크롤·중앙 스냅은 `selectionStyle="wheel"`로 고른다.
|
|
18
|
+
휠 옵션은 2026-10-23 개발 소스에 추가한 미게시 기능이다. 값·초안·확정·취소 계약은 같으며
|
|
19
|
+
[도입 결정](../FINDING.md#휠-피커-검토-결과)에 따라 별도 WheelPicker 공개 API를 추가하지 않았다.
|
|
20
|
+
|
|
17
21
|
## 쓰지 않을 때
|
|
18
22
|
|
|
19
23
|
| 상황 | 대신 쓸 것 |
|
|
@@ -30,7 +34,7 @@ Web Popover / Native Sheet를 열고 명시적으로 확정한다. 기존 날짜
|
|
|
30
34
|
|
|
31
35
|
필수: `value: string | null`, `onValueChange(value: string | null)`,
|
|
32
36
|
`labels: { label, hour, minute, placeholder, confirm, cancel }`.
|
|
33
|
-
선택: `presentation`(`field` 기본/`inline`), `minuteStep`(1 기본), `isTimeUnavailable`,
|
|
37
|
+
선택: `presentation`(`field` 기본/`inline`), `selectionStyle`(`list` 기본/`wheel`), `minuteStep`(1 기본), `isTimeUnavailable`,
|
|
34
38
|
`disabled`, `readOnly`, `description`, `error`, 외부 배치용 `layoutStyle`.
|
|
35
39
|
Native `safeAreaInsets`는 단독 Sheet에 전달한다.
|
|
36
40
|
|
|
@@ -50,6 +54,17 @@ import { TimePicker } from '@hjmds/react/time-picker';
|
|
|
50
54
|
labels={{ label: '시간', hour: '시', minute: '분', placeholder: '시간 선택', confirm: '선택 완료', cancel: '취소' }} />
|
|
51
55
|
```
|
|
52
56
|
|
|
57
|
+
휠 예제는 양 renderer에서 같은 옵션을 쓴다. `presentation="inline"`이면 바깥 폼이 최종 확정을 소유한다.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
// Web 또는 Native
|
|
61
|
+
<TimePicker selectionStyle="wheel" minuteStep={5} value={time} onValueChange={setTime}
|
|
62
|
+
labels={{ label: '시간', hour: '시', minute: '분', placeholder: '시간 선택', confirm: '선택 완료', cancel: '취소' }} />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
실행 예제: Web·Native의 `TimePicker.stories.tsx`에 `Wheel`(필드), `WheelInline`(본문),
|
|
66
|
+
`WheelReducedMotion`(동작 줄이기)이 있다. [Web 휠](https://storybook.jmstudioapps.com/?path=/story/components-inputs-time-picker--wheel)은 공개 사이트 반영 후 이용할 수 있다.
|
|
67
|
+
|
|
53
68
|
실행되는 원본은 [Web preview](https://github.com/jim1286/hjm-design-system/blob/main/showcase/web/src/components/preview-registry.tsx)의 `TimePickerPreview`,
|
|
54
69
|
[Native preview](https://github.com/jim1286/hjm-design-system/blob/main/showcase/native/src/component-examples.tsx)의 `TimePickerExample`이다.
|
|
55
70
|
|
|
@@ -74,6 +89,7 @@ import { TimePicker } from '@hjmds/react/time-picker';
|
|
|
74
89
|
| 고정·스크롤 | 제목·확인은 공통 overlay 영역, 시·분 목록만 세로 스크롤 | Popover / Sheet / TimePicker |
|
|
75
90
|
| 좁은 폭·큰 글자 | 좁은 폭은 두 열을 같은 폭으로 유지. 최대 글자 조건은 사용자 지침으로 범위 제외 | 포트폴리오 AGENTS.md |
|
|
76
91
|
| 선택 표시 | radius.md, surfaceAccent 배경·text 글자·500 굵기 | 시간 값은 주요 글자로 강조하고, 제품 선택 배경과 확정 버튼 대표색을 구분(2026-10-20 토스 공개 팔레트 참고 요청) |
|
|
92
|
+
| 휠 중앙·끝 여백 | 행 44, 5행 높이 220, 위아래 여백 88, 중앙 테두리 stroke.default(1) | timePickerRecipe와 양 renderer. 첫 값·마지막 값도 중앙 정렬 |
|
|
77
93
|
| 설명·오류 | 입력 아래 description/error. 오류는 보조기술에도 알림 | 양 renderer |
|
|
78
94
|
|
|
79
95
|
|
|
@@ -84,5 +100,7 @@ import { TimePicker } from '@hjmds/react/time-picker';
|
|
|
84
100
|
- `HH:mm | null`을 사용한다. `00:00`도 유효하며 날짜·시간대 변환은 제품이 담당한다.
|
|
85
101
|
- `minuteStep`은 60의 양의 정수 약수. 기존 값을 자동 반올림하거나 불가한 분을 몰래 교체하지 않는다.
|
|
86
102
|
- 빈 값은 `--:--`. 두 부분을 고르기 전에는 확정하지 않는다. 닫기는 초안을 취소한다.
|
|
103
|
+
- 휠의 초기 정렬은 선택이 아니다. 스크롤 중에는 초안만 바뀌며 field 확인은 멈춘 뒤 가능하다. 간격 밖 외부 값은 자동 반올림하지 않는다.
|
|
104
|
+
- Web 휠은 scrollend 지원 브라우저를 대상으로 한다. 방향키·Home·End와 탭 선택도 지원한다. Native 휠은 열마다 adjustable 증감 액션을 제공한다. 동작 줄이기는 Provider에서 설정한다.
|
|
87
105
|
- inline은 완전하고 사용 가능한 값만 문자열, 미완성·불가한 조합은 null을 전달한다. 바깥 확인 버튼은 null을 확정하지 않는다.
|
|
88
106
|
- 시간 선택의 공통 배치·초안·취소·키보드 처리를 앱에서 다시 조립하지 않는다. 상세 동작은 [계약](../../time-picker.md)을 따른다.
|
|
@@ -101,3 +101,8 @@ import { ToggleGroup } from "@hjmds/react-native/toggle-group";
|
|
|
101
101
|
사용하고 없으면 기존 `toggleGroupRecipe.radius` 12를 유지한다. 새 style prop이나
|
|
102
102
|
별도 토글 엔진을 추가하지 않는다. 중첩 Provider에서 별도 profile을 생략하면 상속하며,
|
|
103
103
|
명시적인 neutral profile은 중립 모서리로 돌아간다. 테마 변경은 눌린 항목·비활성·초점을 초기화하지 않는다.
|
|
104
|
+
|
|
105
|
+
### 선택 면
|
|
106
|
+
|
|
107
|
+
지속되는 `pressedIds`는 `selection.background`/`selection.content`/`selection.border`를 쓴다.
|
|
108
|
+
순간적인 포인터 누름과 키보드 포커스는 이 선택값과 별개다.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# 저장 결과
|
|
2
2
|
|
|
3
3
|
- 단계: 구성
|
|
4
|
-
- 상태:
|
|
4
|
+
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.37.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-23
|
|
8
8
|
- 근거: 사용자 요청으로 카드 안 저장 완료 예시(이전 3번)를 제거하고, 승인된 라이트·다크 토스트 시안으로 두 유형을 통일했다.
|
|
9
|
-
- 스토리북:
|
|
9
|
+
- 스토리북: `배포/구성/피드백과 복구/저장 결과`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
@@ -14,22 +14,22 @@
|
|
|
14
14
|
|
|
15
15
|
## 구성 요소
|
|
16
16
|
|
|
17
|
-
|
|
|
17
|
+
| 컴포넌트 | 역할 | 지침 |
|
|
18
18
|
| --- | --- | --- |
|
|
19
|
-
| 1 · 한 줄
|
|
20
|
-
| 2 · 요약
|
|
19
|
+
| Toast(`description`, `tone="success"`) | 1 · 한 줄 토스트: 짧은 완료 확인 | [Toast](../components/toast.md) |
|
|
20
|
+
| Toast(`title` + `description`) | 2 · 요약 토스트: 저장한 대상까지 확인 | [Toast](../components/toast.md) |
|
|
21
|
+
| ToastProvider(Web) / ToastRegion(Native) | 앱 루트에서 표시 층·큐·타이머 소유 | [Toast](../components/toast.md) |
|
|
21
22
|
|
|
22
23
|
두 유형은 별도 엔진이나 변형 prop이 아니다. 웹은 ToastProvider, Native는 ToastRegion을 앱 루트에 둔다. 카드형 저장 결과는 선택지에서 제거했다. 일반 Notice·Card·Badge의 다른 용도는 유지한다.
|
|
23
24
|
|
|
24
25
|
## 배치
|
|
25
26
|
|
|
26
|
-
| 영역 |
|
|
27
|
-
| --- | --- |
|
|
28
|
-
| 바깥 틀 | 최대 폭 420, 최소 높이 56, 모서리 24 |
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
| 요약 | 제목 → 보조 설명, 오른쪽 닫기. 장식 아이콘은 생략; 제품 media는 유지 |
|
|
27
|
+
| 영역 | 컴포넌트 | 위치 | 크기·간격 |
|
|
28
|
+
| --- | --- | --- | --- |
|
|
29
|
+
| 바깥 틀 | Toast | 화면 가장자리 16 + 안전 영역. 광고 footer가 있으면 상단 | 최대 폭 420, 최소 높이 56, 모서리 24 |
|
|
30
|
+
| 내부 | Toast | 첫 줄 기준 정렬 | 가로 16·세로 12, 닫기 터치 영역 44 유지 |
|
|
31
|
+
| 한 줄 | Toast `description` | 장식 아이콘 → 본문 → 닫기 | — |
|
|
32
|
+
| 요약 | Toast `title` + `description` | 제목 → 보조 설명, 오른쪽 닫기. 장식 아이콘은 생략; 제품 media는 유지 | — |
|
|
33
33
|
|
|
34
34
|
```text
|
|
35
35
|
한 줄: [ ✓ KE123 저장했어요 × ]
|
|
@@ -41,20 +41,42 @@
|
|
|
41
41
|
|
|
42
42
|
## 흐름과 상태
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
44
|
+
1. 사용자가 저장을 누르면 제품의 기존 제출 상태(버튼 pending 등)만 보여 준다. 이 단계에서는 토스트를 띄우지 않는다.
|
|
45
|
+
2. 저장 응답이나 영속 저장이 끝난 뒤에만 고정 `id`로 `publish`해 토스트 한 장을 띄운다.
|
|
46
|
+
3. 실패하면 토스트 대신 기존 오류·재시도 흐름을 쓰고 입력을 보존한다.
|
|
47
|
+
|
|
48
|
+
| 상태 | 모습 | 포커스·알림 |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| 기본 | 토스트 없음 | — |
|
|
51
|
+
| 진행 중 | 기존 제출 상태, 성공 알림 없음 | 포커스는 제출 버튼에 둔다 |
|
|
52
|
+
| 성공 | 저장 응답/영속 저장 완료 후 토스트 한 장 | polite 낭독 한 번, 포커스를 옮기지 않는다 |
|
|
53
|
+
| 실패 | 입력 보존, 기존 오류·재시도 | 기존 오류 안내가 낭독을 소유한다 |
|
|
49
54
|
|
|
50
55
|
기본 3초이며 긴 요약은 제품에서 시간을 늘린다. 같은 성공을 카드 상태와 토스트로 중복 알리지 않는다. 이전 초안의 늦은 응답을 새 초안의 성공으로 표시하지 않는다.
|
|
51
56
|
|
|
52
57
|
## 코드 골격
|
|
53
58
|
|
|
54
59
|
```tsx
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
// Web — ToastProvider 안에서, 영속 저장 성공 뒤에만 publish한다.
|
|
61
|
+
import { useToast } from "@hjmds/react/toast";
|
|
62
|
+
|
|
63
|
+
const toast = useToast();
|
|
64
|
+
async function save() {
|
|
65
|
+
await persist(draft);
|
|
66
|
+
toast.publish({ id: "saved", description: savedLabel, tone: "success", closeLabel });
|
|
67
|
+
// 대상 요약형: toast.publish({ id: "saved", title: savedTitle, description: subjectSummary, tone: "success", closeLabel });
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// Native — ToastRegion 안에서, 같은 descriptor를 publish한다.
|
|
73
|
+
import { useToastRegion } from "@hjmds/react-native/feedback";
|
|
74
|
+
|
|
75
|
+
const toast = useToastRegion();
|
|
76
|
+
async function save() {
|
|
77
|
+
await persist(draft);
|
|
78
|
+
toast.publish({ id: "saved", description: savedLabel, tone: "success", closeLabel });
|
|
79
|
+
}
|
|
58
80
|
```
|
|
59
81
|
|
|
60
82
|
실행 예제: [Web](../../../../../showcase/web/src/components/save-feedback-preview.tsx), [Native](../../../../../showcase/native/src/save-feedback-preview.tsx), [공통 문구](../../../../../showcase/shared/save-feedback.ts). 비교 예제의 durationMs: null은 제품 기본 타이머를 바꾸지 않는다.
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
| `canvas` = `bg` | `#ffffff` / `#0d1116` | `--hjm-color-bg` | `theme.colors.bg` | 화면 바탕. 카드·패널(Surface·Card)도 모든 tone이 `bg`를 칠하고 층은 테두리·그림자로 나눈다 |
|
|
33
33
|
| `surface.default` = `surface` | `#ffffff` / `#161b22` | `--hjm-color-surface` | `theme.colors.surface` | 바탕과 구분되는 옅은 면 |
|
|
34
34
|
| `surface.sunken` = `surfaceAlt` | `#ebeef2` / `#1e232a` | `--hjm-color-surface-alt` | `theme.colors.surfaceAlt` | 한 단계 더 들어간 면(CodeBlock 바탕) |
|
|
35
|
+
| `selection.background` = `surfaceSelected` | `#f0f1f3` / `#30343b` | `--hjm-color-surface-selected` | `theme.colors.surfaceSelected` | 기본 선택 배경 |
|
|
35
36
|
| `surface.brand` = `surfaceAccent` | `#c9e2ff` / `#224159` | `--hjm-color-surface-accent` | `theme.colors.surfaceAccent` | 브랜드가 드러나는 옅은 면 |
|
|
36
37
|
|
|
37
38
|
### 글자
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
| `content.primary` = `text` | `#191f28` / `#f3f5f7` | `--hjm-color-text` | `theme.colors.text` | 제목·강조 글자. Text `tone="primary"`(기본) |
|
|
42
43
|
| `content.body` = `textBody` | `#333d4b` / `#e1e5ea` | `--hjm-color-text-body` | `theme.colors.textBody` | 긴 본문. Text `tone="body"` |
|
|
43
44
|
| `content.secondary` = `textMuted` | `#4e5968` / `#cad0d8` | `--hjm-color-text-muted` | `theme.colors.textMuted` | 보조 설명. Text `tone="muted"` |
|
|
44
|
-
| `content.tertiary` = `textSub` | `#
|
|
45
|
+
| `content.tertiary` = `textSub` | `#646f7c` / `#9ba5b0` | `--hjm-color-text-sub` | `theme.colors.textSub` | 시간·출처 같은 약한 메타 정보. Text `tone="subtle"` |
|
|
45
46
|
| `content.decorative` = `textWeak` | `#8b95a1` / `#8b96a2` | `--hjm-color-text-weak` | `theme.colors.textWeak` | 비활성·placeholder·장식 표지. Text `tone="weak"`. 읽혀야 하는 글자에는 쓰지 않는다 |
|
|
46
47
|
|
|
47
48
|
### 브랜드
|
|
@@ -86,13 +87,13 @@
|
|
|
86
87
|
| `border.control` = `borderControl` | `#6b7684` / `#929faf` | `--hjm-color-border-control` | `theme.colors.borderControl` | 쉬고 있는 컨트롤 윤곽(Button `secondary`, 필드) |
|
|
87
88
|
| `border.strong` = `textWeak` | `#8b95a1` / `#8b96a2` | `--hjm-color-text-weak` | `theme.colors.textWeak` | 강한 경계 |
|
|
88
89
|
| `border.focus` | `contentBrand` | `--hjm-color-focus` | `theme.colors.contentBrand` | 포커스 링. 컴포넌트가 그린다 |
|
|
89
|
-
| `interaction.hover` · `focus` · `pressed`
|
|
90
|
+
| `interaction.hover` · `focus` · `pressed` | `text` 0.06 · `contentBrand` 0.08 · `text` 0.1 | — | `resolveColorReference(…, theme.palette)` | 상태 덧칠. 제품 고유 누름 영역에만 직접 쓴다 |
|
|
90
91
|
|
|
91
92
|
### 함께 쓰는 쌍과 최소 대비
|
|
92
93
|
|
|
93
94
|
`checkBrandPaletteContrast`가 검사하는 쌍이다. 이 밖의 조합(예: `contentBrand` 글자를 `surfaceAccent` 위에)은 HJM이 대비를 보장하지 않는다.
|
|
94
95
|
|
|
95
|
-
- 4.5 이상: `text`·`textBody`·`textMuted`·`textSub`·`contentBrand`·`danger` on `bg`·`surface`, `onPrimary` on `primary`, `onDanger` on `dangerFill`.
|
|
96
|
+
- 4.5 이상: `text`·`textBody`·`textMuted`·`textSub`·`contentBrand`·`danger` on `bg`·`surface`·`surfaceSelected`, `onPrimary` on `primary`, `onDanger` on `dangerFill`.
|
|
96
97
|
- 3 이상: `primary`·`borderControl` vs `bg`·`surface`(형태), `textWeak` vs `bg`.
|
|
97
98
|
|
|
98
99
|
## 쓰는 법
|
|
@@ -162,3 +163,16 @@ import { HjmProvider } from "@hjmds/react/provider";
|
|
|
162
163
|
표·표면의 회색 면을 줄이려는 사용자 결정으로 `surface`는 흰색 `#ffffff`,
|
|
163
164
|
`surfaceAlt`는 보조 회색 `#ebeef2`를 사용한다. 페이지 바탕 `bg`는 흰색이며,
|
|
164
165
|
표 본문은 `surface`, `raised` 카드는 `bg`를 소비한다. 근거는 [팔레트 결정 기록](../../theme-palette.md)을 따른다.
|
|
166
|
+
|
|
167
|
+
## 기본 선택 역할
|
|
168
|
+
|
|
169
|
+
`semanticColors.selection.background` → `surfaceSelected`: light `#f0f1f3`, dark `#30343b`.
|
|
170
|
+
Web은 `--hjm-color-surface-selected`, Native는 `theme.colors.surfaceSelected`로 읽는다.
|
|
171
|
+
선택 글자는 `semanticColors.selection.content` → `text`, 경계는 `selection.border` → `borderControl`이다.
|
|
172
|
+
휠·선택 Chip·neutral SegmentedControl에 쓰며, 주요 버튼의 `primary`나 브랜드 배경의
|
|
173
|
+
`surfaceAccent`를 선택 배경으로 덮어쓰지 않는다. [결정 근거](../../theme-palette.md#기본-선택-배경을-브랜드에서-분리한다-2026-10-23).
|
|
174
|
+
|
|
175
|
+
기본 선택의 적용 범위와 예외는 [선택 상태 색상 기준](../../theme-palette.md#선택-상태-색상-기준-2026-10-23)을 따른다.
|
|
176
|
+
|
|
177
|
+
`interaction.selected`는 이제 `surfaceSelected`를 가리킨다. 일반 선택 면에는 가능하면
|
|
178
|
+
`semanticColors.selection.background`를 사용하고, hover/focus/pressed의 투명 상태 덧칠과 구분한다.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hjmds/design-contracts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -607,6 +607,12 @@
|
|
|
607
607
|
"import": "./dist/reference-controls.js",
|
|
608
608
|
"default": "./dist/reference-controls.js"
|
|
609
609
|
},
|
|
610
|
+
"./choice-row": {
|
|
611
|
+
"types": "./dist/choice-row.d.ts",
|
|
612
|
+
"react-native": "./dist/choice-row.js",
|
|
613
|
+
"import": "./dist/choice-row.js",
|
|
614
|
+
"default": "./dist/choice-row.js"
|
|
615
|
+
},
|
|
610
616
|
"./duration-field": {
|
|
611
617
|
"types": "./dist/duration-field.d.ts",
|
|
612
618
|
"react-native": "./dist/duration-field.js",
|