@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
|
@@ -100,7 +100,7 @@ Native 이미지 프레임은 `imageRecipe.radius` 역할을 Provider의 `tokens
|
|
|
100
100
|
| 간격 | 카드 안에 넣을 때 카드 padding 안쪽에 둔다 | — |
|
|
101
101
|
| 순서·정렬 | 여러 장은 직접 줄 세우지 말고 [Grid](grid.md)·[Masonry](masonry.md)·[Carousel](carousel.md)로 배치한다 | — |
|
|
102
102
|
| 고정·스크롤 | ImageViewer는 전체 화면을 덮는다. 상·하단 버튼은 `safeAreaInsets` 안쪽에 그려지므로 호스트가 inset을 넘긴다 | `react-native/src/image-viewer.tsx` |
|
|
103
|
-
| 좁은 폭·큰 글자 | Web 루트는 `inline-size: width`, `max-inline-size: 100%`라 부모보다 넓어지지 않는다. Native 루트 폭은 `width` 숫자 그대로이므로 화면 폭 사진은 `layoutStyle={{ width: "100%" }}`로 준다. Grid·Masonry 칸에 채울 때 Web은 `layoutStyle={{
|
|
103
|
+
| 좁은 폭·큰 글자 | Web 루트는 `inline-size: width`, `max-inline-size: 100%`라 부모보다 넓어지지 않는다. Native 루트 폭은 `width` 숫자 그대로이므로 화면 폭 사진은 `layoutStyle={{ width: "100%" }}`로 준다. Grid·Masonry 칸에 채울 때 Web은 `layoutStyle={{ width: "100%" }}`(루트 `style`도 기본 `inline-size`를 덮는다), Native는 `layoutStyle={{ width: "100%" }}`로 칸 폭에 맞춘다. 원본이 칸보다 작으면 기본값으로는 좁게 남는다 | `react/src/styles.css`(`.hjm-image`), `react-native/src/data-display.tsx`(Image) |
|
|
104
104
|
|
|
105
105
|
## 꼭 지킬 것
|
|
106
106
|
|
|
@@ -202,3 +202,10 @@ const geometry = resolveImageInspectionGeometry(
|
|
|
202
202
|
- `renderImage` adapter가 받은 `onLoad`·`onError`를 실제 이미지 요소에 넘기지 않으면 HJM이 실패를 보지 못해
|
|
203
203
|
대체 화면과 GridReveal의 `ready`가 동작하지 않는다.
|
|
204
204
|
- Native 루트 폭은 `width` 숫자 그대로다. 화면 폭에 맞추려면 `layoutStyle`로 폭을 준다.
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
### Native 확대 화면의 행동 배치
|
|
208
|
+
|
|
209
|
+
2026-10-20 번뚝 실제 첨부 사진 확인에서 큰 닫기/이전/다음 주 버튼이 사진 영역을 차지한 문제를 반영했다. ImageViewer는 우측 상단 닫기 IconButton과 하단의 이전·사진 이름/순서·다음 아이콘 행을 제공한다. 제품이 전달한 지역화 label을 접근성 이름으로 유지하고, 한 장이면 페이지 이동 버튼은 표시하지 않는다. 첫/마지막의 disabled, 전체 폭 사진·확대·오류 재시도·좌우 safe area는 유지한다. 현재 개발 소스의 표현 수정이며 공개 prop·플랫폼 지원 범위는 바뀌지 않는다.
|
|
210
|
+
|
|
211
|
+
2026-10-20 번뚝 사진 보기 확인: 고정 크기 뷰어 안에서는 원본 `width`·`height`와 `fit="contain"`을 유지하고 Image 루트의 공개 `style={{ inlineSize: "100%", blockSize: "100%" }}`로 주어진 슬롯을 채울 수 있다. 이 경우에도 실제 이미지의 fit은 HJM이 소유한다. `layoutStyle`은 배치용 `width`만 지원하며 `inlineSize`·`blockSize`를 받는 속성이 아니다.
|
|
@@ -64,21 +64,25 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
|
|
|
64
64
|
| prop | 값 | 기본값 | 설명 |
|
|
65
65
|
| --- | --- | --- | --- |
|
|
66
66
|
| `list` | `ReactNode` | 필수 | 목록 pane 본문. 상세가 열려도 mount를 유지한다 |
|
|
67
|
-
| `detail` | `{ title; content; state?; stateAction?; notice? }` | 없음 | 있으면 상세 pane을 보이고 목록 pane을 숨긴다. 닫힌 상태는 prop을 빼서 표현한다(`undefined`를 넘기지 않는다) |
|
|
67
|
+
| `detail` | `{ title; content; state?; stateAction?; notice?; actions?; footer? }` | 없음 | 있으면 상세 pane을 보이고 목록 pane을 숨긴다. 닫힌 상태는 prop을 빼서 표현한다(`undefined`를 넘기지 않는다) |
|
|
68
68
|
| `back` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | 상세 pane 헤더 시작 쪽 ‹ 아이콘 버튼(`ScreenLayout` `exit` `back`). `label`은 보조기술 이름이고 글자는 보이지 않는다. 2026-10-17부터 글자 버튼 대신 아이콘이며 Native `detail.leading`(제품 뒤로 아이콘 대체)은 제거됐다 |
|
|
69
|
-
| `refresh` | `
|
|
70
|
-
| `loadMore` | `ScreenFlowAction` | 없음 | 목록
|
|
69
|
+
| `refresh` | `ScreenFlowIconAction` | 없음 | 목록 헤더 끝의 ghost 아이콘 도구. 기존 `actions` 뒤에 함께 배치한다 |
|
|
70
|
+
| `loadMore` | `ScreenFlowAction` | 없음 | 스크롤 본문의 목록 바로 아래 중앙 ghost 버튼 |
|
|
71
71
|
| `safeAreaBottom` (Native) | 0 이상의 유한수 | `0` | 목록·상세 pane **둘 다**의 ScreenLayout에 넘긴다(두 pane이 같은 창 아래에 닿는다). 1.28.0까지 상세 pane에는 전달되지 않아 상세 본문 끝이 홈 인디케이터 아래에 놓였다(야잘알, 2026-10-17). Web은 `env()`로 자동 |
|
|
72
72
|
| `layoutStyle` | `HjmCompositionStyleProp` | 없음 | Web·Native 모두 목록·상세 pane을 함께 담는 **바깥 틀**에 적용한다(안쪽 목록 ScreenLayout이 아니다). 목록/상세 전환과 상관없이 같은 루트에 남는다(1.13.0) |
|
|
73
73
|
| 나머지 | `ScreenLayout`과 같음(`children`·`footer` 제외) | — | 목록 pane에만 적용된다(`title` 필수, `state`, `scroll` 등) |
|
|
74
74
|
|
|
75
|
+
## 상세 행동 슬롯 (checkout 변경·미게시)
|
|
76
|
+
|
|
77
|
+
`detail.actions`는 상세 헤더 끝의 도구(`IconButton`), `detail.footer`는 고정된 하단 행동(`BottomCTA`)이다. 목록의 actions/footer와 독립이며 상세가 닫히면 함께 사라진다. 선택의창이 상세 공개 슬롯 부족으로 지도/공유 행동을 결과 본문 안에서 직접 배치하던 공통 구조를 이관하기 위해 추가했다. 데이터·공유 캡처·실행 콜백은 제품이 소유한다. 실행 예시: [Web 상세 복구 예제](../../../../../showcase/web/src/patterns/list-detail-recovery-preview.tsx), [Native 상세 복구 예제](../../../../../showcase/native/src/list-detail-recovery-preview.tsx).
|
|
78
|
+
|
|
75
79
|
## 배치
|
|
76
80
|
|
|
77
81
|
| 항목 | 값 | 근거 |
|
|
78
82
|
| --- | --- | --- |
|
|
79
83
|
| 크기 | 목록 또는 상세 pane 하나만 표시하고 각 pane이 host 높이 100%(Native `flex: 1`)를 채운다; 각 pane은 ScreenLayout 폭(최대 720) | Web `height: 100%`, Native `Pane` |
|
|
80
84
|
| 간격 | 각 pane의 ScreenLayout padding `spacing.md` 16; 추가 간격 없음, 목록 행 간격은 `list`(제품·List) 소유 | `ScreenLayout`, `screen-flows.tsx` |
|
|
81
|
-
| 순서·정렬 | 목록 pane: 헤더(제목 → 새로고침) → 목록 →
|
|
85
|
+
| 순서·정렬 | 목록 pane: 헤더(제목 → 기존 actions → 새로고침) → 목록 → 본문 더 보기; 상세 pane: 헤더(뒤로 → 제목 → `detail.actions`) → `detail.content` → 고정 `detail.footer` 아이콘이 있는 새로고침은 헤더 끝에서 `edge="end"`로 그린다. 제품 `actions`는 그 앞에 오므로 `edge`를 주지 않는다(2026-10-17 실측 감사, [IconButton `edge`](icon-button.md#배치)) | `ListDetailScreen` 렌더 순서 |
|
|
82
86
|
| 고정·스크롤 | 두 pane 모두 헤더 고정·본문 스크롤; 숨긴 목록 pane은 mount를 유지해 스크롤·입력이 남는다(Web `hidden`, Native `display: "none"`) | Web·Native `ListDetailScreen` |
|
|
83
87
|
| 좁은 폭·큰 글자 | 제목 열 최소 폭 120 × 글자 배율, 모자라면 새로고침 버튼이 다음 줄로 내려간다; 넓은 폭에서도 두 pane을 나란히 두지 않는다 | `screenPatternRecipe.headerMinWidth` |
|
|
84
88
|
|
|
@@ -102,3 +106,5 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
|
|
|
102
106
|
- `refresh`를 주면 ScreenLayout `actions`는 무시된다. 둘 다 필요하면 `refresh` 대신 `actions`에 직접 조합한다.
|
|
103
107
|
- 상세 pane은 `title`·`state`·`stateAction`·`notice`와 `back`(헤더 `exit`)을 받는다. 상세 쪽 `description`·`actions`·footer 자리는 없다.
|
|
104
108
|
- `loadMore`는 상태 없는 버튼이다. 끝·오류·중복 요청 방지가 필요하면 LoadMore를 쓴다.
|
|
109
|
+
|
|
110
|
+
2026-10-20 Utilverse 체크리스트 채택: 목록의 생성 행동을 고정 footer에 유지할 수 있도록 `footer`를 공개한다(checkout 개발 API·미게시). 상세 footer는 기존 `detail.footer`가 소유한다.
|
|
@@ -77,6 +77,9 @@ import { Avatar, ListRow } from "@hjmds/react-native/data-display";
|
|
|
77
77
|
|
|
78
78
|
- 상호작용: Web은 `href`면 `<a>`(selected는 `aria-current="page"`), `onClick`이면 `<button>`(selected는 `aria-pressed`),
|
|
79
79
|
둘 다 없으면 `<div>`. Native는 `onPress`가 있으면 누를 수 있는 행이 된다.
|
|
80
|
+
- Web의 `selected`를 생략하면 일반 행동 버튼이며 `aria-pressed`를 붙이지 않는다.
|
|
81
|
+
선택 가능한 행에만 `selected={true|false}`를 명시한다. 프로필 이동 행까지 토글로 읽히던 문제를
|
|
82
|
+
2026-10-19에 수정했으며, 선택되지 않은 기본 모양은 그대로다.
|
|
80
83
|
|
|
81
84
|
## 배치
|
|
82
85
|
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
| 이름 | 역할 | Web | Native |
|
|
42
42
|
| --- | --- | --- | --- |
|
|
43
|
+
| `ListSeparator` | 동반 — 가상화 host의 장식 선(checkout·미게시) | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
43
44
|
| `List` | 기본 | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/data-display` |
|
|
44
45
|
| `TaskList` | 확장 — 완료 체크 목록(optional) | `/task-list` | `/task-list` |
|
|
45
46
|
| `CollectionRail` | 동반·대안 — 독립 행동을 가진 유한 가로 목록([구성 지침](../compositions/collection-detail.md)) | `/collection-rail` | `/collection-rail` |
|
|
@@ -73,12 +74,13 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
73
74
|
| prop | 값 | 기본값 | 설명 |
|
|
74
75
|
| --- | --- | --- | --- |
|
|
75
76
|
| `label` | 현지화 문자열(필수) | — | 목록의 접근성 이름. 비면 `TypeError` |
|
|
76
|
-
| `separator` | `indented` · `full` · `none` | `indented` | `indented`는 위 행의 글자 열에서 시작(leading이 있으면 52, 없으면 0) |
|
|
77
|
+
| `separator` | `indented` · `full` · `content` · `none` | `indented` | `indented`는 위 행의 글자 열에서 시작(leading이 있으면 52, 없으면 0) |
|
|
77
78
|
| `appearance` | `plain` · `grouped` | `plain` | `grouped`는 배경과 둥근 모서리로 한 덩어리 |
|
|
78
79
|
| `layoutStyle` | 배치 key만 | — | 목록 루트 배치. Native `style`은 제거된 속성 — `layoutStyle`, 모양은 `appearance`·`separator` |
|
|
79
80
|
| TaskList `items` | `readonly TaskItem[]` — `{ id, label, completed, description?, disabled? }` | — | `id`·`label`이 비거나 `id`가 중복되거나 `completed`가 boolean이 아니면 `TypeError` |
|
|
80
81
|
| TaskList `onCompletedChange` | `(id: string, completed: boolean) => void` | — | 필수. 체크 하나마다 한 번. 목록 순서·저장은 제품이 정한다 |
|
|
81
82
|
| TaskList `disabled` | `true` · `false` | `false` | 전체 체크박스를 끈다 |
|
|
83
|
+
| TaskList `itemPresentation` | `plain` · `card` | Checkbox 기본값(`card`) | 기존 표면 안의 항목은 `plain`으로 중복 테두리를 피한다. 행·체크·별도 행동 배치는 TaskList가 유지한다(checkout 변경·미게시). |
|
|
82
84
|
| TaskList `renderItemAction` | `({ item, disabled }) => ReactNode` | — | 체크 아래 별도 행동. disabled는 전체/항목 잠금의 합이며 제품 버튼에 전달한다. 간격 spacing.sm 12, 체크 안에 중첩하지 않음 |
|
|
83
85
|
| TaskList `emptyContent` | ReactNode | — | `items`가 비면 `List` 안에 그린다 |
|
|
84
86
|
| TaskList `renderCollection` | `(context: { items, renderItem: (item: TaskItem) => ReactNode }) => ReactNode` | — | SortableCollection을 합성할 수 있다. 이때 목록 루트는 제품이 그린다 |
|
|
@@ -86,10 +88,14 @@ import { Text } from "@hjmds/react-native/primitives";
|
|
|
86
88
|
|
|
87
89
|
- List 자체에는 이벤트 콜백이 없다. 행의 누름은 각 [ListRow](list-row.md)가 받는다.
|
|
88
90
|
|
|
91
|
+
2026-10-19 Utilverse 체크리스트는 단순한 체크 행을 유지하려고 항목·삭제 버튼 배치를 제품에서 재조립하고 있었다. `TaskList itemPresentation="plain"`과 기존 `renderItemAction`으로 연결한다. Web/Native 모두 같은 Checkbox 표현 옵션을 전달하며 생략한 소비자의 모양·완료 콜백은 유지한다. 기존 양쪽 TaskList 실행 예제에서 단순 행과 독립 삭제 행동을 함께 보여준다.
|
|
92
|
+
|
|
89
93
|
## 배치
|
|
90
94
|
|
|
91
95
|
Native `grouped` 프레임은 Provider의 `tokens.radius.lg`를 읽는다. plain 목록과 separator 의미는 변하지 않는다.
|
|
92
96
|
|
|
97
|
+
2026-10-19 오늘야구 순위 행은 공개 `leadingStyle`로 순위+사진 슬롯을 넓혔지만 Native 구분선은 기본 40+12만 사용해 사진 아래에서 시작했다. Native는 앞쪽 프레임의 실제 layout 폭+recipe gap을 항목에 전달해 구분선을 글자 열에 맞춘다(checkout 변경·미게시). 기본 40폭은 여전히52이며 leading 없는 행은0이다. RTL은 논리적 시작 쪽에 적용하고 `full`/`none`은 변하지 않는다. Web은 고정된 기본 leading 프레임을 사용하므로 기존 CSS를 유지한다.
|
|
98
|
+
|
|
93
99
|
| 항목 | 값 | 근거 |
|
|
94
100
|
| --- | --- | --- |
|
|
95
101
|
| 크기 | 화면 본문 폭을 채우는 세로 묶음이다. `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) |
|
|
@@ -113,6 +119,8 @@ Native `grouped` 프레임은 Provider의 `tokens.radius.lg`를 읽는다. plain
|
|
|
113
119
|
|
|
114
120
|
새 `CollectionRail`은 1.16.0 추가 실험 companion이다. 기존 List의 배포/호환 계약은 유지한다.
|
|
115
121
|
|
|
122
|
+
2026-10-18 미게시 타입 보완: Web/Native의 `CollectionRailProps<Item>`은 `Item extends CollectionRailItem`의 제품 필드를 `renderItem(item)`까지 보존한다. 항목은 `id`·`label`을 계속 제공하며 추가 데이터는 제품 소유다. 기존 타입이 추가 필드를 지워 소비 앱이 ID 역조회나 type assertion을 해야 하던 번뚝 추천 카드 이관에서 확인했다. 스크롤·측정·안정 ID 계약은 바뀌지 않는다.
|
|
123
|
+
|
|
116
124
|
#### 언제 쓰나
|
|
117
125
|
|
|
118
126
|
이미 불러온 유한 카드/자료를 여러 개 함께 읽고 가로로 탐색할 때 쓴다. 독립 입력·버튼·링크는 계속 접근 가능하며
|
|
@@ -227,3 +235,27 @@ const room = useScreenBodyScrollRoom();
|
|
|
227
235
|
## 근거
|
|
228
236
|
- 구분선 시작은 위 행의 글자 열: 2026-10-17 사용자 보고(leading 없는 행에서 구분선이 중간에서 잘린 것처럼 보임). 고정 시작 52가 leading 없는 행에도 적용돼 Web 402 대시보드 기록 목록에서 행 글자 16 / 구분선 68~394(콘텐츠 끝 386보다 8 더 나감)였다. 위 행의 leading 여부로 시작을 정하고 끝을 콘텐츠 가장자리로 맞춘 뒤 글자 16 / 구분선 16~386, leading 있는 목록은 글자 85 / 구분선 85~369(콘텐츠 끝 369), RTL도 대칭으로 측정됐다.
|
|
229
237
|
- 구분선·grouped 여백: 2026-10-17 사용자 결정(행 글자는 여백 선 16, 표면만 바깥으로 8). 행 콘텐츠 상자가 목록 콘텐츠 가장자리로 옮겨져 `indented` 시작이 60 → 52가 되어도 화면에서 보이는 위치(글자 시작)는 같다. 기본 화면에서 행 글자가 24, 섹션 제목이 16이던 실측은 [ListRow 근거](list-row.md#근거)에 있다. 이전 plain 목록의 `overflow: hidden`은 grouped 모서리만을 위한 것이라 grouped로 옮겼다.
|
|
238
|
+
|
|
239
|
+
## 가변 높이 가상 목록의 구분선 (checkout·미게시)
|
|
240
|
+
|
|
241
|
+
2026-10-20 번뚝 가변 높이 FlatList는 공개 VirtualList의 고정 rowHeight 계약으로 바꿀 수 없어
|
|
242
|
+
자체 선을 그리고 있었다. `ListSeparator`는 List와 같은 recipe/테마의 장식 선만 제공한다.
|
|
243
|
+
가상화·키보드·측정은 기존 host가 유지하고, 전체 목록의 의미/이름과 행은 해당 host가 제공한다.
|
|
244
|
+
유한 목록은 List를 쓰며 각 행에 ListSeparator를 직접 반복하지 않는다.
|
|
245
|
+
|
|
246
|
+
- `separator`: `content`(기본, 이미 자체 padding이 있는 행의 본문 양 끝), `full`(ListRow 표면까지 확장),
|
|
247
|
+
`indented`(글자 열), `none`(아무것도 그리지 않음). List에도 content를 선택할 수 있다.
|
|
248
|
+
- `leading`: 기본 false. indented에서만 ListRow 기본 leading 폭+간격을 예약한다.
|
|
249
|
+
List 내부의 사용자 지정 leading 실측은 기존 List가 계속 처리한다.
|
|
250
|
+
- `layoutStyle`: 바깥 host에 맞추는 배치 전용 속성. 색/두께는 recipe가 소유한다.
|
|
251
|
+
- 선은 장식이며 별도 초점·접근성 항목·클릭 행동을 만들지 않는다. 마지막 행 뒤에는 host가 생략한다.
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import { ListSeparator } from "@hjmds/react-native/data-display";
|
|
255
|
+
|
|
256
|
+
<FlatList data={rows} keyExtractor={row => row.id} renderItem={renderRow}
|
|
257
|
+
ItemSeparatorComponent={ListSeparator} />
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
양 renderer의 기존 List 스토리 `독립 구분선`은 공개 API의 세 가지 경계를 보여준다.
|
|
261
|
+
Web 가상 host도 같은 ListSeparator를 행 사이에 연결하며, 고정 높이를 쓰는 host는 선의 두께를 행 높이에 포함한다.
|
|
@@ -146,3 +146,5 @@ Native 내장 삭제·메뉴 기호는 고정 아이콘 틀의 크기를 유지
|
|
|
146
146
|
- 답장 중 카드 배치: 2026-10-16 카카오톡 실측.
|
|
147
147
|
- `submitMode` 등 입력 공개 축: 2026-10-07 번뚝 채택에서 기존 blur/IME/검증 연결이 누락됐다.
|
|
148
148
|
- Native 내장 기호 고정 크기: 2026-10-06 최근 검색 삭제 기호가 큰 글자에서 잘리는 것을 재현했다.
|
|
149
|
+
|
|
150
|
+
- 미게시 `descriptionAlign="end"`: 글자 수처럼 입력 아래 끝에 놓을 보조 문구에 사용한다. 기본값은 `start`이며 오류 문구 정렬은 바꾸지 않는다. 2026-10-20 번뚝 아이디어방의 `0/300자` 우측 배치 요청을 반영했다. 제품에서 별도 카운터를 중복 렌더하지 않는다.
|
|
@@ -88,12 +88,14 @@ 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은 제목·설명 전체 묶음과 행동의 세로 중심을 행 높이의 중앙에 맞춘다. 첫 줄에 버튼 높이 기반 여백을 더하지 않는다. 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
|
|
|
95
95
|
## 꼭 지킬 것
|
|
96
96
|
|
|
97
|
+
Web은 기본적으로 danger를 assertive, 나머지를 polite로 알린다. 배경 갱신 실패처럼 기존 본문을 유지하며 끼어들지 않아야 할 때는 공개 HTML prop `aria-live="polite"`를 전달한다. 2026-10-19 번뚝 팔로우 목록에서 전달한 값이 덮어써지던 구현을 보완했으며 기본값은 유지한다(개발 소스, 미게시). Native는 기존 `announcement`를 사용한다.
|
|
98
|
+
|
|
97
99
|
- 제목·설명·행동 문구는 i18n 키로 넣는다. 아이콘은 제품 소유 자산이고 색은 tone이 정한다.
|
|
98
100
|
- 배치는 `layoutStyle`로만 한다. 색·padding·radius를 `style`/`className`으로 덮지 않는다. Native `style`은 제거된 속성이다.
|
|
99
101
|
- 같은 화면에 같은 내용의 Notice와 Toast를 동시에 띄우지 않는다.
|
|
@@ -117,3 +119,5 @@ import { Notice } from "@hjmds/react-native/feedback";
|
|
|
117
119
|
## 근거
|
|
118
120
|
|
|
119
121
|
- 첫 줄 기준: 2026-10-17 정렬 실측(Web 402·1280, 검색 오류 Notice). 행 끝 행동 버튼 가운데 195, 제목 첫 줄 가운데 187로 8px 어긋났고 Web 아이콘 칸에는 규칙이 없었다. Toast의 첫 줄 기준(c053d956)과 같은 방식으로 맞춘 뒤 아이콘·제목 첫 줄·행동 모두 195로 측정됐다.
|
|
122
|
+
|
|
123
|
+
- 2026-10-19 번뚝 댓글 오류 안내에서 두 줄 문구와 재시도 버튼의 높이 중심이 달라 보인다는 사용자 지적을 반영했다. Web은 첫 줄 offset을 제거하고 행의 `align-items: center`로 전체 문구·버튼을 맞춘다. 393px 개발 화면에서 문구·버튼·카드 중심이 모두 239.8046875px로 일치했다. Native의 기존 아래 행동 배치는 이번 수정 범위가 아니다. 개발 소스 수정이며 미게시다.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# NotificationRow
|
|
2
2
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
|
-
- 상태:
|
|
4
|
+
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.26.0
|
|
7
7
|
- 검토일: 2026-10-16
|
|
8
8
|
- 근거: [알림 화면](../screens/common-inbox.md), `src/screen-patterns.ts`의 `notificationRowRecipe`, 양 renderer `src/screens.tsx`
|
|
9
|
-
- 스토리북:
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/알림 미리보기 행`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
@@ -31,19 +31,23 @@
|
|
|
31
31
|
```tsx
|
|
32
32
|
// Web
|
|
33
33
|
import { NotificationRow } from '@hjmds/react/screens';
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
<NotificationRow avatar={avatar} preview={message} metadata={timeLabel} unread={unread}
|
|
35
|
+
separator={index > 0}
|
|
36
|
+
action={{ label: accessibleSummary, disabled: !destination, onActivate: openNotification }} />
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
```tsx
|
|
40
40
|
// Native
|
|
41
41
|
import { NotificationRow } from '@hjmds/react-native/screens';
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
<NotificationRow avatar={avatar} preview={message} metadata={timeLabel} unread={unread}
|
|
43
|
+
separator={index > 0}
|
|
44
|
+
action={{ label: accessibleSummary, disabled: !destination, onActivate: openNotification }} />
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
`action`·`separator`는 로컬 개발 중인 미게시 보완이다. 번뚝이 각 플랫폼에 클릭/비활성 host와
|
|
48
|
+
서로 다른 구분선 inset을 복제해 본문 시작점과 선이 어긋난 것이 계기다. 이 보완 전의 설치 패키지에서는
|
|
49
|
+
기존 바깥 링크/Pressable host를 사용한다. 로컬 소스에서 API를 확인한 뒤 이관한다.
|
|
50
|
+
|
|
47
51
|
## 축과 기본값
|
|
48
52
|
|
|
49
53
|
| prop | 값 | 기본값 | 설명 |
|
|
@@ -54,6 +58,8 @@ import { NotificationRow } from '@hjmds/react-native/screens';
|
|
|
54
58
|
| avatarBadge | ReactNode | 없음 | 아바타 오른쪽 아래 유형 배지 |
|
|
55
59
|
| thumbnail | ReactNode | 없음 | 대상 썸네일 |
|
|
56
60
|
| unread | boolean | false | 시간 옆 읽지 않음 점 |
|
|
61
|
+
| action | `{ label: string, onActivate(): void, disabled?: boolean }` | 없음 | 미게시. 이동/권한은 제품이 결정하고 버튼 대상·포커스·피드백은 HJM 소유. 빈 label은 TypeError |
|
|
62
|
+
| separator | boolean | false | 미게시. 같은 날짜 구역의 두 번째 행부터 true. 행 위에서 미리보기 시작점에 맞춘 구분선 |
|
|
57
63
|
|
|
58
64
|
## 배치
|
|
59
65
|
|
|
@@ -65,11 +71,15 @@ import { NotificationRow } from '@hjmds/react-native/screens';
|
|
|
65
71
|
| 고정·스크롤 | 바깥 목록 host 소유 | 별도 scroller 없음 |
|
|
66
72
|
| 좁은 폭·큰 글자 | 미리보기 최대 2줄 말줄임, 아바타·썸네일은 줄지 않는다 | `previewLines: 2`, `flexShrink: 0` |
|
|
67
73
|
|
|
74
|
+
구분선의 시작점은 행 여백 16 + 아바타 44 + 간격 12 = 72이며 제품은 이를 다시 계산하지 않는다.
|
|
75
|
+
|
|
68
76
|
HJM이 44px 아바타·썸네일, 20px 유형 배지, 2줄 미리보기와 간격을 소유한다. `avatarBadge`·`thumbnail`은 선택 슬롯이다. 앱은 별도 행 padding을 넣지 않는다.
|
|
69
77
|
|
|
70
78
|
## 꼭 지킬 것
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
`action`을 주면 바깥 버튼/링크를 추가하지 않는다. HJM이 하나의 버튼과 전체 접근성 이름을 제공하며
|
|
81
|
+
비활성은 콜백을 실행하지 않는다. 가상화·드래그 등 외부 target이 필요한 기존 소비자는 `action` 없이
|
|
82
|
+
바깥 링크/Pressable 하나에서 이동·disabled·전체 접근성 이름을 연결할 수 있다. 내부는 장식 표현이므로 별도 클릭 대상을 넣지 않는다. `preview`는 인라인 텍스트 콘텐츠다. HJM 내부 색·간격을 제품 CSS로 덮지 않는다.
|
|
73
83
|
|
|
74
84
|
## 플랫폼 차이
|
|
75
85
|
|
|
@@ -85,12 +85,15 @@ import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
|
|
|
85
85
|
| `nextLabel`, `backLabel` | 다음·이전 문구. 첫 단계에는 이전이 없다 |
|
|
86
86
|
| `complete` | 마지막 단계의 주 행동 `{ label, onAction, disabled?, pending? }` |
|
|
87
87
|
| `skip` | 선택. Web은 헤더 actions, Native는 본문 진행 문구 다음에 보조 버튼으로 놓인다 |
|
|
88
|
+
| `exit` (미게시) | 선택. ScreenLayout과 같은 플랫폼별 exit. 재보기의 저장 없는 닫기/뒤로는 헤더 시작 쪽 아이콘으로 연결한다. `complete`/`skip` pending이면 함께 비활성이다. 생략하면 기존 첫 실행 흐름을 유지한다 |
|
|
88
89
|
| `progressLabel(current, total)` | 1부터 센 현재 단계와 전체 수로 진행 문구를 만든다 |
|
|
89
90
|
| `layoutStyle` | 선택. ScreenLayout 루트의 공개 배치 슬롯 |
|
|
90
91
|
| `safeAreaBottom` (Native) | 선택, 기본 0. ScreenLayout에 그대로 넘겨 footer(다음·이전) 아래 여백이 `max(16, inset)`이 된다. 바깥 SafeAreaView bottom·`16 + inset` 패딩 래퍼 대신 쓴다(1.28.1, 2026-10-17 야잘알·다에리가 각자 감쌌다) |
|
|
91
92
|
|
|
92
93
|
## 배치
|
|
93
94
|
|
|
95
|
+
2026-10-19 오늘야구의 설정→시작 안내 재보기에는 완료 이력 저장 없이 나가는 경로가 없었다. 양 renderer의 기존 ScreenLayout exit를 공개하고 제품은 재보기 경로에서만 연결한다. 닫기는 complete/skip을 호출하지 않으며 Native는 이때 공통 헤더에 exit만 두고 단계 제목·안내는 본문에 유지한다. 첫 실행에서 exit를 허용할지는 제품이 결정한다.
|
|
96
|
+
|
|
94
97
|
| 항목 | 값 | 근거 |
|
|
95
98
|
| --- | --- | --- |
|
|
96
99
|
| 크기 | ScreenLayout의 기본 최대 폭 720; 다음·완료·이전은 `Button` 기본 크기 | `screenPatternRecipe.maxWidth` |
|
|
@@ -65,6 +65,8 @@ import { PageIndicator } from "@hjmds/react-native/carousel";
|
|
|
65
65
|
| --- | --- | --- | --- |
|
|
66
66
|
| `label` | `string` | 필수 | 페이지 묶음의 이름. Web은 group 이름, Native는 `label: 현재 페이지 이름`으로 읽힌다. 비면 `TypeError` |
|
|
67
67
|
| `count` | 1 이상 정수 | 필수 | 페이지 수. 밖이면 `RangeError` |
|
|
68
|
+
| `maxVisibleDots` | 1 이상 정수 | 전체 | 현재 페이지를 포함하는 연속 창만 표시. 이름·이동은 전체 페이지 기준이며 창 크기는 페이지 수를 넘지 않는다(checkout 미게시) |
|
|
69
|
+
| `interactive` | `boolean` | `true` | `false`이면 위치만 표시하며 점은 누름 대상/키보드/조절 행동을 만들지 않는다. 별도 이전·다음 컨트롤이 있는 작은 표시용(checkout 미게시) |
|
|
68
70
|
| `currentIndex` | 0부터 `count - 1` | 필수 | 페이저가 확정한 현재 페이지. 컴포넌트는 따로 기억하지 않는다 |
|
|
69
71
|
| `onCurrentIndexChange` | `(index: number) => void` | 필수 | 점 누름·Web 화살표 키·Native 조절 동작이 요청한 페이지. 처음·끝에서는 같은 값이 다시 올 수 있다(넘어가지 않고 멈춘다) |
|
|
70
72
|
| `composeAccessibleName` | `({ position, total }) => string` | 필수 | 점 하나의 이름. 숫자 문법은 제품 문구다. 빈 문자열은 `TypeError` |
|
|
@@ -87,6 +89,7 @@ import { PageIndicator } from "@hjmds/react-native/carousel";
|
|
|
87
89
|
- `onCurrentIndexChange`에서 페이저를 그 페이지로 옮긴다. 이 컴포넌트는 스크롤하지 않는다.
|
|
88
90
|
- 숨은 페이지의 내용은 제품 페이저가 접근성 트리에서 뺀다(`accessibilityElementsHidden`·`importantForAccessibility`).
|
|
89
91
|
- 점의 색·크기를 덮지 않는다. Carousel과 같은 recipe를 쓴다.
|
|
92
|
+
- 제한 창의 대화형 점이 이동하면서 현재 초점의 점을 제거하면 Web은 현재 점으로 초점을 복구한다. 다른 컨트롤로 이동한 초점은 유지한다.
|
|
90
93
|
|
|
91
94
|
## 플랫폼 차이
|
|
92
95
|
|
|
@@ -100,3 +103,12 @@ import { PageIndicator } from "@hjmds/react-native/carousel";
|
|
|
100
103
|
## 근거
|
|
101
104
|
|
|
102
105
|
- PageIndicator 추가: 2026-10-18 Utilverse 홈 런처가 Native 페이저 아래 점을 직접 그렸다(HJM 1.30.0 감사 "HJM gap"). Carousel 점 줄을 공개 부품으로 빼서 같은 recipe·같은 이동 규칙을 쓰게 했다.
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
## 별도 이동 컨트롤이 있는 작은 위치 표시
|
|
109
|
+
|
|
110
|
+
번뚝처럼 이전·다음 버튼과 페이저를 이미 제공하는 화면은 `interactive={false}`와
|
|
111
|
+
`maxVisibleDots={5}`를 사용한다. 8px 점과 8px 간격은 그대로이며 44px 버튼을 작게 만들지 않는다.
|
|
112
|
+
Web은 현재 페이지 이름을 갖는 `img`, Native는 같은 이름의 `image`이다. 이동은 별도 공통
|
|
113
|
+
IconButton 또는 제품 페이저가 소유한다. `interactive`를 생략하면 기존 점 선택·키보드·Native 조절이 유지된다.
|
|
114
|
+
이 옵션은 2026-10-19 번뚝 앱·웹에 각각 있던 5개 점 창과 직접 스타일을 제거하기 위해 추가했으며 미게시다.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# ProfileSummary
|
|
2
2
|
|
|
3
3
|
- 단계: 컴포넌트
|
|
4
|
-
- 상태:
|
|
4
|
+
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.26.0
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-19
|
|
8
8
|
- 근거: [프로필 화면](../screens/common-profile.md), `src/screen-patterns.ts`의 `profileSummaryRecipe`, 양 renderer `src/screen-flows.tsx`
|
|
9
|
-
- 스토리북:
|
|
9
|
+
- 스토리북: `배포/컴포넌트/데이터 표시/프로필 요약`
|
|
10
10
|
|
|
11
11
|
## 언제 쓰나
|
|
12
12
|
|
|
@@ -50,6 +50,7 @@ import { ProfileSummary } from '@hjmds/react-native/screen-flows';
|
|
|
50
50
|
| badge | ReactNode | 없음 | 이름 옆 배지 |
|
|
51
51
|
| nameActions | ReactNode | 없음 | 이름 행의 행동 |
|
|
52
52
|
| stats | `{id,value,label,onPress?,disabled?}[]` | `[]` | 통계 수치. `onPress`가 있으면 버튼 |
|
|
53
|
+
| actionsLayout | `inline` · `stacked` | `inline` | 미게시. 가로 보조 행동은 기존 줄바꿈 행, 팔로우/메시지처럼 같은 폭의 주·보조 행동은 stacked로 세로 배치 |
|
|
53
54
|
| actions | ReactNode | 없음 | 수치 아래 행동 줄. [ProfileScreen](profile-screen.md) `editPlacement="summary"` 안에서는 그 화면의 `edit`이 이 줄 맨 앞에 `secondary` `small` 버튼(`profileSummaryRecipe.editAction`)으로 먼저 들어간다(1.31.0) |
|
|
54
55
|
| children | ReactNode | 없음 | 하단 footer |
|
|
55
56
|
|
|
@@ -74,3 +75,15 @@ HJM이 원형 72px 아바타 프레임, 이름, 수치, 행동의 순서와 간
|
|
|
74
75
|
## 관련 지침
|
|
75
76
|
|
|
76
77
|
[프로필 화면](../screens/common-profile.md), [ProfileScreen](profile-screen.md), [CompositionLayout](composition-layout.md).
|
|
78
|
+
|
|
79
|
+
### 세로 행동과 아바타 그림
|
|
80
|
+
|
|
81
|
+
2026-10-19 번뚝 공개 프로필의 Web inline flex 안에 제품 Stack을 넣으면 fullWidth 버튼도 좁게 모였다. `actionsLayout="stacked"`가 양 renderer의 전체 폭·세로8 간격을 소유한다. actions에는 버튼을 fragment로 공급하며 제품에서 같은 기본 Stack을 다시 조립하지 않는다. 기본 inline과 ProfileScreen의 edit 삽입은 유지한다. 여러 수치/관계 목록 이동/별도 메시지 행동을 가진 요약이며 단일 관계 수치·팔로우 proposal을 제공하는 FollowProfileCard와 역할이 다르다.
|
|
82
|
+
|
|
83
|
+
Web의 텍스트/이모지 artwork는 원형72 프레임 안에서 glyph.xxl48 크기로 중앙 배치한다. SVG/사진 콘텐츠는 프레임을 채우고 제품에서 별도 아바타 좌표를 보정하지 않는다. Native의 Avatar/제품 이미지 adapter는 같은72 프레임 안에 콘텐츠를 공급한다.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<ProfileSummary name={name} avatar={avatar} stats={stats} actionsLayout="stacked"
|
|
87
|
+
actions={<><Button fullWidth onClick={follow}>{followLabel}</Button><Button fullWidth tone="secondary" onClick={message}>{messageLabel}</Button></>} />
|
|
88
|
+
// Native는 같은 actionsLayout에 Button.onPress를 연결한다.
|
|
89
|
+
```
|
|
@@ -75,7 +75,9 @@ Native `ScrollProgress`는 제품 ScrollView의 `onScroll`(contentOffset.y)·`on
|
|
|
75
75
|
| `value` | `0`~`max` | — | 생략하면 불확정 진행 |
|
|
76
76
|
| `size` | `small` · `medium` · `large` | `medium` | 두께 4 · 8 · 12, circular 지름 24 · 40 · 64 |
|
|
77
77
|
| `tone` | `brand` · `success` · `warning` · `danger` | `brand` | — |
|
|
78
|
+
| `segments` | 2 이상 정수 | 없음 | 미게시. determinate linear만 지원. 같은 수량을 같은 폭의 칸으로 나눈다. `max={5} segments={5} value={2.5}`는 두 칸과 셋째 칸 절반이다. 불확정·circular와 함께 쓰면 오류 |
|
|
78
79
|
| `shape` | `linear` · `circular` | `linear` | `circular`일 때만 `children`(링 안 내용)을 그린다 |
|
|
80
|
+
| `showValue` | `boolean` | `true` | 미게시. 라벨에 수량이 이미 있으면 false로 보이는 보조 값만 숨긴다. progress의 접근성 값·비율·label은 유지한다 |
|
|
79
81
|
| `valueText` | 지역화 문구 | 백분율 문구 | 단위가 다른 문구가 필요하면 지역화해 넘긴다 |
|
|
80
82
|
| `layoutStyle` | 배치 전용 style 객체 | — | 바깥 여백·폭만 |
|
|
81
83
|
| `metrics`(ScrollProgress) | `{ offset: number; contentSize: number; viewportSize: number }` | 필수 | 타입 `ScrollMetrics`(`@hjmds/design-contracts/scroll-progress`). `viewportSize` 0이면 0%, 내용이 화면보다 짧으면 100% |
|
|
@@ -148,3 +150,25 @@ const { before, after } = resolveScrollEdges(metrics);
|
|
|
148
150
|
Web은 useScrollMetrics의 resize/mutation 관찰, Native는 ScrollView의 onLayout·
|
|
149
151
|
onContentSizeChange·onScroll을 연결한다. 길이를 줄인 뒤 예전 offset을 그대로 제품 상태로
|
|
150
152
|
저장하지 않는다. 측정 전 viewport=0은 0, 측정 후 화면 안에 들어오는 내용은 1이다.
|
|
153
|
+
|
|
154
|
+
## 수량이 라벨에 들어 있는 진도
|
|
155
|
+
|
|
156
|
+
다에리 도감은 이미 라벨이 `만난 친구 1/30`을 말하므로 보조 백분율을 중복 표시하지 않도록 `showValue={false}`를 쓴다. 기존 제품 막대의 최소 6% 보정은 실제 비율을 바꾸므로 사용하지 않는다. HJM이 전달된 값의 실제 비율을 그린다. Native는 라벨/값을 하나의 accessible progress 대상으로 노출한다.
|
|
157
|
+
|
|
158
|
+
<!-- usage-example: Web ../../../../../showcase/web/src/components/Progress.stories.tsx -->
|
|
159
|
+
<!-- usage-example: Native ../../../../../showcase/native/src/components/Progress.stories.tsx -->
|
|
160
|
+
|
|
161
|
+
## 수량의 단위 칸(미게시)
|
|
162
|
+
|
|
163
|
+
2026-10-19 다에리 활동일 보상은 5일 중 며칠을 모았는지 단위 칸으로 표시한다.
|
|
164
|
+
단계별 화면을 탐색하는 Steps와 의미가 달라 Progress의 `segments` 표현으로 추가했다.
|
|
165
|
+
분할 간격은 `spacing.xxs` 4이고 높이·tone·rounding은 기존 recipe를 공유한다.
|
|
166
|
+
`resolveProgressSegments`(`@hjmds/design-contracts/recipes`)가 양 renderer의 분할 비율과 입력 검증을 소유한다.
|
|
167
|
+
칸은 장식이며 보조기술에는 하나의 실제 진행량만 전달한다.
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<Progress label={t("activity.days", { count: days })} value={days} max={5} segments={5} showValue={false} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
실행 예제는 양 Showcase의 `Progress.stories.tsx` ActivityUnits다.
|
|
174
|
+
Web circular/segments의 native progress는 시각적으로만 숨기며 접근성 트리에서 제거하지 않는다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-19
|
|
8
8
|
- 근거: [cross-platform core](../../cross-platform-core-normalization.md), `src/component-recipes.ts`(`selectionGroupRecipe`·`selectionControlRecipe`)
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/라디오 버튼 그룹`
|
|
10
10
|
|
|
@@ -70,11 +70,13 @@ import { RadioGroup } from "@hjmds/react-native/inputs";
|
|
|
70
70
|
| `value` · `defaultValue` | 항목 값 · `null` | `null` | 선택 없음 허용. items에 없는 값이면 `RangeError` |
|
|
71
71
|
| `onValueChange` | Web `(value: string) => void` · Native `(value: Value \| null) => void` | — | 고를 때 호출 |
|
|
72
72
|
| `orientation` | `vertical` · `horizontal` | `vertical` | Native는 글자 크기 160% 이상이면 가로를 세로로 쌓는다 |
|
|
73
|
-
| `presentation` | `plain` · `card` · `grouped` | `card` | `grouped`는 한 카드 안에 행이 붙는다 |
|
|
73
|
+
| `presentation` | `plain` · `card` · `grouped` · `chips` | `card` | `grouped`는 한 카드 안에 행이 붙는다 |
|
|
74
74
|
| `size` | `small` · `medium` | `medium` | — |
|
|
75
75
|
| `required` | `boolean` | `false` | 켜면 초기 선택을 보정한다 |
|
|
76
76
|
| `description` · `error` | Web `ReactNode` · Native `string` | — | 그룹 설명·오류 문구 |
|
|
77
77
|
| `disabled` · `readOnly` | `boolean` | `false` | — |
|
|
78
|
+
| `renderLabel` | `(item) => ReactNode` | — | 글꼴·상품명 등 제품 미리보기. 선택 상태와 frame은 공통 구현이 소유하며 항목 `label`이 접근성 이름으로 유지된다. Native의 문자열/숫자는 공통 Text로 감싼다 |
|
|
79
|
+
| `items[].action` | `{ label, accessibilityLabel?, onAction, disabled? }` | — | 선택과 별개인 관리 행동. 작은 ghost Button이 행 끝에 놓이고 라디오 target 밖의 형제다. 그룹/항목 disabled와 함께 비활성화되며 readOnly는 선택만 막는다 |
|
|
78
80
|
| `renderLeading` | `(item, appearance) => ReactNode` — appearance Web `{ selected, color: "currentColor", size }`, Native `{ checked, selected, disabled, readOnly, color, size }` | — | 항목 앞 제품 아이콘 |
|
|
79
81
|
| `label` / `accessibilityLabel` | 문자열(Web `label`은 `ReactNode`) | — | 둘 중 하나 필수. 보이는 제목이 없으면 `accessibilityLabel` |
|
|
80
82
|
| `layoutStyle` | 배치 전용 style 객체 | — | 그룹 프레임 배치 |
|
|
@@ -117,7 +119,47 @@ card(vertical) grouped(vertical)
|
|
|
117
119
|
| `onValueChange` 값 | `string` | `Value \| null`(제네릭) |
|
|
118
120
|
| `label`·항목 `label` 타입 | `ReactNode` | `string` |
|
|
119
121
|
| 항목 `leading`·`accessibilityHint` | 없음 | 있음 |
|
|
120
|
-
| indicator 숨김·교체 |
|
|
122
|
+
| indicator 숨김·교체 | `indicator="none"`(미게시) | `indicator="none"`, `renderIndicator` |
|
|
121
123
|
| 낭독 문구 `requiredLabel`·`readOnlyLabel`·`invalidLabel`, `invalid` | 없음 | 있음 |
|
|
122
124
|
| `name` | 있음(기본 자동 생성) | 없음 |
|
|
123
125
|
| 루트 요소 | `<fieldset>` + `<legend>` | `View` `accessibilityRole="radiogroup"` |
|
|
126
|
+
|
|
127
|
+
### 미리보기와 독립 행동 (개발 소스 · 미게시)
|
|
128
|
+
|
|
129
|
+
손글씨 선택에서 앱이 radio/행 frame을 다시 만들던 원인은 사용자 글꼴과 이름 변경 행동을 함께 넣을 슬롯이 없었기 때문이다(다에리, 2026-10-19). `renderLabel`에는 비상호작용 미리보기만 넣고 `action`에 별도 행동을 연결한다. Web은 실제 같은 name의 radio input을 유지해 방향키로 선택한다. action은 label/radio 안에 중첩하지 않아 관리 행동이 선택을 바꾸지 않는다. 제품은 선택 시 미리보기·닫기를 연결하고, 실제 저장은 별도 확인 행동으로 유지할 수 있다. 필터로 현재 값이 보이지 않으면 제품은 표시 값에 `null`을 전달하며 저장된 선택을 임의로 바꾸지 않는다.
|
|
130
|
+
|
|
131
|
+
frame과 선택 행의 기존 recipe 크기·색·간격을 재사용한다. 보조 행동이 있는 행은 frame 안에서 선택 영역이 남는 너비를 채우고 끝 행동에 `spacing.xs` 8의 바깥 여백을 둔다. label의 크기·색 override로 frame이나 indicator를 재구현하지 않는다. 실제 공개 사용 예는 양쪽 RadioGroup Story의 `OptionActions`다.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
### 미선택·다중 선택의 칩 묶음
|
|
135
|
+
|
|
136
|
+
2026-10-19 오늘야구 연도 선택 이관에서 nullable 단일 선택과 드래프트 다중 선택을 앱이
|
|
137
|
+
Chip/직접 wrap으로 조립하고 있었다. `presentation="chips"`는 기존 선택 엔진·항목 타입·값 계약을
|
|
138
|
+
유지하고 Chip recipe의 모서리·색·타이포·크기를 사용한다. 기본 원/체크 indicator는 감추고(Native의 명시적 `renderIndicator`는 유지),
|
|
139
|
+
선택 색·굵기와 실제 radio/checkbox 의미를 유지한다. 그룹은 orientation과 관계없이 시작 쪽부터
|
|
140
|
+
줄바꿈하고 항목 간격은 `spacing.xs` 8이다. 설명·오류·비활성·읽기 전용 계약도 같다.
|
|
141
|
+
|
|
142
|
+
`size="small"`의 보이는 최소 높이는 Chip처럼36, medium은44이며 작은 칩의 터치 영역은44다.
|
|
143
|
+
필수 단일 보기 전환은 SegmentedControl, 선택 없음이 유효한 질문은 RadioGroup,
|
|
144
|
+
동시에 여러 조건을 고르면 CheckboxGroup을 사용한다. chips 모양 때문에 다른 선택 의미로 바꾸지 않는다.
|
|
145
|
+
기존 양쪽 RadioGroup의 `ChipChoices` 예제가 두 그룹과 미선택/다중 선택을 함께 실행한다.
|
|
146
|
+
|
|
147
|
+
연도/카테고리 창이 전체 목록의 일부만 보여주면 제품은 보이는 값만 그룹에 전달하고,
|
|
148
|
+
활성화된 항목의 도메인 변경을 전체 선택에 합쳐야 한다. 현재 보이지 않는 선택을 실제 목록 삭제로
|
|
149
|
+
오인해 지우지 않는다. 삭제·비활성 항목 pruning은 기존 그룹 계약을 그대로 따른다.
|
|
150
|
+
|
|
151
|
+
### 동일 폭의 선택 카드(미게시)
|
|
152
|
+
|
|
153
|
+
`grid`에 기존 Grid descriptor(`columns`, `gap`, `minColumnWidth`)를 전달하면 Web·Native 모두
|
|
154
|
+
선택 항목을 동일 폭으로 배치한다. 생략하면 기존 `orientation` 배치를 유지한다.
|
|
155
|
+
2026-10-20 다에리의 직접 카드/내부 CSS 이관이 계기이며, 선택 엔진은 바꾸지 않고 공통 Grid의 측정·열 축소를 합성한다.
|
|
156
|
+
예: `grid={{ columns: { compact: 2 }, gap: { compact: "xs" }, minColumnWidth: { compact: 152 } }}`.
|
|
157
|
+
그룹 안쪽을 측정해 두 열이 들어가지 않으면 한 열로 줄인다. 제품에서 항목 폭·grid CSS를 다시 계산하지 않는다.
|
|
158
|
+
|
|
159
|
+
### 같은 행의 카드 높이 (checkout 수정·미게시)
|
|
160
|
+
|
|
161
|
+
`presentation="card"` 항목은 같은 Grid 행에서 가장 높은 콘텐츠에 맞춰 칸을 채운다. Native의 Grid 칸 안에서 카드가 내용 높이만 사용하던 문제를 공통 ChoiceRow에서 수정했다(2026-10-20 다에리 공개 범위 선택 카드 지적). 제품에서 높이를 고정하거나 설명을 줄여 맞추지 않는다. 행별 높이는 해당 행의 콘텐츠로 결정한다.
|
|
162
|
+
|
|
163
|
+
### 그림 미리보기와 기본 선택 표시
|
|
164
|
+
|
|
165
|
+
2026-10-20 다에리 반응 카드 이관: Web도 Native와 같이 `indicator="none"`으로 기본 원형 표시를 숨길 수 있다(로컬·미게시). `renderLabel`의 비상호작용 그림/문구와 공통 카드의 선택 색으로 상태를 나타낼 때 사용한다. 실제 radio·그룹 이름·checked·키보드 선택·비활성 계약은 유지되며, 기본값 `default`는 기존 원형 표시를 유지한다. 제품에서 카드 테두리/눌림/선택 틀을 다시 만들지 않는다.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
- 상태: 배포
|
|
5
5
|
- 지원: Web · Native
|
|
6
6
|
- 적용: 1.12.1
|
|
7
|
-
- 검토일: 2026-10-
|
|
7
|
+
- 검토일: 2026-10-19
|
|
8
8
|
- 근거: `src/component-recipes.ts`(`selectionControlRecipe`). 별도 계약 문서는 없다
|
|
9
9
|
- 스토리북: `배포/컴포넌트/입력/라디오 버튼`
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ import { Radio } from "@hjmds/react-native/inputs";
|
|
|
62
62
|
| prop | 값 | 기본값 | 설명 |
|
|
63
63
|
| --- | --- | --- | --- |
|
|
64
64
|
| `label` | Web `ReactNode` · Native `string` | 필수 | 현지화 |
|
|
65
|
-
| `presentation` | `plain` · `card` · `grouped` | `card` | — |
|
|
65
|
+
| `presentation` | `plain` · `card` · `grouped` · `chips` | `card` | — |
|
|
66
66
|
| `size` | `small` · `medium` | `medium` | — |
|
|
67
67
|
| `checked` · `defaultChecked` | `boolean` | `false` | Web은 제어하면 `onCheckedChange` 필수 |
|
|
68
68
|
| `onCheckedChange` | `(checked: true) => void` | — | 라디오는 스스로 해제되지 않아 항상 `true`로만 불린다. 해제는 제품이 다른 항목을 선택해 `checked`를 내려서 한다 |
|
|
@@ -101,3 +101,9 @@ import { Radio } from "@hjmds/react-native/inputs";
|
|
|
101
101
|
| 앞 아이콘 | `renderLeading` | `leading`, `renderLeading` |
|
|
102
102
|
| 이벤트 | `onCheckedChange`, 원시 `onChange` | `onCheckedChange` |
|
|
103
103
|
| 배치 | `layoutStyle`(루트 `<label>`), `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#미선택다중-선택의-칩-묶음)).
|
|
@@ -67,6 +67,7 @@ import { TextField } from "@hjmds/react-native/inputs";
|
|
|
67
67
|
| `removeLabel` | `(id) => string` | 필수 | 행마다 다른 삭제 이름(대상 이름 포함) |
|
|
68
68
|
| `minFields` / `maxFields` | `number` | `0` / `Infinity` | 이하면 삭제, 이상이면 추가 비활성 |
|
|
69
69
|
| `onAdd` | `() => string \| undefined` | 필수 | 새 행 id를 돌려주면 그 행이 descriptor에 나타난 뒤 포커스 |
|
|
70
|
+
| `isRemoveDisabled` | `(id) => boolean` | `false` | 참조 중인 정산 참여자처럼 입력은 허용하고 삭제만 막을 때 사용. checkout 개발 API·미게시(2026-10-20 Utilverse 채택) |
|
|
70
71
|
| `onRemove` | `(id) => void` | 필수 | 제품 상태에서 행 제거 |
|
|
71
72
|
| `layout` · Web `className`·`layoutStyle` | FieldGroup 값 | FieldGroup 기본 | FieldGroup으로 전달 |
|
|
72
73
|
|
|
@@ -68,7 +68,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
68
68
|
|
|
69
69
|
| prop | 값 | 기본값 | 설명 |
|
|
70
70
|
| --- | --- | --- | --- |
|
|
71
|
-
| `keyboardAvoidance` (Native) | `boolean` | `true`(미게시) | 키보드와 실제로 겹치는 만큼 footer·본문을 올리고 포커스된 입력칸을 스크롤해 보인다. `KeyboardAvoiding`·키보드 처리 Sheet/Dialog 안에서는 건너뛴다 |
|
|
71
|
+
| `keyboardAvoidance` (Native) | `boolean` | `true`(미게시) | 키보드와 실제로 겹치는 만큼 footer·본문을 올리고 포커스된 입력칸을 스크롤해 보인다. Native TextField·TextArea·SearchField는 라벨·입력·도움말/오류를 포함한 필드 전체를 측정한다(Spint 작성 화면의 글자 수 안내가 고정 footer에 가린 2026-10-18 재현). `KeyboardAvoiding`·키보드 처리 Sheet/Dialog 안에서는 건너뛴다 |
|
|
72
72
|
| `title` | `string` | 필수 | 화면 제목. `header`를 주면 접근성 이름으로만 쓴다 |
|
|
73
73
|
| `titleVisibility` | `"visible"` · `"hidden"` | `"visible"` | 하단 탭·상단 탐색으로 이미 식별되는 최상위 화면명은 `hidden`. 대상·하위 작업 이름은 식별에 필요하고 본문과 중복되지 않을 때 유지. 기본값이나 뒤로가기 유무로 목적지를 추정하지 않으며 제품이 맥락에 맞게 선택한다. `title` 값은 유지한다 |
|
|
74
74
|
| `titleBadge` | `ReactNode` | 없음 | 기본 제목 바로 옆 배지. 값·의미는 제품, 간격·정렬은 HJM. `header` 교체 또는 제목 숨김이면 그리지 않는다. 1.23.0 추가 API |
|
|
@@ -89,6 +89,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
89
89
|
| `presentation` | `"dashboard"` · `"editorial"` · `"landscape"` | 가장 가까운 profile.screens.overview / 없으면 기존 골격 | [디자인 프로필](../../design-profile.md)의 제공 범위를 따른다. 명시값이 프로필을 이긴다. 헤더 배치·읽기 폭만 바꾸고 상태·입력·행동 슬롯은 유지한다 |
|
|
90
90
|
| `as` (Web) | `"main"` · `"section"` | `"main"` | 제품 셸에 이미 `<main>`이 있으면 `section` |
|
|
91
91
|
| `initialFocus` | `first-input` · `none` | `first-input` | 페이지 진입 시 입력 하나에 한 번 포커스한다. 기본값 변경은 1.33.0부터 제공. 하단 시트 안에서는 자동 포커스하지 않는다. 아래 [진입 포커스](#진입-포커스) |
|
|
92
|
+
| `pullToDismiss` (Native) | `boolean` | `false` | 미게시. 읽기 route 본문의 손잡이·본문 맨 위에서 새로 시작한 아래 당김을 기존 `exit.onPress`로 연결한다. `exit.kind="close"`, 기본 헤더, `scroll="screen"`이 필요하다. 손잡이는 보조기술에서 숨기고 헤더 닫기 하나를 유지한다. `exit.disabled`면 당김/손잡이/접근성 escape도 비활성이다. Web 읽기 패널은 Sheet의 `swipeDirection="down"`을 사용한다 |
|
|
92
93
|
| `safeAreaBottom` (Native) | 0 이상의 유한수 | `0`(미게시) | 화면이 닿는 창 아래 가장자리의 safe area(`useSafeAreaInsets().bottom`, 탭바 위·이미 아래를 들인 host 안이면 0). 마지막 영역(footer, 없으면 본문)이 아래 여백을 `max(16, inset)`으로 한 번만 준다. 넘길 때는 host의 아래 SafeAreaView edge를 빼고, 둘을 같이 쓰지 않는다. 음수·무한대는 `RangeError` |
|
|
93
94
|
| `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 화면 루트 배치 전용(예: 분할 화면의 `flex`·`width`). ScreenLayout 위에 만든 화면은 모두 이 prop을 루트까지 넘긴다 |
|
|
94
95
|
| `className` (Web) | `string` | 없음 | 식별·배치 보조. 색·여백을 덮지 않는다 |
|
|
@@ -107,16 +108,21 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
|
|
|
107
108
|
|
|
108
109
|
## 진입 포커스
|
|
109
110
|
|
|
111
|
+
2026-10-18 후속 사용자 요청("input이 상단에 있을때만"): 상단 검색·본문 입력에만 페이지 진입 포커스를 적용한다.
|
|
112
|
+
위치는 화면 슬롯 기준으로 구분한다. `ScreenLayout.footer`의 하단 고정 입력(채팅·댓글 작성창 포함)은
|
|
113
|
+
`initialFocusTarget`이나 중첩 `ScreenLayout`이 있어도 자동 포커스하지 않는다. 직접 눌러 입력하는 동작은 유지한다.
|
|
114
|
+
이 변경은 checkout 소스에 반영했으며 npm 게시 버전은 아직 지정하지 않았다.
|
|
115
|
+
|
|
110
116
|
2026-10-18 후속 사용자 지시: “페이지 진입에만 적용하고 하단 시트에는 폐지해 포커스”. 일반·대화·인증 페이지의 기존 `none`과 시트의 단일 입력 자동 포커스를 이 기준으로 교체한다. **1.33.0에서 제공한다.**
|
|
111
117
|
|
|
112
118
|
2026-10-18 사용자 요청("input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다"). 앱마다 `autoFocus`·지연 `ref.focus()`를 붙이던 것을 화면 골격이 맡는다. 입력 칸(TextField·TextArea·SearchField·PasswordField, 작성창의 TextArea)은 가장 가까운 화면·시트 범위에 자신을 등록하고, 범위가 진입 뒤 한 칸을 고른다. 규칙의 정본은 contracts `resolveInitialFocusTarget`·`resolveEntryFocus`·`initialFocusDefaults`(`screen-patterns`)다.
|
|
113
119
|
|
|
114
120
|
| 구성 | 기본값 | 이유 |
|
|
115
121
|
| --- | --- | --- |
|
|
116
|
-
| `ScreenLayout`(일반 화면) | `first-input` | 페이지 진입 시
|
|
122
|
+
| `ScreenLayout`(일반 화면) | `first-input` | 페이지 진입 시 상단·본문의 첫 유효 입력 선택. `footer`는 제외 |
|
|
117
123
|
| `SearchScreen` | `first-input`(검색어) | 검색이 화면의 목적 |
|
|
118
124
|
| `EditorScreen` | `first-input` | 작성·편집이 화면의 목적 |
|
|
119
|
-
| `ChatScreen`·`CommentThreadScreen` | `first-input` |
|
|
125
|
+
| `ChatScreen`·`CommentThreadScreen` | `first-input` | 하단 `composer`는 자동 포커스 제외. 본문의 상단 입력만 대상 |
|
|
120
126
|
| 하단 `Sheet` | 자동 입력 포커스 없음 | 입력 개수·`initialFocus`·중첩 화면·`initialFocusTarget`과 무관하게 자동 포커스하지 않음. Web은 모달 컨테이너에 초점 유지 |
|
|
121
127
|
| `Dialog`·`AlertDialog`·측면 `Sheet` | 기존 모달 규칙 | `single-input` 등 기존 모달 포커스 계약 유지 |
|
|
122
128
|
| `AuthScreenLayout` | `first-input` | 페이지 진입 시 텍스트 입력이 있으면 선택. 제공자 버튼만 있으면 입력 포커스 없음 |
|
|
@@ -190,3 +196,40 @@ dashboard는 가로 헤더/1200 목록 폭, editorial은 세로 헤더/720 읽
|
|
|
190
196
|
- 오버레이 안의 화면 진입 포커스: 2026-10-18 번뚝 검색 오버레이. Web은 화면을 감싼 제품 `aria-modal`을 화면 위의 모달로 보고 진입 포커스를 막았고, HJM Sheet 안에 둔 ScreenLayout은 Sheet의 첫 컨트롤(닫기)에 포커스를 빼앗겼다. Native는 오버레이 표시 전에 칸을 포커스해 무시될 수 있었다. 감싸는 모달은 막지 않고 오버레이 안의 화면은 오버레이와 함께 진입하도록 고쳤다(1.31.0 patch).
|
|
191
197
|
|
|
192
198
|
2026-10-18 사용자 결정: 빈 상태 문구는 화면·부분 영역 모두 `body`(14)로 표시한다. 오류·접근 제한의 제목은 기존 위계를 유지한다. 상단 보조 문구는 Spint 헤더 교체 중 커지고 강조된 문제를 계기로 표시를 제거했다. `state.description`과 본문의 안내는 유지한다.
|
|
199
|
+
|
|
200
|
+
### Native 읽기 화면 당겨 닫기 (미게시)
|
|
201
|
+
|
|
202
|
+
다에리 편지 상세가 ScreenLayout 밖에서 손잡이·Animated.View·PanResponder와 본문 상단 당김을
|
|
203
|
+
재조립하던 경로를 공통 골격으로 옮겼다. `pullToDismiss`를 켜도 경로 선택·봉투 역재생·중복 navigation은
|
|
204
|
+
제품의 기존 `exit.onPress`가 소유한다. ScreenLayout은 host가 준 본문 크기와 키보드·safe area·상태 배치를 유지한다. native route의 formSheet 안에서도 모달 형상을 다시 만들지 않는다.
|
|
205
|
+
입력/미저장 확인은 기존 EditorScreen 또는 제품 닫기 소유자에서 처리하며, 손잡이에 별도 닫기 콜백을 넣지 않는다.
|
|
206
|
+
|
|
207
|
+
`screenPatternRecipe.pullDismiss`는 수직 시작6pt, 닫기80pt 또는 최소32pt+속도0.65pt/ms,
|
|
208
|
+
최대 이동160pt·복귀140ms(동작 줄이기는0)·본문 상단 오차1pt를 소유한다. 중간에서 위로 돌아온 스크롤은
|
|
209
|
+
닫지 않고, 이미 위에 있던 상태에서 새로 시작해 -80pt 이상 당겼을 때만 닫는다. 소유 제스처가 취소되면
|
|
210
|
+
이동을 풀고 늦은 release는 무시한다. 본문 `scrollProps`의 시작/종료 콜백과 interactive 키보드는 함께 유지한다.
|
|
211
|
+
네이티브 route/modal의 제스처와 연결할 때 해당 host에서 실제 손잡이·본문 당김·닫기 왕복을 확인해야 한다.
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
// Native — route state and its close callback remain with the product.
|
|
215
|
+
<ScreenLayout title="편지" titleVisibility="hidden" pullToDismiss
|
|
216
|
+
exit={{ kind: "close", label: "닫기", onPress: closeLetter }}
|
|
217
|
+
safeAreaBottom={insets.bottom}
|
|
218
|
+
scrollProps={{ keyboardDismissMode: "interactive", automaticallyAdjustKeyboardInsets: true }}>
|
|
219
|
+
{letterContent}
|
|
220
|
+
</ScreenLayout>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
양쪽 화면 골격을 별도로 복제하지 않는다. Native Showcase `PullToDismiss`는 전체 화면에서 다시 열 수 있는
|
|
224
|
+
실행 예제이며 Web은 기존 Sheet ReadingPanel 예제를 참조한다.
|
|
225
|
+
|
|
226
|
+
### Native에서 콘텐츠 위에 띄우는 상단 바 (checkout 변경·미게시)
|
|
227
|
+
|
|
228
|
+
`headerPosition="overlay"`와 custom `header`를 사용하면 공통 host가 헤더를 콘텐츠 위에 띄우고 실제 측정 높이를 본문 시작 padding에 반영한다.
|
|
229
|
+
배경 장면은 스크롤 영역 전체를 채워 헤더 뒤로 이어지며, 본문 첫 요소는 헤더를 피해서 시작한다. 기본값 `flow`는 기존 배치다.
|
|
230
|
+
제품에서 absolute 좌표나 고정 높이 보정으로 복제하지 않는다. Web은 기존 페이지/브라우저의 스크롤 host를 사용하며 이 Native host prop을 제공하지 않는다.
|
|
231
|
+
다에리의 고정 색 띠가 스크롤 중 다시 끊기는 2026-10-20 사용자 지적과 React Navigation의 transparent overlay/header height 연결 방식을 근거로 한다.
|
|
232
|
+
|
|
233
|
+
Native의 제목/도구 헤더가 없는 edge-to-edge 화면은 `safeAreaTop`에 시스템 inset을 전달한다. 공통 host가 본문 시작만 그만큼 보호하고 스크롤 배경은 상태바 뒤까지 이어진다. 헤더가 있으면 그 헤더의 측정/inset 경로를 사용해 중복 적용하지 않는다(checkout 변경·미게시, 2026-10-20 다에리 작성 화면).
|
|
234
|
+
|
|
235
|
+
`scrollEdgeEffect`는 Native 상단 경계의 장식 재질 슬롯이다. 공통 host가 실측 헤더(헤더가 없으면 safeAreaTop)와 기본 여백만큼 프레임을 제공하고 입력/접근성 대상에서 제외한다. 제품은 브랜드 배경의 gradient/재질 노드만 연결한다. 고정 색 띠가 아니라 내용 뒤로 사라지는 가독성 경계를 만들 때 사용한다(checkout 변경·미게시).
|