@hjmds/design-contracts 1.28.0 → 1.28.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.
Files changed (35) hide show
  1. package/dist/auth-screen.d.ts +6 -0
  2. package/dist/auth-screen.d.ts.map +1 -1
  3. package/dist/auth-screen.js +4 -1
  4. package/dist/auth-screen.js.map +1 -1
  5. package/dist/component-recipes.d.ts.map +1 -1
  6. package/dist/component-recipes.js +5 -2
  7. package/dist/component-recipes.js.map +1 -1
  8. package/dist/screen-patterns.d.ts +36 -0
  9. package/dist/screen-patterns.d.ts.map +1 -1
  10. package/dist/screen-patterns.js +20 -0
  11. package/dist/screen-patterns.js.map +1 -1
  12. package/dist/version.d.ts +1 -1
  13. package/dist/version.js +1 -1
  14. package/dist/version.js.map +1 -1
  15. package/docs/generated/component-maturity.md +1 -1
  16. package/docs/generated/renderer-evidence.json +3 -3
  17. package/docs/generated/renderer-evidence.md +1 -1
  18. package/docs/generated/showcase-manifest.json +1 -1
  19. package/docs/usage/SELECTION.md +1 -0
  20. package/docs/usage/components/auth-screen-layout.md +21 -5
  21. package/docs/usage/components/chat-screen.md +1 -0
  22. package/docs/usage/components/comment-thread-screen.md +2 -0
  23. package/docs/usage/components/dialog.md +1 -0
  24. package/docs/usage/components/editor-screen.md +1 -0
  25. package/docs/usage/components/field.md +1 -0
  26. package/docs/usage/components/list-detail-screen.md +1 -0
  27. package/docs/usage/components/list.md +20 -0
  28. package/docs/usage/components/onboarding-screen.md +1 -0
  29. package/docs/usage/components/screen-layout.md +21 -0
  30. package/docs/usage/components/search-field.md +1 -0
  31. package/docs/usage/components/search-screen.md +3 -1
  32. package/docs/usage/components/sheet.md +1 -0
  33. package/docs/usage/components/skeleton.md +2 -1
  34. package/docs/usage/components/text-area.md +1 -0
  35. package/package.json +1 -1
@@ -85,6 +85,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
85
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
+ | `initialFocus` | `first-input` · `none` | `none` | 1.28.1. 진입 시 텍스트 입력 하나에 한 번 포커스한다. 아래 [진입 포커스](#진입-포커스) |
88
89
  | `safeAreaBottom` (Native) | 0 이상의 유한수 | `0`(미게시) | 화면이 닿는 창 아래 가장자리의 safe area(`useSafeAreaInsets().bottom`, 탭바 위·이미 아래를 들인 host 안이면 0). 마지막 영역(footer, 없으면 본문)이 아래 여백을 `max(16, inset)`으로 한 번만 준다. 넘길 때는 host의 아래 SafeAreaView edge를 빼고, 둘을 같이 쓰지 않는다. 음수·무한대는 `RangeError` |
89
90
  | `layoutStyle` | `HjmCompositionStyleProp` | 없음 | 화면 루트 배치 전용(예: 분할 화면의 `flex`·`width`). ScreenLayout 위에 만든 화면은 모두 이 prop을 루트까지 넘긴다 |
90
91
  | `className` (Web) | `string` | 없음 | 식별·배치 보조. 색·여백을 덮지 않는다 |
@@ -101,6 +102,25 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
101
102
  | 고정·스크롤 | 헤더·notice·footer 고정, `scroll="screen"`은 본문이 스크롤, `"content"`는 자식이 스크롤; footer 위 테두리 1. 마지막 영역(footer, 없으면 본문)의 아래 여백은 `max(16, 하단 inset)`이다 — Web은 `env(safe-area-inset-bottom)`, Native는 `safeAreaBottom`. 16 + inset으로 더하지 않으며, 키보드가 열려 있으면 키보드가 홈 인디케이터를 덮으므로 16만 둔다 | Web `data-scroll`·`.hjm-screen__footer`, Native `ScrollView` |
102
103
  | 좁은 폭·큰 글자 | 제목 열 최소 폭 `headerMinWidth` 120 × 글자 배율, 모자라면 `actions`가 다음 줄로 내려간다; 제목은 줄바꿈(`overflow-wrap: anywhere`)되고 자르지 않는다; Native 위·옆 safe area·탭바 inset은 host, 아래 inset은 `safeAreaBottom` | `screenPatternRecipe.headerMinWidth`, Web `.hjm-screen__heading` |
103
104
 
105
+ ## 진입 포커스
106
+
107
+ 2026-10-18 사용자 요청("input이 있는 화면에 진입하면 포커스되도록, 웹앱 둘 다"). 앱마다 `autoFocus`·지연 `ref.focus()`를 붙이던 것을 화면 골격이 맡는다. 입력 칸(TextField·TextArea·SearchField·PasswordField, 작성창의 TextArea)은 가장 가까운 화면·시트 범위에 자신을 등록하고, 범위가 진입 뒤 한 칸을 고른다. 규칙의 정본은 contracts `resolveInitialFocusTarget`·`initialFocusDefaults`(`screen-patterns`)다.
108
+
109
+ | 구성 | 기본값 | 이유 |
110
+ | --- | --- | --- |
111
+ | `ScreenLayout`(일반 화면) | `none` | 입력이 화면의 주 행동인지 화면이 알 수 없다. 주 입력 화면은 `initialFocus="first-input"` |
112
+ | `SearchScreen` | `first-input`(검색어) | 검색이 화면의 목적 |
113
+ | `EditorScreen` | `first-input` | 작성·편집이 화면의 목적 |
114
+ | `ChatScreen`·`CommentThreadScreen` | `none` | 입력은 읽기의 보조. 키보드가 기록을 가린다. 같은 prop으로 켤 수 있다 |
115
+ | `Sheet`·`Dialog`·`AlertDialog` | `single-input` | 텍스트 입력이 하나뿐일 때만(이름 바꾸기 등). 여럿이면 기존 포커스 규칙 |
116
+
117
+ - 고르는 순서: `initialFocusTarget` 칸 → 등록(트리) 순서 첫 칸. 비활성·읽기 전용 칸, `initialFocusTarget={false}` 칸은 고르지 않는다.
118
+ - 시점: Native는 화면·시트 진입 전환이 끝난 뒤(`InteractionManager`, 시트는 표시 뒤) — 키보드 애니메이션이 전환과 겹치지 않는다. Web은 mount 직후, `preventScroll`.
119
+ - 빼앗지 않는다: 사용자가 이미 입력 칸에 있으면 그대로 둔다(Native 포커스된 입력, Web 편집 가능한 요소). Web은 열린 모달(`aria-modal`) 밑의 화면 칸을 고르지 않는다.
120
+ - 다시 진입: 화면은 mount당 한 번이다. 스택에서 돌아온 화면(이미 mount)은 다시 포커스하지 않는다 — 돌아온 맥락을 키보드가 가리고, HJM은 내비게이션 포커스 신호를 모른다. 시트·대화상자는 열 때마다 다시 고른다.
121
+ - 모션 감소는 애니메이션에만 적용하고 포커스는 그대로 한다.
122
+ - 제품은 `autoFocus`·`setTimeout(() => ref.focus())`를 쓰지 않고 `initialFocus`·`initialFocusTarget`으로 표현한다.
123
+
104
124
  ## 꼭 지킬 것
105
125
 
106
126
  - **헤더 제목은 기본 가운데, 추가 기능 버튼은 오른쪽 위.** `headerAlignment` 기본값은 `"center"`이고 `"start"`는 큰 제목이 필요한 화면에서만 고른다(2026-10-17 사용자 지시: 가운데가 기본, 강제는 아님).
@@ -115,6 +135,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
115
135
  - 새로고침 실패·저장 오류는 `state`를 바꾸지 말고 `ready` + `notice`로 알린다. `loading`으로 바꾸면 본문이
116
136
  unmount되어 입력 초안이 사라진다.
117
137
  - `state.title`·문구는 i18n 키로 넣는다. ready 외 상태의 빈 `title`은 `TypeError`다.
138
+ - **`safeAreaBottom`을 받는 화면 구성(Native, 1.28.1 감사):** ScreenLayout을 그리는 공개 구성은 모두 같은 prop을 받아 자기 ScreenLayout에 넘긴다 — `SettingsScreen`·`NotificationInboxScreen`·`ChatScreen`·`OverviewScreen`·`EditorScreen`·`ProfileScreen`·`ModerationScreen`·`MediaSelectionScreen`·`PermissionScreen`·`CommentThreadScreen`·`SearchScreen`(+`footer`)·`SavedItemsScreen`(ListDetailScreen 경유)·`ListDetailScreen`(목록·상세 pane 둘 다)·`OnboardingScreen`. 로그인은 `AuthScreenLayout` `safeAreaBottom`(같은 max 규칙, 기준은 paddingBlock). 하단 막대를 직접 그리는 `BottomCTA`·`BottomNavigation`은 각자 `safeAreaBottom`, FAB는 `safeAreaBottomInset`을 받는다. `Layout`(primitives)의 `footer`는 배치 슬롯일 뿐 inset을 정하지 않는다.
118
139
  - Web host는 실제 남은 높이(`height:100%` 등)를, Native host는 위·옆 safe area와 탭바 inset을 먼저 처리한다. 아래 홈 인디케이터는 host가 SafeAreaView로 들이지 말고 `safeAreaBottom`으로 넘긴다(들인 뒤 footer 16을 더하면 버튼 아래가 50이 된다).
119
140
  HJM은 기종별 높이를 추측하지 않는다.
120
141
  - 배치는 Web·Native 모두 `layoutStyle`로 한다(Web은 `className`도 있다). 색·여백을 덮지 않는다.
@@ -62,6 +62,7 @@ import { SearchField } from "@hjmds/react-native/inputs";
62
62
 
63
63
  | prop | 값 | 기본값 | 설명 |
64
64
  | --- | --- | --- | --- |
65
+ | `initialFocusTarget` | `boolean` | — | 1.28.1. 둘러싼 화면·시트가 진입 포커스를 켰을 때 `true`면 이 칸을 고르고, `false`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 앱별 `autoFocus`·지연 `ref.focus()` 대신 쓴다([진입 포커스](screen-layout.md#진입-포커스)). TextField·TextArea·SearchField·PasswordField 공통 |
65
66
  | `size` | `medium` · `large` | `medium` | 최소 높이 44 · 52 |
66
67
  | `variant` | `surface` · `inset` | — | — |
67
68
  | `shape` | `medium` · `large` · `full` | `medium` | — |
@@ -162,7 +162,9 @@ import { SearchScreen } from "@hjmds/react-native/screen-flows";
162
162
  | `hostGutter` | `none` · `compact` · `regular` · `spacious` | `none` | 1.13.1 변경. 이 화면을 감싼 host가 이미 준 좌우 여백 이름(`containerRecipe.gutters`: 0 · `spacing.md` 16 · `spacing.lg` 20 · `spacing.xl` 24). Container 안이면 그 `gutter`, Sheet 본문 안이면 `regular`(`sheetRecipe.content.paddingHorizontal`과 같은 `spacing.lg`). `scroll` 줄만 이 값을 쓴다 |
163
163
  | `recentSearches` | `ReactNode` | 없음 | `query`가 공백일 때만 본문 맨 위에 보인다 |
164
164
  | `children` | `ReactNode` | 필수 | 결과 |
165
- | 나머지 | `ScreenLayout`과 같음(`children`·`footer` 제외) | — | `notice`는 필터 아래에 붙는다 |
165
+ | `initialFocus` | `first-input` · `none` | `first-input` | 1.28.1. 진입 후 검색어 입력에 한 번 포커스한다(검색 화면의 주 입력). 결과로 돌아온 화면(이미 mount)은 다시 포커스하지 않는다 |
166
+ | `footer` | `ReactNode` | 없음 | ScreenLayout footer 그대로(1.28.1): 결과 아래 고정 영역(주 행동·광고 막대). 위 경계·여백·아래 inset을 footer가 맡으므로 화면 밖에 막대를 따로 두고 inset을 직접 주지 않는다(unairplane 항공편 조회, 2026-10-17) |
167
+ | 나머지 | `ScreenLayout`과 같음(`children` 제외, Native `safeAreaBottom` 포함) | — | `notice`는 필터 아래에 붙는다 |
166
168
 
167
169
  ## 배치
168
170
 
@@ -78,6 +78,7 @@ import { Sheet } from "@hjmds/react-native/overlays";
78
78
 
79
79
  | prop | 값 | 기본값 | 설명 |
80
80
  | --- | --- | --- | --- |
81
+ | `initialFocus` | `single-input` · `first-input` · `none` | `single-input` | 1.28.1. 열린 뒤(Native: 표시·진입 애니메이션 뒤, Web: 포커스 이동 시점) 포커스할 텍스트 입력. 기본은 텍스트 입력이 **하나뿐일 때만** 그 칸(이름 바꾸기·메모 한 줄), 여럿이면 기존 규칙(Web 첫 포커스 가능 요소, Native 접근성 포커스) 그대로. `first-input`은 `initialFocusTarget` 칸, 없으면 첫 칸. `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다 |
81
82
  | `placement` | `bottom` · `start` · `end` | `bottom` | 나오는 가장자리 |
82
83
  | `size` | `auto` · `medium`(60%) · `large`(85%) · `full`(100%) | `auto` | `auto`는 내용 높이. 비율은 화면 높이 기준(Web `dvh`), 두 플랫폼 모두 위 safe area(Native는 키보드도)를 뺀 남은 높이를 넘지 않는다. `maxHeightRatio` 0.9 상한은 `auto`에만 걸린다 |
83
84
  | `open` · `defaultOpen` | `boolean` | 비제어 `false` | Web은 제어하면 `onOpenChange` 필수, 비제어면 `trigger` 필수 |
@@ -67,7 +67,8 @@ import { Skeleton } from "@hjmds/react-native/feedback";
67
67
 
68
68
  | prop | 값 | 기본값 | 설명 |
69
69
  | --- | --- | --- | --- |
70
- | `shape` | `block`(높이 `spacing.xxl` 32·radius `md`) · `text`(`spacing.md` 16·`sm`) · `circle`(`glyph.xxl` 44 지름·`full`) · `media`(폭 100%·16:9·`md`) · `button`(`control.buttonHeight.medium` 44·`md`) | `block` | 모양과 기본 크기. `media`·`button`은 2026-10-17 추가 |
70
+ | `shape` | `block`(높이 `spacing.xxl` 32·radius `md`) · `text`(`spacing.md` 16·`sm`) · `circle`(`glyph.xxl` 44 지름·`full`) · `media`(폭 100%·16:9·`md`) · `button`(`control.buttonHeight.medium` 44·Button 기본 모양의 모서리 `md`, `buttonShape`로 바꿈) | `block` | 모양과 기본 크기. `media`·`button`은 2026-10-17 추가 |
71
+ | `buttonShape` | `rounded` · `pill`(Button `shape`와 같은 축) | Button 기본값(`rounded`) | `button` 전용. 모서리를 `buttonRecipe.shapes`(rounded `md` · pill `full`)와 테마 radius에서 가져온다. 알약 버튼을 쓰는 제품(번뚝)은 버튼에 준 `shape="pill"`을 그대로 넘긴다(1.28.1) |
71
72
  | `lines` | 양의 정수 | `1` | `text` 전용. 줄 수만큼 그리고 마지막 줄은 60%(`skeletonRecipe.lines.lastLineWidthPercent`), 줄 간격 `spacing.xs` 8. 펄스는 묶음 하나로 돈다. `width`는 묶음 전체 폭 |
72
73
  | `animated` | `boolean` | `true`(펄스) | 0.9.12까지는 `false`였다. 정지 블록이 필요하면 `animated={false}`를 명시한다. reduced motion이면 두 renderer 모두 펄스를 멈추고 불투명한 끝 값으로 그린다(recipe `reducedMotion: "static"`) |
73
74
  | `width` | Web `string \| number` · Native `ViewStyle["width"]` | 폭 100%(circle은 지름) | — |
@@ -62,6 +62,7 @@ import { TextArea } from "@hjmds/react-native/inputs";
62
62
 
63
63
  | prop | 값 | 기본값 | 설명 |
64
64
  | --- | --- | --- | --- |
65
+ | `initialFocusTarget` | `boolean` | — | 1.28.1. 둘러싼 화면·시트가 진입 포커스를 켰을 때 `true`면 이 칸을 고르고, `false`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 앱별 `autoFocus`·지연 `ref.focus()` 대신 쓴다([진입 포커스](screen-layout.md#진입-포커스)). TextField·TextArea·SearchField·PasswordField 공통 |
65
66
  | `variant` | `surface` · `inset` | `surface` | — |
66
67
  | `shape` | `medium` · `large` · `full` | `medium` | — |
67
68
  | `align` | `start` · `center` | `start` | — |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.28.0",
3
+ "version": "1.28.1",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {