@hjmds/design-contracts 1.34.0 → 1.35.1
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/anchor.d.ts +4 -1
- package/dist/anchor.d.ts.map +1 -1
- package/dist/anchor.js +3 -3
- package/dist/anchor.js.map +1 -1
- package/dist/base-recipes.d.ts +15 -1
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +14 -3
- package/dist/base-recipes.js.map +1 -1
- package/dist/behaviors.d.ts +22 -0
- 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 -7
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +31 -2
- package/dist/calendar.js.map +1 -1
- package/dist/card.d.ts +15 -1
- package/dist/card.d.ts.map +1 -1
- package/dist/card.js +14 -1
- package/dist/card.js.map +1 -1
- package/dist/carousel.d.ts +3 -0
- package/dist/carousel.d.ts.map +1 -1
- package/dist/carousel.js +10 -1
- package/dist/carousel.js.map +1 -1
- package/dist/catalog.d.ts +147 -17
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +9 -6
- package/dist/catalog.js.map +1 -1
- package/dist/colors.d.ts +1 -0
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +2 -0
- package/dist/colors.js.map +1 -1
- package/dist/component-recipes.d.ts +45 -11
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +35 -15
- package/dist/component-recipes.js.map +1 -1
- package/dist/component-references.d.ts +2 -2
- package/dist/component-references.js +1 -1
- package/dist/component-references.js.map +1 -1
- package/dist/design-profile.d.ts +14 -0
- package/dist/design-profile.d.ts.map +1 -1
- package/dist/design-profile.js +15 -0
- package/dist/design-profile.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/progress-recipe.d.ts +6 -0
- package/dist/progress-recipe.d.ts.map +1 -1
- package/dist/progress-recipe.js +15 -2
- package/dist/progress-recipe.js.map +1 -1
- package/dist/recipes.d.ts +2 -2
- package/dist/recipes.d.ts.map +1 -1
- package/dist/recipes.js +2 -2
- package/dist/recipes.js.map +1 -1
- package/dist/screen-patterns.d.ts +32 -2
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +26 -2
- package/dist/screen-patterns.js.map +1 -1
- package/dist/semantic-colors.d.ts +5 -0
- package/dist/semantic-colors.d.ts.map +1 -1
- package/dist/semantic-colors.js +2 -0
- package/dist/semantic-colors.js.map +1 -1
- package/dist/sheet.d.ts +15 -0
- package/dist/sheet.d.ts.map +1 -1
- package/dist/sheet.js +11 -0
- package/dist/sheet.js.map +1 -1
- package/dist/shortcut-rail.d.ts +20 -0
- package/dist/shortcut-rail.d.ts.map +1 -0
- package/dist/shortcut-rail.js +28 -0
- package/dist/shortcut-rail.js.map +1 -0
- package/dist/showcase.js +2 -2
- package/dist/showcase.js.map +1 -1
- package/dist/steps.d.ts +6 -4
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +3 -0
- package/dist/steps.js.map +1 -1
- package/dist/time-picker.d.ts +70 -0
- package/dist/time-picker.d.ts.map +1 -0
- package/dist/time-picker.js +51 -0
- package/dist/time-picker.js.map +1 -0
- package/dist/timeline.d.ts +13 -1
- package/dist/timeline.d.ts.map +1 -1
- package/dist/timeline.js +11 -1
- package/dist/timeline.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/calendar.md +19 -1
- package/docs/carousel.md +10 -0
- package/docs/collapsible.md +9 -0
- package/docs/consumer-policy.md +8 -12
- package/docs/design-profile.md +9 -0
- package/docs/expansion-roadmap.md +1 -1
- package/docs/generated/component-maturity.md +5 -4
- package/docs/generated/renderer-evidence.json +141 -16
- package/docs/generated/renderer-evidence.md +5 -3
- package/docs/generated/showcase-manifest.json +69 -7
- package/docs/optional-adapters.md +11 -0
- package/docs/stable-core.md +25 -0
- package/docs/steps.md +4 -0
- package/docs/theme-palette.md +2 -0
- package/docs/time-picker.md +22 -114
- package/docs/usage/README.md +9 -7
- package/docs/usage/SELECTION.md +44 -2
- package/docs/usage/components/accordion.md +4 -2
- package/docs/usage/components/alert-dialog.md +5 -0
- package/docs/usage/components/anchor.md +4 -1
- package/docs/usage/components/badge.md +7 -4
- package/docs/usage/components/bottom-cta.md +29 -4
- package/docs/usage/components/bottom-navigation.md +8 -0
- package/docs/usage/components/button.md +42 -2
- package/docs/usage/components/calendar.md +8 -1
- package/docs/usage/components/card.md +111 -7
- package/docs/usage/components/carousel.md +4 -1
- package/docs/usage/components/chat-screen.md +1 -1
- package/docs/usage/components/checkbox-group.md +33 -3
- package/docs/usage/components/checkbox.md +8 -2
- package/docs/usage/components/chip.md +5 -0
- package/docs/usage/components/collapsible.md +16 -1
- package/docs/usage/components/comment-thread-screen.md +12 -3
- package/docs/usage/components/composition-layout.md +6 -1
- package/docs/usage/components/conversation-row.md +2 -1
- package/docs/usage/components/data-table.md +10 -0
- package/docs/usage/components/description-list.md +1 -0
- package/docs/usage/components/field.md +8 -0
- package/docs/usage/components/icon-tile.md +29 -10
- package/docs/usage/components/image-choice-group.md +6 -0
- package/docs/usage/components/image.md +8 -1
- package/docs/usage/components/list-detail-screen.md +10 -4
- package/docs/usage/components/list-row.md +3 -0
- package/docs/usage/components/list.md +33 -1
- package/docs/usage/components/message-composer.md +2 -0
- package/docs/usage/components/notice.md +5 -1
- package/docs/usage/components/notification-row.md +19 -9
- package/docs/usage/components/onboarding-screen.md +3 -0
- package/docs/usage/components/page-indicator.md +12 -0
- package/docs/usage/components/profile-summary.md +16 -3
- package/docs/usage/components/progress.md +24 -0
- package/docs/usage/components/radio-group.md +45 -3
- package/docs/usage/components/radio.md +8 -2
- package/docs/usage/components/repeatable-field-group.md +1 -0
- package/docs/usage/components/screen-layout.md +46 -3
- package/docs/usage/components/search-screen.md +29 -1
- package/docs/usage/components/segmented-control.md +19 -3
- package/docs/usage/components/select.md +2 -0
- package/docs/usage/components/sheet.md +56 -3
- package/docs/usage/components/spinner.md +10 -0
- package/docs/usage/components/statistic.md +23 -0
- package/docs/usage/components/steps.md +11 -4
- package/docs/usage/components/switch.md +5 -0
- package/docs/usage/components/tabs.md +4 -0
- package/docs/usage/components/time-picker.md +88 -0
- package/docs/usage/components/timeline.md +19 -6
- package/docs/usage/components/top-bar.md +7 -0
- package/docs/usage/compositions/shortcut-rail.md +99 -0
- package/docs/usage/compositions/time-selection.md +5 -0
- package/package.json +17 -4
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
13
13
|
화면에 항상 펼쳐진 한 달 격자에서 날짜 하나를 고를 때 쓴다. 날짜마다 점·개수 같은 짧은
|
|
14
|
-
제품 콘텐츠를 붙일 수 있다.
|
|
14
|
+
제품 콘텐츠를 붙일 수 있다. 표시 월·로케일·오늘 날짜는 제품이 정하고 평범한 Gregorian 격자는 공용 생성기로 만들 수 있다.
|
|
15
15
|
|
|
16
16
|
## 쓰지 않을 때
|
|
17
17
|
|
|
@@ -28,6 +28,13 @@
|
|
|
28
28
|
| --- | --- | --- | --- |
|
|
29
29
|
| `Calendar` | 기본 | `@hjmds/react`, `/calendar` | `@hjmds/react-native`, `/calendar` |
|
|
30
30
|
|
|
31
|
+
평범한 Gregorian 월의 `grid.cells`는 개발 소스의 미게시
|
|
32
|
+
`createCalendarMonthCells(month, { weekStartsOn?: 0..6, outsideMonth?: "adjacent" | "blank" })`로 만든다.
|
|
33
|
+
import는 `@hjmds/design-contracts/components/calendar`이며 Web/Native가 같은 배열을 소비한다.
|
|
34
|
+
기본은 일요일 시작/인접 월 날짜 포함이다. 다른 주 시작을 쓰면 weekdayLabels도 같은 순서로 공급한다.
|
|
35
|
+
월·오늘·시간대·업무별 선택 제한과 콘텐츠는 제품이 정하고 함수는 시계를 읽지 않는다.
|
|
36
|
+
실행 예제는 양 renderer가 공유하는 `showcase/shared/calendar-example.ts`다(2026-10-19 중복 월 계산 제거).
|
|
37
|
+
|
|
31
38
|
## 최소 사용 예
|
|
32
39
|
|
|
33
40
|
```tsx
|
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
- 적용: 1.12.1
|
|
7
7
|
- 검토일: 2026-10-18
|
|
8
8
|
- 근거: recipe `cardRecipe`(`src/card.ts`), 바탕 `surfaceRecipe`(`src/base-recipes.ts`). 별도 계약 문서는 없다
|
|
9
|
-
- 스토리북: `배포/컴포넌트/데이터 표시/카드`
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/카드` · `실험/컴포넌트/데이터 표시/인용 카드`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
13
|
+
인용문과 출처를 묶으면 미게시 동반 API [`QuoteCard`](#인용문과-출처quotecard-미게시)를 사용한다. 일반 카드에 인용 표시·출처 여백을 다시 조립하지 않는다.
|
|
14
|
+
|
|
13
15
|
문서 metadata·미리보기·내보내기 상태는 실험 구성 [DocumentResource](../compositions/document-resource.md)를
|
|
14
16
|
먼저 대조한다. 2026-10-07 파일 사례에서 일반 카드만으로 다운로드/저장 상태까지 동등하다고
|
|
15
17
|
판단할 수 없음을 확인해 별도 구성으로 연결했다.
|
|
@@ -17,7 +19,7 @@
|
|
|
17
19
|
|
|
18
20
|
제목·설명·본문·행동이 한 덩어리로 읽히는 독립된 콘텐츠 단위에 쓴다. 주문 요약, 설정 묶음,
|
|
19
21
|
미리보기처럼 화면 안에서 경계가 보여야 하는 블록이 여기에 속한다. 위에서부터
|
|
20
|
-
`media` → `leading`+`title`+`description` → `children` → `actions` 순서가 고정이다.
|
|
22
|
+
`media` → `badges` → `leading`+`title`+`description` → `children` → `actions` 순서가 고정이다.
|
|
21
23
|
|
|
22
24
|
## 쓰지 않을 때
|
|
23
25
|
|
|
@@ -35,6 +37,7 @@
|
|
|
35
37
|
| 이름 | 역할 | Web | Native |
|
|
36
38
|
| --- | --- | --- | --- |
|
|
37
39
|
| `Card` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
40
|
+
| `QuoteCard` | 미게시 인용문·출처 동반 API | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
38
41
|
| `DocumentResource` | 실험 문서 구성 | `@hjmds/react/document-resource` | `@hjmds/react-native/document-resource` |
|
|
39
42
|
|
|
40
43
|
## 최소 사용 예
|
|
@@ -82,11 +85,48 @@ Web·Native 모두 기존 `raised` Surface의 테두리·그림자를 재사용
|
|
|
82
85
|
| `radius` | radius 이름 | `lg`(foundation 16) | 선택한 designProfile의 같은 radius 역할로 frame과 media clip을 함께 변경 |
|
|
83
86
|
| `selected` | `boolean` | `false` | `true`면 tone이 `accent`로 바뀐다(`cardRecipe.selectedTone`) |
|
|
84
87
|
| `headingLevel`(Web만) | `2` · `3` · `4` | `3` | 제목이 `h3`로 렌더되므로 문서 위계에 맞춰 고른다 |
|
|
85
|
-
| `title` · `description` · `leading` · `media` · `actions` · `children` | `ReactNode` | — | 슬롯. 순서는 HJM이 고정한다 |
|
|
88
|
+
| `title` · `description` · `leading` · `media` · `badges` · `actions` · `children` | `ReactNode` | — | 슬롯. 순서는 HJM이 고정한다 |
|
|
89
|
+
| `headerActions` | `ReactNode` | 없음 | 미게시. 제목 줄 끝의 배지·독립 명령. 카드 열기와 별도 대상으로 남으며 HJM이 위치와 제목 여유 폭을 소유한다 |
|
|
86
90
|
| `onActivate` | `() => void` | — | 카드 전체가 하나의 행동일 때 사용(미게시). Web `aria-label`, Native `accessibilityLabel` 필수. `actions`와 함께 쓰면 행동은 별도 대상으로 남는다(1.31.0 추가) |
|
|
91
|
+
| `href` · `renderLink` (Web) | 문자열 목적지 · anchor props 렌더러 | 없음 | 미게시. 전체 카드 링크. `aria-label` 필수, onActivate/mediaAction/backgroundMedia와 배타. Next 등 라우팅·제품 부수 효과는 renderLink로 연결한다. 비활성은 href 제거·tab 제외로 차단한다 |
|
|
92
|
+
| `backgroundInteraction` · `mediaBadges` | `ReactNode` | 없음 | 미게시. 배경 카드 전용 제스처/접근성 대상과 상단 끝 분류 배지. 아래 설명 참조 |
|
|
93
|
+
| `backgroundMedia` | `ReactNode` | 없음 | 장식용 배경 그림. 아래 미게시 배경 카드 계약 참조 |
|
|
94
|
+
| `mediaAction` | `{ label, onActivate, icon?: ({ color, size }) => ReactNode }` | 없음 | 커버만 누를 때 사용. `media`는 수동 콘텐츠이며 별도 버튼을 넣지 않는다. 전체 `onActivate`와 함께 쓰지 않는다. 아이콘은 HJM이 커버 끝 아래의 원형 판에 장식으로 배치한다. 미게시 |
|
|
87
95
|
| `disabled` | `boolean` | `false` | 전체 카드 행동 차단·비활성 피드백(미게시) |
|
|
88
96
|
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. 두 플랫폼 모두 root Surface에 적용된다 |
|
|
89
97
|
|
|
98
|
+
2026-10-20 번뚝 내 뚝의 짧은 안내와 편집 버튼에서 세로 중심이 어긋나는 것을 확인했다.
|
|
99
|
+
머리줄의 leading·제목/설명 묶음·headerActions는 같은 세로 중심을 사용한다. 긴 문구가 줄바꿈돼도
|
|
100
|
+
명령은 해당 머리줄의 중앙에 남으며, Native에서도 카드 열기와 별도의 접근성 대상으로 유지한다.
|
|
101
|
+
|
|
102
|
+
같은 날 다에리 여정 카드 이관에서 부모가 부여한 높이가 수동 카드의 본문까지 전달되지 않는 것을
|
|
103
|
+
확인했다. 카드가 할당 높이를 받으면 본문과 content 슬롯도 남은 높이를 채운다. 머리줄·media·actions는
|
|
104
|
+
내용 높이를 유지한다. 제품은 content 안의 Stack 등 공개 배치로 하단 요소를 정렬할 수 있다.
|
|
105
|
+
배경 그림 카드의 본문은 기존처럼 내용 높이로 아래에 놓인다.
|
|
106
|
+
|
|
107
|
+
### 그림을 배경으로 쓰는 카드(미게시)
|
|
108
|
+
|
|
109
|
+
2026-10-18 번뚝 추천/피드 머리에서 카드 위 배지·제목·그늘을 제품마다 별도로 조립하고 있었다.
|
|
110
|
+
`backgroundMedia`는 기존 Card의 `badges` → 제목/설명 → 본문 → 행동 순서를 장식용 그림 위에 놓는다.
|
|
111
|
+
`media`는 본문 위의 독립 커버이고 `backgroundMedia`는 카드 전체의 배경이다. 새 카드 이름을 만들지 않는다.
|
|
112
|
+
|
|
113
|
+
- `backgroundMedia`는 수동 장식 ReactNode이며 접근성 트리와 포인터 대상에서 제외한다. 의미 있는 설명은 title/description에 전달한다.
|
|
114
|
+
- `media`, `mediaAction`, 전체 `onActivate`와 함께 쓰지 않는다. 실행·작성자·댓글 등은 본문/`actions`의 독립 행동으로 제공한다.
|
|
115
|
+
- Web/Native 모두 recipe의 60% 검은 그늘과 흰 제목/설명을 사용한다. 브랜드 onPrimary가 어두워도 배경 그림 위 글자를 바꾸지 않는다.
|
|
116
|
+
- 배지 슬롯은 본문 시작에 가로·줄바꿈·8 간격으로 놓인다. 각 배지의 의미/값은 제품이 `Badge`로 공급한다.
|
|
117
|
+
- 높이는 콘텐츠 또는 호스트 viewport가 공급한다. 정해진 영역을 채울 때 호스트 안에서 `layoutStyle={{ flex: 1 }}`을 사용한다. 카드 자체의 클리핑·간격·모서리를 제품 CSS로 덮지 않는다.
|
|
118
|
+
- `backgroundInteraction`은 미게시 배경 카드의 독립 제스처·접근성 host다. `backgroundMedia`가 필수이며 장식 그림 위, 본문/행동 아래에 놓인다. 번뚝의 adjustable 넘기기 대상을 수동 그림에 넣으면 접근성 트리에서 사라지는 문제 때문에 분리했다. 제품의 경로/제스처 판정만 연결하며 제목·작성자·행동 배치를 이 슬롯에 복제하지 않는다.
|
|
119
|
+
- `mediaBadges`는 미게시 배경 카드 상단 끝의 비대화형 분류 표시다. HJM이 inset·간격·겹침 순서를 맡고 제품은 Badge/브랜드 분류 콘텐츠만 공급한다. `backgroundMedia` 없이 쓰면 오류다. 일반 상태는 본문 시작 `badges`를 사용한다.
|
|
120
|
+
- 커버 이미지의 구도·crop·브랜드 artwork는 제품 콘텐츠다. 카드의 그늘·제목·배지·행동 위치는 HJM이다.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
// Web
|
|
124
|
+
<Card backgroundMedia={<Artwork />} title={t("featured.title")}
|
|
125
|
+
badges={<Badge tone="neutral">{t("featured.status")}</Badge>}
|
|
126
|
+
actions={<Button onClick={open}>{t("featured.open")}</Button>} />
|
|
127
|
+
// Native: 같은 slots. Badge는 label={t("featured.status")}, Button은 onPress={open}
|
|
128
|
+
```
|
|
129
|
+
|
|
90
130
|
### 카드 전체 누름(미게시)
|
|
91
131
|
|
|
92
132
|
2026-10-10 번뚝의 세 열 시작점 선택에서 별도 선택 버튼이 좁은 카드의 절반을 차지했다.
|
|
@@ -105,7 +145,14 @@ Web·Native 모두 기존 `raised` Surface의 테두리·그림자를 재사용
|
|
|
105
145
|
```
|
|
106
146
|
|
|
107
147
|
children·media·leading에는 입력/링크/버튼을 넣지 않는다. 카드를 여는 행동과 별도 명령(삭제·더보기)이
|
|
108
|
-
함께 필요하면
|
|
148
|
+
함께 필요하면 하단 명령은 `actions`, 제목 오른쪽의 명령은 `headerActions`에 둔다.
|
|
149
|
+
|
|
150
|
+
2026-10-19 번뚝 목록은 제목·상태 배지·신고를 제품의 Surface/Button과 절대 위치로 다시 조립하고 있었다.
|
|
151
|
+
`headerActions`는 이 우상단 독립 영역을 제공한다. Web은 header flex 행 끝, Native는 실제 측정한 명령 폭과
|
|
152
|
+
헤더 위치를 기준으로 본문 활성 대상 밖에 배치한다. 앱에서 신고 크기만큼 제목 padding을 다시 계산하지 않는다.
|
|
153
|
+
예를 들어 `headerActions={<><Badge label={statusLabel} /><IconButton label={reportLabel} onClick={report} /></>}`처럼 연결한다
|
|
154
|
+
(Native는 `onPress`). 카드 열기에는 별도의 `aria-label`/`accessibilityLabel`과 `onActivate`를 전달한다.
|
|
155
|
+
명령을 `children`, `leading`, `title`에 넣지 않는다. 슬롯이 있으면 카드 활성 대상은 명령과 독립되므로 버튼이 중첩되지 않는다.
|
|
109
156
|
|
|
110
157
|
### 카드 열기와 별도 행동(1.31.0 추가)
|
|
111
158
|
|
|
@@ -127,7 +174,7 @@ children·media·leading에는 입력/링크/버튼을 넣지 않는다. 카드
|
|
|
127
174
|
|
|
128
175
|
| 항목 | Web | Native |
|
|
129
176
|
| --- | --- | --- |
|
|
130
|
-
| 구조 | root는 `<article>`로 남고, 카드 전체를 덮는 투명 `<button>`(`data-slot="activator"`)이
|
|
177
|
+
| 구조 | root는 `<article>`로 남고, 카드 전체를 덮는 투명 `<button>`(`data-slot="activator"`)이 이름·명시된 선택 상태(`aria-pressed`)·`disabled`를 가진다. 행동 영역만 그 위에 놓인다 | media+본문이 하나의 `Pressable`(버튼 role·이름·선택/비활성 상태), 행동 줄은 그 형제다 |
|
|
131
178
|
| 누르는 영역 | 행동 버튼을 뺀 카드 전체(행동 줄의 빈 자리 포함) | media+본문. 행동 줄의 빈 자리는 누름 대상이 아니다 |
|
|
132
179
|
| 초점·낭독 순서 | 카드 → 행동 | 카드 → 행동(각각 별도 접근성 요소) |
|
|
133
180
|
| 눌림·비활성 피드백 | media·본문만 흐려진다. 행동은 자기 상태를 쓴다 | 같음 |
|
|
@@ -170,13 +217,13 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
|
|
|
170
217
|
- 색·radius·padding은 `tone`·`radius`·`padding` 축으로만 바꾼다. 카드마다 브랜드 색을 칠하지 않는다.
|
|
171
218
|
- 배치는 두 플랫폼 모두 `layoutStyle`로 한다. Native `CardProps`에는 `style`이 없다. Web의 `className`·`style`로 recipe 값을 덮지 않는다.
|
|
172
219
|
카드 목록 사이 간격은 바깥 [Stack](stack.md)·[Grid](grid.md)이 정한다.
|
|
173
|
-
- 카드 전체
|
|
220
|
+
- 카드 전체 명령은 `onActivate`, Web 목적지 이동은 `href`·`renderLink`를 사용한다. 카드 안 별도 명령은 `actions`에 두고 children에 버튼을 넣지 않는다.
|
|
174
221
|
|
|
175
222
|
## 플랫폼 차이
|
|
176
223
|
|
|
177
224
|
| 항목 | Web | Native |
|
|
178
225
|
| --- | --- | --- |
|
|
179
|
-
| root | 기본 `<article>`, 전체 행동은 `<button type="button">`, 전체 행동+`actions`는 `<article>` + 덮개 `<button>` | 기본 `View`(Surface), 전체 행동은 `Pressable`, 전체 행동+`actions`는 media·본문 `Pressable` + 형제 행동 줄 |
|
|
226
|
+
| root | 기본 `<article>`, 전체 행동은 `<button type="button">`, 전체 행동+`actions`는 `<article>` + 덮개 `<button>`, `href`는 이름 있는 별도 `<a>` | 기본 `View`(Surface), 전체 행동은 `Pressable`, 전체 행동+`actions`는 media·본문 `Pressable` + 형제 행동 줄 |
|
|
180
227
|
| 제목 | `h{headingLevel}` | `Text` + `accessibilityRole="header"`, 수준 지정 없음 |
|
|
181
228
|
| 배치 | `layoutStyle` | `layoutStyle` |
|
|
182
229
|
| 내용 clip | tone의 `clipsContent`(`raised`는 그림자 때문에 clip 안 함) | 내부 `View`가 항상 clip, 그림자는 바깥에 남는다. frame과 clip이 같은 Provider radius token을 사용 |
|
|
@@ -188,3 +235,60 @@ subtree라 질감을 바꾸거나 host가 실패해도 본문/초안은 유지
|
|
|
188
235
|
Surface만 테마를 따르고 내부 media clip은 foundation 값을 쓰는 누락을 발견해 같은 token으로 연결했다.
|
|
189
236
|
|
|
190
237
|
- passive Card의 `selected`는 tone 표시다. 전체 행동 Card는 Web `aria-pressed`, Native `accessibilityState.selected`로도 선택을 알린다.
|
|
238
|
+
Web에서 `selected`를 생략한 일반 행동 카드에는 `aria-pressed`를 붙이지 않는다. 선택형 카드만
|
|
239
|
+
`true|false`를 명시한다(2026-10-19 일반 이동이 토글로 읽히던 결함 수정). 기본 모양은 선택되지 않은 상태다.
|
|
240
|
+
|
|
241
|
+
커버 실행·작성자·댓글/공유처럼 목적지가 여러 개인 카드는 전체 `onActivate` 대신 `mediaAction`으로 커버 실행을 연결하고 본문/`actions`의 공개 버튼을 독립 대상으로 둔다. 번뚝 Web/Native가 각각 버튼·재생 판 위치를 조립하던 중복을 보완했다(2026-10-18). 커버의 색과 그림·비율은 제품 `media` 슬롯에 남는다.
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
## 인용문과 출처(QuoteCard, 미게시)
|
|
245
|
+
|
|
246
|
+
### 선택과 소유권
|
|
247
|
+
|
|
248
|
+
요청 원문·작성자의 발언처럼 다른 내용과 구분되는 인용문을 출처/시각과 함께 보여 줄 때 사용한다.
|
|
249
|
+
2026-10-18 번뚝 피드와 이야기 화면이 인용 표시·본문·출처·카드 여백을 반복 구현하던 문제에서 분리했다.
|
|
250
|
+
일반 정보 묶음은 Card, 라벨–값 목록은 DescriptionList, 펼침은 Collapsible을 그대로 쓴다.
|
|
251
|
+
QuoteCard는 새 표면 엔진이 아니라 Card의 인용 의미와 본문/출처 순서를 고정하는 동반 API다.
|
|
252
|
+
|
|
253
|
+
### QuoteCard import
|
|
254
|
+
|
|
255
|
+
| API | Web | Native |
|
|
256
|
+
| --- | --- | --- |
|
|
257
|
+
| QuoteCard · QuoteCardProps | `@hjmds/react/display` 또는 root | `@hjmds/react-native/data-display` 또는 root |
|
|
258
|
+
|
|
259
|
+
### 배치와 props
|
|
260
|
+
|
|
261
|
+
위→아래: 장식 인용 표시 → 본문(children) → 출처(attribution). Card의 기본 raised 표면·테두리·모서리·본문 여백을 재사용한다.
|
|
262
|
+
본문과 출처 사이는 `spacing.xs` 8이다. 출처는 caption/muted이며 제품이 이름·버전·시각·locale을 포맷한다.
|
|
263
|
+
Web은 실제 blockquote와 footer를 사용한다. Native는 같은 순서를 유지하고, 본문에 넣은 원문 펼침 버튼을 별도 대상으로 남긴다.
|
|
264
|
+
장식 표시는 읽기 트리에서 제외한다. 왼쪽 인용 선은 그리지 않는다.
|
|
265
|
+
|
|
266
|
+
| prop | 의미 |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| children | 제품의 인용문 또는 HJM 구조(List·DescriptionList·Collapsible) |
|
|
269
|
+
| attribution? | 포맷된 출처 문자열. 없으면 출처 줄도 없음 |
|
|
270
|
+
| renderMark? | `(context: { color, size }) => ReactNode`. 기본은 인용 부호. 제공된 색/크기로 제품 아이콘을 연결할 수 있다. Web 색은 currentColor, Native는 contentBrand. 크기는 glyph.md |
|
|
271
|
+
| layoutStyle? | 공통 배치 전용 속성. 표면/글자/간격을 덮지 않는다 |
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// Web
|
|
275
|
+
import { QuoteCard } from "@hjmds/react/display";
|
|
276
|
+
import { Text } from "@hjmds/react/layout";
|
|
277
|
+
<QuoteCard attribution={formattedAttribution}><Text>{quote}</Text></QuoteCard>;
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// Native
|
|
282
|
+
import { QuoteCard } from "@hjmds/react-native/data-display";
|
|
283
|
+
import { Text } from "@hjmds/react-native/primitives";
|
|
284
|
+
<QuoteCard attribution={formattedAttribution}><Text>{quote}</Text></QuoteCard>;
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
조회/권한/원문 소유권·요약 규칙은 제품에 둔다. 원문을 접고 펼칠 때 Collapsible을 children에 연결하며,
|
|
288
|
+
QuoteCard가 자동으로 원문을 자르거나 열지 않는다. 이 API의 소스 적용·검증과 npm 게시를 구분한다.
|
|
289
|
+
|
|
290
|
+
### 전체 목적지 링크 (미게시)
|
|
291
|
+
|
|
292
|
+
다에리 편지·장소 기록은 새 탭·가운데 클릭·링크 미리보기를 보존해야 하므로 버튼 callback으로 바꾸지 않는다. `href`는 공통 카드의 media·본문을 덮는 별도 이름 있는 anchor를 만들고, `actions`는 그 위의 독립 대상으로 남긴다. 본문/그림은 링크 이름에 모두 합치지 않고 `aria-label`에 제품이 고른 짧은 이름을 준다. 프레임워크 링크·소리·초안 정리는 `renderLink`가 받은 href/className/접근성 props를 그대로 전달하면서 연결한다. 카드·배지 위치·표면·누름 피드백을 제품 CSS로 다시 조립하지 않는다. Native 목적지 이동은 기존 `onActivate` 콜백으로 연결한다.
|
|
293
|
+
|
|
294
|
+
<!-- usage-example: Web ../../../../../showcase/web/src/components/Card.stories.tsx -->
|
|
@@ -81,6 +81,8 @@ import { Carousel } from "@hjmds/react-native/carousel";
|
|
|
81
81
|
| `composeAccessibleName` | `(info: { position: number; total: number; label: string }) => string` | 필수 | `position`은 1부터 |
|
|
82
82
|
| `labels` | `{ previous: string; next: string; pause: string; resume: string; navigation: string }` | 필수 | 다섯 값 모두 비우면 `TypeError` |
|
|
83
83
|
| `currentKey` + `onCurrentKeyChange` · `defaultCurrentKey` | 슬라이드 `id`, `(key: string) => void` | 첫 슬라이드 | 제어(둘 다 필수)·비제어 중 하나만 타입이 허용한다. 값은 인덱스가 아니라 `id`다 |
|
|
84
|
+
| Native `swipeEnabled` | `boolean` | `true` | 지도 등 콘텐츠가 가로 제스처를 소유할 때 `false`로 둔다. 이전·다음·점과 접근성 이동은 유지한다. 미게시 |
|
|
85
|
+
| `slideMaxHeight` | 유한한 양수 `number` | 없음 | 가용 높이가 제한된 host에서 슬라이드 본문만 세로 스크롤한다. 이전·다음·점은 스크롤 밖에 남는다. Web은 px, Native는 pt. 미게시 |
|
|
84
86
|
| `autoplay` | `{ intervalMs: number }`(0보다 큼) | 없음 | 주면 일시정지/재개 버튼이 생긴다. 마지막 슬라이드, reduced motion, 사용자 조작 뒤에는 멈추고 명시적인 재개 전까지 다시 돌지 않는다 |
|
|
85
87
|
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 배치 전용. Native `style`은 제거된 속성 — `layoutStyle`로 배치하고 내부 표현은 recipe를 따른다 |
|
|
86
88
|
| `CarouselMotion` 탐색 | `navigation?: "buttons" \| "counter"` | `buttons` | `buttons`는 이전·다음 아이콘 버튼(`previousLabel`·`nextLabel`이 이름)을 슬라이드 아래 끝 정렬 한 줄에 `spacing.sm` 간격으로 둔다. `counter`는 이전 아이콘·현재/전체·다음 아이콘을 한 줄에 둔다. 아이콘은 읽기 방향을 따라 좌우가 바뀐다. 끝에서는 해당 버튼을 비활성화한다. Web/Native 동일, 미게시 |
|
|
@@ -95,7 +97,7 @@ import { Carousel } from "@hjmds/react-native/carousel";
|
|
|
95
97
|
| 크기 | 부모 폭을 채우고 슬라이드 높이는 내용이 정한다(현재 슬라이드만 보인다). 이전·다음은 문구를 접근성 이름으로 받는 ghost IconButton(chevronStart/chevronEnd, `carouselRecipe.sizes.medium.controlIcon`), 점은 지름 8에 터치 영역 44(`control.minTouchTarget`), 색은 `carouselRecipe.dot.color`. 두 플랫폼 동일 | `carouselRecipe.dot`, `.hjm-carousel__dot` |
|
|
96
98
|
| 간격 | 영역 사이(재생 버튼·슬라이드·조작 줄) `spacing.sm` 12, 조작 줄 안 `spacing.xs` 8 | `carouselRecipe.sizes.medium.gap`, `.hjm-carousel__controls` |
|
|
97
99
|
| 순서·정렬 | 위→아래 [일시정지/재개(autoplay일 때, 시작 쪽)] → [슬라이드] → [이전][점 · · ·][다음] 가운데 정렬. Native 점 줄은 하나의 adjustable 요소(플랫폼 page control과 같은 방식)라 화면 낭독기는 위·아래 쓸기로 넘긴다. 위치 문구는 화면에 쓰지 않고 접근성 값으로만 전한다(2026-10-17) | `react/src/carousel.tsx`, `react-native/src/carousel.tsx` |
|
|
98
|
-
| 고정·스크롤 |
|
|
100
|
+
| 고정·스크롤 | 기본은 내용 높이. `slideMaxHeight`가 있으면 본문만 해당 높이까지 커지고 세로 스크롤한다. Web 본문은 키보드로 초점을 받을 수 있으며 숨긴 페이지는 inert를 유지한다. 슬라이드 넘김은 버튼·점(또는 `CarouselMotion` 스와이프)으로 하고 가로 스크롤 영역을 만들지 않는다 | `.hjm-carousel__slide[hidden]`, 양 renderer `Carousel` |
|
|
99
101
|
| 좁은 폭·큰 글자 | [이전][점][다음]은 한 줄을 유지하고 점 묶음만 줄바꿈된다. 아이콘 버튼이라 큰 글자에서도 폭이 늘지 않는다 | `.hjm-carousel__controls`, `react-native/src/carousel.tsx` |
|
|
100
102
|
|
|
101
103
|
## 꼭 지킬 것
|
|
@@ -104,6 +106,7 @@ import { Carousel } from "@hjmds/react-native/carousel";
|
|
|
104
106
|
- 슬라이드 id는 유일하고 앞뒤 공백이 없어야 한다. 빈 배열은 던지므로 로딩·빈 상태는 마운트 전에 제품이 처리한다.
|
|
105
107
|
- `composeAccessibleName`의 어순·조사는 제품 문구다. HJM은 위치 정보만 넘긴다.
|
|
106
108
|
- 슬라이드 안의 시각 콘텐츠(카드·이미지)는 제품 소유다. 컨트롤·점·접근성 구조는 HJM 소유라 다시 만들지 않는다.
|
|
109
|
+
- 지도 위 추천 카드처럼 가용 높이가 제한된 경우 제품은 허용할 본문 높이를 `slideMaxHeight`로 전달한다. 선택의창의 앱별 ScrollView wrapper를 없애기 위해 본문 스크롤과 탐색 줄의 경계를 공통화했다(2026-10-19). 제품에서 같은 스크롤 틀을 다시 감싸지 않는다.
|
|
107
110
|
- 2026-10-07 [Motion 네 변형 검토](../../../../../docs/qa/2026-10-07-motion-reference-page-review.md)와
|
|
108
111
|
[Cedar Filmstrip 대조](../../../../../docs/qa/2026-10-07-reference-parallel-c.md)에서 여러 카드가
|
|
109
112
|
동시에 보이는 strip은 단일 active panel과 다른 계약임을 확인했다. 현재 Carousel에 임의
|
|
@@ -80,7 +80,7 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
|
|
|
80
80
|
|
|
81
81
|
| prop | 값 | 기본값 | 설명 |
|
|
82
82
|
| --- | --- | --- | --- |
|
|
83
|
-
| `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 첫 유효 입력 또는 `initialFocusTarget` 칸에 한 번 포커스한다. 기본값·진입 한정 정책 변경은 1.33.0부터 제공. 진입 종료 뒤 단계 변경·입력 표시로 자동 포커스를 다시 시작하지 않으며 하단 시트 안에서는 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스) |
|
|
83
|
+
| `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 상단·본문의 첫 유효 입력 또는 `initialFocusTarget` 칸에 한 번 포커스한다. 하단 `composer`는 자동 포커스 대상에서 제외한다(checkout 변경, 미게시). 기본값·진입 한정 정책 변경은 1.33.0부터 제공. 진입 종료 뒤 단계 변경·입력 표시로 자동 포커스를 다시 시작하지 않으며 하단 시트 안에서는 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스) |
|
|
84
84
|
| `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
|
|
85
85
|
| `headerAlignment` | `"center"` · `"start"` | `"center"` | 뒤로·도구 사이에 상대 이름을 가운데 둔다(카카오톡·인스타그램 실측) |
|
|
86
86
|
| `scroll` | `"content"` · `"screen"` | `"content"` | 가상화 목록이 스크롤을 소유하므로 작은 예제 외에는 바꾸지 않는다 |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-19
|
|
8
8
|
- 근거: recipe `selectionGroupRecipe`·`selectionControlRecipe`(`src/component-recipes.ts`), behavior `checkboxGroup`
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/체크박스 그룹`
|
|
10
10
|
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
| 하나만 고름 | [RadioGroup](radio-group.md) |
|
|
22
22
|
| 가로 버튼 줄로 짧은 옵션을 켜고 끔 | [ToggleGroup](toggle-group.md) ([경계](../../toggle-group.md)) |
|
|
23
23
|
| 약관 동의(필수가 제출 가능 여부를 정함) | [Agreement](agreement.md) ([이유](../../agreement.md)) |
|
|
24
|
-
|
|
|
24
|
+
| 그룹 밖 단일 필터 칩 | [Chip](chip.md) `selectionMode="multiple"` |
|
|
25
25
|
| 목록 밖 값을 직접 입력 | [TagsInput](tags-input.md) |
|
|
26
26
|
|
|
27
27
|
## 공개 이름과 import
|
|
@@ -68,7 +68,7 @@ import { CheckboxGroup } from "@hjmds/react-native/inputs";
|
|
|
68
68
|
| `defaultValue` | `ReadonlySet<Key>` | 빈 `Set` | 비제어. 항목에서 빠진 id는 선택에서 자동으로 지워진다 |
|
|
69
69
|
| `label` · `accessibilityLabel` | `string` | — | 둘 중 하나는 필수 |
|
|
70
70
|
| `orientation` | `vertical` · `horizontal` | `vertical` | — |
|
|
71
|
-
| `presentation` | `card` · `plain` · `grouped` | `card` | — |
|
|
71
|
+
| `presentation` | `card` · `plain` · `grouped` · `chips` | `card` | — |
|
|
72
72
|
| `size` | `medium` · `small` | `medium` | — |
|
|
73
73
|
| `renderLeading` | Web `(item, appearance: { selected, color: "currentColor", size }) => ReactNode` · Native `(item, props: { checked, selected, disabled, readOnly, color, size }) => ReactNode` | — | 항목 아이콘 |
|
|
74
74
|
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 그룹 외곽(Web `fieldset`) 배치 |
|
|
@@ -106,3 +106,33 @@ import { CheckboxGroup } from "@hjmds/react-native/inputs";
|
|
|
106
106
|
## 근거
|
|
107
107
|
|
|
108
108
|
2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
### 미선택·다중 선택의 칩 묶음
|
|
112
|
+
|
|
113
|
+
2026-10-19 오늘야구 연도 선택 이관에서 nullable 단일 선택과 드래프트 다중 선택을 앱이
|
|
114
|
+
Chip/직접 wrap으로 조립하고 있었다. `presentation="chips"`는 기존 선택 엔진·항목 타입·값 계약을
|
|
115
|
+
유지하고 Chip recipe의 모서리·색·타이포·크기를 사용한다. 기본 원/체크 indicator는 감추고(Native의 명시적 `renderIndicator`는 유지),
|
|
116
|
+
선택 색·굵기와 실제 radio/checkbox 의미를 유지한다. 그룹은 orientation과 관계없이 시작 쪽부터
|
|
117
|
+
줄바꿈하고 항목 간격은 `spacing.xs` 8이다. 설명·오류·비활성·읽기 전용 계약도 같다.
|
|
118
|
+
|
|
119
|
+
`size="small"`의 보이는 최소 높이는 Chip처럼36, medium은44이며 작은 칩의 터치 영역은44다.
|
|
120
|
+
필수 단일 보기 전환은 SegmentedControl, 선택 없음이 유효한 질문은 RadioGroup,
|
|
121
|
+
동시에 여러 조건을 고르면 CheckboxGroup을 사용한다. chips 모양 때문에 다른 선택 의미로 바꾸지 않는다.
|
|
122
|
+
기존 양쪽 RadioGroup의 `ChipChoices` 예제가 두 그룹과 미선택/다중 선택을 함께 실행한다.
|
|
123
|
+
|
|
124
|
+
연도/카테고리 창이 전체 목록의 일부만 보여주면 제품은 보이는 값만 그룹에 전달하고,
|
|
125
|
+
활성화된 항목의 도메인 변경을 전체 선택에 합쳐야 한다. 현재 보이지 않는 선택을 실제 목록 삭제로
|
|
126
|
+
오인해 지우지 않는다. 삭제·비활성 항목 pruning은 기존 그룹 계약을 그대로 따른다.
|
|
127
|
+
|
|
128
|
+
### 동일 폭의 선택 카드(미게시)
|
|
129
|
+
|
|
130
|
+
`grid`에 기존 Grid descriptor(`columns`, `gap`, `minColumnWidth`)를 전달하면 Web·Native 모두
|
|
131
|
+
선택 항목을 동일 폭으로 배치한다. 생략하면 기존 `orientation` 배치를 유지한다.
|
|
132
|
+
2026-10-20 다에리의 직접 카드/내부 CSS 이관이 계기이며, 선택 엔진은 바꾸지 않고 공통 Grid의 측정·열 축소를 합성한다.
|
|
133
|
+
예: `grid={{ columns: { compact: 2 }, gap: { compact: "xs" }, minColumnWidth: { compact: 152 } }}`.
|
|
134
|
+
그룹 안쪽을 측정해 두 열이 들어가지 않으면 한 열로 줄인다. 제품에서 항목 폭·grid CSS를 다시 계산하지 않는다.
|
|
135
|
+
|
|
136
|
+
### 같은 행의 카드 높이 (checkout 수정·미게시)
|
|
137
|
+
|
|
138
|
+
`presentation="card"` 항목은 같은 Grid 행에서 가장 높은 콘텐츠에 맞춰 칸을 채운다. Native의 Grid 칸 안에서 카드가 내용 높이만 사용하던 문제를 공통 ChoiceRow에서 수정했다(2026-10-20 다에리 공개 범위 선택 카드 지적). 제품에서 높이를 고정하거나 설명을 줄여 맞추지 않는다. 행별 높이는 해당 행의 콘텐츠로 결정한다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-19
|
|
8
8
|
- 근거: recipe `selectionControlRecipe`(`src/component-recipes.ts`), behavior `checkbox`
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/체크박스`
|
|
10
10
|
|
|
@@ -57,7 +57,7 @@ import { Checkbox } from "@hjmds/react-native/inputs";
|
|
|
57
57
|
|
|
58
58
|
| prop | 값 | 기본값 | 설명 |
|
|
59
59
|
| --- | --- | --- | --- |
|
|
60
|
-
| `presentation` | `card` · `plain` · `grouped` | `card` | — |
|
|
60
|
+
| `presentation` | `card` · `plain` · `grouped` · `chips` | `card` | — |
|
|
61
61
|
| `size` | `medium` · `small` | `medium` | — |
|
|
62
62
|
| `checked` · `defaultChecked` | Web `boolean` · Native `boolean \| "mixed"` | `defaultChecked` `false` | `checked`를 주면 제어, 없으면 비제어다 |
|
|
63
63
|
| 부분 선택 | Web `indeterminate: boolean` · Native `checked="mixed"` | Web `false` | Native에서 `mixed`를 누르면 `true`가 된다 |
|
|
@@ -101,3 +101,9 @@ import { Checkbox } from "@hjmds/react-native/inputs";
|
|
|
101
101
|
|
|
102
102
|
- Web은 `className`·`layoutStyle`이 바깥 `label`에, 나머지 HTML 속성(`style`, `name`, `onFocus` 등)은 숨은
|
|
103
103
|
`input`에 붙는다. 배치용 `style`을 넘기면 보이는 행이 아니라 input에 적용되므로 `layoutStyle`을 쓴다.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
`presentation="chips"`는 같은 선택 의미를 칩 모양으로 표시한다. 기본 원/체크 표시 대신
|
|
107
|
+
선택 테두리·전경색·굵기를 쓰며 실제 radio/checkbox 의미와 disabled/readOnly를 유지한다.
|
|
108
|
+
여러 항목의 줄바꿈·그룹 이름·선택 관리는 제품이 직접 조립하지 말고 해당 Group의
|
|
109
|
+
`presentation="chips"`를 사용한다([RadioGroup](radio-group.md#미선택다중-선택의-칩-묶음), [CheckboxGroup](checkbox-group.md#미선택다중-선택의-칩-묶음)).
|
|
@@ -87,6 +87,11 @@ import { Chip } from "@hjmds/react-native/inputs";
|
|
|
87
87
|
- 색·테두리를 덮지 않는다. 배치는 두 renderer 모두 `layoutStyle`로 한다. Native `labelStyle`은 제거된 속성 —
|
|
88
88
|
`size`·`selected`로 글자 모양을 정한다([이관 문서](../../migration-native-legacy-removal.md)).
|
|
89
89
|
|
|
90
|
+
### 열기와 삭제가 독립적인 가로 목록
|
|
91
|
+
|
|
92
|
+
`ShortcutRail`은 Chip의 companion이다. 최근 도시·도구처럼 열기와 기록 삭제가 각각 필요한 항목은
|
|
93
|
+
[바로가기 목록 구성](../compositions/shortcut-rail.md)을 쓴다. Chip.trailing에 삭제 버튼을 넣어 중첩하지 않는다.
|
|
94
|
+
|
|
90
95
|
## 플랫폼 차이
|
|
91
96
|
|
|
92
97
|
| 항목 | Web | Native |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-20
|
|
8
8
|
- 근거: [Collapsible](../../collapsible.md), `FolderPreview`는 [Folder preview](../../folder-preview.md), recipe `collapsibleRecipe`(`src/collapsible.ts`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/데이터 표시/접기와 펼치기` · `배포/컴포넌트/데이터 표시/폴더 미리보기`
|
|
10
10
|
|
|
@@ -73,6 +73,9 @@ import { FolderPreview } from "@hjmds/react-native/folder-preview";
|
|
|
73
73
|
| `open` + `onOpenChange` | `boolean` + `(open: boolean) => void` | — | 제어. `open`만 주고 `onOpenChange`를 빼면 `TypeError`를 던진다 |
|
|
74
74
|
| `defaultOpen` · `onOpenChange` | `boolean` · `(open: boolean) => void` | `defaultOpen` `false` | 비제어. `open`과 `defaultOpen`을 함께 주면 `TypeError`를 던진다 |
|
|
75
75
|
| `trigger` | `ReactNode` | 필수 | 버튼 문구. Native는 문자열이면 HJM `Text`로 감싼다 |
|
|
76
|
+
| `triggerLabel` | `string` | — | 미게시 로컬 소스. 그림뿐인 trigger 또는 보이는 문구보다 자세한 행동 이름. Web aria-label·Native accessibilityLabel에 연결하며 생략하면 기존 자식 문구를 사용한다 |
|
|
77
|
+
| `title` | `ReactNode` | — | 미게시 로컬 소스. 버튼 밖 정적 제목. 제목 시작 쪽·펼침 행동 끝 쪽으로 공통 헤더가 배치한다. heading 위계는 제품이 title 슬롯에 지정한다 |
|
|
78
|
+
| `pendingLabel` | `string` | — | 미게시 로컬 소스. 준비 중 펼침 버튼을 작은 Spinner로 교체하고 본문을 숨긴다. 제목/최소 높이44는 유지하며 root는 busy다. 완료해도 자동으로 펼치지 않는다 |
|
|
76
79
|
| `disabled` | `boolean` | `false` | trigger 옆의 펼침 표시(닫힘 `chevronEnd`·열림 `chevronDown`, glyph.sm 20)는 HJM이 그리며 장식으로 숨겨진다. 닫힘 표시는 RTL에서 읽기 끝 쪽으로 뒤집힌다 |
|
|
77
80
|
| `marker` | `end` · `none` | `end` | `none`이면 표시를 그리지 않고, 한 줄이 아닌 trigger가 `CollapsibleMarker`(`open`)를 직접 둔다(FolderPreview가 사용) |
|
|
78
81
|
| `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 |
|
|
@@ -126,3 +129,15 @@ Web에서 외부 상태 변경으로 닫힐 때 내부에 있던 초점은 trigg
|
|
|
126
129
|
|
|
127
130
|
- `edge` 축을 두지 않는다: 2026-10-17 1.27.2 채택 점검. 포트폴리오 사이트 헤더 끝 메뉴 트리거(402)의 펼침 표시 칸 끝은 화면 끝에서 36이었는데, 트리거 자체는 좌우 padding 0이고 표시 칸 끝이 트리거 끝과 같다(Storybook 측정: 표시 칸 끝 = 트리거 끝 = 루트 끝). 36은 제품이 TopBar(자기 여백 16)를 `Container gutter="regular"`(20) 안에 둔 결과다. IconButton/Button `edge`는 frameless 컨트롤의 안쪽 padding만큼 바깥으로 내미는 규칙인데 이 트리거는 내밀 padding이 없어 `edge`는 아무것도 바꾸지 못한다. 위치는 제품의 겹친 여백에서 고친다.
|
|
128
131
|
- 2026-10-17 구성 리뷰: 텍스트 표시 ▸가 약 8px로 그려져 트리거가 정적 문구처럼 보였다. 그래서 두 플랫폼 모두 glyph.sm 20 chevron 아이콘으로 바꿨다.
|
|
132
|
+
|
|
133
|
+
2026-10-20 번뚝 평가 영역의 제목/펼치기·로딩 헤더 복제를 없애기 위해 `title`과 `pendingLabel`을 양 renderer에 제공한다. 제목은 버튼 이름과 분리하고, 준비 중에도 같은 행동 위치·최소 높이를 유지한다. 기존 제목 없는 trigger와 inline은 유지한다. Web에서 열린 내용에 초점이 있는 동안 준비 상태로 바뀌면 trigger도 없으므로 host로 복구한다.
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
2026-10-20 Spint의 꽃 비율은 제품 그림을 눌러 설명을 펼치면서 자체 Pressable·expanded·조건부 본문을 소유했다. 그림만 있는 trigger의 행동 이름을 `triggerLabel`로 지정하고 `marker="none"`으로 기존 그림을 유지한다. HJM은 최소44 target·펼침 상태·숨긴 본문 경계를 소유하며, 꽃/색상 비율 계산과 햅틱은 제품에 남긴다. 기존 문자열 trigger의 이름은 변하지 않는다.
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<Collapsible trigger={<DistributionArtwork />} triggerLabel={t(open ? "distribution.hide" : "distribution.show")}
|
|
140
|
+
marker="none" open={open} onOpenChange={setOpen}>
|
|
141
|
+
<DistributionLegend />
|
|
142
|
+
</Collapsible>
|
|
143
|
+
```
|
|
@@ -92,8 +92,17 @@ import { MessageComposer } from "@hjmds/react-native/screens";
|
|
|
92
92
|
`CommentThreadItem`: `id`, `parentId`(`null`이면 최상위), `author`, `body`(노드), `timeLabel`, `likeCountLabel`,
|
|
93
93
|
`likeIcon`, `likeLabel`은 필수. 선택은 `bodyText`(작성자와 본문을 한 줄 흐름으로), `avatar`, `likeAction`(기본
|
|
94
94
|
좋아요 버튼 교체, `null`이면 생략), `actions`(하트 바로 옆의 더보기 메뉴 등), `canReply`(기본: 최상위만 true), `replyDisabled`.
|
|
95
|
+
최상위 항목에는 `repliesCount`·`repliesNotice`·`repliesFooter`를 연결할 수 있다(checkout 변경·미게시).
|
|
96
|
+
`repliesCount`는 0 이상 정수인 서버 전체 개수이며 생략하면 받은 답글 수다. 실제 받은 수보다 작으면
|
|
97
|
+
받은 수를 표시한다. 원글 본문 뒤 답글 들여쓰기 영역에 펼침 버튼 → (펼쳤을 때) repliesNotice → 답글 행 → repliesFooter 순으로 둔다.
|
|
98
|
+
받은 답글이 없어도 전체 개수·복구 안내·더 보기 슬롯으로 답글 영역을 열 수 있다. 접으면 세 영역이 함께 숨는다.
|
|
99
|
+
조회·커서·재시도·문구·데이터 보존은 제품이 맡고 오류/페이지 행동은 HJM Notice·Button/LoadMore로 공급한다.
|
|
100
|
+
Spint에서 페이지 오류와 더 보기가 원글 body에 들어가 답글을 접어도 남던 2026-10-18 이관이 확장 근거다.
|
|
101
|
+
실행 원본: [Web 답글 복구](../../../../../showcase/web/src/components/comment-thread-replies-preview.tsx),
|
|
102
|
+
[Native 답글 복구](../../../../../showcase/native/src/components/comment-thread-replies-preview.tsx).
|
|
103
|
+
|
|
95
104
|
`reactions`(미게시)를 주면 작성자·본문을 길게 눌러 ChatMessage와 같은 메뉴(반응 줄 → 미리보기 → 시각 → 답글 달기 → `menuActions` → `reportAction` 신고)가 열린다. 답글 달기는 `canReply`일 때 `onReply`로 자동 연결되고, 아이콘은 `reactions.replyIcon`으로 넘긴다. `reactionSummary`는 답글 달기 앞에 놓는 선택된 반응 표시 슬롯이다. 댓글의 기본은 반응 옵션을 비운(`options: []`) 동작 목록 전용 메뉴다(답글 달기 → `menuActions` → 신고, 인스타그램 댓글 길게 누르기 실측). 댓글 이모지는 길게 누르기가 아니라 작성창의 `quickInserts` 이모지 줄로 입력한다. 반응 줄은 제품이 명시적으로 댓글 반응을 운영할 때만 옵션을 넣는다. 댓글 메뉴에는 시각 제목을 두지 않는다(행에 이미 보인다).
|
|
96
|
-
`media`(미게시, 선택)는 본문 아래 사진·스티커이며 최대 폭 `commentThreadRecipe.mediaMaxWidth` 160, radius 12로 감싼다. 사진 여러 장을 한 장씩 불러와야 하면 `media` 대신 `mediaAlbum`, 시각을 답글 줄로 옮기려면 `timePlacement: "actions"`, 수정·신고·차단 양식은 `panel`을 쓴다(미게시, [CommentThreadRow](comment-thread-row.md#축과-기본값)). `likeCountLabel`은 하트 아래 숫자만(예: "15") 넣고 0이면 빈 문자열로 숨긴다. 개수를 포함한 전체 문장은 `likeLabel`에 둔다.
|
|
105
|
+
`media`(미게시, 선택)는 본문 아래 사진·스티커이며 최대 폭 `commentThreadRecipe.mediaMaxWidth` 160, radius 12로 감싼다. 호스트는 기본 폭 160과 부모 기준 최대 폭 100%를 함께 제공하므로, percentage 폭 이미지도 별도 제품 썸네일 틀 없이 비율을 유지한다. Spint 사진의 본문 내 배치를 media 슬롯으로 옮기면서 확인한 계약이다. 사진 여러 장을 한 장씩 불러와야 하면 `media` 대신 `mediaAlbum`, 시각을 답글 줄로 옮기려면 `timePlacement: "actions"`, 수정·신고·차단 양식은 `panel`을 쓴다(미게시, [CommentThreadRow](comment-thread-row.md#축과-기본값)). `likeCountLabel`은 하트 아래 숫자만(예: "15") 넣고 0이면 빈 문자열로 숨긴다. 개수를 포함한 전체 문장은 `likeLabel`에 둔다.
|
|
97
106
|
|
|
98
107
|
## 배치
|
|
99
108
|
|
|
@@ -113,13 +122,13 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
|
|
|
113
122
|
| --- | --- | --- |
|
|
114
123
|
| 크기 | ScreenLayout 폭(최대 720); 아바타 `commentThreadRecipe.avatarSize` 32, 답글 아바타 `replyAvatarSize` 24(제품은 이 크기로 넘긴다); 본문 열이 남은 폭을 채우고(`flex: 1`, 최소 폭 0) 오른쪽 끝에 좋아요(44 터치 영역)·`actions`를 한 행으로, 좋아요 수는 하트 아래 가운데; 답글·펼침은 흐린 글자 버튼(caption, 굵게) | `commentThreadRecipe`, Web·Native `CommentThreadRow` |
|
|
115
124
|
| 간격 | 화면 padding `spacing.md` 16; 댓글 행 사이 `rowGap` 12(최상위·답글 동일); 답글 묶음은 부모 글 열에 맞춰 `replyIndent` 40 들여쓰기; 아바타–본문 `avatarGap` 8; 작성자·본문·답글 줄 사이 `lineGap` 2; 작성자–시각 `spacing.xxs` 4; 좋아요–actions `spacing.xxs` 4 | `commentThreadRecipe` |
|
|
116
|
-
| 순서·정렬 | 최상위 댓글(아바타 → 작성자·시각 → 본문 → 답글 달기) + 행 오른쪽 좋아요 → `actions` → `commentMenu` 세로 점 버튼(끝), 그 아래 좋아요 수 → 답글 펼침 → 펼친 답글 → … → `threadFooter` → composer(footer) | 렌더 순서 |
|
|
125
|
+
| 순서·정렬 | 최상위 댓글(아바타 → 작성자·시각 → 본문 → 답글 달기) + 행 오른쪽 좋아요 → `actions` → `commentMenu` 세로 점 버튼(끝), 그 아래 좋아요 수 → 답글 펼침 → 펼친 repliesNotice → 답글 → repliesFooter → … → `threadFooter` → composer(footer) | 렌더 순서 |
|
|
117
126
|
| 고정·스크롤 | 헤더·composer 고정, 댓글은 본문 화면 스크롤(`scroll` 기본 `"screen"`); composer는 `ready`·`empty`에서만 | `ScreenLayout`, `screen-flows.tsx` |
|
|
118
127
|
| 좁은 폭·큰 글자 | 시각·좋아요 수·답글 버튼 줄과 작성자 줄은 줄바꿈(`flexWrap: "wrap"`); 답글은 한 단계만 들여써 좁은 폭에서도 본문 폭을 지킨다 | `screen-flows.tsx` |
|
|
119
128
|
|
|
120
129
|
## 꼭 지킬 것
|
|
121
130
|
|
|
122
|
-
- 진입 포커스(`initialFocus`)는 페이지에서 기본 `first-input`이다(1.33.0 정책 변경). 하단 시트 안에서는 중첩 화면·작성창 모두 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스)를 따른다.
|
|
131
|
+
- 진입 포커스(`initialFocus`)는 페이지에서 기본 `first-input`이다(1.33.0 정책 변경). 상단·본문 입력만 대상이며 하단 `composer`는 자동 포커스하지 않는다(2026-10-18 후속 요청, checkout 변경·미게시). 하단 시트 안에서는 중첩 화면·작성창 모두 자동 포커스하지 않는다. [진입 포커스](screen-layout.md#진입-포커스)를 따른다.
|
|
123
132
|
|
|
124
133
|
- id는 비어 있지 않고 유일해야 하며, 답글의 `parentId`는 `items` 안의 **최상위** 댓글이어야 한다. 아니면 렌더 중
|
|
125
134
|
`TypeError`가 난다. 답글의 답글은 표현하지 않으므로 제품이 최상위로 평탄화한다.
|
|
@@ -73,7 +73,7 @@ footer
|
|
|
73
73
|
| 항목 | 값 | 근거 |
|
|
74
74
|
| --- | --- | --- |
|
|
75
75
|
| 크기 | 부모의 가용 폭·높이 | 별도 고정 높이나 스크롤 없음 |
|
|
76
|
-
| 제목·상태 문구 | 제목은 두 플랫폼 모두 `Heading
|
|
76
|
+
| 제목·상태 문구 | 제목은 두 플랫폼 모두 `Heading level5`(문서 단계는 `headingLevel`). 상태 블록은 제목(`Text title`)·설명이 블록 줄로 가운데 정렬된다 | renderer. 2026-10-17 화면 검토: Native 제목이 작은 `Text title`이었고 Web 상태 문구가 한 줄로 이어져 왼쪽 정렬됐다 |
|
|
77
77
|
| 간격 | 영역 spacing.md 16, 제목 행 spacing.sm 12, 배지 spacing.xs 8, 제목/설명 spacing.xxs 4 | Stack props |
|
|
78
78
|
| 순서·정렬 | 머리글 → notice → 본문 → footer | renderer |
|
|
79
79
|
| 고정·스크롤 | 부모 HJM 화면이 소유, 자체 ScrollView 없음 | renderer |
|
|
@@ -92,3 +92,8 @@ footer
|
|
|
92
92
|
| --- | --- | --- |
|
|
93
93
|
| 의미 | Heading·alert/status | header·accessibilityLiveRegion |
|
|
94
94
|
| 환경 | 부모 화면의 높이 | 부모 화면의 safe area·키보드 |
|
|
95
|
+
|
|
96
|
+
제목과 설명 묶음은 자연 너비를 기준으로 늘어나며 부모 너비를 넘지 않는다. 제목과 actions가 한 줄에 들어가지 않으면 actions가 다음 줄로 이동한다. Spint 셀에서 제목이 정렬 버튼 옆의 남은 폭으로 압축되던 문제를 양 renderer에서 수정했다(미게시).
|
|
97
|
+
|
|
98
|
+
2026-10-19 사용자 지시: 앱·모바일 웹뷰의 큰 글씨를 지양하므로 구획 제목은 두 renderer 모두 18(level5)을 기본으로 쓴다.
|
|
99
|
+
headingLevel은 문서 구조만 결정한다. 기존 24는 상한이며 일반 카드·선택 영역 제목의 기본값이 아니다.
|
|
@@ -69,7 +69,8 @@ import { ConversationRow } from "@hjmds/react-native/conversation-row";
|
|
|
69
69
|
| 고정·스크롤 | 바깥 목록 host 소유 | 별도 scroller 없음 |
|
|
70
70
|
| 좁은 폭·큰 글자 | 가용 폭에서 미리보기 말줄임 | 양 renderer |
|
|
71
71
|
|
|
72
|
-
메시지가 길어도 시각과 읽지 않음 점은 유지하고 미리보기만 말줄임한다.
|
|
72
|
+
메시지가 길어도 시각과 읽지 않음 점은 유지하고 미리보기만 말줄임한다.
|
|
73
|
+
미리보기·시각이 비어 있으면 해당 표시를 생략하고, 둘 다 있을 때만 사이 구분점을 표시한다. 첨부·삭제 등의 대체 문구는 실제 데이터에 따라 제품에서 현지화한다. 앱은 별도 행 padding을 넣지 않는다.
|
|
73
74
|
|
|
74
75
|
길게 눌러 여는 행 메뉴(읽지 않음 표시·알림 해제·차단·삭제 등)는 행 호스트를 [ContextMenu](context-menu.md)로 감싼다. 위험 항목은 `tone: "danger"`이며 목록 끝에 둔다. 예제: `배포/화면/소통/대화 목록`.
|
|
75
76
|
|
|
@@ -109,6 +109,7 @@ const sorted = sortDataTableRows(players, sort, (player, columnId) => toDataTabl
|
|
|
109
109
|
| `columns` | `{ id, header: string, align?, sortable?, width?, sortDescFirst?, pinned? }[]` | — (필수, 하나 이상) | `id`는 비어 있지 않고 중복 없음 |
|
|
110
110
|
| `rows` | `{ id, disabled? }[]` | — (필수) | 셀 내용은 `renderCell`이 그린다 |
|
|
111
111
|
| `renderCell` | `(rowId: RowKey, columnId: ColumnKey) => ReactNode` | — (필수) | 행·열 id로 셀을 그린다 |
|
|
112
|
+
| `getCellText` | `(rowId, columnId) => string \| undefined` | 없음 | 미게시. 사진·아이콘 등을 포함한 셀의 읽기용 값. Native 행 이름과 Web 셀 이름에 사용한다. 생략한 문자열·숫자 셀은 기존 표시 텍스트를 읽는다 |
|
|
112
113
|
| `labels` | `{ table: string; selectAll: string; selectRow: (rowId: string) => string; sortColumn: (header: string, direction: DataTableSortState) => string }` | — (필수) | 모든 문구는 i18n 키. Native는 `table`·`sortColumn`만 받는다 |
|
|
113
114
|
| `sortState` | `{ columnId, direction: "ascending" \| "descending" } \| null` | `null` | 제어 전용(내부 상태 없음) |
|
|
114
115
|
| `onSortChange` | `(next: DataTableSortState<ColumnKey>) => void` | — | 다음 정렬 상태(`null` = 정렬 없음)를 알린다. 재정렬은 제품이 한다 |
|
|
@@ -202,3 +203,12 @@ const sorted = sortDataTableRows(players, sort, (player, columnId) => toDataTabl
|
|
|
202
203
|
### ComparisonTable 중복 머리글
|
|
203
204
|
|
|
204
205
|
2026-10-14 야잘알 비교 화면에서 상단 선택 카드의 선수·시즌이 표에 반복되어 읽는 흐름을 끊었다. Web/Native의 `showSideNames={false}`로 시각 머리글만 숨긴다(기본 `true`). `sides`는 계속 전달하며 각 행의 접근성 이름에는 양쪽 이름과 수치를 보존한다. 상단에서 양쪽 대상을 식별할 수 있을 때만 사용한다. 행 여백과 막대 간격을 넓히고 지표·비우세 수치의 대비를 유지해 기록을 구분한다.
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
2026-10-19 오늘야구 선수단/박스스코어에서 사진과 이름을 함께 그린 셀이 Native 행 이름에 포함되지 않아
|
|
209
|
+
"선수"/"타자"로만 읽히는 문제를 확인했다. `getCellText`에는 화면에 표시한 이름·값을 전달한다.
|
|
210
|
+
ReactNode에서 자식 텍스트나 이미지 prop을 추측하지 않는다. Web은 각 셀의 `aria-label`로 사용하며
|
|
211
|
+
선택 체크박스의 이름·키보드 동작은 기존 `labels.selectRow`/selection 계약을 유지한다.
|
|
212
|
+
Native는 고정 열의 행 target 하나만 이름을 갖고, 고정 열이 없는 표도 같은 행 이름을 갖는다.
|
|
213
|
+
`onRowPress`를 쓰는 분할 행의 스크롤 영역은 터치·포인터로 같은 행을 열지만 별도 접근성·키보드 대상은 아니다.
|
|
214
|
+
개발 브라우저에서도 이 구분을 유지하며, 고정 행 이름에는 스크롤 열의 값까지 포함한다.
|
|
@@ -84,6 +84,7 @@ import { DescriptionList } from "@hjmds/react-native/data-display";
|
|
|
84
84
|
## 꼭 지킬 것
|
|
85
85
|
|
|
86
86
|
- `value`는 제품이 locale·단위까지 포맷한 문자열로 넘긴다. HJM은 값을 포맷하지 않는다.
|
|
87
|
+
- 값 안의 줄바꿈은 양 플랫폼에서 보존한다. 2026-10-20 번뚝 작성 확인 단계 이관 중 Web의 기본 공백 처리로 여러 줄 내용이 한 줄로 합쳐지는 것을 확인해 수정했다.
|
|
87
88
|
- 큰 글자 대응으로 화면에서 `fontScale`을 보고 `columns={1}`을 넘기지 않는다. 접힘은 resolver가 한다
|
|
88
89
|
([계약](../../description-list.md#큰-글자에서의-전환--resolver가-소유)).
|
|
89
90
|
- 라벨은 i18n 키로 넣는다. Native는 목록 전체의 접근성 이름 `label`이 필수다.
|
|
@@ -168,3 +168,11 @@ Web TextField의 `aria-invalid={true}`는 외부 오류 ID를 `aria-describedby`
|
|
|
168
168
|
여러 입력이 한 질문에 답하면 `FieldGroup`을 사용한다. Form의 제출 경계와 구분하며
|
|
169
169
|
`@hjmds/react/field-group`, `@hjmds/react-native/field-group`에서 제공한다.
|
|
170
170
|
[관련 입력 묶음 지침](../compositions/field-group.md)의 renderField 연결·오류·잠금 규칙을 따른다.
|
|
171
|
+
|
|
172
|
+
### 오류 상태의 비색상 단서(미게시)
|
|
173
|
+
|
|
174
|
+
`fieldRecipe.states.invalid.borderWidth`는 2px/2dp다. 기본 테두리1과 구별하여 브랜드 포커스와
|
|
175
|
+
오류색이 비슷한 제품에서도 오류를 색만으로 전달하지 않는다(2026-10-20 다에리의 제품별 오류 CSS 이관).
|
|
176
|
+
Web TextArea도 `aria-invalid`를 공통 frame에 전달한다. 오류가 비활성 입력에 남아 있어도
|
|
177
|
+
오류 테두리·별도 안내는 유지하며, label/control의 비활성 흐림과 기존 error 안내/읽기는 그대로다.
|
|
178
|
+
제품에서 `.hjm-field__control:has(...)`로 유효성을 다시 추론하거나 테두리를 덮지 않는다.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# IconTile
|
|
2
2
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
|
-
- 상태:
|
|
4
|
+
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.26.0
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-20
|
|
8
8
|
- 근거: `src/screen-patterns.ts`의 `iconTileRecipe`
|
|
9
|
-
- 스토리북:
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/아이콘 타일`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
@@ -32,17 +32,16 @@
|
|
|
32
32
|
```tsx
|
|
33
33
|
// Web
|
|
34
34
|
import { IconTile } from "@hjmds/react/screens";
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
</button>
|
|
35
|
+
<IconTile icon={artwork} label={title} overlay={productBadge}
|
|
36
|
+
onActivate={openItem} activationLabel={openLabel} />
|
|
38
37
|
```
|
|
39
38
|
|
|
40
39
|
```tsx
|
|
41
40
|
// Native
|
|
42
41
|
import { IconTile } from "@hjmds/react-native/screens";
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
<IconTile icon={artwork} label={title} overlay={productBadge}
|
|
43
|
+
onActivate={openItem} activationLabel={openLabel}
|
|
44
|
+
onLongPress={previewItem} activationHint={previewHint} />
|
|
46
45
|
```
|
|
47
46
|
|
|
48
47
|
```tsx
|
|
@@ -56,6 +55,9 @@ import { IconTile } from "@hjmds/react-native/screens";
|
|
|
56
55
|
| --- | --- | --- | --- |
|
|
57
56
|
| icon | `ReactNode` · `(artwork: IconTileArtwork) => ReactNode` | 필수 | 아이콘 원본. 함수면 그려질 아이콘 한 변 `artwork.edge`를 받는다(`fixed` 64, `fill`은 측정한 칸 크기 48~68). 고정 크기 이미지·생성 아트워크는 이 값으로 크기를 맞춘다(미게시, 1.31.0 이후) |
|
|
58
57
|
| label | string | 필수 | 제목 |
|
|
58
|
+
| onActivate · activationLabel · disabled | callback · string · boolean | 없음 · label · false | 미게시. 단순 항목 열기는 IconTile 자체의 버튼을 사용한다. 44 최소 대상/키보드/초점/비활성/누름 피드백을 HJM이 제공한다 |
|
|
59
|
+
| Native `onLongPress` · `activationHint` | callback · string | 없음 | 미게시. `onActivate`와 함께 길게 누르기 미리보기·메뉴를 연결한다. 비활성일 때 두 행동을 모두 막고 설명은 버튼의 접근성 hint로 전달한다. Web prop으로 제공하지 않는다 |
|
|
60
|
+
| layoutStyle | 배치 전용 style | 없음 | 정적 타일 또는 interactive 루트의 폭·외부 배치 |
|
|
59
61
|
| overlay | ReactNode | 없음 | 제품 배지 |
|
|
60
62
|
| labelLines | 1 또는 2 | 1 | 제목 줄 수 |
|
|
61
63
|
| ghost | boolean | false | 이동 대상 표시 |
|
|
@@ -86,7 +88,9 @@ import { IconTile } from "@hjmds/react-native/screens";
|
|
|
86
88
|
|
|
87
89
|
## 상태와 상호작용
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
`onActivate`가 없으면 표시 전용이며, 있으면 HJM이 버튼·누름 피드백·비활성을 소유한다.
|
|
92
|
+
Native의 길게 누르기 미리보기는 `onLongPress`에 제품 콜백을 연결한다. 별도 Pressable로 감싸지 않는다.
|
|
93
|
+
실제 끌기·재배열 같은 제품 제스처는 정적 타일을 기존 gesture host에 넣어 유지한다.
|
|
90
94
|
|
|
91
95
|
## 접근성
|
|
92
96
|
|
|
@@ -104,3 +108,18 @@ host에 전체 행동의 현지화 이름을 제공한다. 아이콘 내부 장
|
|
|
104
108
|
|
|
105
109
|
- `frame`·`shape`·`size`: 2026-10-18 HJM 1.30.0 감사. 유틸버스 런처는 도구 아트워크가 완성된 아이콘(셸에 따라 iPhone squircle, Windows·Linux square)이고 크기가 격자를 따라 48~68로 변해, 고정 64 틀을 그리는 IconTile 대신 자체 타일을 유지했다. 범위 48~68은 그 런처의 실제 칸 계산값이다.
|
|
106
110
|
- `icon` 함수와 `resolveIconTileFillEdge`: 2026-10-18 HJM 1.32.0 감사. `size="fill"` 채택 뒤에도 유틸버스 런처는 도구 아트워크가 고정 크기 이미지라 격자 폭에서 48~68 제한을 다시 계산해(`ToolboxScreen` `size`) 아트워크에 넘겼다. 타일이 측정한 한 변을 아트워크에 넘겨 계산을 HJM 한 곳에 둔다.
|
|
111
|
+
|
|
112
|
+
### 단순 열기와 제품 제스처 host
|
|
113
|
+
|
|
114
|
+
2026-10-19 번뚝 공개 프로필에서 일반 Button의 face 여백과 label clipping이 64px 그림을 약26px 칸으로 좁혔다. 단순 열기는 `onActivate`로 연결하며 추가 Button/Pressable을 중첩하지 않는다. 내용에 별도 입력/링크/행동을 넣지 않는다. 접근성 이름은 잘리지 않은 label을 기본으로 쓰고 열기 같은 동사를 포함할 때 activationLabel을 공급한다. 읽기 전용 타일은 onActivate를 생략하면 기존 수동 표시를 유지한다. 실제 끌기·재배열 제스처의 런처 host는 정적 IconTile을 계속 사용한다.
|
|
115
|
+
|
|
116
|
+
2026-10-20 Utilverse는 길게 누르기 미리보기 때문에 별도 Pressable·누름 스프링·비활성 처리를 유지했다.
|
|
117
|
+
Native `onLongPress`·`activationHint`를 추가해 미리보기/열기/진동의 제품 콜백만 남기고 공통 누름 host를 재사용한다.
|
|
118
|
+
누름 표현은 `iconTileRecipe.activation`의 공통 opacity 피드백이며 앱별 spring/축소 값을 주입하지 않는다.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<IconTile label={item.title} activationLabel={t("openItem", { title: item.title })}
|
|
122
|
+
onActivate={() => open(item.id)} disabled={pending} icon={artwork} />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
격자가 고정64 그림을 요청할 때는 Grid의 minColumnWidth를 iconTileRecipe.iconSize와 연결한다. 줄 수를 유지하려고 그림/제목을 일반 버튼 여백 안에서 자르지 않는다.
|
|
@@ -110,3 +110,9 @@ virtualized (height 320, 폭 368 → 4열)
|
|
|
110
110
|
|
|
111
111
|
- `showItemLabels`: 2026-10-18 번뚝 공유 받는 사람 선택이 이름을 보이려고 링크 버튼에 아바타·체크·이름을 직접 조립했다.
|
|
112
112
|
- 도입 계기: 2026-10-16 구성 예제 점검 — 제품들이 `selected`를 준 ghost 버튼으로 그림 선택을 흉내 내 선택 상태가 일관되지 않았다.
|
|
113
|
+
|
|
114
|
+
### 열 경계와 대칭 여백
|
|
115
|
+
|
|
116
|
+
2026-10-19 사용자 지적: 고정 크기 타일을 왼쪽으로만 쌓아 미리보기와 우측 경계가 달랐다.
|
|
117
|
+
타일 크기는 recipe를 유지하며 행 간격을 균등 분배해 첫·마지막 열을 컨테이너 양쪽 끝에 맞춘다.
|
|
118
|
+
마지막 부분 행도 위 행의 열 위치를 유지한다. Native는 실제 그룹 폭을 측정하고 가상화 행에도 같은 배치를 사용한다.
|