@hjmds/design-contracts 1.27.2 → 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 (143) hide show
  1. package/dist/activity-chip.d.ts +1 -1
  2. package/dist/activity-chip.js +1 -1
  3. package/dist/activity-chip.js.map +1 -1
  4. package/dist/auth-screen.d.ts +77 -5
  5. package/dist/auth-screen.d.ts.map +1 -1
  6. package/dist/auth-screen.js +63 -2
  7. package/dist/auth-screen.js.map +1 -1
  8. package/dist/base-recipes.d.ts +1 -1
  9. package/dist/base-recipes.js +1 -1
  10. package/dist/base-recipes.js.map +1 -1
  11. package/dist/behaviors.d.ts +1 -1
  12. package/dist/breadcrumb.d.ts +1 -1
  13. package/dist/breadcrumb.js +1 -1
  14. package/dist/breadcrumb.js.map +1 -1
  15. package/dist/calendar.d.ts +1 -1
  16. package/dist/calendar.js +2 -2
  17. package/dist/calendar.js.map +1 -1
  18. package/dist/catalog.d.ts +131 -87
  19. package/dist/catalog.d.ts.map +1 -1
  20. package/dist/component-contracts.d.ts +1 -1
  21. package/dist/component-contracts.js +1 -1
  22. package/dist/component-contracts.js.map +1 -1
  23. package/dist/component-recipes.d.ts +85 -57
  24. package/dist/component-recipes.d.ts.map +1 -1
  25. package/dist/component-recipes.js +98 -60
  26. package/dist/component-recipes.js.map +1 -1
  27. package/dist/conversation-row.d.ts +2 -2
  28. package/dist/conversation-row.d.ts.map +1 -1
  29. package/dist/conversation-row.js +6 -4
  30. package/dist/conversation-row.js.map +1 -1
  31. package/dist/counter-badge-recipe.d.ts +1 -1
  32. package/dist/counter-badge-recipe.js +1 -1
  33. package/dist/counter-badge-recipe.js.map +1 -1
  34. package/dist/data-table.d.ts +4 -4
  35. package/dist/data-table.js +4 -4
  36. package/dist/data-table.js.map +1 -1
  37. package/dist/date-picker.d.ts +1 -1
  38. package/dist/design-profile.d.ts.map +1 -1
  39. package/dist/design-profile.js +19 -16
  40. package/dist/design-profile.js.map +1 -1
  41. package/dist/file-picker.d.ts +1 -1
  42. package/dist/file-picker.js +1 -1
  43. package/dist/file-picker.js.map +1 -1
  44. package/dist/foundations.d.ts +18 -14
  45. package/dist/foundations.d.ts.map +1 -1
  46. package/dist/foundations.js +18 -13
  47. package/dist/foundations.js.map +1 -1
  48. package/dist/heading.d.ts +13 -13
  49. package/dist/heading.js.map +1 -1
  50. package/dist/number-field.d.ts +1 -1
  51. package/dist/otp-field.d.ts +1 -1
  52. package/dist/password-field.d.ts +1 -1
  53. package/dist/result.d.ts +1 -1
  54. package/dist/result.js +2 -2
  55. package/dist/result.js.map +1 -1
  56. package/dist/screen-patterns.d.ts +36 -0
  57. package/dist/screen-patterns.d.ts.map +1 -1
  58. package/dist/screen-patterns.js +20 -0
  59. package/dist/screen-patterns.js.map +1 -1
  60. package/dist/semantic-colors.d.ts +10 -0
  61. package/dist/semantic-colors.d.ts.map +1 -1
  62. package/dist/semantic-colors.js +6 -0
  63. package/dist/semantic-colors.js.map +1 -1
  64. package/dist/sheet.d.ts +7 -4
  65. package/dist/sheet.d.ts.map +1 -1
  66. package/dist/sheet.js +8 -5
  67. package/dist/sheet.js.map +1 -1
  68. package/dist/side-panel.d.ts +1 -1
  69. package/dist/side-panel.js +2 -2
  70. package/dist/side-panel.js.map +1 -1
  71. package/dist/steps.d.ts +1 -1
  72. package/dist/steps.js +1 -1
  73. package/dist/steps.js.map +1 -1
  74. package/dist/tag.d.ts +1 -1
  75. package/dist/tag.js +1 -1
  76. package/dist/tag.js.map +1 -1
  77. package/dist/timeline.d.ts +1 -1
  78. package/dist/timeline.js +1 -1
  79. package/dist/timeline.js.map +1 -1
  80. package/dist/top.d.ts +4 -4
  81. package/dist/top.d.ts.map +1 -1
  82. package/dist/top.js +3 -1
  83. package/dist/top.js.map +1 -1
  84. package/dist/version.d.ts +1 -1
  85. package/dist/version.js +1 -1
  86. package/dist/version.js.map +1 -1
  87. package/docs/activity-chip.md +5 -1
  88. package/docs/auth-screen.md +2 -0
  89. package/docs/design-profile.md +14 -10
  90. package/docs/generated/component-maturity.md +1 -1
  91. package/docs/generated/renderer-evidence.json +9 -5
  92. package/docs/generated/renderer-evidence.md +1 -1
  93. package/docs/generated/showcase-manifest.json +1 -1
  94. package/docs/heading.md +14 -9
  95. package/docs/sheet.md +2 -0
  96. package/docs/top.md +4 -3
  97. package/docs/usage/README.md +1 -1
  98. package/docs/usage/SELECTION.md +1 -0
  99. package/docs/usage/components/auth-screen-layout.md +44 -11
  100. package/docs/usage/components/bottom-cta.md +6 -2
  101. package/docs/usage/components/breadcrumb.md +5 -1
  102. package/docs/usage/components/chat-screen.md +1 -0
  103. package/docs/usage/components/checkbox-group.md +5 -1
  104. package/docs/usage/components/collapsible.md +1 -0
  105. package/docs/usage/components/comment-thread-screen.md +2 -0
  106. package/docs/usage/components/dialog.md +1 -0
  107. package/docs/usage/components/editor-screen.md +1 -0
  108. package/docs/usage/components/effect-surface.md +2 -2
  109. package/docs/usage/components/field.md +1 -0
  110. package/docs/usage/components/heading.md +14 -12
  111. package/docs/usage/components/list-detail-screen.md +1 -0
  112. package/docs/usage/components/list-row.md +7 -3
  113. package/docs/usage/components/list.md +25 -4
  114. package/docs/usage/components/notification-item.md +5 -1
  115. package/docs/usage/components/onboarding-screen.md +1 -0
  116. package/docs/usage/components/photo-source-sheet.md +1 -1
  117. package/docs/usage/components/screen-layout.md +28 -3
  118. package/docs/usage/components/search-field.md +1 -0
  119. package/docs/usage/components/search-screen.md +4 -2
  120. package/docs/usage/components/section.md +5 -1
  121. package/docs/usage/components/segmented-control.md +1 -1
  122. package/docs/usage/components/sheet.md +18 -13
  123. package/docs/usage/components/skeleton.md +42 -15
  124. package/docs/usage/components/statistic.md +5 -1
  125. package/docs/usage/components/switch.md +3 -2
  126. package/docs/usage/components/tag.md +5 -1
  127. package/docs/usage/components/text-area.md +1 -0
  128. package/docs/usage/components/text.md +6 -4
  129. package/docs/usage/components/top.md +1 -1
  130. package/docs/usage/compositions/adaptive-content.md +5 -1
  131. package/docs/usage/compositions/compound-controls.md +2 -1
  132. package/docs/usage/compositions/design-profile-comparison.md +1 -1
  133. package/docs/usage/compositions/family-drawer.md +5 -3
  134. package/docs/usage/compositions/input-sheet.md +1 -1
  135. package/docs/usage/compositions/interaction-flow-apply.md +3 -3
  136. package/docs/usage/compositions/photo-source.md +3 -3
  137. package/docs/usage/compositions/selection-motion.md +5 -1
  138. package/docs/usage/compositions/visual-foundations.md +5 -4
  139. package/docs/usage/screens/common-login.md +9 -7
  140. package/docs/usage/screens/landing.md +15 -14
  141. package/docs/usage/screens/notification-settings.md +4 -2
  142. package/docs/usage/tokens/typography.md +21 -13
  143. package/package.json +1 -1
@@ -55,7 +55,7 @@ import { Tag } from "@hjmds/react-native/data-display";
55
55
  | `accessibilityLabel`(Native) | `string` | 라벨 | — |
56
56
  | `style`·`labelStyle`(Native) | 지원하지 않음 | — | 제거된 속성 — `layoutStyle` 또는 `tone`. 현재 공개 props에서 제거되었다 |
57
57
 
58
- 모양은 radius `sm` 사각형, 최소 높이 20(큰 글자에서 늘어남), caption 크기 semibold 글자다.
58
+ 모양은 radius `sm` 사각형, 최소 높이 20(큰 글자에서 늘어남), caption 크기 medium(500) 글자다.
59
59
 
60
60
  ## 배치
61
61
 
@@ -84,3 +84,7 @@ Native의 모서리는 Provider의 `tokens.radius.sm`이다. metadata 의미와
84
84
  | 추가 style prop | `className`·`style`(HTML 속성) | `style`·`labelStyle`(제거된 속성) |
85
85
  | ref | `forwardRef`(`span`) | 없음 |
86
86
  | import 경로 | `/display` | `/data-display` |
87
+
88
+ ## 근거
89
+
90
+ 2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
@@ -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` | — |
@@ -18,7 +18,7 @@
18
18
  | 상황 | 대신 쓸 것 |
19
19
  | --- | --- |
20
20
  | 짧은 상태·결과 문구를 변경 알림으로 전달 | [StatusText](#statustext) |
21
- | 문서 제목 단계(`h1`~)가 필요하거나 24px보다 큰 제목 | [Heading](heading.md) |
21
+ | 문서 제목 단계(`h1`~)가 필요한 제목(크기는 Text와 같은 단계, 최대 24px) | [Heading](heading.md) |
22
22
  | 화면 첫 자리의 제목 블록(보조 문장·행동 포함) | [Top](top.md) |
23
23
  | 본문 묶음의 헤더 행 | [Section](section.md) |
24
24
  | 숫자 지표와 그 라벨 | [Statistic](statistic.md) |
@@ -61,7 +61,7 @@ import { Text } from "@hjmds/react-native/primitives";
61
61
  | --- | --- | --- | --- |
62
62
  | `variant` | `caption`(11) · `label`(12) · `body`(14) · `bodyLarge`(16) · `title`(18) · `titleLarge`(20) · `heading`(24) | `body` | 화면·섹션 제목은 모양이 아니라 [Heading](heading.md)으로 쓴다 |
63
63
  | `fontRole` | `ui` · `display` · `reading` · `code` | 제목 variant는 display, 본문은 reading, caption/label은 ui | 명시값이 기본을 덮는다. [역할 지침](../tokens/font-roles.md), 1.16.0 변경 |
64
- | `emphasis` | `regular` · `medium`(semibold) · `strong`(bold) | 프로필이 있으면 variant 굵기, 없으면 `regular`. `label`은 프로필과 무관하게 variant 굵기 500 | 명시값이 프로필과 variant보다 우선 |
64
+ | `emphasis` | `regular` 400 · `medium` 500 · `strong` 500(제목 굵기) | 프로필이 있으면 variant 굵기, 없으면 `regular`. `label`은 프로필과 무관하게 variant 굵기 500 | 명시값이 프로필과 variant보다 우선 |
65
65
  | `tone` | `primary` · `body` · `muted` · `subtle` · `weak` · `brand` · `danger` · `inverse` | `primary` | — |
66
66
  | `as` | `span` · `p` · `div` · `strong` · `small` | `span` | Web만. 문단은 `as="p"` |
67
67
  | `align` | `TextStyle["textAlign"]` | 쓰기 방향의 시작 쪽 | Native만 |
@@ -103,13 +103,13 @@ Native는 Text의 역할을 해당 stack에, 입력/조작 host는 제품 프로
103
103
  ## 함정
104
104
 
105
105
  - `emphasis`를 명시하면 `variant`의 굵기를 덮는다. `designProfile`이 있으면 생략한 `emphasis`는 해당 프로필의 `tokens.typography[variant].fontWeight`를 따른다. 프로필이 없으면 기존 기본값 `regular`를 유지하므로 `variant="title"`도 보통 굵기로 그려진다. 그 경우 굵은 제목은 `emphasis="strong"`으로 지정한다(두 renderer 동일).
106
- - `variant="label"`은 예외로, 프로필이 없어도 생략한 `emphasis`가 `typography.label` 굵기 500(medium)으로 그려진다. 500은 `emphasis` 단계에 없으므로(`medium`은 600) label을 400으로 그리려면 `emphasis="regular"`를 명시한다.
106
+ - `variant="label"`은 예외로, 프로필이 없어도 생략한 `emphasis`가 `typography.label` 굵기 500(medium)으로 그려진다. label을 400으로 그리려면 `emphasis="regular"`를 명시한다.
107
107
  Web은 이 기본 굵기를 inline `font-weight`로 주며 `data-emphasis`는 `regular`로 남는다. Native는 `fonts="pretendard"`일 때 `Pretendard-Medium` face로 그린다.
108
108
 
109
109
 
110
110
  ## 근거
111
111
 
112
- 2026-10-17 사용자 결정: 폼 라벨·섹션 부제목이 본문 대비 약해 `label` 기본 굵기를 500으로 정했다(본문 400과 제목 600 사이). 이전에는 토큰이 600이었지만 프로필 없는 Text는 400으로 그렸다. 두 renderer는 `resolveTextDefaultWeight`(`src/component-recipes.ts`) 하나로 기본 굵기를 고른다.
112
+ 2026-10-17 사용자 결정: 폼 라벨·섹션 부제목이 본문 대비 약해 `label` 기본 굵기를 500으로 정했다(당시 본문 400과 제목 600 사이, 제목이 500으로 내려간 뒤에도 500 유지). 이전에는 토큰이 600이었지만 프로필 없는 Text는 400으로 그렸다. 두 renderer는 `resolveTextDefaultWeight`(`src/component-recipes.ts`) 하나로 기본 굵기를 고른다.
113
113
 
114
114
  ## StatusText
115
115
 
@@ -189,3 +189,5 @@ Native Android는 `accessibilityLiveRegion`만 사용한다. iOS는 앱이 activ
189
189
  동일 결과를 매 렌더 다시 알리기 위해 key를 바꾸지 않는다. Toast·Notice에서 이미 알리는 결과를 중복 알리지 않는다.
190
190
  Showcase의 `PatternStatus`는 이 공개 컴포넌트를 가리키는 내부 별칭일 뿐이며 제품에서 Showcase 파일을 import하지 않는다.
191
191
  DOM 속성·Native host 호출 검사는 실제 VoiceOver·TalkBack의 낭독 확인과 구분한다.
192
+ - 2026-10-17 사용자 결정("왜 폰트가 진해"): 제목을 600으로 내린 뒤에도 `strong`이 700이라 읽지 않은 알림 제목·라벨 25곳이 제목보다 굵었다. 강조 단계를 400·500·600으로 낮췄다.
193
+ - 같은 날 두 번째 결정("굵기 좀더 줄여"): 제목을 500, 큰 제목(level1·2)을 600으로 한 단계씩 더 낮췄다. `strong`은 제목 굵기를 따라 500이 되어 `medium`과 같다(`typography.title.fontWeight` 참조). 600은 큰 숫자·선택 상태에만 쓴다(큰 제목 600 단계는 같은 날 큰 글자 단계와 함께 폐지).
@@ -90,6 +90,6 @@ import { Top } from "@hjmds/react-native/top";
90
90
 
91
91
  ### 프로필 모서리·제목 소비
92
92
 
93
- 2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level2`, `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
93
+ 2026-10-07 소비 감사에서 두 renderer의 제목 크기·행간·굵기가 foundation으로 확정된 recipe를 직접 읽어 프로필을 우회했다. `large`는 가까운 프로필의 `heading.level3`(24, 2026-10-17 큰 글자 단계 폐지 전에는 `level2` 32), `medium`은 `typography.titleLarge`를 읽고 프로필이 없으면 기존 recipe를 유지한다. 시각 역할은 `headingLevel`과 독립이며 display 서체·제목 의미·보조 행동·초점은 바꾸지 않는다.
94
94
 
95
95
  프로필 연결은 1.16.0 변경이며, 기본 배포 계약과 실제 Native 기기 검증은 구분한다.
@@ -33,7 +33,7 @@
33
33
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
34
34
  | --- | --- | --- | --- |
35
35
  | 바깥 틀 | Stack. Web은 제품 화면의 `Container size="reading"` 안 | 화면의 본문 흐름 | gap md=16px, 폭은 Container를 따른다(Web 최대 `layout.readingMaxWidth` 720) |
36
- | 패널 제목 | `Heading level="level5"` | 패널 Surface 맨 위 | `title` 글자 크기·semibold |
36
+ | 패널 제목 | `Heading level="level5"` | 패널 Surface 맨 위 | `title` 글자 크기·medium(500) |
37
37
  | 내용 | 해당 공개 API | 입력과 행동 사이 | 자체 recipe 크기, 줄바꿈 허용 |
38
38
  | 행동 | Button/기존 컨트롤 | 관련 항목 바로 뒤 | inline일 때 wrap, md=16px |
39
39
 
@@ -82,3 +82,7 @@ Storybook의 기존 gutter 때문에 contentInset=none을 쓰고, 마지막 입
82
82
  스크롤 경계에 걸리지 않도록 본문 끝에 spacing.md(16) 여백을 둔다.
83
83
  본문 조합 자체에 두 번째 scroll view를 만들지 말고 제품의 기존 화면 host를 재사용한다.
84
84
  KeyboardAvoiding은 아래 여백을 제공하며 포커스된 필드를 자동 탐색하는 API는 아니다.
85
+
86
+ ## 근거
87
+
88
+ 2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
@@ -64,7 +64,7 @@
64
64
  | 소요 시간 | DurationField | 설명 아래 | NumberField 세 칸, 칸 사이 `spacing.md` 16. 세 칸이 모두 들어가면 한 줄(칸 최대 `fieldGroupRecipe.inline.maxWidth`), 아니면 세 칸 모두 세로([FieldGroup](field-group.md)) |
65
65
  | 확인 | InlineConfirm | 트리거 자리 그대로 | 트리거와 확인 행동이 같은 자리, Button 높이 44, 질문·버튼 줄 사이와 두 버튼 사이 `spacing.sm` 12 |
66
66
  | 반응 | ReactionPicker | 제목 아래 | `ghost` `pill` Button 묶음, 사이 `spacing.xs` 8, 넘치면 줄바꿈 |
67
- | 알림 종 | NotificationBell | 블록 맨 위, 시작 쪽 정렬 | IconButton(기본 `medium` 44) + CounterBadge `floating`(끝·위 모서리) |
67
+ | 알림 종 | NotificationBell | 블록 맨 위, 시작 쪽 정렬. TopBar·헤더 끝에 둘 때는 `edge="end"` | IconButton(기본 `medium` 44) + CounterBadge `floating`(끝·위 모서리). `edge`(`start`·`end`·`both`)는 IconButton `edge`와 같은 값(`resolveIconButtonEdgeOverhang`, 44−24)/2 = 10을 벨 루트에 적용해 glyph 칸이 여백 선(TopBar 16)에 오고 배지는 아이콘 모서리를 지킨다 |
68
68
 
69
69
  ## 흐름과 상태
70
70
 
@@ -197,4 +197,5 @@ function CompoundBlocks() {
197
197
 
198
198
  ## 근거
199
199
 
200
+ - NotificationBell `edge`: 2026-10-17 1.27.2 채택 점검. Spint 홈·탐색 TopBar 끝의 벨은 IconButton을 감싸지만 `edge`가 없어 glyph 칸이 바 끝에서 26에 놓였다(Web 402·1280, LTR·RTL 측정 26 → 16, Native 3x 프레임: 버튼 끝 386 기준 15.7). 넘침은 안쪽 버튼이 아니라 벨 루트에 준다. 버튼에 주면 루트가 10 줄어 끝에 붙은 배지가 아이콘에서 10 안쪽으로 밀린다(측정: 배지 끝 − 버튼 끝 0 유지).
200
201
  - 2026-10-17 구성 리뷰: 소요 시간 칸이 데스크톱에서 늘어나고 좁은 폭에서 한 칸만 다음 줄로 끊겨 FieldGroup recipe의 inline 규칙을 바꿨다.
@@ -63,7 +63,7 @@
63
63
  | 테마 | 밝은 모드에서 보이는 차이 |
64
64
  | --- | --- |
65
65
  | 미니멀 | 흰 바탕·무채색 hairline, 그림자 없음, 굵기 500 제목과 넉넉한 행간, 넓은 안쪽 여백의 테두리 카드(cards) |
66
- | 에디토리얼 | 시스템 serif 제목·본문, 가장 큰 제목 단계(56/42/30px), 따뜻한 잉크색과 와인 강조, 각진 모서리, 규칙선으로 나눈 rows |
66
+ | 에디토리얼 | 시스템 serif 제목·본문(제목 크기는 공통 단계, 2026-10-17 56/42/30px 확대 제목 폐지), 따뜻한 잉크색과 와인 강조, 각진 모서리, 규칙선으로 나눈 rows |
67
67
  | 터미널 | 모든 글자 monospace, 모서리 0, 그림자 없음, 녹회색 바탕·짙은 녹색 행동·호박색 강조, 규칙선 rows. 어두운 모드는 검정 콘솔 |
68
68
 
69
69
  ## 흐름과 상태
@@ -20,9 +20,9 @@
20
20
  | 컴포넌트 | 역할 | 지침 |
21
21
  | --- | --- | --- |
22
22
  | `Container` · `ScrollView` | 시작 화면의 바깥 틀. HJM 화면 골격이 소유한다 | [Container](../components/container.md), [화면 여백](../tokens/layout.md) |
23
- | `Heading level="level3"`, `Text` | 시작 화면의 제목·설명. 화면 제목은 semibold `level3`이며 bold 표시 역할 `level1`·`level2`는 쓰지 않는다 | [Heading](../components/heading.md), [Text](../components/text.md) |
23
+ | `Heading level="level3"`, `Text` | 시작 화면의 제목·설명. 화면 제목은 medium(500) `level3`(24, HJM 글자 상한)이다 | [Heading](../components/heading.md), [Text](../components/text.md) |
24
24
  | `Button` primary | 시트를 여는 트리거(시작 화면의 주 행동) | [Button](../components/button.md) |
25
- | `Sheet` | 하단 시트. 제목·닫기·`footer`, 안전 영역·스크롤·`busy` | [Sheet](../components/sheet.md) |
25
+ | `Sheet` | 하단 시트. 접근성 이름(`title`, 보이지 않음)·닫기·`footer`, 안전 영역·스크롤·`busy` | [Sheet](../components/sheet.md) |
26
26
  | `Steps` | 현재 단계와 전체 단계 수, 실패하면 현재 단계 `error` | [Steps](../components/steps.md) |
27
27
  | `ContentTransition preset="slide"` | 단계 본문 교체 | [ContentTransition](../components/content-transition.md) |
28
28
  | `Heading level="level3"`, `Text` | 단계 본문 | [Heading](../components/heading.md), [Text](../components/text.md) |
@@ -57,7 +57,7 @@
57
57
  | 바깥 틀 | 시작 화면: Web 문서 스크롤 > `Container size="reading"`, Native `ScrollView` > `Container size="reading"`. 시트: `Sheet`가 Modal로 화면 위에 뜨며 스크롤·안전 영역·키보드를 소유 | 시작 화면은 제품 화면 본문. 상단 안전 영역은 헤더(또는 [TopBar](../components/top-bar.md))가 맡는다 | 좌우 `Container gutter`: 폭 600 미만 `compact` 16, 이상 `regular` 20([화면 여백](../tokens/layout.md)). Native 위아래 `contentContainerStyle` `paddingVertical: spacing.lg` 20. 시트 안 inset은 제품이 다시 더하지 않는다 |
58
58
  | 시작 화면 | `Stack gap="xl"` | 본문 흐름 안 | 자식 사이 `spacing.xl` 24 |
59
59
  | 시트 표면 | `Sheet placement="bottom" size="auto"`(기본값) | 화면 아래에서 올라옴, 내용 높이 | radius `xl` 24, 최대 높이 90%. 좌우 `spacing.lg` 20·위아래 `spacing.sm` 12(두 플랫폼, `sheetRecipe.content`), Web 최대 폭 640 |
60
- | 시트 머리 | Sheet `title`·`closeLabel` | 시트 맨 위 고정 | 최소 높이 `control.minTouchTarget` 44 |
60
+ | 시트 머리 | Sheet 손잡이, `title`은 보이지 않는 접근성 이름 | 시트 맨 위 고정 | 보이는 제목 줄 없음(2026-10-17 사용자 결정: 시트는 보이는 제목 없이 `title`을 접근성 이름으로만 쓴다) |
61
61
  | 단계 표시 | `Steps` | 본문 맨 위 | 항상 가로 한 줄, 아래 `spacing.xl` 24 |
62
62
  | 단계 본문 | `ContentTransition` > `Stack gap="md"` | Steps 아래, 본문 스크롤 | 제목·설명 사이 `spacing.md` 16, `slide` 가로 16 이동 |
63
63
  | 행동 | Sheet `footer` + Button ×2 | 시트 아래 고정(스크롤 밖) | Web 오른쪽 정렬 가로 [이전 ghost][계속 primary], 사이 `spacing.sm` 12. Native 세로 열 꽉 찬 폭 [계속]→[이전], 사이 `spacing.sm` 12, 위 `spacing.sm` 12. 높이 44 |
@@ -209,4 +209,6 @@ import { Steps } from "@hjmds/react-native/steps";
209
209
 
210
210
  ## 근거
211
211
 
212
+ 2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
213
+
212
214
  - 2026-10-17 구성 리뷰: 시작 화면 제목이 heavy 800 `level2`로 화면 제목 규칙(semibold)보다 무거웠고, 데스크톱에서 버튼과 문구가 1280 폭 전체로 늘어났다. 그래서 제목을 `level3`으로 낮추고 스토리에 읽기 폭 Container를 적용했다.
@@ -51,7 +51,7 @@ Native(하단 시트, 키보드 올라옴) Web(하단 시트)
51
51
  | --- | --- | --- | --- |
52
52
  | 바깥 틀 | 시트 안: `Sheet`(Modal)가 backdrop·본문 스크롤(Native `scrollable`)·키보드(Native `keyboardAvoidance`)·안전 영역을 소유. 트리거: 제품 화면의 바깥 틀(Web 문서 스크롤 > `Container`, Native `ScrollView` > `Container`) | 시트는 화면 위 오버레이, 트리거는 화면 본문 | 시트 안 여백은 아래 행들(Sheet 소유, 제품이 inset·키보드 여백을 다시 더하지 않는다). 트리거 화면 좌우는 `Container gutter` 폭 600 미만 `compact` 16 · 이상 `regular` 20([화면 여백](../tokens/layout.md)) |
53
53
  | 시트 틀 | `Sheet` placement `bottom` | 화면 하단에 붙고 위 모서리만 둥글다 | radius `xl` 24, 최대 높이 `maxHeightRatio` 0.9(뷰포트의 90%), Web 최대 폭 640 |
54
- | 머리 | 제목 + 닫기 IconButton | 시트 맨 위, 고정 | Native 최소 높이 `control.minTouchTarget` 44·좌우 `spacing.lg` 20(+좌우 inset)·위 `spacing.sm` 12. Web padding `spacing.lg` 20(아래 0), 제목–닫기 `spacing.md` 16 |
54
+ | 머리 | 손잡이(+ `showClose`일 때 끝 × IconButton). 보이는 제목 없음, `title`은 접근성 이름(2026-10-17 사용자 결정: 시트는 보이는 제목 없이 `title`을 접근성 이름으로만 쓴다) | 시트 맨 위, 고정 | × 머리 최소 높이 `control.minTouchTarget` 44·좌우 `spacing.lg` 20(+좌우 inset)·위 `spacing.sm` 12(두 플랫폼 같음) |
55
55
  | 실패 알림 | `Notice tone="danger"` | 본문 맨 위(입력 위), 실패했을 때만, 스크롤 | 본문 항목 사이 `spacing.md` 16 |
56
56
  | 본문 | `TextField` + 설명 | 머리 아래, **스크롤** | 항목 사이 `sheetRecipe.body.gap` `spacing.md` 16. Web body padding `spacing.lg` 20 |
57
57
  | 완료 | footer `Button`(primary) | 본문 아래, **고정**(스크롤 밖) | 높이 `medium` 44. Native 위 `spacing.sm` 12, footer 세로 열이라 꽉 찬 폭(`fullWidth` 불필요). Web 오른쪽 정렬, 버튼 사이 `spacing.sm` 12, padding 0 `spacing.lg` 20 `spacing.lg` 20 |
@@ -64,8 +64,8 @@
64
64
  | 제목 | `Heading level="level3"` | 묶음 맨 위 | 24/32(`heading.level3`), 묶음 안 간격 `layout.contentGap` 16(`Stack gap="md"`) |
65
65
  | 현재 값 | `Text` | 제목 아래, 트리거 위 | `spacing.md` 16 |
66
66
  | 트리거 | `Button`(primary) | 현재 값 아래 | `medium` 44(`control.buttonHeight`) |
67
- | 시트 머리 | `Sheet` header | 시트 위 고정 | Native 최소 높이 `control.minTouchTarget` 44, 제목–닫기 `spacing.sm` 12; Web 여백 `spacing.lg` 20, 제목–닫기 `spacing.md` 16 |
68
- | 선택지 | `RadioGroup`(`presentation="card"`, `accessibilityLabel` = 시트 제목) | 시트 body, 스크롤 | 행 최소 높이 56(`layout.rowHeight.singleLine`), 항목 사이 `spacing.xs` 8(`selectionGroupRecipe`). 안내 문구와는 `Stack gap="md"` 16. 설치한 버전의 Sheet body gap과 중첩 여부를 확인하며 checkout 상태를 npm 게시 증거로 삼지 않는다 |
67
+ | 시트 머리 | `Sheet` 손잡이(+ `showClose`일 때 끝 ×) | 시트 위 고정 | 보이는 제목 없음, `title`은 접근성 이름(2026-10-17 사용자 결정: 시트는 보이는 제목 없이 `title`을 접근성 이름으로만 쓴다). × 머리 최소 높이 `control.minTouchTarget` 44 |
68
+ | 선택지 | `RadioGroup`(`presentation="card"`, `accessibilityLabel` = 시트 `title`) | 시트 body, 스크롤 | 행 최소 높이 56(`layout.rowHeight.singleLine`), 항목 사이 `spacing.xs` 8(`selectionGroupRecipe`). 안내 문구와는 `Stack gap="md"` 16. 설치한 버전의 Sheet body gap과 중첩 여부를 확인하며 checkout 상태를 npm 게시 증거로 삼지 않는다 |
69
69
  | 행동 | `Button` 적용·취소 | 시트 footer, 스크롤 밖 고정 | 간격 `spacing.sm` 12(`sheetRecipe.footer.gap`), Native 위 여백 `spacing.sm` 12 |
70
70
  | 시트 틀 | `Sheet` | 하단, 모달 | Web 최대 폭 640(`sheetRecipe.web.maxWidth`)·최대 높이 90%, Native 가로 여백 `spacing.lg` 20·최대 높이 90%(`maxHeightRatio` 0.9), 하단 안전 영역 추가 |
71
71
 
@@ -199,7 +199,7 @@ Web `gutter`는 [화면 여백과 너비](../tokens/layout.md)처럼 폭 구간
199
199
  | 항목 | Web | Native |
200
200
  | --- | --- | --- |
201
201
  | 트리거 ref | `Button ref` | `View ref collapsable={false}`로 감쌈 |
202
- | 시트 머리 | 여백 `spacing.lg` 20, 제목–닫기 `spacing.md` 16 | 최소 높이 44, 제목–닫기 `spacing.sm` 12 |
202
+ | 시트 머리 | 보이는 제목 없음(시각 숨김 `h2`) | 보이는 제목 없음(접근성 머리글) |
203
203
  | footer | 가로 flex(wrap, 끝 정렬), [취소][적용] | 세로 View, 위 여백 `spacing.sm` 12, [적용] 위 [취소] 아래 |
204
204
  | 초점 | focus trap + `returnFocusRef` | `returnFocusRef` |
205
205
  | 상태 알림 | `role="status"` | live region(Android) + iOS 알림 호출 |
@@ -25,13 +25,13 @@
25
25
 
26
26
  ```text
27
27
  부모 화면의 공개 슬롯
28
- └─ 제목 → 앨범 → 촬영
28
+ └─ 손잡이 → 앨범 → 촬영 (제목은 접근성 이름만)
29
29
  ```
30
30
 
31
31
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
32
32
  | --- | --- | --- | --- |
33
33
  | 바깥 틀 | PhotoSourceSheet 또는 포함 Surface | 부모 화면의 해당 슬롯 | [배치](../components/photo-source-sheet.md#배치)에 따른다 |
34
- | 내용 | PhotoSourceSheet 내부 슬롯 | 제목 → 앨범 → 촬영 | spacing 토큰과 포함 컴포넌트 recipe |
34
+ | 내용 | PhotoSourceSheet 내부 슬롯 | 앨범 → 촬영(보이는 제목 없음(2026-10-17 사용자 결정: 시트는 보이는 제목 없이 `title`을 접근성 이름으로만 쓴다)) | spacing 토큰과 포함 컴포넌트 recipe |
35
35
  | 행동 | Button 또는 공개 콜백 | 내용과 가까운 명시 진입점 | 주 행동 하나, 보조 행동과 구분 |
36
36
 
37
37
  ## 흐름과 상태
@@ -42,7 +42,7 @@
42
42
 
43
43
  | 상태 | 모습 | 포커스·알림 |
44
44
  | --- | --- | --- |
45
- | 기본 | 제목 → 앨범 → 촬영 | 이름·선택 여부를 보조공학에 노출 |
45
+ | 기본 | 앨범 → 촬영(시트 이름 = `labels.title`) | 이름·선택 여부를 보조공학에 노출 |
46
46
  | 진행 중 | 해당 작업 pending, 입력·기존 결과 보존 | 중복 요청 차단, 로딩에 포커스를 옮기지 않음 |
47
47
  | 실패 | 권한 거부·취소는 기존 선택 유지 | 오류 근처 재시도, 필요할 때만 오류 읽기 |
48
48
 
@@ -33,7 +33,7 @@
33
33
  | 영역 | 컴포넌트 | 위치 | 크기·간격 |
34
34
  | --- | --- | --- | --- |
35
35
  | 바깥 틀 | Stack. Web은 제품 화면의 `Container size="reading"` 안 | 화면의 본문 흐름 | gap lg=20px, 폭은 Container를 따른다(Web 최대 `layout.readingMaxWidth` 720) |
36
- | 제목 | `Heading level="level3"` | 선택 위 | `heading` 글자 크기·semibold |
36
+ | 제목 | `Heading level="level3"` | 선택 위 | `heading` 글자 크기·medium(500) |
37
37
  | 선택 | SegmentedControl 2개 | 제목 아래, 연결형 다음 필터형 | 기본 44px 터치 영역, 실제 글자에 따른 높이 |
38
38
  | 결과·메모 | Text · TextField | 선택 컨트롤 아래 | 선택 변경 시 필드 재생성 없음 |
39
39
  | Native 화면 host | ScreenLayout · KeyboardAvoiding | 본문 스크롤과 바깥 키보드 여백 | flex 1, 끝 여백 spacing.md=16 |
@@ -74,3 +74,7 @@ Web은 `@hjmds/react`의 해당 granular entry, Native는 `@hjmds/react-native`
74
74
  | --- | --- | --- |
75
75
  | 배치/테마 | Stack과 HjmProvider | Stack과 HjmNativeProvider |
76
76
  | 큰 글자·좁은 폭 | 줄바꿈·단일 내용 | 같은 순서, OS 화면 검증은 별도 |
77
+
78
+ ## 근거
79
+
80
+ 2026-10-17 사용자 결정("굵기 좀더 줄여"): 모든 글자 굵기를 한 단계 낮췄다. 큰 숫자·선택 상태 600(큰 제목 단계는 같은 날 폐지), 제목·강조 500, label 500, 본문 400이며 정본은 [타이포그래피](../tokens/typography.md)다.
@@ -23,7 +23,7 @@
23
23
  | Icon + `createLucideGlyph` | 의미 이름(`search`·`settings`·`notifications`·`back`)을 Lucide 그림에 연결 | [Icon](../components/icon.md) |
24
24
  | Avatar + `createBlobatarFallback` | 사진이 없을 때 seed로 정해지는 얼굴, `expression: "happy"` | [Avatar](../components/avatar.md) |
25
25
  | TextTransition | 문장이 바뀔 때 `fade`·`rise`·`slide`·`scale` 전환 | [TextTransition](../components/text-transition.md) |
26
- | Heading | 효과 표면 안 제목, 얼굴 묶음 제목(`level2`) | [Heading](../components/heading.md) |
26
+ | Heading | 효과 표면 안 제목, 얼굴 묶음 제목(`level3` + `semanticLevel={2}`, 24) | [Heading](../components/heading.md) |
27
27
  | Switch | 배경 움직임 켜고 끄기(`presentation="row"`) | [Switch](../components/switch.md) |
28
28
  | Button | 다음 장면 | [Button](../components/button.md) |
29
29
  | `ScrollView` + `Container`(Native) | 바깥 틀. 세로 스크롤·위아래 여백, 좌우 gutter | [Container](../components/container.md), [화면 여백과 너비](../tokens/layout.md) |
@@ -46,7 +46,7 @@ Native 화면(ScrollView, 위아래 spacing.lg 20) > Container gutter 16(폭 < 6
46
46
  ┌──── EffectSurface (mesh) ────────────────────┐
47
47
  │ 안쪽 여백 spacing.xl 24(두 플랫폼 같음) │
48
48
  │ mesh (label) │
49
- │ 작은 시작, 새로운 장면. (Heading level2) ↕ lg 20 │
49
+ │ 작은 시작, 새로운 장면. (Heading level3) ↕ lg 20 │
50
50
  │ 설명 (body) │
51
51
  │ [ 이야기 시작하기 ] secondary │
52
52
  └──────────────────────────────────────────────┘
@@ -106,7 +106,7 @@ const sceneKeys = ["intro.scene.welcome", "intro.scene.collect", "intro.scene.re
106
106
  {/* EffectSurface는 여백이 없다. 안쪽 틀: .hero-inner { padding: var(--hjm-space-xl) } */}
107
107
  <div className="hero-inner">
108
108
  <Stack gap="lg">
109
- <Heading level="level2">{t("hero.title")}</Heading>
109
+ <Heading level="level3" semanticLevel={2}>{t("hero.title")}</Heading>
110
110
  <Text>{t("hero.body")}</Text>
111
111
  <Button tone="secondary" onClick={start}>{t("hero.start")}</Button>
112
112
  </Stack>
@@ -148,7 +148,7 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
148
148
  {/* EffectSurface는 여백이 없다. 안쪽 틀만 View padding으로 준다 */}
149
149
  <View style={{ padding: spacing.xl }}>
150
150
  <Stack gap="lg">
151
- <Heading level="level2">{t("hero.title")}</Heading>
151
+ <Heading level="level3" semanticLevel={2}>{t("hero.title")}</Heading>
152
152
  <Text>{t("hero.body")}</Text>
153
153
  <Button tone="secondary" onPress={start}>{t("hero.start")}</Button>
154
154
  </Stack>
@@ -185,6 +185,7 @@ seed·문구·아이콘 세트·얼굴 목록은 제품 소유다. `createBlobat
185
185
  - Blobatar seed에 이름·이메일을 쓰지 않는다. 공개 제품 식별자를 쓴다.
186
186
  - EffectSurface는 화면 위쪽 히어로 하나에만 둔다. 스토리의 네 장 세로 쌓기는 비교용이다.
187
187
  - 2026-10-06 예제 검수에서 Container·Stack·Heading을 두 플랫폼에 맞췄다. 효과 안쪽 여백은 spacing.xxl(32), 얼굴은 64, 제목은 level2로 동일하다. 행은 큰 글자에서 줄바꿈한다.
188
+ - 2026-10-17 사용자 결정("글자 너무 커", "큰 글자 정책은 없애"): 효과 표면·얼굴 묶음 제목 level2(32px)를 HJM 상한인 level3(24px)으로 바꿨다. 문서 단계는 `semanticLevel={2}`로 유지한다.
188
189
  - Native EffectSurface는 AppState·reducedMotion을 반영한다. 백그라운드 중단과 내비게이션으로 가려진 화면의 중단은 다르므로, 마운트된 채 가려지는 제품 화면·목록은 `visible`을 연결한다. Storybook 정지 화면만으로 이 제품 연결을 검증했다고 보고하지 않는다.
189
190
 
190
191
  ## 근거
@@ -59,7 +59,8 @@ host: 남은 높이·safe area·키보드
59
59
  | --- | --- |
60
60
  | [AuthScreenLayout](../components/auth-screen-layout.md) | 필수 props·슬롯·플랫폼 차이 |
61
61
  | [AuthProviderButton](../components/auth-provider-button.md) | 제공자 버튼(이름만) |
62
- | [Top](../components/top.md) | 히어로 제목·설명(제목을 `Text`로 그리지 않는다) |
62
+ | [AuthScreenLayout](../components/auth-screen-layout.md#공개-이름과-import)의 `AuthScreenHero` | 히어로 마크·eyebrow·태그라인·설명(`Top`·`Text`로 조립하지 않는다) |
63
+ | [AuthScreenLayout](../components/auth-screen-layout.md#공개-이름과-import)의 `AuthScreenFooter` | 동의 고지·정책 링크(ghost `Button`으로 만들지 않는다) |
63
64
  | [Notice](../components/notice.md) | 로그인 실패 한 줄 |
64
65
  | [Button](../components/button.md) | 동작·로딩·보조 행동 |
65
66
  | [ScreenLayout](../components/screen-layout.md) | 화면 높이·본문 교체·스크롤 소유 |
@@ -68,27 +69,27 @@ host: 남은 높이·safe area·키보드
68
69
 
69
70
  ```tsx
70
71
  // Web
71
- import { AuthScreenLayout } from "@hjmds/react/auth-screen";
72
+ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react/auth-screen";
72
73
 
73
74
  <AuthScreenLayout
74
75
  mainCard
75
76
  {...(pending ? { pendingLabel: t("auth.pending") } : {})}
76
- hero={<ProductHero />} // 제품 마크·제목·설명
77
+ hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
77
78
  main={<ProviderButtons />} // pending이어도 같은 목록
78
- footer={<ConsentAndPolicyLinks />}
79
+ footer={<AuthScreenFooter notice={t("auth.consent")} links={policyLinks} />} // { label, href | onClick }
79
80
  />
80
81
  ```
81
82
 
82
83
  ```tsx
83
84
  // Native
84
- import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
85
+ import { AuthScreenFooter, AuthScreenHero, AuthScreenLayout } from "@hjmds/react-native/auth-screen";
85
86
 
86
87
  <AuthScreenLayout
87
88
  mainCard
88
89
  {...(pending ? { pendingLabel: t("auth.pending") } : {})}
89
- hero={<ProductHero />} // 제품 마크·제목·설명
90
+ hero={<AuthScreenHero mark={<ProductMark />} title={t("auth.tagline")} description={t("auth.description")} />}
90
91
  main={<ProviderButtons />} // pending이어도 같은 목록
91
- footer={<ConsentAndPolicyLinks />}
92
+ footer={<AuthScreenFooter notice={t("auth.consent")} links={policyLinks} />} // { label, onPress }
92
93
  />
93
94
  ```
94
95
 
@@ -112,4 +113,5 @@ import { AuthScreenLayout } from "@hjmds/react-native/auth-screen";
112
113
  ## 근거
113
114
 
114
115
  - 제공자 목록과 로그인 요청 분리 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
116
+ - 히어로·하단 동의를 `AuthScreenHero`·`AuthScreenFooter`로 연결: 2026-10-17 사용자 피드백("굵기랑 크기 줄여", "간격 줄여") — `Top large` 히어로가 402 폭에서 두 줄 32/40이었고 ghost Button 링크 사이가 42였다. 수치는 [API 배치 규칙](../components/auth-screen-layout.md#배치).
115
117
  - 빈 화면·로그인 필요 스토리 없음: 2026-10-06 해당하지 않는 본문 상태 스토리를 뺐다.
@@ -29,20 +29,20 @@
29
29
  │ ┌ ① 히어로 EffectSurface(mesh+grain) ──┐ │
30
30
  │ │ (안쪽 여백 spacing.xl 24 — HJM 구성 소유) │ │
31
31
  │ │ eyebrow Text tone=muted │ │ Stack gap lg 20
32
- │ │ 제목 Heading level1 (h1) │ │
32
+ │ │ 제목 Heading level3 (h1) 24 │ │
33
33
  │ │ 소개 Text as=p │ │
34
34
  │ │ [ 첫 기록 써보기 ] ← 주 행동(primary) │ │
35
35
  │ └──────────────────────────────────────┘ │
36
36
  │ ↕ spacing.xl 24 │
37
37
  │ ┌ ② 미리보기 Surface padding lg 20 ────┐ │
38
- │ │ Heading level2 │ │ Stack gap md 16
38
+ │ │ Heading level4 (h2) 20 │ │ Stack gap md 16
39
39
  │ │ (저장 직후) "추가했어요" status │ │
40
40
  │ │ List > ListRow 한 줄씩(최신이 위) │ │
41
41
  │ └──────────────────────────────────────┘ │
42
- │ ③ 기능 Heading level2 │
43
- │ ┌ Surface padding lg ─┐ Heading level3 + Text (Stack gap sm 12)
42
+ │ ③ 기능 Heading level4 (h2) │
43
+ │ ┌ Surface padding lg ─┐ Heading level5 (h3) + Text (Stack gap sm 12)
44
44
  │ └─────────────────────┘ × 3, 사이 24 │
45
- │ ④ FAQ Heading level2 │
45
+ │ ④ FAQ Heading level4 (h2) │
46
46
  │ ▸ 질문 1 Collapsible (트리거 최소 44) │
47
47
  │ ▸ 질문 2 │
48
48
  │ ⑤ [ 첫 기록 써보기 ] ← 같은 행동 반복 │ secondary
@@ -67,8 +67,8 @@
67
67
  | 바깥 틀 | Web `main` > `Container size="content" gutter={gutter}` > `Stack gap="xl"`(문서 스크롤) · Native `ScrollView`(`contentContainerStyle={{ paddingVertical: spacing.lg }}`) > `Container size="content" gutter={gutter}` > `Stack gap="xl"` | 화면 전체, 스크롤. 위 안전 영역은 내비게이션 헤더(또는 [TopBar](../components/top-bar.md) `safeAreaTop`)가 맡고 헤더 없이 띄울 때만 제품이 감싼다. 아래 고정 영역은 없다. 바깥에는 입력이 없어 키보드 처리는 ⑥ 시트가 맡는다 | 좌우 gutter: 폭 600 미만 `compact` 16(`layout.pagePadding.compact`), 이상 `regular` 20([화면 여백](../tokens/layout.md)). Native 위아래 `spacing.lg` 20. 직계 요소 사이 `spacing.xl` 24(`layout.sectionGap`) |
68
68
  | ① 히어로 | EffectSurface `descriptor={{ layers: ["mesh","grain"], active: false }}` > Surface `padding="xl"` > Stack `gap="lg"` > Text·Heading·Text·Button | 맨 위 | 요소 사이 `spacing.lg` 20. EffectSurface 안에 Surface `padding="xl"`을 두어 spacing.xl 24를 준다. 실제 양 플랫폼 예제와 같은 공개 구성이다 |
69
69
  | ② 미리보기 | Surface `padding="lg"` > Stack `gap="md"` > Heading·상태 Text·List | 히어로 아래 | 안쪽 20, 요소 사이 `spacing.md` 16, 한 줄 행 최소 56(`layout.rowHeight.singleLine`). 새 기록이 맨 위 |
70
- | ③ 기능 | Heading level2 + Surface `padding="lg"` ×3 > Stack `gap="sm"` | 미리보기 아래 | 카드 안 `spacing.sm` 12, 카드 사이 24 |
71
- | ④ FAQ | Heading level2 + Collapsible × n | 기능 아래 | 트리거 최소 44(`control.minTouchTarget`), 트리거 위아래 `spacing.xs` 8(Web) |
70
+ | ③ 기능 | Heading level4(h2) + Surface `padding="lg"` ×3 > Stack `gap="sm"` | 미리보기 아래 | 카드 안 `spacing.sm` 12, 카드 사이 24 |
71
+ | ④ FAQ | Heading level4(h2) + Collapsible × n | 기능 아래 | 트리거 최소 44(`control.minTouchTarget`), 트리거 위아래 `spacing.xs` 8(Web) |
72
72
  | ⑤ 마지막 행동 | Button `secondary` + Text `tone="muted"` | 맨 아래 | 사이 24 |
73
73
  | ⑥ 입력 | Sheet(Native `keyboardAvoidance` `scrollable`) > Stack `gap="lg"` > Notice(실패 때)·TextField, 제출은 `footer` | 오버레이, 화면 아래 | 시트 좌우 `spacing.lg` 20, TextField 높이 44(`control.fieldHeight`). 아래 안전 영역·키보드는 시트가 맡는다 |
74
74
 
@@ -134,7 +134,7 @@ footer의 저장과 같은 동작을 실행한다. Web에서 IME 조합 확정
134
134
  | [Container](../components/container.md) | 바깥 틀 폭·좌우 여백 |
135
135
  | [화면 여백과 너비](../tokens/layout.md) | gutter 선택(`resolveWindowClass`) |
136
136
  | [EffectSurface](../components/effect-surface.md) | ① 히어로 배경 장식 |
137
- | [Heading](../components/heading.md) | 히어로 level1, 영역 제목 level2, 기능 카드 level3 |
137
+ | [Heading](../components/heading.md) | 히어로 level3(h1, 24), 영역 제목 level4(h2, 20), 기능 카드 level5(h3, 18) |
138
138
  | [Text](../components/text.md) | eyebrow·소개·범위 안내·상태 문구 |
139
139
  | [Button](../components/button.md) | 주 행동, 반복 행동, 시트 제출 |
140
140
  | [Surface](../components/surface.md) | ② 미리보기, ③ 기능 카드 |
@@ -179,20 +179,20 @@ const gutter = resolveWindowClass(window.innerWidth) === "compact" ? "compact" :
179
179
  <EffectSurface descriptor={{ layers: ["mesh", "grain"], seed: "landing", active: false }}><Surface padding="xl">
180
180
  <Stack gap="lg" align="start">
181
181
  <Text tone="muted">{t("landing.eyebrow")}</Text>
182
- <Heading level="level1">{t("landing.title")}</Heading>
182
+ <Heading level="level3" semanticLevel={1}>{t("landing.title")}</Heading>
183
183
  <Text as="p">{t("landing.intro")}</Text>
184
184
  <Button onClick={start}>{t("landing.cta")}</Button>
185
185
  </Stack>
186
186
  </Surface></EffectSurface>
187
187
  <Surface padding="lg"><Stack gap="md">
188
- <Heading level="level2">{t("landing.preview")}</Heading>
188
+ <Heading level="level4" semanticLevel={2}>{t("landing.preview")}</Heading>
189
189
  {saved ? <Text role="status">{t("landing.saved")}</Text> : null}
190
190
  <List label={t("landing.preview")}>{notes.map(n => <ListRow key={n.id} title={n.text} />)}</List>
191
191
  </Stack></Surface>
192
- <Heading level="level2">{t("landing.features")}</Heading>
192
+ <Heading level="level4" semanticLevel={2}>{t("landing.features")}</Heading>
193
193
  {features.map(f => <Surface key={f.id} padding="lg"><Stack gap="sm">
194
- <Heading level="level3">{t(f.titleKey)}</Heading><Text>{t(f.bodyKey)}</Text></Stack></Surface>)}
195
- <Heading level="level2">{t("landing.faq")}</Heading>
194
+ <Heading level="level5" semanticLevel={3}>{t(f.titleKey)}</Heading><Text>{t(f.bodyKey)}</Text></Stack></Surface>)}
195
+ <Heading level="level4" semanticLevel={2}>{t("landing.faq")}</Heading>
196
196
  {faqs.map(q => <Collapsible key={q.id} trigger={t(q.questionKey)}><Text>{t(q.answerKey)}</Text></Collapsible>)}
197
197
  <Stack gap="xl" align="start"><Button tone="secondary" onClick={start}>{t("landing.cta")}</Button>
198
198
  <Text tone="muted">{t("landing.scope")}</Text></Stack>
@@ -229,7 +229,7 @@ const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
229
229
  <Container size="content" gutter={gutter}><Stack gap="xl">
230
230
  <EffectSurface descriptor={{ layers: ["mesh", "grain"], seed: "landing", active: false }}><Surface padding="xl">
231
231
  <Stack gap="lg">
232
- <Heading level="level1">{t("landing.title")}</Heading>
232
+ <Heading level="level3" semanticLevel={1}>{t("landing.title")}</Heading>
233
233
  <Text>{t("landing.intro")}</Text>
234
234
  <Button onPress={start}>{t("landing.cta")}</Button>
235
235
  </Stack>
@@ -366,6 +366,7 @@ import { ScreenLayout } from "@hjmds/react-native/screens";
366
366
 
367
367
  ## 근거
368
368
 
369
+ - 2026-10-17 사용자 결정("글자 너무 커", "큰 글자 정책은 없애"): 히어로 제목 level1 40px·영역 제목 level2 32px가 지나치게 커서 큰 글자 단계를 폐지했다. 히어로는 HJM 상한인 level3 24px(h1), 영역 제목은 level4 20px(h2), 기능 카드는 level5 18px(h3)로 위계를 만든다.
369
370
  - FAQ는 Collapsible(기본·변형): 2026-10-16 사용자 지적 — 변형 스토리가 `selected` 토글 Button 하나로 펼침을 흉내 내 눌림 토글로 읽혔다.
370
371
  - 닫을 때 초안 보존·성공 제출만 한 번 추가: 2026-10-13 실제 예제 검수에서 닫은 뒤 초안 소실과 연속 제출 시 중복 행을 재현했다.
371
372
  - 히어로 padding·시트 저장 footer: 2026-10-06 지침과 예제 정합성 수정.
@@ -28,7 +28,7 @@ ScreenLayout: 제목(고정) → 본문(스크롤) → footer(고정)
28
28
  | --- | --- | --- | --- |
29
29
  | 바깥 틀 | ScreenLayout | host의 남은 높이 | 공통 화면 폭·padding·본문 스크롤 |
30
30
  | 설정 묶음 | CompositionLayout | 본문 | 공통 제목·설명·내용 간격 |
31
- | 설정 값 | Switch | 구성 본문 | 제품 데이터·변경 콜백 |
31
+ | 설정 값 | List(`separator="none"`) > Switch `presentation="row"` | 구성 본문 | 행 사이 gap 없음 — 행 간격 56은 Switch 행 높이·여백이 맡는다. 제품 데이터·변경 콜백 |
32
32
  | 저장 | footer | 화면 하단 | HJM 고정 영역; 제품 저장 요청과 pending 연결 |
33
33
 
34
34
  ## 버튼과 행동 위치
@@ -74,7 +74,8 @@ ScreenLayout: 제목(고정) → 본문(스크롤) → footer(고정)
74
74
  | [Notice](../components/notice.md) | 저장 결과·오류 |
75
75
  | [Skeleton](../components/skeleton.md) | 첫 값 불러오기 |
76
76
  | [Button](../components/button.md) | ③ 하단 저장 |
77
- | [Stack](../components/stack.md) | 세 층 사이, 스위치 목록 |
77
+ | [Stack](../components/stack.md) | 세 층 사이 |
78
+ | [List](../components/list.md) | 스위치 목록(행 사이 여백 없이) |
78
79
  | [화면 여백과 너비](../tokens/layout.md) | `layout.readingMaxWidth`, `layout.pagePadding.compact`·`regular`, `resolveWindowClass` |
79
80
  | [간격](../tokens/spacing.md) | `spacing.xxs`·`xs`·`sm`·`md`·`lg` |
80
81
 
@@ -114,3 +115,4 @@ ScreenLayout이 화면 여백과 스크롤을, CompositionLayout이 묶음 간
114
115
  ## 근거
115
116
 
116
117
  - 설정 초안의 저장 경계 상황 표: 2026-10-13 전수 점검 — 최초 조회와 변경 요청의 상태를 같은 골격으로 설명하던 부분을 구체화했다.
118
+ - 스위치 목록 List·gap 없음: 2026-10-17 사용자 보고("간격이 커") — Stack `gap="md"` 16이 Switch 행 최소 높이 위에 더해져 행 간격이 84였다.
@@ -23,9 +23,9 @@
23
23
  | `typography.label` | 12 / 줄 18 / 500 | `--hjm-type-label-*` | `theme.tokens.typography.label` | 라벨·도움말·small 버튼 글자 |
24
24
  | `typography.body` | 14 / 줄 20 / 400 | `--hjm-type-body-*` | `theme.tokens.typography.body` | 기본 본문·medium 버튼·필드 글자 |
25
25
  | `typography.bodyLarge` | 16 / 줄 24 / 400 | `--hjm-type-body-large-*` | `theme.tokens.typography.bodyLarge` | 읽기용 긴 본문·large 버튼 글자 |
26
- | `typography.title` | 18 / 줄 26 / 600 | `--hjm-type-title-*` | `theme.tokens.typography.title` | 카드 제목(`heading.level5`) |
27
- | `typography.titleLarge` | 20 / 줄 28 / 600 | `--hjm-type-title-large-*` | `theme.tokens.typography.titleLarge` | 큰 구획 제목(`heading.level4`) |
28
- | `typography.heading` | 24 / 줄 32 / 600 | `--hjm-type-heading-*` | `theme.tokens.typography.heading` | 화면의 가장 큰 제목 |
26
+ | `typography.title` | 18 / 줄 26 / 500 | `--hjm-type-title-*` | `theme.tokens.typography.title` | 카드 제목(`heading.level5`) |
27
+ | `typography.titleLarge` | 20 / 줄 28 / 500 | `--hjm-type-title-large-*` | `theme.tokens.typography.titleLarge` | 큰 구획 제목(`heading.level4`) |
28
+ | `typography.heading` | 24 / 줄 32 / 500 | `--hjm-type-heading-*` | `theme.tokens.typography.heading` | 화면의 가장 큰 제목. **HJM 글자 크기의 상한** |
29
29
 
30
30
  굵기 열은 토큰 원본값이다. 프로필 없는 Text는 기본 `emphasis="regular"`(400)를 사용하므로
31
31
  variant만 지정해도 위 굵기가 자동 적용되는 것은 아니다. 예외는 `label`이다. `label`은 프로필이 없어도
@@ -37,11 +37,10 @@ Heading은 제목 단계의 굵기를 쓴다. 두 renderer가 같은 resolver를
37
37
 
38
38
  | 토큰 | 값 | Web CSS 변수 | Native 경로 | 용도 |
39
39
  | --- | --- | --- | --- | --- |
40
- | `heading.level1` | 40 / 줄 48 / 700 | — | `heading.level1` | 랜딩·문서의 대표 제목 |
41
- | `heading.level2` | 32 / 줄 40 / 700 | — | `heading.level2` | 긴 페이지의 큰 구획 제목 |
42
- | `heading.level3` | = `typography.heading` 24 | `--hjm-type-heading-*` | `heading.level3` | 화면 제목 |
40
+ | `heading.level3` | = `typography.heading` 24 | `--hjm-type-heading-*` | `heading.level3` | 화면 제목·랜딩 대표 제목 |
43
41
  | `heading.level4` | = `typography.titleLarge` 20 | `--hjm-type-title-large-*` | `heading.level4` | 구획 제목 |
44
42
  | `heading.level5` | = `typography.title` 18 | `--hjm-type-title-*` | `heading.level5` | 카드 제목 |
43
+ | `heading.level1` · `level2` | = `heading.level3` 24 (사용 중단) | `--hjm-type-heading-*` | `heading.level1` · `level2` | 새 코드에서 쓰지 않는다. 기존 소비 코드가 깨지지 않도록 이름만 남기고 값은 상한 24로 고정했다 |
45
44
 
46
45
  ### 서체·굵기·자간·숫자
47
46
 
@@ -49,12 +48,12 @@ Heading은 제목 단계의 굵기를 쓴다. 두 renderer가 같은 resolver를
49
48
  | --- | --- | --- | --- | --- |
50
49
  | `fontFamily.ui` | Pretendard Variable, Pretendard, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif | `--hjm-font-family-ui` | `fontFamily.ui` | 화면 글자. Native는 `fonts="pretendard"`일 때 굵기별 Pretendard face, 그 전에는 OS 서체. 불러오기는 [글꼴 역할](font-roles.md#기본-서체-pretendard-불러오기필수) |
51
50
  | `fontFamily.code` | ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Cascadia Mono, Consolas, Liberation Mono, monospace | — (`--hjm-font-family-code` fallback) | `fontFamily.code` | 코드 |
52
- | `fontWeight.regular` · `medium` · `semibold` · `bold` · `heavy` | 400 · 500 · 600 · 700 · 800 | `--hjm-font-weight-<이름>` | `fontWeight.<이름>` | Text `emphasis`: `regular` 400 · `medium` 600 · `strong` 700 |
51
+ | `fontWeight.regular` · `medium` · `semibold` · `bold` · `heavy` | 400 · 500 · 600 · 700 · 800 | `--hjm-font-weight-<이름>` | `fontWeight.<이름>` | Text `emphasis`: `regular` 400 · `medium` 500 · `strong` 500 (2026-10-17, 가장 강한 강조 = 제목 굵기). 컴포넌트 recipe는 제목 500, 큰 숫자(Statistic 값)·선택 상태 600을 쓰고 700·800은 브루탈리즘 프리셋 외에 쓰지 않는다 |
53
52
  | `letterSpacing.tight` · `normal` · `wide` | -0.2 · 0 · 0.2 | — | `letterSpacing.<이름>` | 자간 조정이 필요한 제품 고유 글자. HJM 컴포넌트는 자간을 지정하지 않는다 |
54
53
  | `numeric.tabular` · `proportional` | `tabular-nums` · `proportional-nums` | — | `numeric.<이름>` | 바뀌는 숫자(카운터·통계·날짜 칸)는 `tabular`로 폭을 고정 |
55
54
  | `largeTextThreshold` | 1.6 | 루트 `data-large-text="true"` | `largeTextThreshold` | 글자 배율이 이 값 이상이면 큰 글자 배치로 바꾼다 |
56
55
 
57
- Native는 `fonts="pretendard"`일 때 500을 `Pretendard-Medium`, 700을 `Pretendard-Bold` face로 그린다.
56
+ Native는 `fonts="pretendard"`일 때 500을 `Pretendard-Medium`, 600을 `Pretendard-SemiBold` face로 그린다(`resolvePretendardNativeFace`).
58
57
  `@hjmds/react-native/fonts`의 `hjmPretendardFonts`가 400~800 다섯 face를 모두 번들한다. Web은 Pretendard Variable 한 파일이 모든 굵기를 덮는다.
59
58
 
60
59
  Native 경로의 `theme`은 `useHjmNativeTheme()`, 나머지 이름은 `@hjmds/design-contracts/foundations` import다.
@@ -62,9 +61,17 @@ Web 크기 변수는 `rem × --hjm-text-scale`이라 Provider의 `textScale`을
62
61
 
63
62
  ## 근거
64
63
 
65
- 2026-10-17 사용자 결정: `label`은 폼 라벨·섹션 부제목이 본문 대비 약해 500(본문 400과 제목 600 사이)으로 정하고
64
+ 2026-10-17 사용자 결정: `label`은 폼 라벨·섹션 부제목이 본문 대비 약해 500으로 정하고
66
65
  Web·Native 모두 기본으로 그린다. `heading.level1`·`level2`는 Pretendard 800이 큰 크기에서 무겁고 거칠어 700으로
67
- 낮췄다(제목 단계 3~5는 같은 날 600). 브루탈리즘 프리셋은 강한 강조 프리셋이라 level1·2를 800으로 명시해 유지한다.
66
+ 낮췄다(제목 단계 3~5는 같은 날 600). 같은 날 두 번째 검토("굵기 좀더 줄여")에서 한 단계씩 더 낮춰 큰 제목 600,
67
+ 제목 단계 500, 강조 `strong` 500이 됐다. Web CSS는 `--hjm-type-title-weight`·`--hjm-text-emphasis-*` 역할 변수로 같은 값을 읽는다.
68
+ 브루탈리즘 프리셋은 강한 강조 프리셋이라 `typography.heading`(따라서 Heading level1~3) 굵기를 800으로 명시해 유지한다. 크기는 공통 단계를 따른다.
69
+
70
+ 2026-10-17 사용자 결정("글자 너무 커", "큰 글자 정책은 없애"): 큰 글자 단계(display)를 폐지했다. HJM에서 가장 큰 글자는
71
+ `typography.heading` 24 / 줄 32다. 계기는 Native Storybook 서비스 소개 화면의 히어로 제목(level1 40)과 구획 제목(level2 32)이
72
+ 화면에 비해 지나치게 컸던 것이다. `heading.level1`·`level2`의 40·32와 semibold 600 단계를 없애고 두 이름을 `level3`(24/32/500)과
73
+ 같은 값으로 고정했다. 디자인 프로필도 이 상한을 넘는 글자 크기를 `RangeError`로 거부하므로 에디토리얼(56/42/30)·브루탈리즘(48/38/30)의
74
+ 확대 크기도 함께 없앴다. 랜딩 대표 제목은 `level3`, 구획 제목은 `level4`, 카드 제목은 `level5`로 위계를 만든다.
68
75
 
69
76
  ## 쓰는 법
70
77
 
@@ -94,6 +101,7 @@ import { Heading } from "@hjmds/react-native/heading";
94
101
  ## 하지 말 것
95
102
 
96
103
  - `font-size: 15px`, `fontWeight: "900"`처럼 단계 밖 값을 쓰지 않는다.
104
+ - 24보다 큰 글자(히어로·큰 숫자·표지 제목)를 제품 CSS·StyleSheet로 만들지 않는다. 강조는 위계(level3 → level4 → level5)·굵기·여백으로 한다.
97
105
  - 크기를 맞추려고 Heading의 `semanticLevel`을 바꾸지 않는다. 시각 크기는 `level`, 문서 구조는 `semanticLevel`이다.
98
106
  - 글자 크기를 고정해 OS 글자 크기 설정을 막지 않는다(`allowFontScaling={false}`, px 고정 `font-size`).
99
107
  - 줄 높이를 지워 글자를 겹치게 하지 않는다. 한국어 본문은 토큰 줄 높이를 그대로 쓴다.
@@ -117,10 +125,10 @@ import { Heading } from "@hjmds/react-native/heading";
117
125
  프로필 계약에서 확인하고 앱 CSS로 내부 값을 덮지 않는다.
118
126
 
119
127
 
120
- Heading의 다섯 시각 단계는 `defineHjmDesignProfile({ tokens: { heading: ... } })`로
121
- 일괄 지정한다. Web `--hjm-heading-size/line-height/weight`, Native Heading의 Text
128
+ Heading의 시각 단계는 `defineHjmDesignProfile({ tokens: { heading: ... } })`로
129
+ 일괄 지정한다. 어느 단계도 `typography.heading` 24를 넘을 수 없다. Web `--hjm-heading-size/line-height/weight`, Native Heading의 Text
122
130
  metrics로 연결된다. 해당 heading을 담은 Provider에서 가장 가까운 profile을 읽는다.
123
- level3/4/5는 기존 typography heading/titleLarge/title override를 기본으로 병합하며
131
+ level1/2/3은 typography heading, level4/5는 titleLarge/title override를 기본으로 병합하며
124
132
  명시적인 heading 값이 우선한다. 문서의 h1~h6 순서는 `semanticLevel`이 소유한다.
125
133
 
126
134
  제목·본문에 서로 다른 글꼴이 필요하면 [표시·읽기·기술 글자](font-roles.md)를 사용한다.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hjmds/design-contracts",
3
- "version": "1.27.2",
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": {