@hjmds/design-contracts 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) 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/catalog.d.ts +308 -0
  6. package/dist/catalog.d.ts.map +1 -1
  7. package/dist/catalog.js +42 -0
  8. package/dist/catalog.js.map +1 -1
  9. package/dist/component-definitions.d.ts +22 -0
  10. package/dist/component-definitions.d.ts.map +1 -1
  11. package/dist/component-definitions.js +23 -0
  12. package/dist/component-definitions.js.map +1 -1
  13. package/dist/component-recipes.d.ts.map +1 -1
  14. package/dist/component-recipes.js +5 -2
  15. package/dist/component-recipes.js.map +1 -1
  16. package/dist/screen-patterns.d.ts +94 -1
  17. package/dist/screen-patterns.d.ts.map +1 -1
  18. package/dist/screen-patterns.js +51 -0
  19. package/dist/screen-patterns.js.map +1 -1
  20. package/dist/version.d.ts +1 -1
  21. package/dist/version.js +1 -1
  22. package/dist/version.js.map +1 -1
  23. package/docs/generated/component-maturity.md +26 -4
  24. package/docs/generated/renderer-evidence.json +3229 -1051
  25. package/docs/generated/renderer-evidence.md +47 -3
  26. package/docs/generated/showcase-manifest.json +1057 -1
  27. package/docs/screen-patterns.md +8 -1
  28. package/docs/usage/SELECTION.md +1 -0
  29. package/docs/usage/components/auth-screen-layout.md +22 -5
  30. package/docs/usage/components/chat-screen.md +1 -0
  31. package/docs/usage/components/comment-thread-screen.md +2 -0
  32. package/docs/usage/components/dialog.md +1 -0
  33. package/docs/usage/components/editor-screen.md +1 -0
  34. package/docs/usage/components/field.md +1 -0
  35. package/docs/usage/components/list-detail-screen.md +1 -0
  36. package/docs/usage/components/list.md +20 -0
  37. package/docs/usage/components/onboarding-screen.md +1 -0
  38. package/docs/usage/components/screen-layout.md +25 -0
  39. package/docs/usage/components/search-field.md +1 -0
  40. package/docs/usage/components/search-screen.md +3 -1
  41. package/docs/usage/components/sheet.md +1 -0
  42. package/docs/usage/components/skeleton.md +2 -1
  43. package/docs/usage/components/text-area.md +1 -0
  44. package/package.json +1 -1
@@ -5,7 +5,14 @@
5
5
  사용자가 로그인·설정·알림·채팅 화면을 미리 일반화해 개발 생산성을 높이도록 요청했다.
6
6
  기존 AuthScreenLayout, Section, ListRow, TopBar, Layout, EmptyState, action-session,
7
7
  NotificationBell과 두 renderer의 exports를 비교했다. 새 primitive catalog를 늘리는 대신
8
- `@hjmds/react/screens`, `@hjmds/react-native/screens`의 supplemental 화면 조합으로 제공한다.
8
+ `@hjmds/react/screens`, `@hjmds/react-native/screens`의 화면 조합으로 제공한다.
9
+
10
+ 2026-10-17 카탈로그 등록: 처음에는 동결된 catalog를 늘리지 않으려고 supplemental로 분류했으나, 소비 앱 검사기가 canonical id로만
11
+ 심볼을 해석해 여섯 제품의 미해석 finding 690건 중 606건(88%)이 이 화면 조합이었다(app-portfolio
12
+ `docs/design/STANDARD_SCOPE_REVIEW_2026-10.md` §1.2). 같은 날 동결이 종료돼([승격 기준](stable-promotion.md)) 22종을 canonical
13
+ 행으로 등록했다. 양 surface `beta`이며 renderer 증거의 누락 시나리오(`webScenarioGaps`·`nativeScenarioGaps`)와 Native
14
+ `OnboardingScreen` claim 부재(마운트 시 `ScrollView.scrollTo` 호출이 테스트 host에 없음)는 승격 부채다. 소비 앱은 `optionalBetaAdoptions`로
15
+ 채택을 기록한다.
9
16
 
10
17
  ## 현재 제품에서 확인한 반복과 차이
11
18
 
@@ -46,6 +46,7 @@
46
46
  | 화면 나가기(뒤로·닫기·설정 나가기) | `ScreenLayout`(과 이를 쓰는 화면)의 `exit` — 헤더 시작 쪽 아이콘 버튼, 글자 없음 | 본문·하단의 "나가기" 글자 버튼, 화면마다 다른 화살표 |
47
47
  | 버튼 묶음 간격 | 세로로 쌓으면 `gap="xs"`(8), 가로로 나란하면 `gap="sm"`(12) — `actionGroupRecipe` | 세로 버튼 사이 12 이상 |
48
48
  | 간격 | `Stack`·`ScreenLayout`·recipe 간격 | 임의 `margin`·빈 `View` |
49
+ | 화면 진입 시 입력 포커스 | `ScreenLayout`(과 이를 쓰는 화면)·`AuthScreenLayout`·`Sheet`·`Dialog`의 `initialFocus`, 주 입력 지정은 칸의 `initialFocusTarget`([진입 포커스](components/screen-layout.md#진입-포커스)) | 앱별 `autoFocus`·지연 `ref.focus()`·단계 전환·검색 열기 뒤 `ref.focus()`, 대화·댓글 화면의 작성창 자동 포커스 |
49
50
 
50
51
  리뷰 기준: 위 "쓰지 않을 것" 패턴이 보이면 예제 조립 문제인지 컴포넌트 recipe 문제인지 먼저 가리고, 원인 쪽(예제 또는 HJM 컴포넌트)을 고친다.
51
52
 
@@ -3,7 +3,7 @@
3
3
  - 단계: 컴포넌트
4
4
  - 상태: 배포
5
5
  - 지원: Web · Native
6
- - 적용: 1.12.1
6
+ - 적용: 1.12.1 (`safeAreaBottom`·링크 `target`/`rel`/`accessibilityHint` 1.28.1)
7
7
  - 검토일: 2026-10-17
8
8
  - 근거: [로그인 화면 골격](../../auth-screen.md), [1.4 제품 채택 가이드](../../product-adoption-1.4.md), recipe `authScreenRecipe`(`src/auth-screen.ts`), 포트폴리오 상위 기준 루트 `docs/LOGIN_SCREEN_STANDARD.md`(LS)
9
9
  - 스토리북: `배포/컴포넌트/레이아웃/로그인 화면`
@@ -28,7 +28,7 @@
28
28
  | --- | --- | --- | --- |
29
29
  | `AuthScreenLayout` | 기본(배치) | `@hjmds/react`, `/auth-screen` | `@hjmds/react-native`, `/auth-screen` |
30
30
  | `AuthScreenHero` | `hero` 슬롯: 마크·eyebrow·태그라인·설명 | 같음 | 같음 |
31
- | `AuthScreenFooter` | `footer` 슬롯: 동의 고지·정책 링크(·구분) | 같음, 링크 `href` 또는 `onClick` | 같음, 링크 `onPress` |
31
+ | `AuthScreenFooter` | `footer` 슬롯: 동의 고지·정책 링크(·구분) | 같음, 링크 `href`(+`target`·`rel`) 또는 `onClick` | 같음, 링크 `onPress` |
32
32
 
33
33
  ## 최소 사용 예
34
34
 
@@ -41,7 +41,11 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
41
41
  {...(pending ? { pendingLabel: t("auth.pending") } : {})}
42
42
  hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
43
43
  main={<ProviderButtons />} // pending이어도 같은 목록
44
- footer={<AuthScreenFooter notice={t("auth.consent")} links={[{ label: t("legal.privacy"), href: privacyUrl }, { label: t("legal.terms"), href: termsUrl }]} />}
44
+ footer={<AuthScreenFooter notice={t("auth.consent")} links={[
45
+ // 새 탭에서 여는 문서: target만 주면 rel은 "noreferrer noopener"가 기본이다.
46
+ { label: t("legal.privacy"), href: privacyUrl, target: "_blank", accessibilityHint: t("legal.opensNewTab") },
47
+ { label: t("legal.terms"), href: termsUrl, target: "_blank", accessibilityHint: t("legal.opensNewTab") },
48
+ ]} />}
45
49
  />
46
50
  ```
47
51
 
@@ -54,7 +58,11 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
54
58
  {...(pending ? { pendingLabel: t("auth.pending") } : {})}
55
59
  hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
56
60
  main={<ProviderButtons />}
57
- footer={<AuthScreenFooter links={[{ label: t("legal.privacy"), onPress: openPrivacy }, { label: t("legal.terms"), onPress: openTerms }]} />}
61
+ footer={<AuthScreenFooter links={[
62
+ { label: t("legal.privacy"), onPress: openPrivacy, accessibilityHint: t("legal.opensInBrowser") },
63
+ { label: t("legal.terms"), onPress: openTerms, accessibilityHint: t("legal.opensInBrowser") },
64
+ ]} />}
65
+ safeAreaBottom={useSafeAreaInsets().bottom} // 바깥 SafeAreaView bottom·paddingBottom 래퍼 대신
58
66
  testID="login-screen"
59
67
  />
60
68
  ```
@@ -71,9 +79,13 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
71
79
  | `pendingLabel` | 문자열 | — | 주면 진행 상태다. 공백만 있으면 `TypeError`. 제거하면 원래 상태로 돌아온다 |
72
80
  | Web `as` | `main` · `section` | `main` | 최대 폭은 416이다 |
73
81
  | `layoutStyle` | margin·width·flex·`alignSelf` | — | 바깥 배치 전용(Web·Native 모두) |
82
+ | `initialFocus` | `first-input` · `none` | `none` | 1.28.2. [ScreenLayout 진입 포커스](screen-layout.md#진입-포커스)와 같은 규칙. 로그인은 제공자 버튼이 주 행동이라 기본 `none`, 가입 닉네임 단계처럼 입력이 주 행동인 `main`만 `first-input`. 닉네임 단계가 같은 화면에서 나중에 나타나면 그 칸에 `initialFocusTarget`을 준다 |
74
83
  | Native `testID` | `string` | — | 뿌리 `ScrollView` 식별 |
84
+ | Native `safeAreaBottom` | 0 이상의 유한수 | `0` | 화면이 닿는 창 아래 가장자리의 safe area(`useSafeAreaInsets().bottom`, 이미 아래를 들인 host 안이면 0). 아래 여백이 `max(paddingBlock, inset)` — `regular` max(40, inset), `compact` max(20, inset) — 으로 한 번만 들어간다(ScreenLayout과 같은 규칙, 합산 없음). 키보드가 열리면 paddingBlock만. 음수·무한대는 `RangeError` |
75
85
  | `AuthScreenHero` `title` · `eyebrow` · `description` · `mark` · `headingLevel` | 문자열 · `ReactNode` · `1`~`3` | `headingLevel` 1 | 제목은 필수이고 빈 문자열이면 `TypeError`. 밀도는 바깥 `AuthScreenLayout`을 따른다 |
76
86
  | `AuthScreenFooter` `links` · `notice` · `children` | 링크 배열(1개 이상) · `ReactNode` | — | 빈 링크 목록·빈 라벨은 `TypeError`(LS-04: 약관 링크는 로그인 전 같은 화면에 있어야 한다). `children`은 링크 아래 제품 내용 |
87
+ | 링크 `accessibilityHint` | 문자열 | — | 라벨 뒤에 읽히는 보조 안내(예: 제품이 번역한 "브라우저에서 열림"). Native `accessibilityHint`, Web 숨긴 설명(`aria-describedby`). 보이는 라벨은 그대로 링크 이름이다. 공백만 있으면 `TypeError` |
88
+ | Web 링크 `target` · `rel` | anchor 속성 | — | `href`가 있을 때만. `target="_blank"`이면 `rel` 기본값 `noreferrer noopener`(Link와 같음) |
77
89
 
78
90
  ## 배치
79
91
 
@@ -84,7 +96,7 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
84
96
  | 히어로 문구 | 마크 ↔ 문구 묶음은 hero 간격. eyebrow `label` 12/18·500·브랜드색, 아래 `spacing.xxs` 4 → 태그라인 `regular` `typography.heading` 24/32 · `compact` `titleLarge` 20/28, 굵기는 제목 단계 500, 기본 글자색, 실제 heading 요소(Native `header`) → 설명 `body` 14/20·400·보조색(`muted`), 위 `spacing.xs` 8 | `authScreenRecipe.hero`, `densities.*.titleVariant` |
85
97
  | 동의 고지·정책 링크 | 고지 `caption` 11/16 흐린 색, 링크 줄과 `spacing.xxs` 4. 링크 `caption`·브랜드색·밑줄, 링크 사이 `linkGap` 8 + `·` + 8(보이는 글자 간격 약 19). 각 링크 높이 44(배경 없음), 가로 터치 폭은 구분점 앞까지 보이지 않게 넓힘(Web 가상 요소, Native `hitSlop`) | `authScreenRecipe.footer`, `.hjm-auth-screen__link` |
86
98
  | 순서·정렬 | 위→아래 hero(가운데 정렬 문구) → main(폭 꽉 채움) → footer(동의 고지·정책 링크). 위 블록은 남는 세로 공간에서 가운데, footer는 바닥에 붙는다. 진행 중 로딩은 main 카드 가운데에 겹친다 | `.hjm-auth-screen__block`(`justify-content: center`), `.hjm-auth-screen__pending` |
87
- | 고정·스크롤 | 고정 영역이 없다. 넘치면 화면 전체가 스크롤한다. 안전 영역은 Native `contentInsetAdjustmentBehavior="automatic"`과 키보드 inset 자동 조정이 처리하고, Web은 safe-area inset을 더하지 않는다 | `react-native/src/auth-screen.tsx`, `.hjm-auth-screen` |
99
+ | 고정·스크롤 | 고정 영역이 없다. 넘치면 화면 전체가 스크롤한다. 위 safe area는 host(TopBar·SafeAreaView top)가, 아래는 레이아웃이 `max(paddingBlock, 하단 inset)`으로 맡는다 — Web `env(safe-area-inset-bottom)`, Native `safeAreaBottom`. Native 키보드 inset은 자동 조정이 처리한다 | `react-native/src/auth-screen.tsx`, `.hjm-auth-screen` |
88
100
  | 좁은 폭·큰 글자 | 키 작은 화면·큰 글자에서는 `compact`를 고른다. 그래도 넘치면 스크롤되며 footer는 맨 아래로 밀린다 | `authScreenRecipe.densities` |
89
101
 
90
102
  ```text
@@ -128,9 +140,13 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
128
140
  | 배치·식별 | `layoutStyle`, `className`, HTML 속성 | `layoutStyle`, `testID` |
129
141
  | 진행 중 숨김 | `inert` + `aria-hidden` + `visibility: hidden`, 로딩은 `role="status"` | 터치·접근성 제외 + 불투명도 0, 로딩은 `progressbar` + live region |
130
142
  | 키보드 | 브라우저 기본 | 키보드 inset 자동 조정, 탭 유지(`handled`), iOS `interactive`·Android `on-drag` 내리기 |
143
+ | 아래 안전 영역 | `env(safe-area-inset-bottom)` 자동 | `safeAreaBottom` prop(iOS 자동 content inset 조정은 쓰지 않는다) |
144
+ | 정책 링크 보조 안내 | `aria-describedby` 숨긴 텍스트, `target`·`rel` | `accessibilityHint` |
131
145
 
132
146
  ## 함정
133
147
 
148
+ - Native에서 아래 홈 인디케이터를 `SafeAreaView edges={['bottom']}`·`paddingBottom: insets.bottom` 래퍼로 들이지 말고 `safeAreaBottom`으로 넘긴다. 둘을 같이 주면 inset + paddingBlock이 다시 더해진다.
149
+ - 1.28.0까지 Native는 iOS `contentInsetAdjustmentBehavior="automatic"`으로 위·아래 safe area를 iOS에서만 더했다. 1.28.1부터 위는 host, 아래는 `safeAreaBottom`이다. 위 safe area를 아무도 들이지 않던 화면은 host에서 TopBar `safeAreaTop`이나 SafeAreaView top으로 들인다.
134
150
  - Native는 자체로 키보드 스크롤 여백을 준다. 부모가 다시 keyboard avoidance를 하면 여백이 중복될 수 있으니
135
151
  심사자 폼처럼 입력칸이 있는 화면은 소비 화면에서 확인한다.
136
152
  - 앱 셸이 이미 `<main>`을 가진 Web 화면에서 기본값을 쓰면 main landmark가 중첩된다. `as="section"`을 쓴다.
@@ -143,6 +159,7 @@ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react
143
159
 
144
160
  ## 근거
145
161
 
162
+ - `safeAreaBottom`·링크 `target`/`rel`/`accessibilityHint`(1.28.1): 2026-10-17 1.28 채택에서 다에리·utilverse·spint가 레이아웃을 각자 아래 inset 래퍼로 감쌌고(iOS는 자동 content inset이 이미 더해 합산, Android는 아무도 안 들여 spint가 Android만 덧붙임), 다에리 웹은 정책 문서 새 탭 열기(`target="_blank"`)를, spint는 "브라우저에서 열림" 접근성 안내를 링크 슬롯이 없어 잃었다. ScreenLayout의 max 규칙을 그대로 쓰고 링크에 같은 의미의 보조 안내·anchor 속성을 연다.
146
163
  - `AuthScreenHero`·`AuthScreenFooter`와 히어로·링크 수치: 2026-10-17 사용자 피드백("굵기랑 크기 줄여", "간격 줄여").
147
164
  예제가 히어로에 `Top large`(display 32/40·600)를 넣어 402 폭에서 태그라인이 두 줄이 됐고, 정책 링크는 ghost Button
148
165
  안쪽 여백 12+12에 Stack gap 16이 더해져 글자 사이가 Web 42·Native 40이었다. 슬롯 내용을 앱이 조립하면 같은 실수가
@@ -79,6 +79,7 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
79
79
 
80
80
  | prop | 값 | 기본값 | 설명 |
81
81
  | --- | --- | --- | --- |
82
+ | `initialFocus` | `first-input` · `none` | `none` | 1.28.1. 대화는 기록을 읽는 것이 먼저라 기본으로 작성창에 포커스하지 않는다 — 키보드가 올라오면 읽던 메시지를 가린다. 새 대화 시작처럼 쓰기가 먼저인 화면만 `first-input` |
82
83
  | `composer` | `ReactNode` | 필수 | footer에 고정되는 작성창. `ready`와 `empty`에서 보인다(빈 대화의 첫 메시지 입력) |
83
84
  | `headerAlignment` | `"center"` · `"start"` | `"center"` | 뒤로·도구 사이에 상대 이름을 가운데 둔다(카카오톡·인스타그램 실측) |
84
85
  | `scroll` | `"content"` · `"screen"` | `"content"` | 가상화 목록이 스크롤을 소유하므로 작은 예제 외에는 바꾸지 않는다 |
@@ -119,6 +119,8 @@ threadHeader도 댓글과 함께 숨겨지므로 항상 보여야 하는 안내
119
119
 
120
120
  ## 꼭 지킬 것
121
121
 
122
+ - 진입 포커스(`initialFocus`)는 기본 `none`이다(1.28.1). 댓글은 읽는 것이 먼저라 작성창 키보드가 스레드를 가리면 안 된다. 답글 쓰기로 바로 들어가는 화면만 `initialFocus="first-input"`을 준다.
123
+
122
124
  - id는 비어 있지 않고 유일해야 하며, 답글의 `parentId`는 `items` 안의 **최상위** 댓글이어야 한다. 아니면 렌더 중
123
125
  `TypeError`가 난다. 답글의 답글은 표현하지 않으므로 제품이 최상위로 평탄화한다.
124
126
  - 모든 문구·시각·좋아요 수 라벨은 제품 i18n에서 만든다. `likeCountLabel`이 빈 문자열이면 그리지 않는다.
@@ -113,6 +113,7 @@ await handle.closed; // portal 제거·초점 복구 뒤. 다음 오버레이는
113
113
 
114
114
  | prop | 값 | 기본값 | 설명 |
115
115
  | --- | --- | --- | --- |
116
+ | `initialFocus` | `single-input` · `first-input` · `none` | `single-input` | 1.28.1. 열린 뒤(Native: 표시·진입 애니메이션 뒤, Web: 포커스 이동 시점) 포커스할 텍스트 입력. 기본은 텍스트 입력이 **하나뿐일 때만** 그 칸(이름 바꾸기·메모 한 줄), 여럿이면 기존 규칙(Web 첫 포커스 가능 요소, Native 접근성 포커스) 그대로. `first-input`은 `initialFocusTarget` 칸, 없으면 첫 칸. `initialFocusRef`(Web)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다. 1.28.2: 열린 뒤 칸이 늦게 나타나도 조작 전이면 고르고, 단계형 내용은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다(앱의 단계별 `ref.focus()` 대신). 위에 다른 오버레이가 열려 있는 동안은 고르지 않는다 |
116
117
  | `size` | `small` · `medium` · `large` | `medium` | Native recipe 최대 폭 320 · 420 · 640. Web 최대 폭 28rem · 36rem · 48rem(아래 배치 표) |
117
118
  | `dismissible` | `true` · `false` | `true` | `false`면 닫기 버튼과 바깥·Escape/back 닫기가 사라진다 |
118
119
  | `busy` | `true` · `false` | `false` | 비동기 작업 중에는 `open`을 유지하고 `busy`로 반복 행동과 닫기를 막는다 |
@@ -91,6 +91,7 @@ import { TextField } from "@hjmds/react-native/inputs";
91
91
 
92
92
  | prop | 값 | 기본값 | 설명 |
93
93
  | --- | --- | --- | --- |
94
+ | `initialFocus` | `first-input` · `none` | `first-input` | 1.28.1. 작성·편집이 화면의 목적이라 진입 후 첫 입력(또는 `initialFocusTarget` 칸)에 한 번 포커스한다. 읽고 고칠 내용이 먼저인 편집은 `none`. 1.28.2: 단계형 작성(제목 → 본문)은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다 |
94
95
  | `dirty` | `boolean` | 필수 | `true`에서 닫기를 누르면 `discard` 확인창을 띄우고, 확인하면 `cancel.onAction`을 부른다. `false`면 바로 부른다 |
95
96
  | `submit` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | `pending`이면 loading·비활성. 그동안 닫기 버튼도 비활성. 본문 `state`가 `ready`가 아니면 저장 비활성 |
96
97
  | `cancel` | `ScreenFlowAction` | 필수 | 헤더 시작 쪽 × 아이콘 닫기 버튼(`ScreenLayout` `exit` `close`). `label`은 보조기술 이름이다. 2026-10-17부터 글자 버튼 대신 아이콘 |
@@ -70,6 +70,7 @@ import { Field } from "@hjmds/react-native/forms";
70
70
 
71
71
  | prop | 값 | 기본값 | 설명 |
72
72
  | --- | --- | --- | --- |
73
+ | `initialFocusTarget` | `boolean` | — | 1.28.1. 둘러싼 화면·시트가 진입 포커스를 켰을 때 `true`면 이 칸을 고르고, `false`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 1.28.2부터 `true` 칸이 진입 뒤에 나타나면(검색 열기·단계 이동) 모드와 무관하게 그때 포커스한다. 앱별 `autoFocus`·지연 `ref.focus()` 대신 쓴다([진입 포커스](screen-layout.md#진입-포커스)). TextField·TextArea·SearchField·PasswordField 공통 |
73
74
  | `variant`(`TextField`) | `surface` · `inset` | `surface` | — |
74
75
  | `shape`(`TextField`) | `medium` · `large` · `full` | `medium` | — |
75
76
  | `align`(`TextField`) | `start` · `center` | `start` | `center`는 닉네임·코드처럼 짧은 한 값 |
@@ -68,6 +68,7 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
68
68
  | `back` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | 필수 | 상세 pane 헤더 시작 쪽 ‹ 아이콘 버튼(`ScreenLayout` `exit` `back`). `label`은 보조기술 이름이고 글자는 보이지 않는다. 2026-10-17부터 글자 버튼 대신 아이콘이며 Native `detail.leading`(제품 뒤로 아이콘 대체)은 제거됐다 |
69
69
  | `refresh` | `ScreenFlowAction` | 없음 | 목록 pane `actions` 자리의 ghost 버튼. 주면 `actions`를 덮는다 |
70
70
  | `loadMore` | `ScreenFlowAction` | 없음 | 목록 pane footer의 ghost 버튼 |
71
+ | `safeAreaBottom` (Native) | 0 이상의 유한수 | `0` | 목록·상세 pane **둘 다**의 ScreenLayout에 넘긴다(두 pane이 같은 창 아래에 닿는다). 1.28.0까지 상세 pane에는 전달되지 않아 상세 본문 끝이 홈 인디케이터 아래에 놓였다(야잘알, 2026-10-17). Web은 `env()`로 자동 |
71
72
  | `layoutStyle` | `HjmCompositionStyleProp` | 없음 | Web·Native 모두 목록·상세 pane을 함께 담는 **바깥 틀**에 적용한다(안쪽 목록 ScreenLayout이 아니다). 목록/상세 전환과 상관없이 같은 루트에 남는다(1.13.0) |
72
73
  | 나머지 | `ScreenLayout`과 같음(`children`·`footer` 제외) | — | 목록 pane에만 적용된다(`title` 필수, `state`, `scroll` 등) |
73
74
 
@@ -157,6 +157,7 @@ import { CollectionRail } from "@hjmds/react/collection-rail";
157
157
  | `defaultStartKey` | item ID | 첫 항목 | 초기 위치만. 선택/상세 값 아님 |
158
158
  | `onStartKeyChange` | `(key: string|null) => void` | 생략 | 앞쪽 항목 변화 관찰; 선택 확정 callback 아님 |
159
159
  | `emptyContent` | ReactNode | 생략 | 제품의 빈 목록 내용 |
160
+ | `bleed` | boolean | `false` | 1.28.1. 기본 inset ScreenLayout 본문 안에서 화면 좌우 끝까지 스크롤하고 첫·끝 항목은 본문 여백(기본 `spacing.md` 16, ChatScreen 10)에 맞춘다. viewport가 여백만큼 밖으로 나가고 같은 양을 안쪽 padding으로 되돌린다(ListRow bleed와 같은 규칙, Web `--hjm-screen-body-inline`, Native 본문 inset context). 본문 바로 아래 섹션으로 둔다 — Card·Surface 안, `body-edge`/`none` 본문, Sheet·Dialog 안에서는 넘을 여백이 0이라 움직이지 않는다 |
160
161
  | `layoutStyle` | 공개 배치 key | 생략 | 루트 배치. 카드 시각 표현은 Card/Provider |
161
162
 
162
163
  #### 배치
@@ -169,6 +170,25 @@ import { CollectionRail } from "@hjmds/react/collection-rail";
169
170
  | 고정·스크롤 | viewport만 실제 가로 scroll, controls는 밖. 끝에서 멈추고 자동 이동 없음 | 공통 maxOffset·target resolver |
170
171
  | 좁은 폭·큰 글자 | 기본 글자의 좁은 폭에서 실제 측정으로 카드 폭을 줄임. 최대 글자 조건은 제외 | 2026-10-07 사용자 범위, root AGENTS.md |
171
172
 
173
+ #### 화면 끝까지 닿는 가로 목록(`bleed`)
174
+
175
+ ```text
176
+ │←16→│ 제목(본문 여백) │
177
+ │[카드1][카드2][카드3][카… ← 스크롤하면 화면 끝까지, 처음·끝 카드는 16 여백선
178
+ ```
179
+
180
+ 한 섹션만 화면 끝까지 스크롤하려고 화면 전체를 `contentInset="body-edge"`로 바꾸고 섹션마다 `Container gutter`를 다시 주지 않는다(spint 홈 "더 보기" 2026-10-17). 기본 inset을 두고 그 섹션의 CollectionRail에 `bleed`를 준다.
181
+ `scroll="content"`에서 제품 목록(FlatList·스크롤 `div`)이 본문을 스크롤하면 그 viewport가 여백 안에서 rail을 자른다. 그 목록에 `useScreenBodyScrollRoom()`(`/screens`, Web·Native 같은 이름)을 준다 — Native `style`·`contentContainerStyle`, Web `style`. viewport는 화면 끝까지, 내용은 여백선에 그대로 남고, 안의 `bleed` rail이 끝까지 스크롤한다.
182
+
183
+ ```tsx
184
+ // Native: spint 홈처럼 FlatList가 본문을 스크롤하는 화면
185
+ const room = useScreenBodyScrollRoom();
186
+ <ScreenLayout title={t("hot.title")} scroll="content">
187
+ <FlatList style={[{ flex: 1 }, room.style]} contentContainerStyle={room.contentContainerStyle} data={rows} renderItem={renderCard}
188
+ ListHeaderComponent={<CollectionRail bleed label={t("more.title")} items={more} labels={railLabels} renderItem={renderTile} />} />
189
+ </ScreenLayout>
190
+ ```
191
+
172
192
  #### 꼭 지킬 것
173
193
 
174
194
  같은 항목에 안정 ID를 쓰고 테마/폭을 subtree key로 두지 않는다. 카드 전체를 버튼으로 만들어 입력/상세 버튼을 중첩하지 않는다.
@@ -87,6 +87,7 @@ import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
87
87
  | `skip` | 선택. Web은 헤더 actions, Native는 본문 진행 문구 다음에 보조 버튼으로 놓인다 |
88
88
  | `progressLabel(current, total)` | 1부터 센 현재 단계와 전체 수로 진행 문구를 만든다 |
89
89
  | `layoutStyle` | 선택. ScreenLayout 루트의 공개 배치 슬롯 |
90
+ | `safeAreaBottom` (Native) | 선택, 기본 0. ScreenLayout에 그대로 넘겨 footer(다음·이전) 아래 여백이 `max(16, inset)`이 된다. 바깥 SafeAreaView bottom·`16 + inset` 패딩 래퍼 대신 쓴다(1.28.1, 2026-10-17 야잘알·다에리가 각자 감쌌다) |
90
91
 
91
92
  ## 배치
92
93
 
@@ -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,29 @@ 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`·`resolveEntryFocus`·`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
+ | `AuthScreenLayout` | `none` | 로그인 화면은 제공자 버튼이 주 행동. 가입 닉네임 단계만 `initialFocus="first-input"`(1.28.2) |
117
+
118
+ - 고르는 순서: `initialFocusTarget` 칸 → 등록(트리) 순서 첫 칸. 비활성·읽기 전용 칸, `initialFocusTarget={false}` 칸은 고르지 않는다.
119
+ - 시점: Native는 화면·시트 진입 전환이 끝난 뒤(`InteractionManager`, 시트는 표시 뒤) — 키보드 애니메이션이 전환과 겹치지 않는다. Web은 mount 직후, `preventScroll`.
120
+ - 빼앗지 않는다: 사용자가 이미 입력 칸에 있으면 그대로 둔다(Native 포커스된 입력, Web 편집 가능한 요소).
121
+ - 오버레이 아래(1.28.2, Web·Native): 위에 HJM `Dialog`·`AlertDialog`·`Sheet`가 열려 있으면 아래 화면·시트는 고르지 않는다. Web은 제품 자체 모달(`aria-modal`·`dialog[open]`)도 막는다. Native는 HJM 오버레이만 알 수 있으므로 화면을 덮는 모달은 HJM 오버레이로 만든다.
122
+ - 진입 창(1.28.2): 진입 뒤 아직 포커스하지 못한 범위는 사용자가 그 범위를 직접 조작(Native 터치·스크롤, Web 포인터·키·휠·터치)하기 전까지 열려 있다. 그 동안 ① 칸이 새로 나타나면(데이터 로드 뒤 드러나는 확인 칸) ② 진입 때 위에 있던 오버레이가 닫히면(첫 가입 약관 동의) 다시 고른다. 오버레이 안의 조작(동의 탭)은 아래 화면의 조작으로 세지 않는다. 오버레이가 닫힌 시점은 그 오버레이의 반환 포커스와 같은 시점(Native: 퇴장·dismiss 뒤, Web: 반환 포커스 뒤)이다.
123
+ - 나중에 드러나는 주 입력(1.28.2): 진입이 끝난 뒤 `initialFocusTarget`(`true`) 칸이 새로 mount되면 그 칸을 포커스한다. `initialFocus` 모드와 무관하다 — 검색 버튼으로 여는 검색 칸(`none` 화면), 단계형 시트·`EditorScreen`의 단계 칸(각 단계 칸에 `initialFocusTarget`)이 이 경우다. 다른 입력 칸에 초점이 있거나 위에 오버레이가 있으면 하지 않는다. 스스로 다시 mount되는 칸(가상화 목록 행)에는 주지 않는다.
124
+ - 다시 진입: 화면은 mount당 한 번이다. 스택에서 돌아온 화면(이미 mount)은 다시 포커스하지 않는다 — 돌아온 맥락을 키보드가 가리고, HJM은 내비게이션 포커스 신호를 모른다. 시트·대화상자는 열 때마다 다시 고른다.
125
+ - 모션 감소는 애니메이션에만 적용하고 포커스는 그대로 한다.
126
+ - 제품은 `autoFocus`·`setTimeout(() => ref.focus())`·단계 전환 뒤 `ref.focus()`를 쓰지 않고 `initialFocus`·`initialFocusTarget`으로 표현한다. 검증 실패 칸으로 옮기는 포커스(제출 오류)는 제품의 동작이라 그대로 둔다.
127
+
104
128
  ## 꼭 지킬 것
105
129
 
106
130
  - **헤더 제목은 기본 가운데, 추가 기능 버튼은 오른쪽 위.** `headerAlignment` 기본값은 `"center"`이고 `"start"`는 큰 제목이 필요한 화면에서만 고른다(2026-10-17 사용자 지시: 가운데가 기본, 강제는 아님).
@@ -115,6 +139,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
115
139
  - 새로고침 실패·저장 오류는 `state`를 바꾸지 말고 `ready` + `notice`로 알린다. `loading`으로 바꾸면 본문이
116
140
  unmount되어 입력 초안이 사라진다.
117
141
  - `state.title`·문구는 i18n 키로 넣는다. ready 외 상태의 빈 `title`은 `TypeError`다.
142
+ - **`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
143
  - Web host는 실제 남은 높이(`height:100%` 등)를, Native host는 위·옆 safe area와 탭바 inset을 먼저 처리한다. 아래 홈 인디케이터는 host가 SafeAreaView로 들이지 말고 `safeAreaBottom`으로 넘긴다(들인 뒤 footer 16을 더하면 버튼 아래가 50이 된다).
119
144
  HJM은 기종별 높이를 추측하지 않는다.
120
145
  - 배치는 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`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 1.28.2부터 `true` 칸이 진입 뒤에 나타나면(검색 열기·단계 이동) 모드와 무관하게 그때 포커스한다. 앱별 `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)가 있으면 그쪽이 이긴다. 열 때마다 다시 고른다. 1.28.2: 열린 뒤 칸이 늦게 나타나도 조작 전이면 고르고, 단계형 내용은 각 단계 칸에 `initialFocusTarget`을 주면 단계가 바뀔 때 그 칸을 포커스한다(앱의 단계별 `ref.focus()` 대신). 위에 다른 오버레이가 열려 있는 동안은 고르지 않는다 |
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`면 절대 고르지 않는다. 생략하면 트리 순서대로 후보다. 비활성·읽기 전용 칸은 고르지 않는다. 1.28.2부터 `true` 칸이 진입 뒤에 나타나면(검색 열기·단계 이동) 모드와 무관하게 그때 포커스한다. 앱별 `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.29.0",
4
4
  "description": "Renderer-neutral design contracts, tokens, recipes, and behaviors shared by HJM products.",
5
5
  "license": "MIT",
6
6
  "repository": {