@hjmds/design-contracts 1.27.0 → 1.27.2
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/base-recipes.d.ts +8 -0
- package/dist/base-recipes.d.ts.map +1 -1
- package/dist/base-recipes.js +6 -0
- package/dist/base-recipes.js.map +1 -1
- package/dist/catalog.d.ts +19 -4
- package/dist/catalog.d.ts.map +1 -1
- package/dist/component-recipes.d.ts +22 -5
- package/dist/component-recipes.d.ts.map +1 -1
- package/dist/component-recipes.js +48 -14
- package/dist/component-recipes.js.map +1 -1
- package/dist/icon-button-recipe.d.ts +23 -0
- package/dist/icon-button-recipe.d.ts.map +1 -1
- package/dist/icon-button-recipe.js +20 -1
- package/dist/icon-button-recipe.js.map +1 -1
- package/dist/screen-patterns.d.ts +12 -1
- package/dist/screen-patterns.d.ts.map +1 -1
- package/dist/screen-patterns.js +12 -1
- package/dist/screen-patterns.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/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/usage/components/bottom-navigation.md +5 -1
- package/docs/usage/components/button.md +2 -0
- package/docs/usage/components/chat-message.md +6 -2
- package/docs/usage/components/comment-thread-row.md +1 -1
- package/docs/usage/components/icon-button.md +5 -0
- package/docs/usage/components/inline-edit.md +1 -1
- package/docs/usage/components/list-detail-screen.md +1 -1
- package/docs/usage/components/list-row.md +19 -11
- package/docs/usage/components/list.md +6 -2
- package/docs/usage/components/notice.md +5 -1
- package/docs/usage/components/repeatable-field-group.md +1 -1
- package/docs/usage/components/saved-items-screen.md +1 -1
- package/docs/usage/components/screen-layout.md +3 -3
- package/docs/usage/components/search-screen.md +1 -1
- package/docs/usage/components/table-column-settings.md +1 -1
- package/docs/usage/components/timeline.md +5 -1
- package/docs/usage/components/toast.md +10 -1
- package/docs/usage/components/top-bar.md +5 -1
- package/docs/usage/components/upload-item.md +1 -1
- package/docs/usage/compositions/toast-layout.md +4 -4
- package/docs/usage/screens/common-search.md +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [ListRow](../../list-row.md), recipe `listRowRecipe`(`src/component-recipes.ts`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/데이터 표시/목록 행`
|
|
10
10
|
|
|
@@ -71,7 +71,7 @@ import { Avatar, ListRow } from "@hjmds/react-native/data-display";
|
|
|
71
71
|
| `onClick`(Web) | `(event: MouseEvent<HTMLElement>) => void` | — | 있으면 `<button>` 행 |
|
|
72
72
|
| `onPress`(Native) | `(event: GestureResponderEvent) => void` | — | 있으면 누를 수 있는 행 |
|
|
73
73
|
| `loading`·`loadingLabel` | `boolean` · 현지화 문자열 | `false` · — | 같은 슬롯 모양의 자리표시 행. `loadingLabel`이 상태로 읽힌다 |
|
|
74
|
-
| `trailingAction`
|
|
74
|
+
| `trailingAction` | ReactNode | — | 행 명령 옆에 따로 그리는 별도 target(IconButton 등, 끝에 둘 때 `edge="end"`). 자기 `onPress`/`onClick`을 가진다 |
|
|
75
75
|
| `layoutStyle` | 배치 key만 | — | 행 루트 배치. Native는 슬롯 배치용 `leadingStyle`·`contentStyle`·`titleRowStyle`·`trailingStyle`·`trailingActionStyle`(모두 배치 key만)도 받는다 |
|
|
76
76
|
| `titleStyle`·`descriptionStyle`(Native) | — | — | 제거된 속성 — 배치는 `layoutStyle`, 제목의 위험색은 `tone="danger"`, 글자는 listRowRecipe가 정한다 |
|
|
77
77
|
|
|
@@ -84,9 +84,9 @@ Native 원형 leading의 `full`은 Provider token에서 읽지만 고정 원형
|
|
|
84
84
|
|
|
85
85
|
| 항목 | 값 | 근거 |
|
|
86
86
|
| --- | --- | --- |
|
|
87
|
-
| 크기 |
|
|
88
|
-
| 간격 | leading·content·trailing 사이 `spacing.sm` 12(`listRowRecipe.gap`).
|
|
89
|
-
| 순서·정렬 | leading → content(제목·설명) → trailing. Native `trailingAction
|
|
87
|
+
| 크기 | 행 표면(누름·hover·선택 배경, 포커스 링, 누를 수 있는 영역)은 부모 콘텐츠 폭보다 양쪽으로 `listRowRecipe.surfaceBleed`(= density `paddingHorizontal`, `spacing.xs` 8)씩 넓다(Web `inline-size: calc(100% + 16)` + `margin-inline: -8`, Native `marginHorizontal: -8`). 글자·leading·trailing은 부모 콘텐츠 가장자리에 놓인다. leading 프레임은 40×40(`leadingSize`), `circle`이면 `radius.full`. trailing 아이콘은 `glyph.sm` 20. 최소 높이는 아래 density 표 | `design-contracts/src/component-recipes.ts`(`listRowRecipe`), `design-contracts/src/foundations.ts`(`layout.rowHeight`) |
|
|
88
|
+
| 간격 | leading·content·trailing 사이 `spacing.sm` 12(`listRowRecipe.gap`). 행 안 좌우 여백 8은 표면이 바깥으로 넓어진 만큼이라 글자는 부모 콘텐츠 가장자리(기본 화면이면 여백 선 16)에서 시작한다. 위아래 여백은 아래 density 표. Native의 투명 선택 테두리 1은 이 여백 안에 포함된다(테두리 + padding = 8). 제품은 행에 margin을 주지 않는다 | `design-contracts/src/component-recipes.ts`(`listRowRecipe`), `react/src/styles.css`(`.hjm-list-row`) |
|
|
89
|
+
| 순서·정렬 | leading → content(제목·설명) → trailing. trailing 내용(아이콘·Switch·문구)은 행의 세로 가운데에 둔다(Web `.hjm-list-row__trailing` flex 가운데, Native 행 가운데 정렬 View). `trailingAction`(두 플랫폼)은 행 명령 바깥 끝에 `listRowRecipe.gap` 12 떨어져 놓이고, 명령과 함께 하나의 표면(선택 배경·테두리)을 쓴다. 행동 상자의 끝은 부모 콘텐츠 가장자리에 맞으므로 ghost IconButton에 `edge="end"`를 주면 glyph 칸 끝이 여백 선 16에 온다. 행 사이 구분선·바깥 둥근 배경은 [List](list.md)가 그린다 | `react-native/src/data-display.tsx`(ListRow), `react/src/styles.css`(`.hjm-list-row__trailing`) |
|
|
90
90
|
| 고정·스크롤 | — | — |
|
|
91
91
|
| 좁은 폭·큰 글자 | 긴 제목·설명은 줄바꿈되고(`overflow-wrap: anywhere`) 행 높이가 늘어난다. 큰 글자에서도 자르지 않는다 | `react/src/styles.css`(`.hjm-list-row`) |
|
|
92
92
|
|
|
@@ -98,10 +98,12 @@ Native 원형 leading의 `full`은 Provider token에서 읽지만 고정 원형
|
|
|
98
98
|
| `spacious` | 72 | 84 | `spacing.md` 16 |
|
|
99
99
|
|
|
100
100
|
```text
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
│
|
|
104
|
-
|
|
101
|
+
화면 끝 ↓ 여백 선 16 ↓ ↓ 여백 선 16
|
|
102
|
+
┌─8─┬──────────────────────────────────────────┬─8─┐ ← 행 표면(누름·선택 배경), 8씩 바깥으로
|
|
103
|
+
│ │[40×40]│12│ 제목(bodyLarge bold) │12│ 값 ›│ │
|
|
104
|
+
│ │leading│ │ 설명(body, secondary) │ │trail│ │
|
|
105
|
+
└───┴──────────────────────────────────────────┴───┘
|
|
106
|
+
섹션 제목·결과 개수(16)와 행 글자·leading(16)이 같은 선에서 시작한다
|
|
105
107
|
최소 높이: comfortable 한 줄 56 · 두 줄 68
|
|
106
108
|
```
|
|
107
109
|
|
|
@@ -110,7 +112,8 @@ Native 원형 leading의 `full`은 Provider token에서 읽지만 고정 원형
|
|
|
110
112
|
- Web `href`는 탐색 링크이며 `download` prop은 없다. 다운로드 속성이 필요한 파일은 [Link](link.md)를 사용한다. 큰 미리보기와 다운로드·메뉴 등 독립 행동은 Card로 구성하며 클릭 가능한 행 안에 링크를 중첩하지 않는다. 2026-10-07 파일 사례 대조에서 파일 행의 외형만으로 다운로드 지원을 추론한 대응표를 바로잡았다.
|
|
111
113
|
|
|
112
114
|
- 제목·설명은 i18n 키로 넣는다. leading의 사진·아이콘은 장식으로 두고 의미는 제목이 말한다.
|
|
113
|
-
- 행 안에 다른 버튼을 넣지 않는다.
|
|
115
|
+
- 행 안에 다른 버튼을 넣지 않는다. 별도 target은 `trailingAction`에 둔다(행 명령 옆에 따로 그린다).
|
|
116
|
+
- 행은 부모가 좌우로 8 이상의 여백을 가진 곳에 둔다(화면 본문 16, Card·Sheet·SidePanel 본문, `grouped` List). 여백이 0인 상자(예: 여백 없는 분할 패널, 제품의 꽉 찬 div)에 바로 두면 표면이 잘리고 Web은 가로 스크롤이 생긴다. 그 상자에 콘텐츠 여백을 준다. `ScreenLayout contentInset="body-edge"` 본문은 자기 여백 16을 가진 전체 폭 행(ConversationRow·NotificationRow) 전용이다. ListRow·NotificationItem은 `default` 본문에 둔다(그래야 글자가 여백 선 16에 온다).
|
|
114
117
|
- 배치는 `layoutStyle`로 한다. 높이·여백·배경을 덮지 않는다. 밀도는 `density`로 바꾼다.
|
|
115
118
|
- 아바타 프레임을 제품이 다시 그리지 않는다. `leadingShape`를 쓴다.
|
|
116
119
|
|
|
@@ -121,7 +124,7 @@ Native 원형 leading의 `full`은 Provider token에서 읽지만 고정 원형
|
|
|
121
124
|
| 제목·설명 타입 | `ReactNode` | `string` |
|
|
122
125
|
| 자리표시 행 | `loading` + `loadingLabel` | `loading` + `loadingLabel` |
|
|
123
126
|
| 링크 행 | `href` | 없음(`onPress`에서 router 호출) |
|
|
124
|
-
| 제목 옆
|
|
127
|
+
| 제목 옆 메타·뒤 문구 | — | `titleMetadata`, `trailingText` |
|
|
125
128
|
| 접근성 이름 조합 | 요소 내용 | `accessibilityLabel` 없으면 제목·`metadataLabel`·설명·`trailingLabel`을 이어 붙임 |
|
|
126
129
|
| 기본 `density` | provider density가 compact면 `compact` | 항상 `comfortable` |
|
|
127
130
|
|
|
@@ -142,3 +145,8 @@ Web도 Native와 같이 `trailingAction`을 지원한다. 이 슬롯은 행의 l
|
|
|
142
145
|
팔로우·삭제 같은 행동을 링크에 중첩하지 않는다. `renderLink`는 HJM이 만든 anchor props를
|
|
143
146
|
프레임워크 Link에 전달하는 연결점이다. 배치·제목·앞/뒤 슬롯은 HJM이 계속 만든다.
|
|
144
147
|
예: `renderLink={props => <Link {...props} href={props.href!} />}`.
|
|
148
|
+
|
|
149
|
+
## 근거
|
|
150
|
+
|
|
151
|
+
- trailing 세로 가운데: 2026-10-17 정렬 실측(Web 402). trailing span이 block이라 안의 inline svg가 글자 기준선에 놓여 행 가운데보다 2~2.5px 위였다(설정 334/331.5, 컬렉션 184/181.5). Native는 행 가운데(388/388)였다. trailing을 flex 가운데 상자로 바꾼 뒤 Web도 334/334, 184/184로 측정됐다.
|
|
152
|
+
- 글자를 여백 선에, 표면은 바깥으로: 2026-10-17 사용자 결정. 행이 누름 배경용 좌우 여백 8을 콘텐츠 안에 두어 기본 화면에서 행 글자가 16 + 8 = 24에서 시작했고, 같은 화면의 섹션 제목·검색 결과 머리("결과 N개")·설정 묶음 제목은 16에서 시작했다(Web 402 실측: 검색 결과 행 24 / 머리 16, 설정 leading 24 / 묶음 제목 16, 알림 leading 24 / "오늘" 16. Native 3x 화면 잉크: 결과 행 제목 25 / 개수 16.7 — Native는 투명 선택 테두리 1까지 더해졌다). 표면을 바깥으로 8 넓히는 방식으로 바꾼 뒤 Web 402·1280, LTR·RTL 모두 행 글자·leading 시작과 trailing 끝이 16(1280은 화면 열 기준 296)으로, Native는 결과 행 제목 잉크 16, 설정 leading 아이콘 잉크 36 → 27, 알림 42.3 → 33(프레임 16 + 아이콘 안쪽 여백)으로 측정됐다. 구분선 시작도 글자 위치(52)로 옮겼다.
|
|
@@ -92,8 +92,8 @@ Native `grouped` 프레임은 Provider의 `tokens.radius.lg`를 읽는다. plain
|
|
|
92
92
|
|
|
93
93
|
| 항목 | 값 | 근거 |
|
|
94
94
|
| --- | --- | --- |
|
|
95
|
-
| 크기 | 화면 본문 폭을 채우는 세로 묶음이다. `grouped`는 배경 `--hjm-color-bg`와 모서리 `radius.lg` 16으로 한 덩어리가
|
|
96
|
-
| 간격 | 행 사이 간격은 없고 구분선 1px(`--hjm-color-border`)만 들어간다.
|
|
95
|
+
| 크기 | 화면 본문 폭을 채우는 세로 묶음이다. `plain`은 자르지 않으므로 행 표면이 부모 여백 쪽으로 8씩 넓어진다(행 글자는 목록 콘텐츠 가장자리). `grouped`는 배경 `--hjm-color-bg`와 모서리 `radius.lg` 16으로 한 덩어리가 되고 그 모양으로 자르므로, 좌우 안쪽 여백 `listRecipe.grouped.paddingInline`(= `listRowRecipe.surfaceBleed` 8)을 두어 행 표면이 잘리지 않고 행 글자는 이전처럼 덩어리 끝에서 8 안쪽에 온다 | `design-contracts/src/component-recipes.ts`(`listRecipe`), `react/src/styles.css`(`.hjm-list`), `react-native/src/data-display.tsx`(List) |
|
|
96
|
+
| 간격 | 행 사이 간격은 없고 구분선 1px(`--hjm-color-border`)만 들어간다. 구분선 위치는 목록 콘텐츠 가장자리(= 행 글자 시작) 기준이다: `indented`는 시작 52(leading 40 + `spacing.sm` 12, 글자 시작과 같은 위치), 끝 −8(행 표면 끝까지). `full`은 시작·끝 모두 −8로 행 표면 폭과 같다(`listRecipe.separators`, 두 플랫폼, Web `--hjm-list-separator-*`). 덩어리 바깥 여백·섹션 사이 간격은 감싸는 Stack이 준다(페이지 좌우 여백은 `layout.pagePadding`, 섹션 간격은 `layout.sectionGap` = `spacing.xl` 24) | `design-contracts/src/component-recipes.ts`(`listRecipe`), `design-contracts/src/foundations.ts`(`layout`), `react/src/styles.css`(`.hjm-list`), `react-native/src/data-display.tsx`(List) |
|
|
97
97
|
| 순서·정렬 | 다음 페이지 요청은 목록 바로 아래에 [LoadMore](load-more.md)를 둔다 | — |
|
|
98
98
|
| 고정·스크롤 | List 자체는 스크롤하지 않는다. 화면 스크롤 안에 둔다 | `react-native/src/data-display.tsx`(List) |
|
|
99
99
|
| 좁은 폭·큰 글자 | — | — |
|
|
@@ -189,6 +189,7 @@ import { CollectionRail } from "@hjmds/react/collection-rail";
|
|
|
189
189
|
|
|
190
190
|
- 자식 하나가 행 하나다. 자식마다 안정된 `key`를 준다(없으면 순서 index로 key를 만든다).
|
|
191
191
|
- 구분선을 행에 직접 그리지 않는다. `separator`로 정한다.
|
|
192
|
+
- `plain` 목록은 좌우 여백이 8 이상인 부모(화면 본문, Card·Sheet 본문 등) 안에 둔다. 여백 0인 상자에 두면 행 표면이 잘리거나 Web에 가로 스크롤이 생긴다.
|
|
192
193
|
- TaskList는 체크해도 순서를 바꾸거나 지우지 않는다. 저장·실패·되돌리기는 제품이 `onCompletedChange`에서 처리한다.
|
|
193
194
|
|
|
194
195
|
## 플랫폼 차이
|
|
@@ -202,3 +203,6 @@ import { CollectionRail } from "@hjmds/react/collection-rail";
|
|
|
202
203
|
2026-10-07 Utilverse 항목 삭제 채택을 위해 `renderItemAction`을 추가했다. 체크와 삭제의 초점·누름을 분리하고 큰 글자 라벨 폭을 보존하도록 행동을 다음 줄에 둔다. `renderCollection`의 `renderItem`에도 포함된다. 삭제 저장·실패·되돌리기는 제품이 처리한다.
|
|
203
204
|
|
|
204
205
|
항목 삭제 뒤에는 제품이 다음 항목의 독립 행동(없으면 이전 항목, 목록이 비면 후속 행동)으로 초점을 복구한다. Web Showcase는 ref와 커밋 후 focus 예시를 제공한다. Native 접근성 초점 검증은 아직 남아 있다.
|
|
206
|
+
|
|
207
|
+
## 근거
|
|
208
|
+
- 구분선·grouped 여백: 2026-10-17 사용자 결정(행 글자는 여백 선 16, 표면만 바깥으로 8). 행 콘텐츠 상자가 목록 콘텐츠 가장자리로 옮겨져 `indented` 시작이 60 → 52가 되어도 화면에서 보이는 위치(글자 시작)는 같다. 기본 화면에서 행 글자가 24, 섹션 제목이 16이던 실측은 [ListRow 근거](list-row.md#근거)에 있다. 이전 plain 목록의 `overflow: hidden`은 grouped 모서리만을 위한 것이라 grouped로 옮겼다.
|
|
@@ -88,7 +88,7 @@ import { Notice } from "@hjmds/react-native/feedback";
|
|
|
88
88
|
| --- | --- | --- |
|
|
89
89
|
| 크기 | 본문 폭을 꽉 채운다. 아이콘 20(`glyph.sm`), 테두리 1(`stroke.default`), radius `radius.md` 12 | `noticeRecipe` |
|
|
90
90
|
| 간격 | 안쪽 `spacing.md` 16, 아이콘·문구·행동 사이 `spacing.sm` 12, 제목·설명 사이 `spacing.xxs` 4. 이웃 블록과는 `layout.contentGap` 16 | `noticeRecipe`, `.hjm-notice*` |
|
|
91
|
-
| 순서·정렬 | 아이콘 — 제목·설명 — 행동. 행동은 하나, `Button` `size="small"` `tone="secondary"`(또는 `ghost`), `primary` 금지. Web은 행동이 같은 줄 끝, Native는 행동이 문구 줄
|
|
91
|
+
| 순서·정렬 | 아이콘 — 제목·설명 — 행동. 행동은 하나, `Button` `size="small"` `tone="secondary"`(또는 `ghost`), `primary` 금지. Web은 행동이 같은 줄 끝, Native는 행동이 문구 줄 **아래**. 아이콘·제목 첫 줄·(Web) 행동은 한 첫 줄 기준에 가운데를 맞춘다. 기준 높이는 행동이 없으면 제목 줄 높이(`1lh`), Web 행동이 있으면 small 버튼 높이 36(`control.buttonHeight.small`)이고 추가 줄은 아래로 늘어난다. Native 아이콘 칸은 제목 첫 줄 높이(토큰×글자 배율) | `.hjm-notice`·`.hjm-notice__icon`·`.hjm-notice__action`, `react-native/src/feedback.tsx` |
|
|
92
92
|
| 고정·스크롤 | 본문 흐름 안. 먼저 읽어야 하는 경고·입력 오류는 영향을 받는 내용 바로 위(화면 상태는 제목 아래, 폼 검증은 제출 버튼 위). 제출 후 성공·실패 결과는 구성 지침이 지정한 행동 인접 위치에 둔다. 떠 있거나 고정되지 않고 함께 스크롤되며 여러 개 쌓지 않는다 | — |
|
|
93
93
|
| 좁은 폭·큰 글자 | Web은 문구 칸이 60% 아래로 줄면 행동이 다음 줄로 감긴다. 제목·설명은 줄바꿈된다 | `.hjm-notice__content`(`flex: 1 1 60%`) |
|
|
94
94
|
|
|
@@ -113,3 +113,7 @@ import { Notice } from "@hjmds/react-native/feedback";
|
|
|
113
113
|
- Web은 반대로 항상 발표되므로, 화면 진입 때부터 늘 있는 안내를 Notice로 두면 매번 읽힌다. 그런 조건 안내는 BottomInfo다.
|
|
114
114
|
|
|
115
115
|
- 2026-10-06 독립 재구현에서 입력 전 경고 위치와 시간 선택의 확정 후 결과 위치가 충돌했다. 시간 선택의 결과는 행동 아래, 입력 시트의 저장 실패는 본문 입력 위로 각 구성에 명시한다. Notice 자체가 결과 위치를 강제하지 않는다.
|
|
116
|
+
|
|
117
|
+
## 근거
|
|
118
|
+
|
|
119
|
+
- 첫 줄 기준: 2026-10-17 정렬 실측(Web 402·1280, 검색 오류 Notice). 행 끝 행동 버튼 가운데 195, 제목 첫 줄 가운데 187로 8px 어긋났고 Web 아이콘 칸에는 규칙이 없었다. Toast의 첫 줄 기준(c053d956)과 같은 방식으로 맞춘 뒤 아이콘·제목 첫 줄·행동 모두 195로 측정됐다.
|
|
@@ -76,7 +76,7 @@ import { TextField } from "@hjmds/react-native/inputs";
|
|
|
76
76
|
| --- | --- | --- |
|
|
77
77
|
| 크기 | 입력은 제품이 넣은 컨트롤, 버튼은 Button 기본 | 양 renderer |
|
|
78
78
|
| 간격 | 묶음과 추가 버튼 사이 `collectionEditingRecipe.gap`=`md`(16), 행 안 입력–삭제 `sm`(12) | `collectionEditingRecipe`, 양 renderer |
|
|
79
|
-
| 순서·정렬 | FieldGroup 제목 → 행마다 입력 →
|
|
79
|
+
| 순서·정렬 | FieldGroup 제목 → 행마다 입력 → 삭제 IconButton(ghost, 행 이름을 담은 `label`) → … → 마지막 추가 Button(secondary). 행 끝 삭제는 `edge="end"`로 아이콘 칸이 묶음 끝에 온다(전에는 10 안쪽, 2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | 렌더 순서 |
|
|
80
80
|
| 고정·스크롤 | 자체 고정·스크롤 없음, 바깥 폼·화면 host 소유 | 별도 scroller 없음 |
|
|
81
81
|
| 좁은 폭·큰 글자 | 행은 세로로 쌓여 폭이 좁아도 입력과 삭제가 겹치지 않는다 | 세로 Stack |
|
|
82
82
|
|
|
@@ -100,7 +100,7 @@ Native collection cover의 모서리는 Provider의 `tokens.radius.md`다. 저
|
|
|
100
100
|
| --- | --- | --- |
|
|
101
101
|
| 크기 | host의 남은 높이 100%, ScreenLayout 폭(최대 720); 컬렉션 표지·게시물 썸네일 1:1, 표지 radius `radius.md` | `ListDetailScreen`, Web `.hjm-saved-*`, Native `saved-items.tsx` |
|
|
102
102
|
| 간격 | 화면 padding `spacing.md` 16; 개인 저장 안내–격자 `spacing.md` 16; 컬렉션 2열 간격 `spacing.md` 16; 표지 4장 사이·게시물 3열 간격 `spacing.xxs` 4; 표지–컬렉션 제목 `spacing.sm` 12 | `Stack gap="md"`, `Grid gap`, Web `.hjm-saved-collection`, Native `gap: spacing.sm` |
|
|
103
|
-
| 순서·정렬 | 홈: 헤더(제목 → 새 컬렉션) → 개인 저장 안내 → 모든 게시물 → 사용자 컬렉션; 컬렉션: 헤더(뒤로 → 컬렉션 제목) → 사진 격자 또는 빈 안내; 상세: 헤더(뒤로 → 항목 제목) → `renderDetail` | `SavedItemsScreen` 렌더 순서 |
|
|
103
|
+
| 순서·정렬 | 홈: 헤더(제목 → 새 컬렉션) → 개인 저장 안내 → 모든 게시물 → 사용자 컬렉션; 컬렉션: 헤더(뒤로 → 컬렉션 제목) → 사진 격자 또는 빈 안내; 상세: 헤더(뒤로 → 항목 제목) → `renderDetail` 새 컬렉션 버튼은 `edge="end"`로 아이콘 칸이 헤더 끝 여백 16에 온다(26 → 16, 2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | `SavedItemsScreen` 렌더 순서 |
|
|
104
104
|
| 고정·스크롤 | 헤더 고정, 본문 스크롤; 상세 동안 격자 mount 유지 | ListDetailScreen·ScreenLayout |
|
|
105
105
|
| 좁은 폭·큰 글자 | 좁은 폭(320 창, 본문 288)에서도 컬렉션 2열·사진 3열을 유지한다. 열 수는 사용 가능 폭이 컬렉션 176 미만(최소 열 폭 80), 사진 140 미만(최소 열 폭 44)일 때만 줄어든다; 컬렉션 제목은 줄바꿈; 사진 속 글자를 유일한 이름으로 쓰지 않는다(버튼 접근성 이름은 `title`) | `Grid minColumnWidth`, `grid.ts` 열 계산 |
|
|
106
106
|
|
|
@@ -82,7 +82,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
82
82
|
| `scroll` | `"screen"` · `"content"` | `"screen"` | `content`는 본문 자식(가상화 목록)이 스크롤을 소유한다. 상태 교체 중에는 `screen`으로 돌아간다 |
|
|
83
83
|
| `contentAlignment` | `"start"` · `"center"` | `"start"` | ready 본문을 사용 영역 중앙에 배치. 긴 본문은 위가 잘리지 않게 스크롤한다. 로그인/제품 안내를 가짜 query state로 바꾸지 않는다(1.24.0) |
|
|
84
84
|
| `surface` | `"default"` · `"transparent"` | `"default"` | 투명은 제품 host의 배경·wallpaper를 보존. 배경색 style 우회 대신 사용(1.24.0) |
|
|
85
|
-
| `contentInset` | `"default"` · `"none"` · `"body-edge"` | `"default"` | `none`은 헤더·notice·본문·footer padding을 0으로(Native는 footer 위 테두리도 뺀다). host가 모든 여백을 이미 줄 때 쓴다. `body-edge`는 헤더·notice·footer 여백(16)과 footer 테두리를 유지하고 **본문 좌우 padding만 0**으로 한다. 자기 좌우 여백을 가진 전체 폭 목록 행(ConversationRow·NotificationRow 등 행 padding 16)을 본문에 바로 둘 때 쓴다(미게시). 값 목록은 계약 `ScreenContentInset`·`screenContentInsets` |
|
|
85
|
+
| `contentInset` | `"default"` · `"none"` · `"body-edge"` | `"default"` | `none`은 헤더·notice·본문·footer padding을 0으로(Native는 footer 위 테두리도 뺀다). host가 모든 여백을 이미 줄 때 쓴다. `body-edge`는 헤더·notice·footer 여백(16)과 footer 테두리를 유지하고 **본문 좌우 padding만 0**으로 한다. 자기 좌우 여백을 가진 전체 폭 목록 행(ConversationRow·NotificationRow 등 행 padding 16)을 본문에 바로 둘 때 쓴다(미게시). ListRow·NotificationItem은 `default`에 둔다. 값 목록은 계약 `ScreenContentInset`·`screenContentInsets` |
|
|
86
86
|
| `presentation` | `"dashboard"` · `"editorial"` · `"landscape"` | 가장 가까운 profile.screens.overview / 없으면 기존 골격 | [디자인 프로필](../../design-profile.md)의 제공 범위를 따른다. 명시값이 프로필을 이긴다. 헤더 배치·읽기 폭만 바꾸고 상태·입력·행동 슬롯은 유지한다 |
|
|
87
87
|
| `as` (Web) | `"main"` · `"section"` | `"main"` | 제품 셸에 이미 `<main>`이 있으면 `section` |
|
|
88
88
|
| `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 화면 루트 배치 전용(예: 분할 화면의 `flex`·`width`). ScreenLayout 위에 만든 화면은 모두 이 prop을 루트까지 넘긴다 |
|
|
@@ -96,7 +96,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
96
96
|
| 크기 | 폭 100%, 최대 `screenPatternRecipe.maxWidth`(`layout.readingMaxWidth` 720), 가운데 정렬; 높이는 host가 준 남은 높이(Web `block-size: 100%`, Native `flex: 1`) | Web `.hjm-screen`, Native `ScreenLayout` |
|
|
97
97
|
| 간격 | 바깥 padding `screenPatternRecipe.padding`(`spacing.md` 16)을 헤더 사방·notice 좌우·본문 사방·footer 사방에 준다(`contentInset="none"`이면 0, `"body-edge"`면 본문 좌우만 0); 헤더 안 `exit`·제목·`actions` 간격 `itemGap`(`spacing.sm`) 12(두 플랫폼, `contentInset="none"`이어도 유지); 상태 안내는 위아래 `sectionGap`(`spacing.xl`) 24, 안내–`stateAction` `stateGap`(`spacing.md`) 16 | Web `src/screens.tsx`(`--hjm-screen-item-gap`·`--hjm-screen-state-gap`)·`.hjm-screen__*`, Native `ScreenLayout` |
|
|
98
98
|
| 제목 | 기본 가운데 `Heading` `screenHeaderRecipe.centeredTitleLevel`(level5), `headerAlignment="start"`면 `titleLevel`(level3). 추가 기능 버튼(`actions`)은 끝(오른쪽) 위, 나가기(`exit`)는 시작(왼쪽) 위. 헤더 높이는 두 플랫폼 모두 `screenHeaderRecipe`의 고정 띠 | `screenHeaderRecipe` |
|
|
99
|
-
| 순서·정렬 | 헤더(`exit` → 제목·설명 → `actions`) → notice → 본문 → footer; 상태 안내는 본문 세로 가운데 | 렌더 순서 |
|
|
99
|
+
| 순서·정렬 | 헤더(`exit` → 제목·설명 → `actions`) → notice → 본문 → footer; 상태 안내는 본문 세로 가운데 `exit`는 HJM이 `edge="start"`로 그려 아이콘 칸이 헤더 여백 선 16에 온다(Web·Native 31 → 16, 가운데 헤더의 같은 폭 양쪽 열·제목 중앙은 그대로). 끝 `actions`의 마지막 ghost IconButton은 제품이 `edge="end"`를 준다(2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | 렌더 순서 |
|
|
100
100
|
| 고정·스크롤 | 헤더·notice·footer 고정, `scroll="screen"`은 본문이 스크롤, `"content"`는 자식이 스크롤; footer 위 테두리 1, Web은 하단 safe area만큼 padding을 늘린다 | Web `data-scroll`·`.hjm-screen__footer`, Native `ScrollView` |
|
|
101
101
|
| 좁은 폭·큰 글자 | 제목 열 최소 폭 `headerMinWidth` 120 × 글자 배율, 모자라면 `actions`가 다음 줄로 내려간다; 제목은 줄바꿈(`overflow-wrap: anywhere`)되고 자르지 않는다; Native safe area·탭바 inset은 host | `screenPatternRecipe.headerMinWidth`, Web `.hjm-screen__heading` |
|
|
102
102
|
|
|
@@ -130,7 +130,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
130
130
|
|
|
131
131
|
- 이미 `<main>`이 있는 제품 셸 안에서는 `as="section"`을 준다. 중첩 main이 생긴다.
|
|
132
132
|
|
|
133
|
-
- 본문 좌우 여백을 직접 빼려고 목록에 음수 margin이나 제품 CSS를 쓰지 않는다. 행이 자기 여백을 가지면 `contentInset="body-edge"`, host가 모든 여백을 가지면 `"none"`이다.
|
|
133
|
+
- 본문 좌우 여백을 직접 빼려고 목록에 음수 margin이나 제품 CSS를 쓰지 않는다. 행이 자기 여백을 가지면 `contentInset="body-edge"`, host가 모든 여백을 가지면 `"none"`이다. ListRow·NotificationItem은 글자를 부모 콘텐츠 가장자리에 두고 표면만 8 바깥으로 넓히므로(2026-10-17) `default` 본문에서 글자가 여백 선 16에 오고 설정 묶음 제목·알림 그룹 제목과 같은 선에 선다. `body-edge` 본문에 두면 글자가 화면 끝에 붙으므로 두지 않는다.
|
|
134
134
|
|
|
135
135
|
### 디자인 프로필의 화면 표현
|
|
136
136
|
|
|
@@ -170,7 +170,7 @@ import { SearchScreen } from "@hjmds/react-native/screen-flows";
|
|
|
170
170
|
| --- | --- | --- |
|
|
171
171
|
| 크기 | ScreenLayout 폭(최대 720); 검색 입력은 padding 안 전체 폭 | `ScreenLayout`, `SearchField` |
|
|
172
172
|
| 간격 | 화면 padding `spacing.md` 16(notice 영역은 좌우만); 검색 입력·`filters`·`notice` 사이 `spacing.sm` 12; 본문 최근 검색–결과 `spacing.lg` 20 | Web·Native `SearchScreen` `Stack gap="sm"`·`gap="lg"` |
|
|
173
|
-
| 순서·정렬 | 헤더(제목) → 검색 입력 → [`filterSheet.trigger`, `filters`] → `notice` → 본문. 본문은 검색 전: `recentSearches` → `recentQueries` → `suggestedQueries`(사이 `spacing.xl` 24) / 입력 중: 확정 행 → 제안 / 결과: `resultSummary.notice` → 개수·정렬 → 적용 필터(사이 `spacing.sm` 12) → `children`(위 `spacing.lg` 20) | 렌더 순서, `searchScreenRecipe` |
|
|
173
|
+
| 순서·정렬 | 헤더(제목) → 검색 입력 → [`filterSheet.trigger`, `filters`] → `notice` → 본문. 본문은 검색 전: `recentSearches` → `recentQueries` → `suggestedQueries`(사이 `spacing.xl` 24) / 입력 중: 확정 행 → 제안 / 결과: `resultSummary.notice` → 개수·정렬 → 적용 필터(사이 `spacing.sm` 12) → `children`(위 `spacing.lg` 20) 결과 머리 끝 정렬 트리거와 최근 검색 머리 끝 `전체 삭제`는 ghost Button `edge="end"`로 화살표·라벨 끝이 개수·제목 시작과 같은 여백 선 16에 온다(Web 29 → 16, Native 31.3 → 19.3: 화살표 잉크의 자체 여백 포함, 2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | 렌더 순서, `searchScreenRecipe` |
|
|
174
174
|
| 고정·스크롤 | 검색 입력·필터는 notice 자리에 고정, 본문만 스크롤; `state`가 바뀌어도 입력·필터는 남는다 | `ScreenLayout` `notice` |
|
|
175
175
|
| 좁은 폭·큰 글자 | 입력은 폭을 줄여 맞춘다. `filtersOverflow="wrap"`은 필터가 줄바꿈되어 고정 영역이 줄 수만큼 커진다. `scroll`은 큰 글자에서도 한 줄(Web 2배 글자 실측: 칩 줄 52, 1배 44)로 고정 영역을 묶는다 | `SearchScreen`, `.hjm-search-screen__filters` |
|
|
176
176
|
|
|
@@ -66,7 +66,7 @@ import { TableColumnSettings } from "@hjmds/react-native/collection-editing";
|
|
|
66
66
|
| --- | --- | --- |
|
|
67
67
|
| 크기 | 체크박스는 [Checkbox](checkbox.md) 기본 `medium`, 이동 버튼은 `ghost` [Button](button.md) 기본 `medium`(높이 44) | `collection-editing.tsx` |
|
|
68
68
|
| 간격 | 열 사이·체크박스와 버튼 줄 사이·버튼 사이 모두 `spacing.sm` 12 | `Stack gap="sm"` |
|
|
69
|
-
| 순서·정렬 | 제목 → 열마다 [체크박스] 다음 줄 [앞으로] [뒤로], `columns` 순서대로 | 양 renderer |
|
|
69
|
+
| 순서·정렬 | 제목 → 열마다 [체크박스] 다음 줄 [앞으로] [뒤로], `columns` 순서대로 마지막 [뒤로]는 `edge="end"`로 아이콘 칸이 묶음 끝에 온다(전에는 10 안쪽, 2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | 양 renderer |
|
|
70
70
|
| 고정·스크롤 | 자체 스크롤 없음. 시트·팝오버 등 놓는 영역이 소유한다 | — |
|
|
71
71
|
| 좁은 폭·큰 글자 | 이동 버튼 줄이 `wrap`으로 줄바꿈 | `Stack axis="inline" wrap` |
|
|
72
72
|
|
|
@@ -84,7 +84,7 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
84
84
|
| --- | --- | --- |
|
|
85
85
|
| 크기 | 각 항목은 [레일 16][본문] 두 열. 레일은 지름 10의 dot과 다음 항목까지 이어지는 1px connector. 항목은 누를 수 없어 터치 영역 요구가 없고 행 안에 버튼을 넣지 않는다 | `timelineRecipe`, `.hjm-timeline__item`·`__dot`·`__connector` |
|
|
86
86
|
| 간격 | 항목 사이 `spacing.md` 16, 레일과 본문 열 간격 `spacing.sm` 12, 본문 줄 간격 `spacing.xxs` 4 | `timelineRecipe.gap`, `.hjm-timeline*` |
|
|
87
|
-
| 순서·정렬 | 본문은 label·timestamp 줄 → description. Web은 label과 timestamp를 한 줄 양 끝에
|
|
87
|
+
| 순서·정렬 | 본문은 label·timestamp 줄 → description. Web은 label과 timestamp를 한 줄 양 끝에 둔다. dot·label 첫 줄·timestamp는 label 첫 줄(`body` 줄 높이)의 가운데에 맞춘다(Web `1lh`, Native 토큰×글자 배율). label이 여러 줄이면 dot·timestamp는 첫 줄 옆에 남는다 | `.hjm-timeline__heading`·`__dot`·`__timestamp`, Native `Timeline` |
|
|
88
88
|
| 고정·스크롤 | 본문 스크롤 영역 안의 한 섹션으로 폭을 꽉 채운다. 더 불러오기는 Timeline 아래에 [LoadMore](load-more.md) | — |
|
|
89
89
|
| 좁은 폭·큰 글자 | Web은 좁으면 timestamp가 다음 줄로 내려간다(`flex-wrap`). 큰 글자에서도 글을 자르지 않는다 | `.hjm-timeline__heading`·`__label` |
|
|
90
90
|
|
|
@@ -103,3 +103,7 @@ import { Timeline } from "@hjmds/react-native/data-display";
|
|
|
103
103
|
| 구조 | `<ol>` / `<li>`, dot·connector `aria-hidden` | `View` 행, rail·본문은 접근성 트리에서 숨김 |
|
|
104
104
|
| 항목 접근 이름 | `<li aria-label={accessibleName}>` | `accessibleName, timestamp, description`을 이어 붙인 `accessibilityLabel` |
|
|
105
105
|
| ref | `HTMLOListElement`로 전달 | 없음(함수 컴포넌트) |
|
|
106
|
+
|
|
107
|
+
## 근거
|
|
108
|
+
|
|
109
|
+
- 첫 줄 가운데: 2026-10-17 정렬 실측(Web 402). dot 396 · label 397 · timestamp 398.5로 어긋났다(dot은 4px 고정 여백, timestamp는 작은 글자가 기준선을 공유). Native는 dot이 맨 위에 붙었다. 수정 후 Web 402·1280에서 세 요소가 397/397/397, 377/377/377로 측정됐다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-17
|
|
8
8
|
- 근거: [Toast](../../toast.md), [Liquid Toast](../../../../react-native/docs/liquid-toast.md), `src/component-recipes.ts`(`toastRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/상태와 알림/토스트`, `배포/컴포넌트/상태와 알림/리퀴드 토스트`, `배포/구성/피드백과 복구/백그라운드 작업 상태`
|
|
10
10
|
|
|
@@ -119,6 +119,8 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`
|
|
|
119
119
|
| 항목 | 값 | 근거 |
|
|
120
120
|
| --- | --- | --- |
|
|
121
121
|
| 크기 | 카드 최대 폭 420(Web `min(420px, 100vw − 2×spacing.md)`, Native 화면 폭 − 안전 영역 − 2×16에서 420으로 자름). 최소 높이 56(`layout.rowHeight.singleLine`). 모서리 `radius.xl` 24(패널, 미게시 1.17.2 이후. 그전 `lg` 16). 열은 [아이콘 배지 32 또는 `media` 44][문구][닫기 44]. `action`은 높이 44(`control.minTouchTarget`), 좌우 여백 `spacing.md` 16의 pill. `progress` 점 6(현재 단계 1.4배) | `toastRecipe.viewport`·`surface`·`icon`·`action`, `.hjm-toast`, `react-native/src/feedback.tsx` |
|
|
122
|
+
| 안쪽 여백 | 카드 위아래 `spacing.sm` 12, 좌우 양쪽 `spacing.md` 16(`toastRecipe.surface.padding`, 미게시). 닫기는 44 터치 영역을 유지하되 끝 쪽으로 (44 − 글리프 14)/2 = 15만큼 여백에 겹쳐(`close.inlineOverhang`) × 글리프 끝이 카드 끝에서 16 — 배지의 시작 여백 16과 같다. RTL에서도 논리 방향(시작·끝)으로 대칭이다. `action`·`progress`는 문구 열에 맞춘다 | `toastRecipe.surface`·`close`, `.hjm-toast`·`.hjm-toast__close`, `react-native/src/feedback.tsx` |
|
|
123
|
+
| 첫 줄 기준 | 배지 32 상자(`toastRecipe.anchor.blockSize`)가 기준선이다. 첫 줄(제목, 없으면 설명)은 (32 − 그 줄의 행간)/2만큼 내려 배지 가운데에 맞추고, 닫기는 위아래 −(44 − 32)/2 = −6(`close.blockOverhang`)으로 같은 가운데에 둔 채 행을 늘리지 않는다. 한 줄이면 12 + 32 + 12 = 56에 배지·글자·닫기 가운데가 모두 28. 여러 줄이면 배지·닫기는 첫 줄 옆에 남고 나머지 줄은 아래로 자란다(문구 덩어리 전체의 가운데에 맞추지 않는다). `media` 44는 같은 위쪽에서 시작하고 기준 상자가 아니다 — 제목을 media 가운데에 맞추면 설명이 아바타 아래로 밀려나서다. Web은 첫 줄의 `1lh`, Native는 body 토큰 행간 × 현재 글자 배율로 계산해 프로필·글자 확대에서도 맞는다 | `toastRecipe.anchor`·`close`, `.hjm-toast__content`, `react-native/src/feedback.tsx` |
|
|
122
124
|
| 간격 | 화면 가장자리에서 `spacing.md` 16 + 안전 영역(`safeAreaMode: "additive"`). 여러 장 사이 `spacing.sm` 12. 카드 안 열 간격 `spacing.sm` 12 | `toastRecipe.viewport`, `.hjm-toast-viewport` |
|
|
123
125
|
| 순서·정렬 | 기본은 아래 가운데(`placement="bottom"`). 위쪽 배치는 위에서 아래로, 아래쪽 배치는 가장자리부터 위로 쌓인다. 문구 열 안 순서는 제목 → 설명 → `progress` → `action`. `action`은 문구 아래 시작 쪽(`toastRecipe.action.align` `start`, 미게시 1.17.2 이후. 그전 둘째 줄 끝) | `toastRecipe.placements`, `.hjm-toast__action` |
|
|
124
126
|
| 고정·스크롤 | 화면 흐름 밖에 떠 있는 층이라 본문 레이아웃에 자리를 만들지 않는다. 하단 고정 바(BottomNavigation·BottomCTA)가 있으면 Web `bottomOffset`(px 또는 CSS 길이), Native `keyboardOffset`으로 그 위에 띄운다. Native 아래쪽 배치는 `avoidKeyboard`(기본 true)로 키보드 위에 선다 | `react/src/toast.tsx`, `react-native/src/feedback.tsx`(`ToastRegion`) |
|
|
@@ -202,3 +204,10 @@ Native의 선택형 `/toast-liquid`도 가장 가까운 프로필의 `radius.xl`
|
|
|
202
204
|
Web은 전체 영역의 투명한 button과 두 제어 버튼을 형제로, Native는 뒤쪽 Pressable과 위쪽 제어 버튼을
|
|
203
205
|
독립적으로 배치한다. label은 동작 이름, 설명은 기존 알림 문구이며 기본 카드 배치와 크기는 바뀌지 않는다.
|
|
204
206
|
라우터는 HJM이 가져오지 않고 제품 콜백이 실행한다. 화면 이동 뒤 provider가 없어져도 중복 실행하지 않는다.
|
|
207
|
+
|
|
208
|
+
## 근거
|
|
209
|
+
|
|
210
|
+
2026-10-17 사용자 지적("기본 토스트 좌우랑 글자 라인이 안맞는데"). Web 402 한 줄 알림에서 카드 안쪽 기준으로 배지 가운데 28, 글자 32~33,
|
|
211
|
+
닫기 글리프 30이 서로 달랐고(배지 `start`·문구 `center`·닫기 `start`−4의 세 정렬), 시작 여백 16과 닫기 글리프 끝 여백 22가 비대칭이었다
|
|
212
|
+
(여백 12/8/12/16 안에 44 터치 영역). Native는 사방 16 여백과 문구 `center` 정렬로 따로 어긋났다. 첫 줄 기준과 대칭 여백을 recipe 값으로 두고
|
|
213
|
+
두 renderer가 같은 값을 읽게 바꿨다. 수정 후 한 줄·여러 줄·action·media·RTL 모두 가운데 28(테두리 포함 29), 양쪽 여백 16으로 측정됐다.
|
|
@@ -86,7 +86,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
|
|
|
86
86
|
| 항목 | 값 | 근거 |
|
|
87
87
|
| --- | --- | --- |
|
|
88
88
|
| 크기 | 최소 높이 `control.buttonHeight.large` 52 + 위쪽 안전 영역. Web은 `max(env(safe-area-inset-top), safeAreaTop)`을 위 여백으로, Native는 `safeAreaTop`을 `paddingTop`과 `minHeight`(52 + `safeAreaTop`)에 더한다. 좌우 열 최소 폭 `control.minTouchTarget` 44, Native `TopBarAction` 최소 44×44 | `topBarRecipe.minHeight`·`sideMinWidth`·`action`, `.hjm-top-bar` |
|
|
89
|
-
| 간격 | 좌우 여백 `spacing.md` 16, 슬롯 사이·슬롯 안 행동 사이 `spacing.xs` 8. `TopBarAction` 아이콘–라벨 `spacing.xxs` 4, 좌우 여백 4. 배경 `canvas` | `topBarRecipe.paddingHorizontal`·`gap`·`action` |
|
|
89
|
+
| 간격 | 좌우 여백 `spacing.md` 16, 슬롯 사이·슬롯 안 행동 사이 `spacing.xs` 8. `NavigationBar`는 바 안쪽 여백 `spacing.sm` 12를 브랜드 시작과 끝 행동에 똑같이 쓰고 브랜드에 별도 inset을 두지 않는다(Web·Native). `TopBarAction` 아이콘–라벨 `spacing.xxs` 4, 좌우 여백 4. 배경 `canvas` | `topBarRecipe.paddingHorizontal`·`gap`·`action` |
|
|
90
90
|
| 순서·정렬 | 세 열: `leading`(뒤로·닫기) · 제목 · `actions`(끝 정렬). `centered`(기본 `true`)는 남은 제목 영역 안에서 가운데 정렬. 왼쪽 정렬은 `centered={false}`로 고른다. 행동은 1~2개만 끝 열에, 넘치면 하나만 남기고 [Menu](menu.md)로 묶는다. 주 행동(저장·완료)은 끝 열의 마지막 | `.hjm-top-bar` grid, Native `TopBar` |
|
|
91
91
|
| 고정·스크롤 | 화면 맨 위, 스크롤 영역 **밖**에 둔다. TopBar 자체는 `position: sticky`/고정을 걸지 않으므로 화면 골격이 그 아래에 스크롤 영역을 둬야 고정된다. 바로 아래 본문 첫 블록은 [Top](top.md) | `.hjm-top-bar`(position 없음) |
|
|
92
92
|
| 좁은 폭·큰 글자 | 글자 배율 ≥ `largeTextThreshold` 1.6: Web은 첫 행 leading·행동, 둘째 행 제목(시작 정렬, 위아래 `spacing.xs` 8). Native는 첫 행 leading+제목, 둘째 행 행동(끝 정렬, 줄바꿈 허용) | `.hjm-top-bar[data-large-text]`, `react-native/src/navigation.tsx` `TopBar` |
|
|
@@ -130,3 +130,7 @@ NavigationBar의 Native 프레임 모서리는 Provider의 `tokens.radius.xl`을
|
|
|
130
130
|
`title`, `subtitle`, `titleLeading`, 제목 클릭/접근성 props와 함께 쓰지 않는다.
|
|
131
131
|
실행 미리보기의 펼침 칩을 앱이 자체 헤더로 다시 조립하던 문제를 없애기 위해 추가했다.
|
|
132
132
|
색은 기존 provider의 브랜드/semantic palette 범위로 연결한다.
|
|
133
|
+
|
|
134
|
+
## 근거
|
|
135
|
+
|
|
136
|
+
- NavigationBar 양끝 여백: 2026-10-17 정렬 실측(Web 1280). 브랜드에 `spacing.xs` 8이 더해져 시작 21, 끝 행동 13으로 비대칭이었다. Native 브랜드에는 추가 inset이 없었다. Web 브랜드 inset을 없앤 뒤 양끝 13/13으로 측정됐다.
|
|
@@ -78,7 +78,7 @@ import { UploadItem } from "@hjmds/react-native/upload-item";
|
|
|
78
78
|
| --- | --- | --- |
|
|
79
79
|
| 크기 | 최소 높이 `layout.rowHeight.twoLine` 68, 위아래 여백 `row.paddingVertical` `spacing.xs` 8(두 플랫폼). 행동 버튼 최소 44×44. 테두리 1px, `radius.md` 12인 카드 모양 | `uploadItemRecipe.row`, `.hjm-upload-item`, `react-native/src/upload-item.tsx` |
|
|
80
80
|
| 간격 | 요소 사이 `spacing.sm` 12, 좌우 여백 `spacing.md` 16. 행 사이 간격·목록 틀은 제품이 정한다(HJM 목록 레이아웃 없음) | `uploadItemRecipe.row` |
|
|
81
|
-
| 순서·정렬 | `leading`(아이콘·썸네일) → 이름·메타·진행/상태 → 끝의 행동 버튼(취소·재시도). 여러 개면 고른 순서대로 세로로 쌓는다 | `react/src/upload-item.tsx`, `.hjm-upload-item__action`(margin-inline-start: auto) |
|
|
81
|
+
| 순서·정렬 | `leading`(아이콘·썸네일) → 이름·메타·진행/상태 → 끝의 행동 버튼(취소·재시도). 여러 개면 고른 순서대로 세로로 쌓는다 끝 행동 버튼은 `edge="end"`라 아이콘 칸이 끝 여백 16에 와서 시작 쪽 이름 여백과 같다(Web 26 → 16, Native 약 33 → 16). Native 아이콘은 글꼴 "×"·"↻" 대신 그린 close·refresh로 버튼 가운데에 온다(전에는 카드 가운데보다 약 5 아래, 2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | `react/src/upload-item.tsx`, `.hjm-upload-item__action`(margin-inline-start: auto) |
|
|
82
82
|
| 고정·스크롤 | 파일을 고르는 [FilePicker](file-picker.md) 바로 아래, 본문과 함께 스크롤 | — |
|
|
83
83
|
| 좁은 폭·큰 글자 | Web은 본문 블록이 14rem 아래로 줄면 행동 버튼이 다음 줄 끝으로 내려간다. Native는 한 줄을 유지하고 이름이 줄바꿈된다 | `.hjm-upload-item__body`(flex: 1 1 14rem), `.hjm-upload-item`(flex-wrap) |
|
|
84
84
|
|
|
@@ -51,10 +51,10 @@ Toast 카드 한 장의 내부 배치(톤 배지·제목·설명·닫기·실행
|
|
|
51
51
|
| --- | --- | --- | --- |
|
|
52
52
|
| 바깥 틀 | `ToastProvider` viewport(앱 루트 하나) | 화면 콘텐츠·스크롤 위에 `position: fixed`로 떠 있다. 콘텐츠 스크롤과 무관하고 콘텐츠 여백을 바꾸지 않는다. 안전 영역은 viewport가 `env(safe-area-inset-*)`로 직접 피한다. 하단 고정 바는 `bottomOffset`으로 피한다. 모바일 Web 화면 키보드는 따로 피하지 않는다 | 가장자리 `max(spacing.md 16, safe-area)`, `layer.toast` 1000, viewport는 포인터를 통과시킨다(`pointer-events: none`, 카드만 받음) |
|
|
53
53
|
| viewport | `ToastProvider` | 화면 고정(`position: fixed`), 기본 하단 가운데. `top`·`top-start`·`top-end`·`bottom-start`·`bottom-end` 선택 | 가장자리 `max(spacing.md 16, safe-area)`, 최대 폭 `min(420, 100vw - 32)`, 카드 사이 `spacing.sm` 12. 하단이면 새 카드가 아래부터 쌓인다 |
|
|
54
|
-
| 카드 | `Toast` | viewport 안. 단독 controlled 사용에서도 카드가 최대 폭 `min(420, 100%)`(Native `toastRecipe.surface.maxWidth` 420)을 지킨다 | 최소 높이 3.5rem, padding 위아래 `spacing.sm` 12
|
|
55
|
-
| 배지 | tone 아이콘 | 첫 줄
|
|
56
|
-
| 문구 | 제목 + 설명 | 첫 줄
|
|
57
|
-
| 닫기 | IconButton | 첫 줄
|
|
54
|
+
| 카드 | `Toast` | viewport 안. 단독 controlled 사용에서도 카드가 최대 폭 `min(420, 100%)`(Native `toastRecipe.surface.maxWidth` 420)을 지킨다 | 최소 높이 3.5rem, padding 위아래 `spacing.sm` 12·좌우 `spacing.md` 16(`toastRecipe.surface.padding`, 2026-10-17 대칭화), 열 간격 `spacing.sm` 12, radius `lg` 16 |
|
|
55
|
+
| 배지 | tone 아이콘 | 첫 줄 시작. 배지 32 상자가 첫 줄 기준(`toastRecipe.anchor`) | 지름 `toastRecipe.icon.badgeDiameter` 32 |
|
|
56
|
+
| 문구 | 제목 + 설명 | 첫 줄(제목, 없으면 설명)을 배지 가운데에 맞추고 나머지 줄은 아래로 자람, 남은 폭 전부 | 제목–설명 `spacing.xxs` 4, `word-break: keep-all`, 잘림 없음 |
|
|
57
|
+
| 닫기 | IconButton | 첫 줄 끝, 글리프 가운데를 첫 줄에 맞춤 | `control.minTouchTarget` 44 × 44, 위아래 −6·끝 −15로 여백에 겹쳐 글리프 끝이 카드 끝에서 16(`close.blockOverhang`·`inlineOverhang`) |
|
|
58
58
|
| 실행 | 알약 버튼 | 둘째 줄, 문구 열부터 끝까지, 끝 정렬 | 최소 높이 44, 좌우 `spacing.md` 16, 줄 간격 `spacing.xs` 8 |
|
|
59
59
|
|
|
60
60
|
- 하단에 고정 바(BottomNavigation·BottomCTA)가 있으면 `bottomOffset`으로 그 높이만큼 띄운다.
|
|
@@ -79,7 +79,7 @@ Native · Web < 960: 아래 시트(size large) Web ≥ 960(expanded): 옆
|
|
|
79
79
|
| 결과 머리 | `resultSummary`: 개수 Text(muted) + 정렬 Menu(Button ghost small) | 본문 첫 줄 | `justify="between"` wrap, 큰 글자에서 두 줄 |
|
|
80
80
|
| 적용 조건 | `appliedFilters`: Chip action(trailing ×) + Button ghost small `모두 해제` | 결과 머리 아래 | 머리와 `searchScreenRecipe.headerGap`(`spacing.sm` 12), 칩 사이 `spacing.xs` 8 |
|
|
81
81
|
| 결과 | 제품 `children`: ListRow(두 줄 68) + LoadMore `manual` | 적용 조건 아래 | 위 `spacing.lg` 20(SearchScreen 본문 Stack) |
|
|
82
|
-
| 최근 검색 | `recentQueries`: Heading level5 + Button ghost small `전체 삭제` + ListRow compact
|
|
82
|
+
| 최근 검색 | `recentQueries`: Heading level5 + Button ghost small `전체 삭제` + ListRow compact + `trailingAction` IconButton × `edge="end"`(두 플랫폼, 행 글자·× glyph 칸이 여백 선 16) | 검색 전 본문 맨 위 | `searchScreenRecipe.recentVisible` 5행 표시(보관 개수는 제품), 섹션 사이 `searchScreenRecipe.sectionGap`(`spacing.xl` 24) |
|
|
83
83
|
| 추천 검색어 | `suggestedQueries`: Heading level5 + Chip action wrap | 최근 검색 아래, 검색어 때문인 0건 아래 | 칩 사이 `spacing.xs` 8 |
|
|
84
84
|
| 자동완성 | `suggestions`: ListRow compact ×(1 + `searchScreenRecipe.suggestionVisible` 6) | 입력 중 본문 | 행 최소 44 |
|
|
85
85
|
| 필터 시트 | `filterSheet`: Sheet(`size` 기본 `large`, 섹션은 제품이 `auto`, Native `scrollable`, Web ≥ 960 `placement="end"`) | 오버레이 | 머리·footer 고정, 섹션은 제품 `renderContent`(섹션 사이 `spacing.xl` 24) |
|