@hjmds/design-contracts 1.18.0 โ†’ 1.19.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 (244) hide show
  1. package/dist/action-session.d.ts +16 -0
  2. package/dist/action-session.d.ts.map +1 -1
  3. package/dist/action-session.js +18 -0
  4. package/dist/action-session.js.map +1 -1
  5. package/dist/behaviors.d.ts +7 -6
  6. package/dist/behaviors.d.ts.map +1 -1
  7. package/dist/behaviors.js +4 -3
  8. package/dist/behaviors.js.map +1 -1
  9. package/dist/bottom-navigation.d.ts +4 -0
  10. package/dist/bottom-navigation.d.ts.map +1 -1
  11. package/dist/bottom-navigation.js +11 -0
  12. package/dist/bottom-navigation.js.map +1 -1
  13. package/dist/carousel.d.ts +3 -0
  14. package/dist/carousel.d.ts.map +1 -1
  15. package/dist/carousel.js.map +1 -1
  16. package/dist/catalog.d.ts +42 -0
  17. package/dist/catalog.d.ts.map +1 -1
  18. package/dist/component-recipes.d.ts +42 -0
  19. package/dist/component-recipes.d.ts.map +1 -1
  20. package/dist/component-recipes.js +14 -1
  21. package/dist/component-recipes.js.map +1 -1
  22. package/dist/field-group.d.ts +14 -0
  23. package/dist/field-group.d.ts.map +1 -1
  24. package/dist/field-group.js +9 -0
  25. package/dist/field-group.js.map +1 -1
  26. package/dist/form.d.ts.map +1 -1
  27. package/dist/form.js +10 -3
  28. package/dist/form.js.map +1 -1
  29. package/dist/screen-patterns.d.ts +4 -0
  30. package/dist/screen-patterns.d.ts.map +1 -1
  31. package/dist/screen-patterns.js +12 -3
  32. package/dist/screen-patterns.js.map +1 -1
  33. package/dist/toast.d.ts +10 -1
  34. package/dist/toast.d.ts.map +1 -1
  35. package/dist/toast.js +43 -2
  36. package/dist/toast.js.map +1 -1
  37. package/dist/version.d.ts +1 -1
  38. package/dist/version.js +1 -1
  39. package/dist/version.js.map +1 -1
  40. package/docs/action-session.md +2 -0
  41. package/docs/asset.md +7 -0
  42. package/docs/bottom-navigation.md +11 -0
  43. package/docs/brand-boundary.md +3 -3
  44. package/docs/collection-rail.md +4 -0
  45. package/docs/compound-controls.md +8 -3
  46. package/docs/consumer-policy.md +39 -120
  47. package/docs/generated/component-maturity.md +1 -1
  48. package/docs/generated/renderer-evidence.json +3 -3
  49. package/docs/generated/renderer-evidence.md +1 -1
  50. package/docs/generated/showcase-manifest.json +1 -1
  51. package/docs/gravity-letters.md +2 -0
  52. package/docs/grid-reveal.md +2 -0
  53. package/docs/layout.md +2 -2
  54. package/docs/load-more.md +5 -0
  55. package/docs/migration-native-legacy-removal.md +30 -17
  56. package/docs/optional-adapters.md +4 -0
  57. package/docs/screen-patterns.md +23 -1
  58. package/docs/toast.md +41 -2
  59. package/docs/usage/README.md +248 -245
  60. package/docs/usage/SELECTION.md +43 -0
  61. package/docs/usage/STANDARD.md +22 -10
  62. package/docs/usage/components/accordion.md +5 -3
  63. package/docs/usage/components/agreement.md +6 -9
  64. package/docs/usage/components/alert-dialog.md +12 -6
  65. package/docs/usage/components/asset.md +10 -4
  66. package/docs/usage/components/auth-provider-button.md +2 -2
  67. package/docs/usage/components/avatar.md +8 -6
  68. package/docs/usage/components/badge.md +4 -4
  69. package/docs/usage/components/bottom-cta.md +4 -4
  70. package/docs/usage/components/bottom-info.md +2 -2
  71. package/docs/usage/components/bottom-navigation.md +16 -5
  72. package/docs/usage/components/button.md +2 -2
  73. package/docs/usage/components/card.md +1 -1
  74. package/docs/usage/components/carousel.md +12 -4
  75. package/docs/usage/components/chat-message.md +6 -2
  76. package/docs/usage/components/chat-screen.md +5 -5
  77. package/docs/usage/components/checkbox-group.md +1 -1
  78. package/docs/usage/components/checkbox.md +2 -2
  79. package/docs/usage/components/chip.md +2 -2
  80. package/docs/usage/components/code-block.md +3 -4
  81. package/docs/usage/components/collapsible.md +6 -4
  82. package/docs/usage/components/combobox.md +2 -2
  83. package/docs/usage/components/command-palette.md +1 -1
  84. package/docs/usage/components/comment-thread-screen.md +7 -4
  85. package/docs/usage/components/container.md +1 -1
  86. package/docs/usage/components/content-transition.md +2 -2
  87. package/docs/usage/components/context-menu.md +2 -2
  88. package/docs/usage/components/counter-badge.md +3 -3
  89. package/docs/usage/components/data-table.md +1 -1
  90. package/docs/usage/components/date-picker.md +4 -4
  91. package/docs/usage/components/description-list.md +3 -3
  92. package/docs/usage/components/design-system-provider.md +2 -2
  93. package/docs/usage/components/dialog.md +14 -3
  94. package/docs/usage/components/divider.md +2 -2
  95. package/docs/usage/components/editor-screen.md +5 -3
  96. package/docs/usage/components/effect-surface.md +1 -1
  97. package/docs/usage/components/empty-state.md +6 -6
  98. package/docs/usage/components/field.md +5 -3
  99. package/docs/usage/components/file-picker.md +6 -3
  100. package/docs/usage/components/form.md +1 -1
  101. package/docs/usage/components/heading.md +3 -3
  102. package/docs/usage/components/icon-button.md +3 -3
  103. package/docs/usage/components/icon.md +1 -1
  104. package/docs/usage/components/image.md +5 -4
  105. package/docs/usage/components/keyboard-dock.md +1 -1
  106. package/docs/usage/components/layout.md +1 -1
  107. package/docs/usage/components/link.md +2 -2
  108. package/docs/usage/components/list-detail-screen.md +6 -6
  109. package/docs/usage/components/list-row.md +13 -4
  110. package/docs/usage/components/list.md +5 -4
  111. package/docs/usage/components/masonry.md +2 -1
  112. package/docs/usage/components/media-selection-screen.md +11 -5
  113. package/docs/usage/components/mentions.md +2 -2
  114. package/docs/usage/components/menu.md +3 -2
  115. package/docs/usage/components/menubar.md +2 -2
  116. package/docs/usage/components/message-composer.md +3 -3
  117. package/docs/usage/components/moderation-screen.md +3 -2
  118. package/docs/usage/components/notice.md +2 -2
  119. package/docs/usage/components/notification-inbox-screen.md +1 -1
  120. package/docs/usage/components/notification-item.md +2 -2
  121. package/docs/usage/components/number-field.md +3 -3
  122. package/docs/usage/components/otp-field.md +1 -1
  123. package/docs/usage/components/overview-screen.md +1 -1
  124. package/docs/usage/components/permission-screen.md +1 -1
  125. package/docs/usage/components/photo-source-sheet.md +1 -1
  126. package/docs/usage/components/popover.md +2 -2
  127. package/docs/usage/components/profile-screen.md +1 -1
  128. package/docs/usage/components/progress.md +2 -2
  129. package/docs/usage/components/qr-code.md +2 -2
  130. package/docs/usage/components/radio-group.md +1 -2
  131. package/docs/usage/components/radio.md +1 -2
  132. package/docs/usage/components/result.md +1 -1
  133. package/docs/usage/components/saved-items-screen.md +4 -2
  134. package/docs/usage/components/screen-layout.md +4 -3
  135. package/docs/usage/components/search-field.md +1 -1
  136. package/docs/usage/components/search-screen.md +4 -4
  137. package/docs/usage/components/section.md +3 -3
  138. package/docs/usage/components/segmented-control.md +4 -4
  139. package/docs/usage/components/select.md +2 -3
  140. package/docs/usage/components/settings-screen.md +11 -49
  141. package/docs/usage/components/sheet.md +10 -5
  142. package/docs/usage/components/sidebar.md +1 -1
  143. package/docs/usage/components/skeleton.md +1 -1
  144. package/docs/usage/components/skip-nav.md +1 -1
  145. package/docs/usage/components/slider.md +2 -2
  146. package/docs/usage/components/spinner.md +2 -2
  147. package/docs/usage/components/splitter.md +1 -1
  148. package/docs/usage/components/statistic.md +5 -4
  149. package/docs/usage/components/steps.md +2 -2
  150. package/docs/usage/components/surface.md +2 -3
  151. package/docs/usage/components/switch.md +1 -1
  152. package/docs/usage/components/tabs.md +4 -4
  153. package/docs/usage/components/tag.md +2 -2
  154. package/docs/usage/components/tags-input.md +4 -4
  155. package/docs/usage/components/text-area.md +2 -3
  156. package/docs/usage/components/text.md +85 -4
  157. package/docs/usage/components/timeline.md +2 -2
  158. package/docs/usage/components/toast.md +17 -7
  159. package/docs/usage/components/toggle-group.md +3 -3
  160. package/docs/usage/components/tooltip.md +1 -1
  161. package/docs/usage/components/top-bar.md +5 -4
  162. package/docs/usage/components/top.md +3 -3
  163. package/docs/usage/components/transfer-list.md +5 -4
  164. package/docs/usage/components/upload-item.md +3 -3
  165. package/docs/usage/components/virtual-list.md +1 -0
  166. package/docs/usage/compositions/action-feedback.md +42 -6
  167. package/docs/usage/compositions/action-recovery-optimistic.md +6 -1
  168. package/docs/usage/compositions/action-recovery-save.md +19 -23
  169. package/docs/usage/compositions/action-recovery-undo.md +6 -1
  170. package/docs/usage/compositions/activity-status.md +11 -5
  171. package/docs/usage/compositions/collection-detail.md +2 -0
  172. package/docs/usage/compositions/command-records.md +15 -5
  173. package/docs/usage/compositions/common-message.md +4 -2
  174. package/docs/usage/compositions/common-notification.md +5 -3
  175. package/docs/usage/compositions/compound-controls.md +13 -5
  176. package/docs/usage/compositions/content-transition-comparison.md +2 -2
  177. package/docs/usage/compositions/date-entry.md +10 -5
  178. package/docs/usage/compositions/date-time-selection.md +3 -3
  179. package/docs/usage/compositions/design-profile-comparison.md +7 -5
  180. package/docs/usage/compositions/disclosure.md +1 -1
  181. package/docs/usage/compositions/document-resource.md +10 -3
  182. package/docs/usage/compositions/expo-interactions.md +3 -2
  183. package/docs/usage/compositions/family-drawer.md +9 -4
  184. package/docs/usage/compositions/field-group.md +7 -1
  185. package/docs/usage/compositions/floating-action-button.md +18 -18
  186. package/docs/usage/compositions/illustrated-outcome.md +3 -2
  187. package/docs/usage/compositions/input-sheet.md +6 -2
  188. package/docs/usage/compositions/interaction-adapters.md +1 -1
  189. package/docs/usage/compositions/interaction-flow-apply.md +6 -3
  190. package/docs/usage/compositions/interaction-flow-draft.md +5 -2
  191. package/docs/usage/compositions/interaction-flow-search.md +5 -2
  192. package/docs/usage/compositions/live-list.md +7 -5
  193. package/docs/usage/compositions/navigation-bar-collection.md +8 -7
  194. package/docs/usage/compositions/optional-adapters.md +1 -1
  195. package/docs/usage/compositions/origin-dialog.md +5 -5
  196. package/docs/usage/compositions/paper-surface.md +1 -1
  197. package/docs/usage/compositions/photo-source.md +1 -1
  198. package/docs/usage/compositions/purpose-input-comment.md +5 -3
  199. package/docs/usage/compositions/purpose-input-message.md +5 -3
  200. package/docs/usage/compositions/reference-first.md +63 -42
  201. package/docs/usage/compositions/reference-review.md +60 -50
  202. package/docs/usage/compositions/reference-settings.md +60 -50
  203. package/docs/usage/compositions/selection-scope.md +48 -35
  204. package/docs/usage/compositions/stea-order-progress.md +1 -0
  205. package/docs/usage/compositions/stea-otp-verify.md +5 -3
  206. package/docs/usage/compositions/stea-pixel-empty.md +3 -3
  207. package/docs/usage/compositions/time-selection.md +6 -5
  208. package/docs/usage/compositions/upload-recovery.md +10 -1
  209. package/docs/usage/compositions/video-dialog.md +10 -3
  210. package/docs/usage/compositions/web-additions.md +5 -5
  211. package/docs/usage/compositions/web-navigation.md +1 -1
  212. package/docs/usage/screens/common-chat.md +21 -8
  213. package/docs/usage/screens/common-comments.md +13 -1
  214. package/docs/usage/screens/common-inbox.md +19 -6
  215. package/docs/usage/screens/common-login.md +19 -7
  216. package/docs/usage/screens/common-profile.md +22 -9
  217. package/docs/usage/screens/common-saved.md +16 -2
  218. package/docs/usage/screens/common-search.md +24 -4
  219. package/docs/usage/screens/common-settings.md +22 -50
  220. package/docs/usage/screens/common-shell.md +14 -2
  221. package/docs/usage/screens/dashboard.md +52 -109
  222. package/docs/usage/screens/discovery-gallery.md +57 -148
  223. package/docs/usage/screens/flow-collection.md +38 -9
  224. package/docs/usage/screens/flow-editor.md +25 -7
  225. package/docs/usage/screens/flow-media.md +23 -7
  226. package/docs/usage/screens/flow-moderation.md +15 -2
  227. package/docs/usage/screens/flow-onboarding.md +3 -3
  228. package/docs/usage/screens/flow-permission.md +17 -3
  229. package/docs/usage/screens/landing.md +37 -20
  230. package/docs/usage/screens/mockup-studio.md +22 -7
  231. package/docs/usage/screens/notification-settings.md +18 -4
  232. package/docs/usage/screens/product-bento.md +18 -9
  233. package/docs/usage/screens/reference-comparison.md +29 -10
  234. package/docs/usage/tokens/color.md +6 -3
  235. package/docs/usage/tokens/elevation-opacity.md +4 -2
  236. package/docs/usage/tokens/font-roles.md +2 -2
  237. package/docs/usage/tokens/layers.md +3 -3
  238. package/docs/usage/tokens/motion.md +2 -2
  239. package/docs/usage/tokens/radius.md +4 -2
  240. package/docs/usage/tokens/spacing.md +3 -3
  241. package/docs/usage/tokens/theme-studio.md +2 -2
  242. package/docs/usage/tokens/typography-studio.md +14 -8
  243. package/docs/usage/tokens/typography.md +15 -5
  244. package/package.json +1 -1
@@ -4,7 +4,7 @@
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
6
  - ์ ์šฉ: 1.12.1
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: `showcase/web/src/patterns/DiscoveryGallery.stories.tsx`, `showcase/native/src/DiscoveryGallery.stories.tsx`, `showcase/shared/discovery-gallery.ts`, `src/base-recipes.ts`(`buttonRecipe.states.selected`), `src/grid.ts`
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/๊ฒ€์ƒ‰/์ž‘ํ’ˆ ํƒ์ƒ‰`
10
10
 
@@ -28,7 +28,7 @@
28
28
  โ”‚ ์ž‘ํ’ˆ ๊ฒ€์ƒ‰ (๋ณด์ด๋Š” ๋ผ๋ฒจ) โ”‚
29
29
  โ”‚ [๐Ÿ” ๋ชจ๋ฐ”์ผ, ์ž‘์—… ๊ณต๊ฐ„ ........... (x)] โ”‚ SearchField ์ตœ์†Œ 44
30
30
  โ”‚ โ†• spacing.xl 24 โ”‚
31
- โ”‚ โ‘ก [์ „์ฒด] [์›น์‚ฌ์ดํŠธ] [๋ชจ๋ฐ”์ผ] [๋Œ€์‹œ๋ณด๋“œ] โ”‚ ghost, ์„ ํƒ๋œ ๊ฒƒ๋งŒ selected
31
+ โ”‚ โ‘ก [์ „์ฒด] [์›น์‚ฌ์ดํŠธ] [๋ชจ๋ฐ”์ผ] [๋Œ€์‹œ๋ณด๋“œ] โ”‚ SegmentedControl pills, ๋‹จ์ผ ์„ ํƒ
32
32
  โ”‚ โ‘ข [์ •๋ ฌ: ์ธ๊ธฐ์ˆœ] [์ €์žฅํ•œ ์ž‘ํ’ˆ 2] โ”‚ secondary(์ •๋ ฌ) ยท ghost+selected(์ €์žฅ๋งŒ ๋ณด๊ธฐ)
33
33
  โ”‚ โ‘ฃ "N๊ฐœ์˜ ์ž‘ํ’ˆ" (์ƒํƒœ ์•Œ๋ฆผ) โ”‚
34
34
  โ”‚ (์˜ค๋ฅ˜๋ฉด ์—ฌ๊ธฐ Notice danger + [๋‹ค์‹œ ์‹œ๋„]) โ”‚
@@ -76,9 +76,9 @@
76
76
  | ํ–‰๋™ | ์ปดํฌ๋„ŒํŠธยทtone | ์œ„์น˜ | ๊ฐœ์ˆ˜ยท์ˆœ์„œ |
77
77
  | --- | --- | --- | --- |
78
78
  | ๊ฒ€์ƒ‰ | SearchField `onValueChange` | โ‘  ๋งจ ์•„๋ž˜ | 1. ์ž…๋ ฅ ์ฆ‰์‹œ ๊ฑฐ๋ฅธ๋‹ค |
79
- | ์นดํ…Œ๊ณ ๋ฆฌ | Button `ghost`, ์„ ํƒ๋œ ๊ฒƒ๋งŒ `selected` | โ‘ก | ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜, "์ „์ฒด"๊ฐ€ ๋งจ ์•ž |
79
+ | ์นดํ…Œ๊ณ ๋ฆฌ | SegmentedControl `presentation="pills"` | โ‘ก | ์นดํ…Œ๊ณ ๋ฆฌ ์ˆ˜, "์ „์ฒด"๊ฐ€ ๋งจ ์•ž |
80
80
  | ์ •๋ ฌ ์ „ํ™˜ | Button `secondary`, ๋ผ๋ฒจ์— ํ˜„์žฌ ๊ฐ’(`t("gallery.sort", { value: t(sortKey[sort]) })`) | โ‘ข ์ฒซ ์ž๋ฆฌ | 1. ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค ์ธ๊ธฐ์ˆœ โ†” ์ตœ์‹ ์ˆœ |
81
- | ์ €์žฅํ•œ ์ž‘ํ’ˆ๋งŒ | SegmentedControl `presentation="pills"`, ๋ผ๋ฒจ์— ๊ฐœ์ˆ˜ | โ‘ข ์ •๋ ฌ ๋’ค | 1 |
81
+ | ์ €์žฅํ•œ ์ž‘ํ’ˆ๋งŒ | Button `ghost` + `selected`, ๋ผ๋ฒจ์— ๊ฐœ์ˆ˜ | โ‘ข ์ •๋ ฌ ๋’ค | 1 |
82
82
  | ์ƒ์„ธ ๋ณด๊ธฐ | Button `ghost` | ์นด๋“œ ๋งจ ์•„๋ž˜ ๋ฒ„ํŠผ ์ค„ ์ฒซ ์ž๋ฆฌ | ์นด๋“œ๋งˆ๋‹ค 1. `aria-label`/`accessibilityLabel`์— ์ž‘ํ’ˆ ์ œ๋ชฉ ํฌํ•จ |
83
83
  | ์นด๋“œ ์ €์žฅ | Button `secondary` + `selected`(ํ† ๊ธ€) | ์นด๋“œ ๋ฒ„ํŠผ ์ค„ ๋‘˜์งธ ์ž๋ฆฌ | ์นด๋“œ๋งˆ๋‹ค 1. ๋ผ๋ฒจ "์ €์žฅ" โ†” "์ €์žฅ๋จ", ์ ‘๊ทผ์„ฑ ์ด๋ฆ„์— ์ œ๋ชฉ๊ณผ ์ €์žฅ/์ €์žฅ ์ทจ์†Œ |
84
84
  | ์‹œํŠธ ์ €์žฅ(์‹œํŠธ์˜ ์ฃผ ํ–‰๋™) | Button ๊ธฐ๋ณธ primary + `selected` | โ‘ฆ Sheet `footer`(Native ๊ฝ‰ ์ฐฌ ํญ, Web ๋ ์ •๋ ฌ) | 1. ๋ผ๋ฒจ "์ปฌ๋ ‰์…˜์— ์ €์žฅ" โ†” "์ €์žฅ ์ทจ์†Œ". ์‹œํŠธ๋ฅผ ๋‹ซ์ง€ ์•Š๋Š” ํ† ๊ธ€์ด๋‹ค |
@@ -87,7 +87,7 @@
87
87
  | ํŒŒ๊ดด ํ–‰๋™ | โ€” | โ€” | ์—†์Œ(์ €์žฅ ์ทจ์†Œ๋Š” ๊ฐ™์€ ๋ฒ„ํŠผ์˜ ํ† ๊ธ€) |
88
88
 
89
89
  primary๋Š” ํ™”๋ฉด์—์„œ ๋นˆ ๊ฒฐ๊ณผ ๋ณต๊ตฌ ํ•˜๋‚˜, ์‹œํŠธ ์•ˆ์—์„œ ์ €์žฅ ํ•˜๋‚˜๋‹ค. ์‹œํŠธ ์•ˆ ํ–‰๋™์€ ๊ทธ ํ‘œ๋ฉด ์•ˆ์—์„œ ์„ธ๊ณ , `selected`๋ฅผ ์ค€ ๋ฒ„ํŠผ์€
90
- tone๊ณผ ๊ด€๊ณ„์—†์ด ์„ ํƒ ๋ชจ์–‘(๋ฐฐ๊ฒฝ `bg`, ๊ธ€์žยทํ…Œ๋‘๋ฆฌ `contentBrand`)์œผ๋กœ ์น ํ•ด์ ธ ์„ธ์ง€ ์•Š๋Š”๋‹ค([Button](../components/button.md) "๊ผญ ์ง€ํ‚ฌ ๊ฒƒ").
90
+ tone๊ณผ ๊ด€๊ณ„์—†์ด ์„ ํƒ ๋ชจ์–‘(๋ฐฐ๊ฒฝ `bg`, ๊ธ€์ž `contentBrand`, ๋ณ„๋„ ์„ ํƒ ํ…Œ๋‘๋ฆฌ ์—†์Œ)์œผ๋กœ ์น ํ•ด์ ธ ์„ธ์ง€ ์•Š๋Š”๋‹ค([Button](../components/button.md) "๊ผญ ์ง€ํ‚ฌ ๊ฒƒ").
91
91
  ์นด๋“œ ์•ˆ ๋ฒ„ํŠผ์€ [๋ณด๊ธฐ][์ €์žฅ] ์ˆœ์„œ๋กœ ์‹œ์ž‘ ์ •๋ ฌ, `wrap`์ด๋‹ค. ์นด๋“œ ์ „์ฒด๋ฅผ ๋ˆ„๋ฆ„ ๋Œ€์ƒ์œผ๋กœ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค โ€” ์นด๋“œ ์•ˆ์— ๋ฒ„ํŠผ์ด ๋‘˜์ด๋ผ
92
92
  ๊ฒน์นœ ๋ˆ„๋ฆ„ ๋Œ€์ƒ์ด ๋œ๋‹ค.
93
93
 
@@ -96,9 +96,9 @@ tone๊ณผ ๊ด€๊ณ„์—†์ด ์„ ํƒ ๋ชจ์–‘(๋ฐฐ๊ฒฝ `bg`, ๊ธ€์žยทํ…Œ๋‘๋ฆฌ `contentBrand`
96
96
  | ์ƒํƒœ | ํ™”๋ฉด ๋ชจ์Šต | ํ–‰๋™ |
97
97
  | --- | --- | --- |
98
98
  | ๊ธฐ๋ณธ | "์ „์ฒด"ยท์ธ๊ธฐ์ˆœ, ์ „์ฒด ์นด๋“œ, ๊ฒฐ๊ณผ ์ˆ˜ | ๊ฒ€์ƒ‰ยทํ•„ํ„ฐยท์ •๋ ฌยท์ €์žฅยท๋ณด๊ธฐ |
99
- | ๋กœ๋”ฉ | ์Šคํ† ๋ฆฌ์— ์—†์Œ. โ‘ ~โ‘ข์€ ๊ทธ๋Œ€๋กœ, โ‘ค ์ž๋ฆฌ์— ๊ฐ™์€ Grid๋กœ ์นด๋“œ 3๊ฐœ ๋ชจ์–‘์˜ [Skeleton](../components/skeleton.md)(AspectRatio 1.6 ์•ˆ `shape="block"` `height="100%"` + ์ œ๋ชฉ `shape="text"` 60%). ๋กœ๋”ฉ ์‚ฌ์‹ค์€ ์˜์—ญ ๋‹จ์œ„๋กœ ํ•œ ๋ฒˆ(Web Grid `aria-busy`ยท`aria-label`, Native ์ฒซ Skeleton `accessibilityLabel`). ๊ฒ€์ƒ‰ ์ค‘์ด๋ฉด SearchField Web `loading` ยท Native `busy` + `busyLabel` | ๊ฒ€์ƒ‰ยทํ•„ํ„ฐ๋Š” ๊ทธ๋Œ€๋กœ |
100
- | ๋นˆ | โ‘ค ์ž๋ฆฌ์— EmptyState(`t("gallery.empty.title")`, ์„ค๋ช…) | [ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”] |
101
- | ์˜ค๋ฅ˜ | ์Šคํ† ๋ฆฌ์— ์—†์Œ. โ‘ ~โ‘ข์€ ๋‚จ๊ธฐ๊ณ  โ‘ฃ ์•„๋ž˜ [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` โ€” ๊ธฐ๋ณธ `none`). ๋ฌธ๊ตฌ๋Š” ์›์ธ๋ณ„ ํ‚ค(`errorKey`: ๋„คํŠธ์›Œํฌ `gallery.error.network`, ์„œ๋ฒ„ `gallery.error.server`). ์ด์ „ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด โ‘ค๋ฅผ ์œ ์ง€ํ•˜๊ณ , ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด EmptyState๋ฅผ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค(๋นˆ ๊ฒฐ๊ณผ์™€ ์‹คํŒจ๋ฅผ ์„ž์ง€ ์•Š๋Š”๋‹ค). ๋‹ค์‹œ ์‹œ๋„๊ฐ€ ๋˜ ์‹คํŒจํ•˜๋ฉด ๊ฐ™์€ Notice๋ฅผ ๊ฐ™์€ ์ž๋ฆฌ์— ๋‘๊ณ  ๋ฒ„ํŠผ `loading`๋งŒ ํ‘ผ๋‹ค | Notice `action` ๋‹ค์‹œ ์‹œ๋„ 1๊ฐœ |
99
+ | ๋กœ๋”ฉ | ๋™๊ธฐ ์Šคํ† ๋ฆฌ์— ์—†์Œ. ์ œํ’ˆ ์ตœ์ดˆ ์กฐํšŒ๋Š” ScreenLayout ๋ณธ๋ฌธ ์ค‘์•™ spinner, ์žฌ์กฐํšŒ๋Š” ๊ธฐ์กด ๊ฒฐ๊ณผ์™€ ๊ฒ€์ƒ‰ยทํ•„ํ„ฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค | ๊ฐ™์€ ์š”์ฒญ ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€ |
100
+ | ๋นˆ | ์„ฑ๊ณต ์‘๋‹ต 0๊ฑด์ผ ๋•Œ๋งŒ ScreenLayout ๋ณธ๋ฌธ ์ค‘์•™ ๋นˆ ์ƒํƒœ(`t("gallery.empty.title")`, ์„ค๋ช…) | [ํ•„ํ„ฐ ์ดˆ๊ธฐํ™”] |
101
+ | ์˜ค๋ฅ˜ | ์Šคํ† ๋ฆฌ์— ์—†์Œ. โ‘ ~โ‘ข์€ ๋‚จ๊ธฐ๊ณ  โ‘ฃ ์•„๋ž˜ [Notice](../components/notice.md) `tone="danger"`(Native `announcement="assertive"` โ€” ๊ธฐ๋ณธ `none`). ๋ฌธ๊ตฌ๋Š” ์›์ธ๋ณ„ ํ‚ค(`errorKey`: ๋„คํŠธ์›Œํฌ `gallery.error.network`, ์„œ๋ฒ„ `gallery.error.server`). ์ด์ „ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด โ‘ค๋ฅผ ์œ ์ง€ํ•˜๊ณ , ์ฒซ ์กฐํšŒ ์‹คํŒจ๋ฉด ScreenLayout ์ค‘์•™ ์˜ค๋ฅ˜์™€ ์žฌ์‹œ๋„๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  EmptyState๋ฅผ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค(๋นˆ ๊ฒฐ๊ณผ์™€ ์‹คํŒจ๋ฅผ ์„ž์ง€ ์•Š๋Š”๋‹ค). ๋‹ค์‹œ ์‹œ๋„๊ฐ€ ๋˜ ์‹คํŒจํ•˜๋ฉด ๊ฐ™์€ Notice๋ฅผ ๊ฐ™์€ ์ž๋ฆฌ์— ๋‘๊ณ  ๋ฒ„ํŠผ `loading`๋งŒ ํ‘ผ๋‹ค | Notice `action` ๋‹ค์‹œ ์‹œ๋„ 1๊ฐœ |
102
102
  | ์ €์žฅ ํ† ๊ธ€ | ์นด๋“œ ๋ฒ„ํŠผ์ด `selected`๋กœ ๋ฐ”๋€Œ๊ณ  โ‘ข ์ €์žฅ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚œ๋‹ค. ์ €์žฅ๋งŒ ๋ณด๊ธฐ ์ค‘ ์ €์žฅ ์ทจ์†Œํ•˜๋ฉด ์นด๋“œ๊ฐ€ ์‚ฌ๋ผ์ง€๊ณ  ๊ฒฐ๊ณผ ์ˆ˜๊ฐ€ ์ฝํžŒ๋‹ค | โ€” |
103
103
  | ์ƒ์„ธ ์—ด๋ฆผ | ์‹œํŠธ๊ฐ€ ์˜ฌ๋ผ์˜ค๊ณ  ๊ฐ™์€ ๊ทธ๋ฆผ์„ ํฌ๊ฒŒ ๋ณด์ธ๋‹ค. ์‹œํŠธ์—์„œ ์ €์žฅํ•˜๋ฉด ์นด๋“œ ์ƒํƒœ๋„ ๊ฐ™์ด ๋ฐ”๋€๋‹ค | ์ €์žฅยท๋‹ซ๊ธฐ |
104
104
 
@@ -115,7 +115,7 @@ tone๊ณผ ๊ด€๊ณ„์—†์ด ์„ ํƒ ๋ชจ์–‘(๋ฐฐ๊ฒฝ `bg`, ๊ธ€์žยทํ…Œ๋‘๋ฆฌ `contentBrand`
115
115
  | [Grid](../components/grid.md) | โ‘ค ์นด๋“œ ๊ฒฉ์ž |
116
116
  | [AspectRatio](../components/aspect-ratio.md) ยท [Image](../components/image.md) | ์ œํ’ˆ์˜ ์‹ค์ œ ์ธ๋„ค์ผ, ๋กœ๋”ฉ Skeleton ํ‹€ |
117
117
  | [EmptyState](../components/empty-state.md) | ๊ฒฐ๊ณผ ์—†์Œ |
118
- | [Skeleton](../components/skeleton.md) | ๋กœ๋”ฉ |
118
+ | [Skeleton](../components/skeleton.md) | ๋กœ๋”ฉ | ๋™๊ธฐ ์Šคํ† ๋ฆฌ์— ์—†์Œ. ์ œํ’ˆ ์ตœ์ดˆ ์กฐํšŒ๋Š” ScreenLayout ๋ณธ๋ฌธ ์ค‘์•™ spinner, ์žฌ์กฐํšŒ๋Š” ๊ธฐ์กด ๊ฒฐ๊ณผ์™€ ๊ฒ€์ƒ‰ยทํ•„ํ„ฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค | ๊ฐ™์€ ์š”์ฒญ ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€ |
119
119
  | [Notice](../components/notice.md) | ์˜ค๋ฅ˜ |
120
120
  | [Sheet](../components/sheet.md) | โ‘ฆ ์ƒ์„ธ |
121
121
  | [ํ™”๋ฉด ์—ฌ๋ฐฑ๊ณผ ๋„ˆ๋น„](../tokens/layout.md) | gutterยท์ตœ๋Œ€ ํญยทbreakpoint |
@@ -123,155 +123,61 @@ tone๊ณผ ๊ด€๊ณ„์—†์ด ์„ ํƒ ๋ชจ์–‘(๋ฐฐ๊ฒฝ `bg`, ๊ธ€์žยทํ…Œ๋‘๋ฆฌ `contentBrand`
123
123
 
124
124
  ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
125
125
 
126
- ์นดํ…Œ๊ณ ๋ฆฌยท์ž‘ํ’ˆยท์ธ๋„ค์ผยท๋ฌธ๊ตฌ๋Š” ์ œํ’ˆ ์†Œ์œ ๋‹ค. `Thumbnail`์€ ์ œํ’ˆ ์ปดํฌ๋„ŒํŠธ ์ž๋ฆฌ๋‹ค(AspectRatio 1.6 + Image). ์ƒํƒœ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋ฌธ๊ตฌ๋Š”
127
- ์ƒํƒœโ†’ํ‚ค ์ƒ์ˆ˜ ํ‘œ๋กœ ๊ณ ๋ฅธ๋‹ค. ์•„๋ž˜ ๋‘ ์˜ˆ๋Š” ์„ค์น˜ ๋ฒ„์ „ ํƒ€์ž…์œผ๋กœ ๊ฒ€์‚ฌํ–ˆ๋‹ค(`query`ยท`results`ยท`selected` ๋“ฑ ์ƒํƒœ ๊ฐ’๊ณผ ํ•ธ๋“ค๋Ÿฌ๋Š” ์ œํ’ˆ์ด ๋‘”๋‹ค).
126
+ ๋™๊ธฐ ๊ฒ€์ƒ‰ยท์นดํ…Œ๊ณ ๋ฆฌยท๋…๋ฆฝ ์ €์žฅ ํ† ๊ธ€ยท์ƒ์„ธ Sheet์˜ ์‹คํ–‰ ์›๋ณธ์€
127
+ `showcase/web/src/patterns/DiscoveryGallery.stories.tsx`์™€
128
+ `showcase/native/src/DiscoveryGallery.stories.tsx`๋‹ค. ์ด ์ด๋ฆ„์˜ ๊ณต๊ฐœ ํ™”๋ฉด API๋Š” ์—†๋‹ค.
129
+ ์ œํ’ˆ์˜ ๋น„๋™๊ธฐ ๊ฒ€์ƒ‰์€ [๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์™€ ํ•„ํ„ฐ](common-search.md)์˜ SearchScreen์„ ๋จผ์ € ๊ฒ€ํ† ํ•œ๋‹ค.
130
+ ๊ฐค๋Ÿฌ๋ฆฌ ๊ฒฉ์ž๊ฐ€ ํ•„์š”ํ•˜๋ฉด ๊ธฐ์กด GridยทSheet์™€ ์•„๋ž˜ ScreenLayout ์ƒํƒœ๋ฅผ ๊ฒฐํ•ฉํ•œ๋‹ค.
128
131
 
129
132
  ```tsx
130
133
  // Web
131
- import { AspectRatio, Container, Grid, Stack, Text } from "@hjmds/react/layout";
132
- import { Heading } from "@hjmds/react/heading";
134
+ import { ScreenLayout } from "@hjmds/react/screens";
133
135
  import { Button } from "@hjmds/react/actions";
134
- import { SegmentedControl } from "@hjmds/react/selection";
135
- import { SearchField } from "@hjmds/react/forms";
136
- import { Sheet } from "@hjmds/react/overlays";
137
- import { EmptyState, Notice, Skeleton } from "@hjmds/react/feedback";
138
- import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
139
-
140
- type Category = "all" | "web" | "mobile" | "dashboard";
141
- type Sort = "popular" | "latest";
142
- type LoadError = "network" | "server";
143
- const categories: readonly Category[] = ["all", "web", "mobile", "dashboard"];
144
- const categoryKey = { all: "gallery.category.all", web: "gallery.category.web", mobile: "gallery.category.mobile", dashboard: "gallery.category.dashboard" } as const satisfies Record<Category, string>;
145
- const sortKey = { popular: "gallery.sort.popular", latest: "gallery.sort.latest" } as const satisfies Record<Sort, string>;
146
- const errorKey = { network: "gallery.error.network", server: "gallery.error.server" } as const satisfies Record<LoadError, string>;
147
- const gutter = resolveWindowClass(window.innerWidth) === "compact" ? "compact" : "regular";
148
- const gridProps = { columns: { compact: 1, medium: 2, expanded: 3 }, gap: { compact: "xl" }, minColumnWidth: { compact: 320 } } as const;
149
-
150
- <main><Container size="content" gutter={gutter}><Stack gap="xl">
151
- <Stack gap="md">
152
- <Text tone="brand">{t("gallery.eyebrow")}</Text>
153
- <Heading level="level3" semanticLevel={1}>{t("gallery.title")}</Heading>
154
- <Text as="p">{t("gallery.intro")}</Text>
155
- <SearchField label={t("gallery.search")} clearLabel={t("gallery.clear")} loading={loading} value={query} onValueChange={setQuery} />
156
- </Stack>
157
- <SegmentedControl label={t("gallery.category")} presentation="pills"
158
- items={categories.map(id => ({ value: id, label: t(categoryKey[id]) }))} value={category} onValueChange={setCategory} />
159
- <Stack axis="inline" wrap gap="sm">
160
- <Button tone="secondary" onClick={toggleSort}>{t("gallery.sort", { value: t(sortKey[sort]) })}</Button>
161
- <Button tone="ghost" selected={savedOnly} onClick={() => setSavedOnly(v => !v)}>{t("gallery.savedOnly", { count: savedCount })}</Button>
162
- </Stack>
163
- <Text role="status">{loading ? "" : t("gallery.count", { count: results.length })}</Text>
164
- {error ? <Notice tone="danger" title={t(errorKey[error])}
165
- action={<Button tone="secondary" size="small" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}
166
- {loading && results.length === 0 ? (
167
- <Grid {...gridProps} aria-busy="true" aria-label={t("gallery.loading")}>
168
- {[0, 1, 2].map(i => <Stack key={i} gap="sm"><AspectRatio ratio={1.6} aria-hidden="true"><Skeleton shape="block" height="100%" /></AspectRatio><Skeleton shape="text" width="60%" /></Stack>)}
169
- </Grid>
170
- ) : results.length ? (
171
- <Grid {...gridProps}>
172
- {results.map(item => <article key={item.id}><Stack gap="sm">
173
- <Thumbnail item={item} />
174
- <Text variant="label">{item.title}</Text>
175
- <Text tone="muted">{t("gallery.meta", { author: item.author, category: item.categoryLabel })}</Text>
176
- <Stack axis="inline" wrap gap="sm">
177
- <Button tone="ghost" aria-label={t("gallery.viewNamed", { title: item.title })} onClick={() => setSelected(item)}>{t("gallery.details")}</Button>
178
- <Button tone="secondary" selected={isSaved(item)} onClick={() => toggleSaved(item.id)}
179
- aria-label={t(isSaved(item) ? "gallery.unsaveNamed" : "gallery.saveNamed", { title: item.title })}>
180
- {t(isSaved(item) ? "gallery.saved" : "gallery.save")}</Button>
181
- </Stack>
182
- </Stack></article>)}
183
- </Grid>
184
- ) : error ? null : <EmptyState title={t("gallery.empty.title")} description={t("gallery.empty.body")}
185
- action={<Button onClick={reset}>{t("gallery.reset")}</Button>} />}
186
- <Sheet open={selected !== null} onOpenChange={o => { if (!o) setSelected(null); }}
187
- title={selected?.title ?? t("gallery.detail")} closeLabel={t("common.close")}
188
- footer={selected ? <Button selected={isSaved(selected)} onClick={() => toggleSaved(selected.id)}
189
- aria-label={t(isSaved(selected) ? "gallery.unsaveNamed" : "gallery.saveNamed", { title: selected.title })}>
190
- {t(isSaved(selected) ? "gallery.unsave" : "gallery.saveToCollection")}</Button> : null}>
191
- {selected ? <Stack gap="lg">
192
- <Thumbnail item={selected} />
193
- <Text>{t("gallery.meta", { author: selected.author, category: selected.categoryLabel })}</Text>
194
- <Text as="p">{selected.description}</Text>
195
- </Stack> : null}
196
- </Sheet>
197
- </Stack></Container></main>
136
+ import { Notice } from "@hjmds/react/feedback";
137
+
138
+ // hasLoaded๋Š” ์„ฑ๊ณตํ•œ ์กฐํšŒ๊ฐ€ ์žˆ๋Š”์ง€๋‹ค. 0๊ฑด ์„ฑ๊ณต๋„ true์ด๋ฉฐ ๋ฐฐ์—ด ๊ธธ์ด๋กœ ๋Œ€์‹ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
139
+ <>
140
+ <ScreenLayout title={t("gallery.title")} actions={categoryControls}
141
+ notice={<>{searchAndSort}{hasLoaded && error ? <Notice tone="danger" title={t(errorKey[error])}
142
+ action={<Button tone="secondary" loading={retrying} onClick={retry}>{t("common.retry")}</Button>} /> : null}</>}
143
+ state={!hasLoaded
144
+ ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("gallery.loading") }
145
+ : results.length === 0 ? { kind: "empty", title: t("gallery.empty.title") } : { kind: "ready" }}
146
+ stateAction={!hasLoaded && error ? <Button loading={retrying} onClick={retry}>{t("common.retry")}</Button>
147
+ : hasLoaded && results.length === 0 ? <Button onClick={reset}>{t("gallery.reset")}</Button> : null}>
148
+ {galleryGrid}
149
+ </ScreenLayout>
150
+ {detailSheet}
151
+ </>
198
152
  ```
199
153
 
200
154
  ```tsx
201
155
  // Native
202
- import { ScrollView, useWindowDimensions } from "react-native";
203
- import { spacing } from "@hjmds/design-contracts/foundations";
204
- import { resolveWindowClass } from "@hjmds/design-contracts/responsive";
205
- import { AspectRatio, Container, Grid, Stack, Text } from "@hjmds/react-native/primitives";
206
- import { Heading } from "@hjmds/react-native/heading";
156
+ import { ScreenLayout } from "@hjmds/react-native/screens";
207
157
  import { Button } from "@hjmds/react-native/actions";
208
- import { SegmentedControl } from "@hjmds/react-native/inputs";
209
- import { SearchField } from "@hjmds/react-native/inputs";
210
- import { Sheet } from "@hjmds/react-native/overlays";
211
- import { EmptyState, Notice, Skeleton } from "@hjmds/react-native/feedback";
212
-
213
- // categoriesยทcategoryKeyยทsortKeyยทerrorKey๋Š” Web๊ณผ ๊ฐ™๋‹ค.
214
- const { width } = useWindowDimensions();
215
- const gutter = resolveWindowClass(width) === "compact" ? "compact" : "regular";
216
- const gridProps = { columns: { compact: 1, medium: 2, expanded: 3 }, gap: { compact: "xl" }, minColumnWidth: { compact: 320 } } as const;
217
-
218
- <ScrollView automaticallyAdjustKeyboardInsets keyboardShouldPersistTaps="handled"
219
- contentContainerStyle={{ paddingVertical: spacing.lg }}>
220
- <Container size="content" gutter={gutter}><Stack gap="xl">
221
- <Stack gap="md">
222
- <Text tone="brand">{t("gallery.eyebrow")}</Text>
223
- <Heading level="level3" semanticLevel={1}>{t("gallery.title")}</Heading>
224
- <Text>{t("gallery.intro")}</Text>
225
- <SearchField label={t("gallery.search")} clearLabel={t("gallery.clear")} busy={loading} busyLabel={t("gallery.searching")}
226
- value={query} onValueChange={setQuery} />
227
- </Stack>
228
- <SegmentedControl label={t("gallery.category")} presentation="pills"
229
- items={categories.map(id => ({ value: id, label: t(categoryKey[id]) }))} value={category} onValueChange={setCategory} />
230
- <Stack axis="inline" wrap gap="sm">
231
- <Button tone="secondary" onPress={toggleSort}>{t("gallery.sort", { value: t(sortKey[sort]) })}</Button>
232
- <Button tone="ghost" selected={savedOnly} onPress={() => setSavedOnly(v => !v)}>{t("gallery.savedOnly", { count: savedCount })}</Button>
233
- </Stack>
234
- <Text accessibilityLiveRegion="polite">{loading ? "" : t("gallery.count", { count: results.length })}</Text>
235
- {error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
236
- action={<Button tone="secondary" size="small" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}
237
- {loading && results.length === 0 ? (
238
- <Grid {...gridProps}>
239
- {[0, 1, 2].map(i => <Stack key={i} gap="sm">
240
- <AspectRatio ratio={1.6}><Skeleton shape="block" layoutStyle={{ flex: 1 }} {...(i === 0 ? { accessibilityLabel: t("gallery.loading") } : {})} /></AspectRatio>
241
- <Skeleton shape="text" width="60%" />
242
- </Stack>)}
243
- </Grid>
244
- ) : results.length ? (
245
- <Grid {...gridProps}>
246
- {results.map(item => <Stack key={item.id} gap="sm">
247
- <Thumbnail item={item} />
248
- <Text variant="label">{item.title}</Text>
249
- <Text tone="muted">{t("gallery.meta", { author: item.author, category: item.categoryLabel })}</Text>
250
- <Stack axis="inline" wrap gap="sm">
251
- <Button tone="ghost" accessibilityLabel={t("gallery.viewNamed", { title: item.title })} onPress={() => setSelected(item)}>{t("gallery.details")}</Button>
252
- <Button tone="secondary" selected={isSaved(item)} onPress={() => toggleSaved(item.id)}
253
- accessibilityLabel={t(isSaved(item) ? "gallery.unsaveNamed" : "gallery.saveNamed", { title: item.title })}>
254
- {t(isSaved(item) ? "gallery.saved" : "gallery.save")}</Button>
255
- </Stack>
256
- </Stack>)}
257
- </Grid>
258
- ) : error ? null : <EmptyState title={t("gallery.empty.title")} description={t("gallery.empty.body")}
259
- action={<Button onPress={reset}>{t("gallery.reset")}</Button>} />}
260
- <Sheet scrollable open={selected !== null} onOpenChange={o => { if (!o) setSelected(null); }}
261
- title={selected?.title ?? t("gallery.detail")} closeLabel={t("common.close")}
262
- footer={selected ? <Button fullWidth selected={isSaved(selected)} onPress={() => toggleSaved(selected.id)}
263
- accessibilityLabel={t(isSaved(selected) ? "gallery.unsaveNamed" : "gallery.saveNamed", { title: selected.title })}>
264
- {t(isSaved(selected) ? "gallery.unsave" : "gallery.saveToCollection")}</Button> : null}>
265
- {selected ? <Stack gap="lg">
266
- <Thumbnail item={selected} />
267
- <Text>{t("gallery.meta", { author: selected.author, category: selected.categoryLabel })}</Text>
268
- <Text>{selected.description}</Text>
269
- </Stack> : null}
270
- </Sheet>
271
- </Stack></Container>
272
- </ScrollView>
158
+ import { Notice } from "@hjmds/react-native/feedback";
159
+
160
+ <>
161
+ <ScreenLayout title={t("gallery.title")} actions={categoryControls}
162
+ notice={<>{searchAndSort}{hasLoaded && error ? <Notice tone="danger" announcement="assertive" title={t(errorKey[error])}
163
+ action={<Button tone="secondary" loading={retrying} onPress={retry}>{t("common.retry")}</Button>} /> : null}</>}
164
+ state={!hasLoaded
165
+ ? error ? { kind: "error", title: t(errorKey[error]) } : { kind: "loading", title: t("gallery.loading") }
166
+ : results.length === 0 ? { kind: "empty", title: t("gallery.empty.title") } : { kind: "ready" }}
167
+ stateAction={!hasLoaded && error ? <Button loading={retrying} onPress={retry}>{t("common.retry")}</Button>
168
+ : hasLoaded && results.length === 0 ? <Button onPress={reset}>{t("gallery.reset")}</Button> : null}>
169
+ {galleryGrid}
170
+ </ScreenLayout>
171
+ {detailSheet}
172
+ </>
273
173
  ```
274
174
 
175
+ ์œ„ ReactNode ์Šฌ๋กฏ์€ ์ œํ’ˆ์ด ๊ธฐ์กด SearchFieldยทSegmentedControlยทGridยทSheet๋กœ ๊ตฌ์„ฑํ•œ๋‹ค.
176
+ ScreenLayout host๋Š” ๋‚จ์€ ๋ณธ๋ฌธ ๋†’์ด๋ฅผ ํ™•๋ณดํ•˜๊ณ  ๋กœ๋”ฉยท์˜ค๋ฅ˜ยท๋นˆ ์ƒํƒœ๋ฅผ ๊ทธ ์˜์—ญ ์ค‘์•™์— ๋‘”๋‹ค.
177
+ ์žฌ์กฐํšŒ๋Š” hasLoaded์™€ ๋งˆ์ง€๋ง‰ ์„ฑ๊ณต ๊ฒฐ๊ณผ๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ๊ฒ€์ƒ‰์–ดยทํ•„ํ„ฐยท์ •๋ ฌ์„ request key์— ๋„ฃ๊ณ  ์‘๋‹ต key๊ฐ€
178
+ ํ˜„์žฌ ์„ ํƒ๊ณผ ๊ฐ™์„ ๋•Œ๋งŒ ์ ์šฉํ•œ๋‹ค. ์ด์ „ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๋™์•ˆ ๊ทธ ๊ฒฐ๊ณผ์˜ ํ˜„์žฌ์„ฑ์€ ์ œํ’ˆ์ด ํ‘œ์‹œํ•˜๊ณ 
179
+ ๋งž์ง€ ์•Š๋Š” ํ•ญ๋ชฉ์˜ ํ™•์ • ํ–‰๋™์„ ๋ง‰๋Š”๋‹ค. ์ €์žฅ pendingยท์‹คํŒจ๋Š” ํ•ด๋‹น ์ž‘ํ’ˆ ID๋ณ„๋กœ ๊ด€๋ฆฌํ•ด ์ „์ฒด ๊ฒฉ์ž๋ฅผ ๊ต์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค.
180
+
275
181
  ## ํฐ ๊ธ€์žยท๋‹คํฌยท์ข์€ ํญ
276
182
 
277
183
  | ์กฐ๊ฑด | ๋ฐ”๋€Œ๋Š” ๊ฒƒ |
@@ -287,7 +193,7 @@ const gridProps = { columns: { compact: 1, medium: 2, expanded: 3 }, gap: { comp
287
193
  | --- | --- | --- |
288
194
  | ๊ฒฉ์ž | Grid(ํญ์€ `ResizeObserver`๋กœ ์žฐ๋‹ค), ์‚ฌ์ด `spacing.xl` 24 | Grid(ํญ์€ `onLayout`์œผ๋กœ ์žฐ๋‹ค), ์‚ฌ์ด `spacing.xl` 24 |
289
195
  | ๊ทธ๋ฆผ ์ ‘๊ทผ์„ฑ | `aria-hidden="true"` | `accessibilityElementsHidden` + `importantForAccessibility="no-hide-descendants"` |
290
- | ๊ฒฐ๊ณผ ์ˆ˜ ์•Œ๋ฆผ | `role="status"` | ๋ผ์ด๋ธŒ ์˜์—ญ(Android) + iOS๋Š” ์ œํ’ˆ์ด ์ง์ ‘ ์•Œ๋ฆผ(๋ฌธ๊ตฌ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ๋งŒ, `AppState` active์ผ ๋•Œ๋งŒ โ€” [๋Œ€์‹œ๋ณด๋“œ](dashboard.md) ํ•จ์ •) |
196
+ | ๊ฒฐ๊ณผ ์ˆ˜ ์•Œ๋ฆผ | `role="status"` | ๊ณต๊ฐœ StatusText(Android live region + iOS active ๋ฌธ๊ตฌ ๋ณ€๊ฒฝ ์•Œ๋ฆผ โ€” [์‚ฌ์šฉ๋ฒ•](../components/text.md#statustext)) |
291
197
  | ๊ฒ€์ƒ‰ ์ง„ํ–‰ | `loading` | `busy` + `busyLabel`(ํ•„์ˆ˜) |
292
198
  | ์‹œํŠธ footer | ์˜ค๋ฅธ์ชฝ ์ •๋ ฌ ๊ฐ€๋กœ ์ค„ | ์„ธ๋กœ ์—ด, `fullWidth` |
293
199
  | ์˜ค๋ฅ˜ Notice ์•Œ๋ฆผ | `tone="danger"`๊ฐ€ ์•Œ๋ฆผ ์˜์—ญ | `announcement="assertive"`๋ฅผ ๋ช…์‹œ(๊ธฐ๋ณธ `none`) |
@@ -304,3 +210,6 @@ const gridProps = { columns: { compact: 1, medium: 2, expanded: 3 }, gap: { comp
304
210
  - ์‹œํŠธ ํ™•์ • ํ–‰๋™์€ footer์— ๋‘”๋‹ค. Native๋Š” scrollable ๋ณธ๋ฌธ๊ณผ ๋ถ„๋ฆฌํ•ด ํฐ ๊ธ€์ž์—์„œ๋„ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค.
305
211
  - ํ˜„์žฌ ์Šคํ† ๋ฆฌ์˜ ์นด๋“œ ๋ฉ”ํƒ€๋Š” "์ž‘๊ฐ€ ยท ์นดํ…Œ๊ณ ๋ฆฌ ยท ์ข‹์•„์š” N"์„ ํ•œ๊ตญ์–ด ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์กฐ๋ฆฝํ•œ๋‹ค. ์ œํ’ˆ์€ `t("gallery.meta", { author, category })`์ฒ˜๋Ÿผ
306
212
  ๋ณด๊ฐ„ ํ‚ค ํ•˜๋‚˜๋กœ ๋‘”๋‹ค. ์ข‹์•„์š” ์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๋ฉด ๊ฐ™์€ ํ‚ค์— ๊ฐ’์„ ๋”ํ•œ๋‹ค.
213
+
214
+
215
+ 2026-10-13 ํ–‰๋™ ๊ฒ€์ˆ˜์—์„œ ์ œ๊ฑฐ/๋ณต๊ตฌ ๋’ค ํ‚ค๋ณด๋“œ ๋ฌธ๋งฅ ์œ ์‹ค์„ ํ™•์ธํ•ด ๋ณด๊ฐ•ํ–ˆ๋‹ค. Web ์ €์žฅ๋งŒ ๋ณด๊ธฐ์—์„œ ์ €์žฅ ์ทจ์†Œ๋กœ ์นด๋“œ๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉด ์ €์žฅ ํ•„ํ„ฐ๋กœ ์ดˆ์ ์„ ๋Œ๋ฆฐ๋‹ค. ์ƒ์„ธ๋ฅผ ๋‹ซ์„ ๋•Œ๋Š” ์›๋ž˜ ์ƒ์„ธ ๋ฒ„ํŠผ, ํ•ด๋‹น ์นด๋“œ๊ฐ€ ์ œ๊ฑฐ๋์œผ๋ฉด ์ €์žฅ ํ•„ํ„ฐ๋ฅผ ์–‘ ํ”Œ๋žซํผ Sheet returnFocusRef๋กœ ์ง€์ •ํ•œ๋‹ค. Web ๋นˆ ๊ฒฐ๊ณผ ์ดˆ๊ธฐํ™”๋Š” ๊ฒ€์ƒ‰ ํ•„๋“œ๋กœ ์ด์–ด์ง„๋‹ค. Native ref ์—ฐ๊ฒฐ๊ณผ ์‹ค์ œ OS ์ ‘๊ทผ์„ฑ ์ดˆ์  ์ด๋™ ๊ฒ€์ฆ์€ ๊ตฌ๋ถ„ํ•œ๋‹ค.
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md); ๊ณตํ†ต API์™€ ์‹ค์ œ WebยทNative ์˜ˆ์ œ์˜ ์Šฌ๋กฏยท์ƒํƒœ๋ฅผ ๋Œ€์กฐํ•ด ์ค‘๋ณต ์กฐ๋ฆฝ ๋ฐฉ์ง€. 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ(์ด์ „ `์‹คํ—˜/ํ™”๋ฉด/๊ธฐ๋ณธ ํ๋ฆ„/๋ชฉ๋ก๊ณผ ์ƒ์„ธ`, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์ฝ˜ํ…์ธ /๋ชฉ๋ก๊ณผ ์ƒ์„ธ`
10
10
 
@@ -39,7 +39,24 @@ host: ๋‚จ์€ ๋†’์ดยทsafe areaยทํ‚ค๋ณด๋“œ
39
39
  | ๊ธฐ๋ณธ | ๋ชฉ๋ก ํ—ค๋” โ†’ ๋ชฉ๋ก โ†’ ๋” ๋ณด๊ธฐ / ์ƒ์„ธ ํ—ค๋” โ†’ ์ƒ์„ธ | ๊ฐ ๊ณต๊ฐœ ์ฝœ๋ฐฑ์„ ์ œํ’ˆ ์ƒํƒœ์— ์—ฐ๊ฒฐ |
40
40
  | ๋กœ๋”ฉ | ์ตœ์ดˆ ์กฐํšŒ๋Š” ๋ณธ๋ฌธ ์ƒํƒœ, ์ €์žฅ์€ ํ•ด๋‹น ํ–‰๋™ pending | ์ค‘๋ณต ์ œ์ถœ ์ฐจ๋‹จ; ์„ฑ๊ณต์„ ๋จผ์ € ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ |
41
41
  | ๋นˆ | ์‹ค์ œ ์กฐํšŒ 0๊ฑด ๋˜๋Š” ์•„์ง ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ƒํƒœ ์•ˆ๋‚ด | ์‹œ์ž‘ยท์กฐ๊ฑด ํ•ด์ œ ๋“ฑ ๋งฅ๋ฝ์— ๋งž๋Š” ๋Œ€์•ˆ |
42
- | ์˜ค๋ฅ˜ | ๋ชฉ๋ก pane ์œ ์ง€; ์ƒ์„ธ ์‹คํŒจ๋Š” detail.content ์•ˆ์—์„œ ๋ณต๊ตฌ | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
42
+ | ์˜ค๋ฅ˜ | ๋ชฉ๋ก pane ์œ ์ง€; ์ƒ์„ธ ์‹คํŒจ๋Š” detail.stateยทdetail.stateAction์œผ๋กœ ๋ณต๊ตฌ | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
43
+
44
+ ### ๋ชฉ๋ก๊ณผ ์ƒ์„ธ์˜ ๋…๋ฆฝ ์ƒํƒœ
45
+
46
+ `state`๋Š” ๋ชฉ๋ก์—, `detail.state`๋Š” ์ƒ์„ธ์—๋งŒ ์ ์šฉํ•œ๋‹ค. ์ƒˆ ์ƒ์„ธ prop์€ ๋ฏธ๊ฒŒ์‹œ(1.18.0 ์ดํ›„)์ด๋ฉฐ
47
+ ๊ธฐ์กด `detail: { title, content }` ์‚ฌ์šฉ๋ฒ•์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•œ๋‹ค. ๋ชฉ๋ก ์ž…๋ ฅยท์Šคํฌ๋กค์„ ๋ณด์กดํ•œ ์ฑ„
48
+ ์ƒ์„ธ์˜ ์ตœ์ดˆ ์กฐํšŒยท์˜ค๋ฅ˜ยท์žฌ์‹œ๋„๋ฅผ ์ค‘์•™ ์ƒํƒœ๋กœ ํ‘œํ˜„ํ•˜๋ ค๊ณ  ์ถ”๊ฐ€ํ–ˆ๋‹ค.
49
+
50
+ | ์ƒํ™ฉ | ๊ณต๊ฐœ ์Šฌ๋กฏ | ๋‚จ๊ฒจ์•ผ ํ•˜๋Š” ๊ฒƒ |
51
+ | --- | --- | --- |
52
+ | ์ƒ์„ธ ์ตœ์ดˆ ์กฐํšŒ | detail.state=loading | ์ˆจ๊ฒจ์ง„ ๋ชฉ๋ก์˜ ๊ฒ€์ƒ‰ยท์„ ํƒยท์Šคํฌ๋กค |
53
+ | ์ƒ์„ธ ์กฐํšŒ ์‹คํŒจ | detail.state=error + detail.stateAction | ์ƒ์„ธ ์ œ๋ชฉยท๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ |
54
+ | ์ด๋ฏธ ์ฝ์€ ์ƒ์„ธ ์žฌ์กฐํšŒ ์‹คํŒจ | detail.state=ready + detail.notice | ๊ธฐ์กด ์ƒ์„ธ ๋‚ด์šฉ |
55
+ | ๋ชฉ๋ก ์ถ”๊ฐ€ ์กฐํšŒ ์‹คํŒจ | list ์•ˆ LoadMore | ์ด๋ฏธ ํ‘œ์‹œ๋œ ํ–‰ยทํŽ˜์ด์ง€ cursor |
56
+ | ์ƒ์„ธ ๋‹ซ๊ธฐ | detail ์ƒ๋žต | ์›๋ž˜ ๋ชฉ๋ก์œผ๋กœ ๋ณต๊ท€, Web ์›๋ž˜ ํฌ์ปค์Šค ๋ณต์› |
57
+
58
+ ์„ ํƒ id๊ฐ€ ๋ฐ”๋€Œ๊ฑฐ๋‚˜ ์ƒ์„ธ๋ฅผ ๋‹ซ์œผ๋ฉด ํ•ด๋‹น ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ๋Šฆ์€ ์‘๋‹ต์„ ๋ฌด์‹œํ•œ๋‹ค.
59
+ ์ƒ์„ธ ์š”์ฒญ ์‹คํŒจ๋ฅผ ๋ชฉ๋ก `state=error`๋กœ ๋„˜๊ธฐ๋ฉด ๋ณต๊ท€ํ•œ ๋ชฉ๋ก๊นŒ์ง€ ์‚ฌ๋ผ์ง„๋‹ค.
43
60
 
44
61
  ## ์‚ฌ์šฉํ•˜๋Š” ์ง€์นจ
45
62
 
@@ -59,7 +76,7 @@ import { ListDetailScreen } from "@hjmds/react/screen-flows";
59
76
  <ListDetailScreen
60
77
  title={t("orders.title")}
61
78
  list={<List label={t("orders.list")}>{rows}</List>}
62
- {...(selected ? { detail: { title: selected.name, content: <OrderDetail order={selected} /> } } : {})}
79
+ {...(selected ? { detail: { title: selected.name, content: <OrderDetail order={selected} />, state: detailState, stateAction: detailRetry, notice: detailNotice } } : {})}
63
80
  back={{ label: t("common.back"), onAction: () => setSelected(null) }}
64
81
  refresh={{ label: t("common.refresh"), onAction: refetch, pending: isRefetching }}
65
82
  />
@@ -73,24 +90,36 @@ import { ListDetailScreen } from "@hjmds/react-native/screen-flows";
73
90
  <ListDetailScreen
74
91
  title={t("orders.title")}
75
92
  list={<List label={t("orders.list")}>{rows}</List>}
76
- {...(selected ? { detail: { title: selected.name, content: <OrderDetail order={selected} /> } } : {})}
93
+ {...(selected ? { detail: { title: selected.name, content: <OrderDetail order={selected} />, state: detailState, stateAction: detailRetry, notice: detailNotice } } : {})}
77
94
  back={{ label: t("common.back"), onAction: () => setSelected(null) }}
78
95
  refresh={{ label: t("common.refresh"), onAction: refetch, pending: isRefetching }}
79
96
  />
80
97
  ```
81
98
 
99
+ `detailState`๋Š” `{ kind: "ready" }` ๋˜๋Š” ์ง€์—ญํ™”๋œ title์„ ๊ฐ€์ง„ loading/error/restricted ์ƒํƒœ๋‹ค.
100
+ `detailRetry`๋Š” ํ•ด๋‹น id๋งŒ ๋‹ค์‹œ ์กฐํšŒํ•˜๋Š” Button, `detailNotice`๋Š” ๊ธฐ์กด ์ƒ์„ธ๋ฅผ ์œ ์ง€ํ•˜๋Š” Notice๋‹ค.
101
+
102
+ <!-- usage-example: Web ../../../../../showcase/web/src/patterns/list-detail-recovery-preview.tsx -->
103
+ - [Web ์ƒ์„ธ ๋ณต๊ตฌ ์‹คํ–‰ ์˜ˆ์ œ](../../../../../showcase/web/src/patterns/list-detail-recovery-preview.tsx)
104
+ <!-- usage-example: Native ../../../../../showcase/native/src/list-detail-recovery-preview.tsx -->
105
+ - [Native ์ƒ์„ธ ๋ณต๊ตฌ ์‹คํ–‰ ์˜ˆ์ œ](../../../../../showcase/native/src/list-detail-recovery-preview.tsx)
106
+
107
+ ์Šคํ† ๋ฆฌ `์ƒ์„ธ ์กฐํšŒ ์‹คํŒจ์™€ ๋ณต๊ตฌ`: ๋ชฉ๋ก ๋ฉ”๋ชจ ์ž…๋ ฅ โ†’ ๋‹ค์Œ ์ƒ์„ธ ์กฐํšŒ ์‹คํŒจ์‹œํ‚ค๊ธฐ โ†’ ํ•ญ๋ชฉ ์—ด๊ธฐ โ†’
108
+ ์ƒ์„ธ๋งŒ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ โ†’ ๋ชฉ๋ก์œผ๋กœ ๋ณต๊ท€ํ•ด ๋ฉ”๋ชจ ์œ ์ง€ ํ™•์ธ. ์˜ˆ์ œ์˜ ์ง€์—ฐยท์„œ๋ฒ„ ์‘๋‹ต์€ ๋ฉ”๋ชจ๋ฆฌ ๋ชจ์˜๋‹ค.
109
+
82
110
  ์ฝœ๋ฐฑยท๋ฐ์ดํ„ฐยท์ง€์—ญํ™” ํ•จ์ˆ˜๋Š” ์ œํ’ˆ์—์„œ ๊ณต๊ธ‰ํ•œ๋‹ค. WebยทNative import์™€ ํ•„์ˆ˜ props๋Š” ์œ„ API ์ง€์นจ์—์„œ ํ™•์ธํ•œ๋‹ค.
83
111
 
84
- ## ํฐ ๊ธ€์žยท๋‹คํฌยท์ข์€ ํญ
112
+ ## ๋‹คํฌยท์ข์€ ํญ
85
113
 
86
114
  | ์กฐ๊ฑด | ๋ฐ”๋€Œ๋Š” ๊ฒƒ |
87
115
  | --- | --- |
88
- | ํฐ ๊ธ€์ž | 2๋ฐฐ ๊ธ€์ž์—์„œ ์ œ๋ชฉยทํ–‰์€ ๋‚ด์šฉ ๋†’์ด๋กœ ์ฆ๊ฐ€. footerยท๋‹ซ๊ธฐยท์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ |
89
116
  | ๋‹คํฌ | semantic ์ƒ‰์œผ๋กœ ๋‚ด์šฉ๊ณผ ํ‘œ๋ฉด์„ ํ•จ๊ป˜ ์ „ํ™˜; ์˜ˆ์ œ ๋ธŒ๋žœ๋“œ ์ƒ‰์„ ์ œํ’ˆ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ณต์‚ฌํ•˜์ง€ ์•Š์Œ |
90
117
  | ์ข์€ ํญ | 320px๋ถ€ํ„ฐ ํ•œ ์—ด๋กœ ์ฝ๊ธฐ ์ˆœ์„œ ์œ ์ง€. ๊ฐ€์ƒํ™” ๋ณธ๋ฌธ์€ scroll=content, ์ค‘์ฒฉ ์Šคํฌ๋กค ๊ธˆ์ง€ |
91
118
  | ํ‚ค๋ณด๋“œ | Native host๊ฐ€ safe area์™€ ํ‚ค๋ณด๋“œ๋ฅผ ํ•œ ๋ฒˆ ์ฒ˜๋ฆฌ; Web์€ ํฌ์ปค์Šค๋œ ์ž…๋ ฅ๊ณผ footer ๊ฐ€๋ฆผ ํ™•์ธ |
92
119
 
93
120
  ## ํ•จ์ •
94
121
 
95
- - ๋ชฉ๋ก pane ์œ ์ง€; ์ƒ์„ธ ์‹คํŒจ๋Š” detail.content ์•ˆ์—์„œ ๋ณต๊ตฌ.
96
- - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌยทํฐ ๊ธ€์ž์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
122
+ - ๋ชฉ๋ก pane ์œ ์ง€; ์ƒ์„ธ ์‹คํŒจ๋Š” detail.stateยทdetail.stateAction์œผ๋กœ ๋ณต๊ตฌ.
123
+ - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌ์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
124
+
125
+ 2026-10-13 ์‹ค์ œ ์˜ˆ์ œ ์—ฐ๊ฒฐ ๊ฒ€์ˆ˜: ์ƒˆ ๊ธฐ๋ก์€ ๋นˆ ์ดˆ์•ˆ์œผ๋กœ ์‹œ์ž‘ํ•˜๊ณ , ์„ฑ๊ณตํ•œ ์ œ๋ชฉยท๋ณธ๋ฌธ์„ ๊ณ ์œ  ID์˜ ๋ชฉ๋ก ํ•ญ๋ชฉ์— ํ•จ๊ป˜ ๋ณด๊ด€ํ•œ๋‹ค. ๋‹ค๋ฅธ ๊ธฐ๋ก ์ƒ์„ธ๋Š” ํ˜„์žฌ ํŽธ์ง‘ ์ดˆ์•ˆ์„ ์ฝ์ง€ ์•Š๋Š”๋‹ค. ์ €์žฅ ์„ฑ๊ณต ์‹œ ์ „์ฒด ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€ ์ƒˆ ํ•ญ๋ชฉ์„ ๋ณด์ด๋ฉฐ, ๋นˆ ๋ชฉ๋ก์—์„œ ์ž‘์„ฑํ•œ ๋’ค์—๋Š” ready๋กœ ์ „ํ™˜ํ•œ๋‹ค. ํ•„ํ„ฐ์™€ ์ƒˆ ๊ธฐ๋ก ํ–‰๋™์„ ํ‘œ์‹œํ•ด๋„ ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ Notice๋ฅผ ๋ฎ์–ด์“ฐ์ง€ ์•Š๋Š”๋‹ค.
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md); ๊ณตํ†ต API์™€ ์‹ค์ œ WebยทNative ์˜ˆ์ œ์˜ ์Šฌ๋กฏยท์ƒํƒœ๋ฅผ ๋Œ€์กฐํ•ด ์ค‘๋ณต ์กฐ๋ฆฝ ๋ฐฉ์ง€. 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ(์ด์ „ `์‹คํ—˜/ํ™”๋ฉด/๊ธฐ๋ณธ ํ๋ฆ„/์ž‘์„ฑ๊ณผ ์ˆ˜์ •`, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์ฝ˜ํ…์ธ /์ž‘์„ฑ๊ณผ ์ˆ˜์ •`
10
10
 
@@ -41,6 +41,23 @@ host: ๋‚จ์€ ๋†’์ดยทsafe areaยทํ‚ค๋ณด๋“œ
41
41
  | ๋นˆ | ์‹ค์ œ ์กฐํšŒ 0๊ฑด ๋˜๋Š” ์•„์ง ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ƒํƒœ ์•ˆ๋‚ด | ์‹œ์ž‘ยท์กฐ๊ฑด ํ•ด์ œ ๋“ฑ ๋งฅ๋ฝ์— ๋งž๋Š” ๋Œ€์•ˆ |
42
42
  | ์˜ค๋ฅ˜ | ์ €์žฅ ์‹คํŒจ๋Š” ์ž…๋ ฅ ์œ ์ง€; ์ €์žฅ ์„ฑ๊ณต ํ™•์ธ ํ›„์—๋งŒ dirty ํ•ด์ œ | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
43
43
 
44
+
45
+ ### ์ดˆ์•ˆ๊ณผ ์„œ๋ฒ„ ํ™•์ •
46
+
47
+ 2026-10-13 ์ „์ˆ˜ ์ ๊ฒ€์—์„œ ์ตœ์ดˆ ์กฐํšŒ์™€ ๋ณ€๊ฒฝ ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ๊ฐ™์€ ๊ณจ๊ฒฉ์œผ๋กœ ์„ค๋ช…ํ•˜๋˜ ๋ถ€๋ถ„์„ ๊ตฌ์ฒดํ™”ํ–ˆ๋‹ค.
48
+
49
+ | ์ƒํ™ฉ | ์—ฐ๊ฒฐ | ์œ ์ง€ยท๋ณต๊ตฌ |
50
+ | --- | --- | --- |
51
+ | ์ƒˆ ์ž‘์„ฑ | ready + ๋นˆ ์ž…๋ ฅ | ๋นˆ ์ดˆ์•ˆ์€ ํ™”๋ฉด empty๊ฐ€ ์•„๋‹ˆ๋‹ค. ์ž…๋ ฅ์„ ๋ฐ”๋กœ ๋ณด์—ฌ ์ค€๋‹ค |
52
+ | ์ €์žฅ ์‹œ์ž‘ | submit.pending + ์ œ์ถœ snapshot | cancel๊ณผ ์ด๋ฏธ ์—ด๋ฆฐ ์ดˆ์•ˆ ๋ฒ„๋ฆฌ๊ธฐ ํ™•์ธ์„ HJM์ด ์ž ๊ทผ๋‹ค. ํ™•์ธ์ฐฝ์€ ์ข…๋ฃŒํ•˜๋ฉฐ ์ž…๋ ฅ ์ž ๊ธˆยท์ถ”๊ฐ€ ์š”์ฒญ ์ฐจ๋‹จ์€ ์ œํ’ˆ์ด ์—ฐ๊ฒฐํ•œ๋‹ค |
53
+ | ์ €์žฅ ์‹คํŒจ | ready ์œ ์ง€ + notice | dirty๋ฅผ ํ•ด์ œํ•˜๊ฑฐ๋‚˜ ์ž…๋ ฅ์„ ๋‹ค์‹œ mountํ•˜์ง€ ์•Š๋Š”๋‹ค |
54
+ | ์ €์žฅ ์„ฑ๊ณต | ์ œ์ถœ snapshot์„ saved๋กœ ๊ฐฑ์‹  | ์š”์ฒญ ํ›„ ์ƒˆ๋กœ ์“ด ๋‚ด์šฉ๊นŒ์ง€ saved๋กœ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค. draftStatus๋Š” ์‹ค์ œ ์ž„์‹œ ์ €์žฅ ์„ฑ๊ณต ๋•Œ๋งŒ ํ‘œ์‹œํ•œ๋‹ค |
55
+ | ๋ธŒ๋ผ์šฐ์ €ยทOS ๋’ค๋กœ | ์ œํ’ˆ router์˜ ์ดํƒˆ ๊ฐ€๋“œ | EditorScreen cancel์˜ discard์™€ ๊ฐ™์€ ์ œํ’ˆ ๊ฒฐ์ •์„ ๊ณต์œ ํ•œ๋‹ค |
56
+
57
+ ๋ณธ๋ฌธ์ด ready๊ฐ€ ์•„๋‹Œ ์ตœ์ดˆ ์กฐํšŒยท์˜ค๋ฅ˜ยท์ œํ•œ ์ƒํƒœ์—์„œ๋Š” ํ—ค๋”/ํ•˜๋‹จ ์–ด๋А ์œ„์น˜์˜ ์ €์žฅ๋„ ๋น„ํ™œ์„ฑ์ด๋‹ค.
58
+ 2026-10-13 ์ž…๋ ฅ์„ ํ‘œ์‹œํ•˜์ง€ ์•Š๋Š” ์ƒํƒœ์—์„œ๋„ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋˜ ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค. ์‹คํŒจํ•œ ์ €์žฅ์€
59
+ ๋ณธ๋ฌธ์„ ์˜ค๋ฅ˜ ํ™”๋ฉด์œผ๋กœ ๊ต์ฒดํ•˜์ง€ ์•Š๊ณ  ์œ„ ํ‘œ์ฒ˜๋Ÿผ ready + notice๋กœ ์œ ์ง€ํ•œ๋‹ค.
60
+
44
61
  ## ์‚ฌ์šฉํ•˜๋Š” ์ง€์นจ
45
62
 
46
63
  | ์ง€์นจ | ์“ฐ๋Š” ๊ณณ |
@@ -71,7 +88,7 @@ import { TextField } from "@hjmds/react/forms";
71
88
  tone: "danger",
72
89
  fallbackErrorMessage: t("common.error"),
73
90
  }}
74
- draftStatus={<Text variant="caption" tone="muted">{t("post.edit.draftSaved")}</Text>}
91
+ draftStatus={draftStatus ? <Text variant="caption" tone="muted">{draftStatus}</Text> : null}
75
92
  >
76
93
  <TextField label={t("post.edit.body")} value={draft} onValueChange={setDraft} />
77
94
  </EditorScreen>
@@ -97,7 +114,7 @@ import { TextField } from "@hjmds/react-native/inputs";
97
114
  tone: "danger",
98
115
  fallbackErrorMessage: t("common.error"),
99
116
  }}
100
- draftStatus={<Text variant="caption" tone="muted">{t("post.edit.draftSaved")}</Text>}
117
+ draftStatus={draftStatus ? <Text variant="caption" tone="muted">{draftStatus}</Text> : null}
101
118
  >
102
119
  <TextField label={t("post.edit.body")} value={draft} onValueChange={setDraft} />
103
120
  </EditorScreen>
@@ -105,11 +122,10 @@ import { TextField } from "@hjmds/react-native/inputs";
105
122
 
106
123
  ์ฝœ๋ฐฑยท๋ฐ์ดํ„ฐยท์ง€์—ญํ™” ํ•จ์ˆ˜๋Š” ์ œํ’ˆ์—์„œ ๊ณต๊ธ‰ํ•œ๋‹ค. WebยทNative import์™€ ํ•„์ˆ˜ props๋Š” ์œ„ API ์ง€์นจ์—์„œ ํ™•์ธํ•œ๋‹ค.
107
124
 
108
- ## ํฐ ๊ธ€์žยท๋‹คํฌยท์ข์€ ํญ
125
+ ## ๋‹คํฌยท์ข์€ ํญ
109
126
 
110
127
  | ์กฐ๊ฑด | ๋ฐ”๋€Œ๋Š” ๊ฒƒ |
111
128
  | --- | --- |
112
- | ํฐ ๊ธ€์ž | 2๋ฐฐ ๊ธ€์ž์—์„œ ์ œ๋ชฉยทํ–‰์€ ๋‚ด์šฉ ๋†’์ด๋กœ ์ฆ๊ฐ€. footerยท๋‹ซ๊ธฐยท์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ |
113
129
  | ๋‹คํฌ | semantic ์ƒ‰์œผ๋กœ ๋‚ด์šฉ๊ณผ ํ‘œ๋ฉด์„ ํ•จ๊ป˜ ์ „ํ™˜; ์˜ˆ์ œ ๋ธŒ๋žœ๋“œ ์ƒ‰์„ ์ œํ’ˆ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ณต์‚ฌํ•˜์ง€ ์•Š์Œ |
114
130
  | ์ข์€ ํญ | 320px๋ถ€ํ„ฐ ํ•œ ์—ด๋กœ ์ฝ๊ธฐ ์ˆœ์„œ ์œ ์ง€. ๊ฐ€์ƒํ™” ๋ณธ๋ฌธ์€ scroll=content, ์ค‘์ฒฉ ์Šคํฌ๋กค ๊ธˆ์ง€ |
115
131
  | ํ‚ค๋ณด๋“œ | Native host๊ฐ€ safe area์™€ ํ‚ค๋ณด๋“œ๋ฅผ ํ•œ ๋ฒˆ ์ฒ˜๋ฆฌ; Web์€ ํฌ์ปค์Šค๋œ ์ž…๋ ฅ๊ณผ footer ๊ฐ€๋ฆผ ํ™•์ธ |
@@ -117,4 +133,6 @@ import { TextField } from "@hjmds/react-native/inputs";
117
133
  ## ํ•จ์ •
118
134
 
119
135
  - ์ €์žฅ ์‹คํŒจ๋Š” ์ž…๋ ฅ ์œ ์ง€; ์ €์žฅ ์„ฑ๊ณต ํ™•์ธ ํ›„์—๋งŒ dirty ํ•ด์ œ.
120
- - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌยทํฐ ๊ธ€์ž์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
136
+ - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌ์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
137
+
138
+ 2026-10-13 ์‹ค์ œ ์˜ˆ์ œ ์—ฐ๊ฒฐ ๊ฒ€์ˆ˜: ์ €์žฅ ์ค‘ ์ž…๋ ฅ์€ Web readOnlyยทNative disabled๋กœ ์ž ๊ทธ๊ณ , ๊ฐ™์€ ๋ Œ๋” ์‚ฌ์ด์— ๋“ค์–ด์˜จ ์ €์žฅ ์š”์ฒญ๋„ ๋™๊ธฐ ์ž ๊ธˆ์œผ๋กœ ํ•œ ๋ฒˆ๋งŒ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์‹คํŒจ ์˜ˆ์•ฝ์€ ์š”์ฒญ ์‹œ์ž‘ ๋•Œ ์†Œ๋น„ํ•˜๋ฉฐ ์ง„ํ–‰ ์ค‘ ์˜ˆ์•ฝ ๋ณ€๊ฒฝ์€ ๋ฌด์‹œํ•œ๋‹ค. ์‹คํŒจ ํ›„ ํ˜„์žฌ ์ดˆ์•ˆ์œผ๋กœ ์žฌ์‹œ๋„ํ•˜๊ณ , ํ๊ธฐํ•˜๋ฉด ๋งˆ์ง€๋ง‰์œผ๋กœ ํ™•์ •ํ•œ snapshot์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค. ์˜ˆ์ œ์˜ 400ms ์ง€์—ฐ๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์ €์žฅ์€ ์„œ๋ฒ„ ์ €์žฅ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค.
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md); ๊ณตํ†ต API์™€ ์‹ค์ œ WebยทNative ์˜ˆ์ œ์˜ ์Šฌ๋กฏยท์ƒํƒœ๋ฅผ ๋Œ€์กฐํ•ด ์ค‘๋ณต ์กฐ๋ฆฝ ๋ฐฉ์ง€. 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ(์ด์ „ `์‹คํ—˜/ํ™”๋ฉด/๊ธฐ๋ณธ ํ๋ฆ„/์‚ฌ์ง„ ์„ ํƒ๊ณผ ์—…๋กœ๋“œ`, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์ฝ˜ํ…์ธ /์‚ฌ์ง„ ์„ ํƒ๊ณผ ์—…๋กœ๋“œ`
10
10
 
@@ -41,6 +41,21 @@ host: ๋‚จ์€ ๋†’์ดยทsafe areaยทํ‚ค๋ณด๋“œ
41
41
  | ๋นˆ | ์‹ค์ œ ์กฐํšŒ 0๊ฑด ๋˜๋Š” ์•„์ง ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ƒํƒœ ์•ˆ๋‚ด | ์‹œ์ž‘ยท์กฐ๊ฑด ํ•ด์ œ ๋“ฑ ๋งฅ๋ฝ์— ๋งž๋Š” ๋Œ€์•ˆ |
42
42
  | ์˜ค๋ฅ˜ | ์—…๋กœ๋“œ ์‹คํŒจ ํ•ญ๋ชฉ๋งŒ ์žฌ์‹œ๋„, ์™„๋ฃŒ ๋ฒ„ํŠผ์€ ์ œํ’ˆ ์ •์ฑ…์œผ๋กœ ์ œํ•œ | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
43
43
 
44
+
45
+ ### ํ•ญ๋ชฉ๋ณ„ ์—…๋กœ๋“œ์™€ ์™„๋ฃŒ ์กฐ๊ฑด
46
+
47
+ WebยทNative `media-picker-preview.tsx`๋Š” ์ตœ๋Œ€ 5์žฅ์„ ์ˆœ์„œ๋Œ€๋กœ ๊ณ ๋ฅธ ๋’ค ๊ณตํ†ต `items` ๊ฒฉ์ž์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์—…๋กœ๋“œ๋ฅผ ์‹œ์—ฐํ•œ๋‹ค. `์‹คํŒจ์™€ ๋ณต๊ตฌ`๋Š” ์ฒซ ํ•ญ๋ชฉ ํ•œ ๋ฒˆ๋งŒ ์‹คํŒจํ•˜๋ฉฐ, ํ•ญ๋ชฉ์˜ ์žฌ์‹œ๋„๋กœ ํ•ด๋‹น ์‚ฌ์ง„๋งŒ ๋‹ค์‹œ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์—…๋กœ๋“œยท์‹คํŒจยท0์žฅ ์ƒํƒœ์—์„œ๋Š” ์™„๋ฃŒ๋ฅผ ๋ง‰๊ณ , ์‚ฌ์ง„ ์ œ๊ฑฐยท์„ ํƒ ํ™”๋ฉด ๋ณต๊ท€ ๋’ค ๋„์ฐฉํ•œ ์ด์ „ ๊ฒฐ๊ณผ๋Š” ๋ฌด์‹œํ•œ๋‹ค. ๋ชจ๋‘ ์„ฑ๊ณตํ•œ ๋’ค ์™„๋ฃŒํ•˜๋ฉด ์ˆœ์„œ๋ฅผ ํฌํ•จํ•œ ์ฝ๊ธฐ ์ „์šฉ ๊ฒฐ๊ณผ๊ฐ€ ๋‚จ๊ณ  `์ƒˆ๋กœ ์„ ํƒ`์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค. `showcase/shared/media-picker-example.ts`๋Š” ์ด ์˜ˆ์ œ๋งŒ์˜ ๋ฉ”๋ชจ๋ฆฌ ์–ด๋Œ‘ํ„ฐ์ด๋ฉฐ ์‹ค์ œ OS ์‚ฌ์ง„ ์ ‘๊ทผยท๊ถŒํ•œ์ด๋‚˜ ์„œ๋ฒ„ ์—…๋กœ๋“œ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
48
+
49
+ 2026-10-13 ์ „์ˆ˜ ์ ๊ฒ€์—์„œ ์ตœ์ดˆ ์กฐํšŒ์™€ ๋ณ€๊ฒฝ ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ๊ฐ™์€ ๊ณจ๊ฒฉ์œผ๋กœ ์„ค๋ช…ํ•˜๋˜ ๋ถ€๋ถ„์„ ๊ตฌ์ฒดํ™”ํ–ˆ๋‹ค.
50
+
51
+ | ์ƒํ™ฉ | ์—ฐ๊ฒฐ | ์œ ์ง€ยท๋ณต๊ตฌ |
52
+ | --- | --- | --- |
53
+ | ์„ ํƒ 0๊ฐœ | empty + add | ์ƒˆ ์‚ฌ์ง„ ์„ ํƒ์€ ๊ณ„์† ๊ฐ€๋Šฅํ•˜๊ณ  done์€ ์ œํ’ˆ ์ •์ฑ…์— ๋”ฐ๋ผ ์ž ๊ทผ๋‹ค |
54
+ | ์—…๋กœ๋“œ ํ˜ผ์žฌ | ์ธ๋„ค์ผยท์ˆœ์„œ์™€ ๊ฐ UploadItem ์ƒํƒœ ์œ ์ง€ | ์‹คํŒจํ•œ ํ•ญ๋ชฉ๋งŒ ์žฌ์‹œ๋„ํ•œ๋‹ค. ์„ฑ๊ณตํ•œ ์‚ฌ์ง„์„ ๋‹ค์‹œ ์—…๋กœ๋“œํ•˜์ง€ ์•Š๋Š”๋‹ค |
55
+ | ์™„๋ฃŒ ๊ฐ€๋Šฅ | ํ•„์ˆ˜ ํ•ญ๋ชฉ ๋ชจ๋‘ ์„œ๋ฒ„ ํ™•์ • | ์—…๋กœ๋“œ ์ค‘์ด ์•„๋‹ˆ์–ด๋„ ์‹คํŒจยท์ทจ์†Œ ํ•ญ๋ชฉ์ด ๋‚จ์œผ๋ฉด done์„ ๋ฐ”๋กœ ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š๋Š”๋‹ค |
56
+ | ๊ถŒํ•œ ์ œํ•œ | restricted + ๊ถŒํ•œ ์•ˆ๋‚ด ํ–‰๋™ | ๋กœ์ปฌ ์ธ๋„ค์ผ์ด ์กด์žฌํ•œ๋‹ค๋Š” ์ด์œ ๋กœ OS ์ ‘๊ทผ ๊ถŒํ•œ์„ ์–ป์—ˆ๋‹ค๊ณ  ํŒ๋‹จํ•˜์ง€ ์•Š๋Š”๋‹ค |
57
+ | ์ œ๊ฑฐยท์ทจ์†Œ | ์ œํ’ˆ์ด ์š”์ฒญ ์ทจ์†Œ ๋ฐ id๋ณ„ ๊ฒฐ๊ณผ ๋ฌด์‹œ | ๋Šฆ์€ ์‘๋‹ต์ด ์ œ๊ฑฐํ•œ ์‚ฌ์ง„์„ ๋‹ค์‹œ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค |
58
+
44
59
  ## ์‚ฌ์šฉํ•˜๋Š” ์ง€์นจ
45
60
 
46
61
  | ์ง€์นจ | ์“ฐ๋Š” ๊ณณ |
@@ -60,7 +75,7 @@ import { MediaSelectionScreen } from "@hjmds/react/screen-flows";
60
75
  items={picked.map((p) => ({ descriptor: { id: p.id, name: p.fileName, state: p.upload },
61
76
  preview: <img src={p.uri} alt="" width={p.width} height={p.height} /> }))}
62
77
  add={{ label: t("media.add"), onAction: openPicker }}
63
- done={{ label: t("media.done"), onAction: submit, disabled: uploading }}
78
+ done={{ label: t("media.done"), onAction: submit, disabled: uploading || !canComplete }}
64
79
  labels={{ pending: t("upload.pending"), uploading: t("upload.uploading"),
65
80
  success: t("upload.success"), cancel: t("upload.cancel"), retry: t("upload.retry") }}
66
81
  actionLabels={{ remove: t("media.remove"), moveUp: t("media.moveUp"), moveDown: t("media.moveDown") }}
@@ -82,7 +97,7 @@ import { Image } from "react-native";
82
97
  items={picked.map((p) => ({ descriptor: { id: p.id, name: p.fileName, state: p.upload },
83
98
  preview: <Image src={p.uri} width={p.width} height={p.height} /> }))}
84
99
  add={{ label: t("media.add"), onAction: openPicker }}
85
- done={{ label: t("media.done"), onAction: submit, disabled: uploading }}
100
+ done={{ label: t("media.done"), onAction: submit, disabled: uploading || !canComplete }}
86
101
  labels={{ pending: t("upload.pending"), uploading: t("upload.uploading"),
87
102
  success: t("upload.success"), cancel: t("upload.cancel"), retry: t("upload.retry") }}
88
103
  actionLabels={{ remove: t("media.remove"), moveUp: t("media.moveUp"), moveDown: t("media.moveDown") }}
@@ -94,13 +109,14 @@ import { Image } from "react-native";
94
109
  />
95
110
  ```
96
111
 
112
+ `canComplete`๋Š” ํ•„์ˆ˜ ํŒŒ์ผ์˜ ์„ฑ๊ณตยท์„ ํƒ ๊ฐœ์ˆ˜ ๋“ฑ ์ œํ’ˆ ์™„๋ฃŒ ์กฐ๊ฑด์ด๋‹ค. ๋‹จ์ˆœํžˆ uploading=false์ธ ์‹คํŒจ ์ƒํƒœ๋ฅผ ์™„๋ฃŒ๋กœ ์ทจ๊ธ‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
113
+
97
114
  ์ฝœ๋ฐฑยท๋ฐ์ดํ„ฐยท์ง€์—ญํ™” ํ•จ์ˆ˜๋Š” ์ œํ’ˆ์—์„œ ๊ณต๊ธ‰ํ•œ๋‹ค. WebยทNative import์™€ ํ•„์ˆ˜ props๋Š” ์œ„ API ์ง€์นจ์—์„œ ํ™•์ธํ•œ๋‹ค. ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€๋Š” ์ œํ’ˆ์ด ๊ณต๊ธ‰ํ•œ๋‹ค(Web `img`, Native `Image`).
98
115
 
99
- ## ํฐ ๊ธ€์žยท๋‹คํฌยท์ข์€ ํญ
116
+ ## ๋‹คํฌยท์ข์€ ํญ
100
117
 
101
118
  | ์กฐ๊ฑด | ๋ฐ”๋€Œ๋Š” ๊ฒƒ |
102
119
  | --- | --- |
103
- | ํฐ ๊ธ€์ž | 2๋ฐฐ ๊ธ€์ž์—์„œ ์ œ๋ชฉยทํ–‰์€ ๋‚ด์šฉ ๋†’์ด๋กœ ์ฆ๊ฐ€. footerยท๋‹ซ๊ธฐยท์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ |
104
120
  | ๋‹คํฌ | semantic ์ƒ‰์œผ๋กœ ๋‚ด์šฉ๊ณผ ํ‘œ๋ฉด์„ ํ•จ๊ป˜ ์ „ํ™˜; ์˜ˆ์ œ ๋ธŒ๋žœ๋“œ ์ƒ‰์„ ์ œํ’ˆ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ณต์‚ฌํ•˜์ง€ ์•Š์Œ |
105
121
  | ์ข์€ ํญ | ๊ธฐ๋ณธ ๊ฒฉ์ž๋Š” `minColumnWidth` ์—†์ด `columns={{ compact: 2, expanded: 3 }}`๋ผ 320px์—์„œ๋„ 2์—ด์ด๋‹ค(ํ•œ ์—ด๋กœ ์ ‘์ง€ ์•Š๋Š”๋‹ค). 960 ๋ฏธ๋งŒ 2์—ด, ์ด์ƒ 3์—ด. ์ด๋™ยท์ œ๊ฑฐ ๋ฒ„ํŠผ ํ–‰์€ ์ค„๋ฐ”๊ฟˆ๋œ๋‹ค. ํ•œ ์—ด์ด ํ•„์š”ํ•˜๋ฉด `library` ์Šฌ๋กฏ์œผ๋กœ ์ œํ’ˆ ๊ฒฉ์ž๋ฅผ ๋„˜๊ธด๋‹ค |
106
122
  | ํ‚ค๋ณด๋“œ | Native host๊ฐ€ safe area์™€ ํ‚ค๋ณด๋“œ๋ฅผ ํ•œ ๋ฒˆ ์ฒ˜๋ฆฌ; Web์€ ํฌ์ปค์Šค๋œ ์ž…๋ ฅ๊ณผ footer ๊ฐ€๋ฆผ ํ™•์ธ |
@@ -108,4 +124,4 @@ import { Image } from "react-native";
108
124
  ## ํ•จ์ •
109
125
 
110
126
  - ์—…๋กœ๋“œ ์‹คํŒจ ํ•ญ๋ชฉ๋งŒ ์žฌ์‹œ๋„, ์™„๋ฃŒ ๋ฒ„ํŠผ์€ ์ œํ’ˆ ์ •์ฑ…์œผ๋กœ ์ œํ•œ.
111
- - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌยทํฐ ๊ธ€์ž์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
127
+ - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยทOS ๊ถŒํ•œยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท๋‹คํฌ์™€ ์‹คํŒจ/๋ณต๊ตฌ๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ตœ๋Œ€ ๊ธ€์ž์™€ ์ด๋ฅผ ๋ชจ์‚ฌํ•˜๋Š” ํ™•๋Œ€ ์กฐ๊ฑด์€ ๊ณตํ†ต ์‚ฌ์šฉ์ž ์ง€์นจ์— ๋”ฐ๋ผ ๊ฒ€์ฆยท์„ค๊ณ„ ๊ณ ๋ ค์—์„œ ์ œ์™ธํ•œ๋‹ค.
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md); ๊ณตํ†ต API์™€ ์‹ค์ œ WebยทNative ์˜ˆ์ œ์˜ ์Šฌ๋กฏยท์ƒํƒœ๋ฅผ ๋Œ€์กฐํ•ด ์ค‘๋ณต ์กฐ๋ฆฝ ๋ฐฉ์ง€. 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ(์ด์ „ `์‹คํ—˜/ํ™”๋ฉด/๊ธฐ๋ณธ ํ๋ฆ„/์‹ ๊ณ ์™€ ์ฐจ๋‹จ`, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์†Œํ†ต/์‹ ๊ณ ์™€ ์ฐจ๋‹จ`
10
10
 
@@ -41,6 +41,19 @@ host: ๋‚จ์€ ๋†’์ดยทsafe areaยทํ‚ค๋ณด๋“œ
41
41
  | ๋นˆ | ์‹ค์ œ ์กฐํšŒ 0๊ฑด ๋˜๋Š” ์•„์ง ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ƒํƒœ ์•ˆ๋‚ด | ์‹œ์ž‘ยท์กฐ๊ฑด ํ•ด์ œ ๋“ฑ ๋งฅ๋ฝ์— ๋งž๋Š” ๋Œ€์•ˆ |
42
42
  | ์˜ค๋ฅ˜ | ์‹ ๊ณ  ์‹คํŒจ์— ์„ ํƒ ์‚ฌ์œ ยท์„ค๋ช… ์œ ์ง€; ์ฐจ๋‹จ์€ ์„ฑ๊ณต ํ›„ ํ™•์ • | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
43
43
 
44
+
45
+ ### ์‹ ๊ณ ์™€ ์ฐจ๋‹จ์˜ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ
46
+
47
+ 2026-10-13 ์ „์ˆ˜ ์ ๊ฒ€์—์„œ ์ตœ์ดˆ ์กฐํšŒ์™€ ๋ณ€๊ฒฝ ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ๊ฐ™์€ ๊ณจ๊ฒฉ์œผ๋กœ ์„ค๋ช…ํ•˜๋˜ ๋ถ€๋ถ„์„ ๊ตฌ์ฒดํ™”ํ–ˆ๋‹ค.
48
+
49
+ | ์ƒํ™ฉ | ์—ฐ๊ฒฐ | ์œ ์ง€ยท๋ณต๊ตฌ |
50
+ | --- | --- | --- |
51
+ | ์‚ฌ์œ  ๋ฏธ์„ ํƒ | submit ๋น„ํ™œ์„ฑ | reason์€ ํ˜„์žฌ reasons์˜ ๊ฐ’์ด์–ด์•ผ ํ•œ๋‹ค |
52
+ | ์‹ ๊ณ  ์‹คํŒจ | ์„ ํƒ ์‚ฌ์œ ยท์„ค๋ช… ์œ ์ง€ + notice | ๋‹ค์‹œ ์‹ ๊ณ ๋Š” ๊ฐ™์€ ๋Œ€์ƒ๊ณผ ํ˜„์žฌ ์ดˆ์•ˆ์œผ๋กœ ํ•œ๋‹ค |
53
+ | ์ฐจ๋‹จ ํ™•์ธ ์ทจ์†Œ | ๊ธฐ์กด ์ฐจ๋‹จ ์ƒํƒœ ์œ ์ง€ | ์‹ ๊ณ  ์ดˆ์•ˆ์€ ๋ณ„๊ฐœ์ด๋ฏ€๋กœ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค |
54
+ | ์ฐจ๋‹จ ์‹คํŒจ | AlertDialog ์œ ์ง€ | Promise ์‹คํŒจ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. ํ™•์ธ์ฐฝ์„ ๋จผ์ € ๋‹ซ๊ณ  ์„ฑ๊ณต ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๋Š”๋‹ค |
55
+ | ๋Œ€์ƒ ๋ณ€๊ฒฝ | ๋Œ€์ƒ id์™€ ์ดˆ์•ˆยท์š”์ฒญ ๋ฌถ์Œ ๊ฐฑ์‹  | ์ด์ „ ๋Œ€์ƒ์˜ ๋Šฆ์€ ์‘๋‹ต์ด ์ƒˆ ๋Œ€์ƒ ์ƒํƒœ๋ฅผ ๋ฎ์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค |
56
+
44
57
  ## ์‚ฌ์šฉํ•˜๋Š” ์ง€์นจ
45
58
 
46
59
  | ์ง€์นจ | ์“ฐ๋Š” ๊ณณ |
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md), WebยทNative `src/screen-flows.tsx`(`OnboardingScreen`), ์˜ˆ์ œ `showcase/*/screen-flow-previews.tsx`(`OnboardingFlowPreview`)ยท`showcase/shared/onboarding-pattern.ts`(์ฃผ์ œยท์š”์•ฝ). 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์‹คํ—˜ `๊ธฐ๋ณธ ํ๋ฆ„/์˜จ๋ณด๋”ฉ`(OnboardingScreen)์„ ๋ฐฐํฌํ•˜๋ฉด์„œ ๊ฐ™์€ ์ผ์„ ์ง์ ‘ ์กฐ๋ฆฝํ•˜๋˜ ๋ฐฐํฌ `ํ™”๋ฉด/์˜จ๋ณด๋”ฉ`์„ ๋Œ€์ฒดํ–ˆ๋‹ค(Web id `patterns-onboarding` ๋ณด์กด, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์†Œ๊ฐœ/์˜จ๋ณด๋”ฉ`
10
10
 
@@ -188,7 +188,7 @@ const topicPicker = <Stack axis="inline" wrap gap="xs">
188
188
  ## ํ•จ์ •
189
189
 
190
190
  - `notice` ์ž๋ฆฌ๋Š” ์ง„ํ–‰ ๋ฌธ๊ตฌ๊ฐ€ ์ฐจ์ง€ํ•œ๋‹ค. ์ €์žฅ ์‹คํŒจ๋ฅผ ๋„์šฐ๋ ค๊ณ  OnboardingScreen์— notice๋ฅผ ๋„˜๊ธธ ์ˆ˜ ์—†์œผ๋‹ˆ ๋งˆ์ง€๋ง‰ ๋‹จ๊ณ„ `content`์— ๋‘”๋‹ค.
191
- - ์™„๋ฃŒ `pending` ๋™์•ˆ์—๋„ ์ด์ „ ๋ฒ„ํŠผ์€ ๋ง‰ํžˆ์ง€ ์•Š๋Š”๋‹ค. ์ €์žฅ ์ค‘ ๋‹จ๊ณ„ ์ด๋™์ด ๋ฌธ์ œ๊ฐ€ ๋˜๋ฉด `onIndexChange`์—์„œ ๋ฌด์‹œํ•œ๋‹ค.
191
+ - ์™„๋ฃŒ ๋˜๋Š” ๊ฑด๋„ˆ๋›ฐ๊ธฐ `pending` ๋™์•ˆ HJM์€ ์ด๋™ ๋ฒ„ํŠผ์„ ํ•จ๊ป˜ ์ž ๊ทผ๋‹ค(๋ฏธ๊ฒŒ์‹œ(1.18.0 ์ดํ›„)). ์ œํ’ˆ์€ ๊ฐ™์€ ์™„๋ฃŒ ์š”์ฒญ์— pending์„ ์—ฐ๊ฒฐํ•˜๊ณ , ์‹คํŒจํ•˜๋ฉด ํ˜„์žฌ ๋‹จ๊ณ„ยท์„ ํƒ๊ฐ’์„ ์œ ์ง€ํ•œ ์ฑ„ ์ž ๊ธˆ์„ ํ‘ผ๋‹ค. ๋ผ์šฐํ„ฐ ์ดํƒˆ์€ ๋ณ„๋„๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค.
192
192
  - ํ˜„์žฌ ์Šคํ† ๋ฆฌ์˜ ์ง„ํ–‰ ๋ฌธ๊ตฌ๋Š” `` `${index} / ${total}` `` ๊ณ ์ • ๋ฌธ์ž์—ด์ด๋‹ค. ์ œํ’ˆ์€ ๋ณด๊ฐ„ ํ‚ค ํ•˜๋‚˜(`onboarding.progress`)๋ฅผ ์“ด๋‹ค.
193
193
  - Storybook์€ ์‹ค์ œ ์„œ๋ฒ„ยท๋ผ์šฐํ„ฐ ์—ฐ๋™ ์ฆ๊ฑฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. ๊ธฐ๋ณธยท์–ด๋‘์šด ํ…Œ๋งˆยทํฐ ๊ธ€์ž์™€ ์‹คํŒจ์™€ ๋ณต๊ตฌ๋ฅผ ๊ฐ๊ฐ ํ™•์ธํ•œ๋‹ค.
194
194
 
@@ -3,8 +3,8 @@
3
3
  - ๋‹จ๊ณ„: ํ™”๋ฉด
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
- - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.12.1 ์ดํ›„)
7
- - ๊ฒ€ํ† ์ผ: 2026-10-06
6
+ - ์ ์šฉ: ๊ตฌ์„ฑ API ์ง€์นจ ์ฐธ์กฐ
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-13
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md); ๊ณตํ†ต API์™€ ์‹ค์ œ WebยทNative ์˜ˆ์ œ์˜ ์Šฌ๋กฏยท์ƒํƒœ๋ฅผ ๋Œ€์กฐํ•ด ์ค‘๋ณต ์กฐ๋ฆฝ ๋ฐฉ์ง€. 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ(์ด์ „ `์‹คํ—˜/ํ™”๋ฉด/๊ธฐ๋ณธ ํ๋ฆ„/๊ถŒํ•œ ์•ˆ๋‚ด`, [์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •))
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์†Œ๊ฐœ/๊ถŒํ•œ ์•ˆ๋‚ด`
10
10
 
@@ -38,9 +38,23 @@ host: ๋‚จ์€ ๋†’์ดยทsafe areaยทํ‚ค๋ณด๋“œ
38
38
  | --- | --- | --- |
39
39
  | ๊ธฐ๋ณธ | ์„ ํƒ์  ๊ทธ๋ฆผ โ†’ ๊ถŒํ•œ ์ด์œ  โ†’ ์ƒํƒœ๋ณ„ ํ–‰๋™ | ๊ฐ ๊ณต๊ฐœ ์ฝœ๋ฐฑ์„ ์ œํ’ˆ ์ƒํƒœ์— ์—ฐ๊ฒฐ |
40
40
  | ๋กœ๋”ฉ | ์ตœ์ดˆ ์กฐํšŒ๋Š” ๋ณธ๋ฌธ ์ƒํƒœ, ์ €์žฅ์€ ํ•ด๋‹น ํ–‰๋™ pending | ์ค‘๋ณต ์ œ์ถœ ์ฐจ๋‹จ; ์„ฑ๊ณต์„ ๋จผ์ € ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ |
41
- | ๋นˆ | ์‹ค์ œ ์กฐํšŒ 0๊ฑด ๋˜๋Š” ์•„์ง ์ž‘์„ฑํ•˜์ง€ ์•Š์€ ์ƒํƒœ ์•ˆ๋‚ด | ์‹œ์ž‘ยท์กฐ๊ฑด ํ•ด์ œ ๋“ฑ ๋งฅ๋ฝ์— ๋งž๋Š” ๋Œ€์•ˆ |
41
+ | ๋นˆ | ํ•ด๋‹น ์—†์Œ: ๊ถŒํ•œ์€ ๋ชฉ๋ก 0๊ฑด์ด ์•„๋‹ˆ๋ผ OS ์ƒํƒœ๋‹ค | ์ƒํƒœ์— ๋งž๋Š” ํ–‰๋™์„ ์ œ๊ณต |
42
+ | ์‚ฌ์šฉ ๋ถˆ๊ฐ€ | status=unavailable, ๊ธฐ๋Šฅ ๋ฏธ์ง€์›ยท๋Œ€์ฒด ๊ฒฝ๋กœ ์•ˆ๋‚ด | skip ๋“ฑ ์ œํ’ˆ ๋Œ€์•ˆ |
42
43
  | ์˜ค๋ฅ˜ | ๋ Œ๋” ์‹œ OS ์š”์ฒญ ๊ธˆ์ง€; ์„ค์ • ๋ณต๊ท€ ์‹œ ์ œํ’ˆ์ด ์ƒํƒœ ์žฌ์กฐํšŒ | ์‹คํŒจ ์›์ธ๊ณผ ์žฌ์‹œ๋„ ๊ฒฝ๋กœ ์ œ๊ณต |
43
44
 
45
+
46
+ ### OS ์ƒํƒœ์— ๋งž๋Š” ๋‹ค์Œ ํ–‰๋™
47
+
48
+ 2026-10-13 ์ „์ˆ˜ ์ ๊ฒ€์—์„œ ์ตœ์ดˆ ์กฐํšŒ์™€ ๋ณ€๊ฒฝ ์š”์ฒญ์˜ ์ƒํƒœ๋ฅผ ๊ฐ™์€ ๊ณจ๊ฒฉ์œผ๋กœ ์„ค๋ช…ํ•˜๋˜ ๋ถ€๋ถ„์„ ๊ตฌ์ฒดํ™”ํ–ˆ๋‹ค.
49
+
50
+ | ์ƒํ™ฉ | ์—ฐ๊ฒฐ | ์œ ์ง€ยท๋ณต๊ตฌ |
51
+ | --- | --- | --- |
52
+ | prompt | request | ์‚ฌ์šฉ์ž ํ–‰๋™์—์„œ๋งŒ OS ๊ถŒํ•œ ์š”์ฒญ |
53
+ | denied | settings | ์„ค์ • ์•ฑยท๋ธŒ๋ผ์šฐ์ € ๋„์›€์œผ๋กœ ์ด๋™; ๋ณต๊ท€ ์‹œ ์‹ค์ œ ๊ถŒํ•œ ์žฌ์กฐํšŒ |
54
+ | granted | continueAction | ์‚ฌ์šฉํ•  ๊ธฐ๋Šฅ์œผ๋กœ ์ด๋™ |
55
+ | unavailable | skip ๋˜๋Š” ์ œํ’ˆ ๋Œ€์•ˆ | ๊ถŒํ•œ ์žฌ์š”์ฒญ์„ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋Š”๋‹ค |
56
+ | ์กฐํšŒยท์š”์ฒญ ์‹คํŒจ | notice + ๊ฐ™์€ ์š”์ฒญ ์žฌ์‹œ๋„ | denied์™€ ํ†ต์‹ ยทํ˜ธ์ŠคํŠธ ์˜ค๋ฅ˜๋ฅผ ๊ฐ™์€ ์ƒํƒœ๋กœ ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค |
57
+
44
58
  ## ์‚ฌ์šฉํ•˜๋Š” ์ง€์นจ
45
59
 
46
60
  | ์ง€์นจ | ์“ฐ๋Š” ๊ณณ |