@hjmds/design-contracts 1.29.0 โ†’ 1.31.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 (97) hide show
  1. package/dist/behaviors.d.ts +1 -0
  2. package/dist/behaviors.d.ts.map +1 -1
  3. package/dist/bottom-navigation.d.ts +24 -0
  4. package/dist/bottom-navigation.d.ts.map +1 -1
  5. package/dist/bottom-navigation.js +34 -0
  6. package/dist/bottom-navigation.js.map +1 -1
  7. package/dist/carousel.d.ts +30 -0
  8. package/dist/carousel.d.ts.map +1 -1
  9. package/dist/carousel.js +32 -0
  10. package/dist/carousel.js.map +1 -1
  11. package/dist/catalog.d.ts +382 -64
  12. package/dist/catalog.d.ts.map +1 -1
  13. package/dist/catalog.js +147 -131
  14. package/dist/catalog.js.map +1 -1
  15. package/dist/component-definitions.d.ts +21 -131
  16. package/dist/component-definitions.d.ts.map +1 -1
  17. package/dist/component-definitions.js +9 -132
  18. package/dist/component-definitions.js.map +1 -1
  19. package/dist/component-recipes.d.ts +87 -0
  20. package/dist/component-recipes.d.ts.map +1 -1
  21. package/dist/component-recipes.js +44 -1
  22. package/dist/component-recipes.js.map +1 -1
  23. package/dist/icon.d.ts +58 -1
  24. package/dist/icon.d.ts.map +1 -1
  25. package/dist/icon.js +69 -0
  26. package/dist/icon.js.map +1 -1
  27. package/dist/image-choice.d.ts +7 -0
  28. package/dist/image-choice.d.ts.map +1 -1
  29. package/dist/image-choice.js +7 -0
  30. package/dist/image-choice.js.map +1 -1
  31. package/dist/index.d.ts +4 -4
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +3 -3
  34. package/dist/index.js.map +1 -1
  35. package/dist/recipes.d.ts +1 -1
  36. package/dist/recipes.d.ts.map +1 -1
  37. package/dist/recipes.js.map +1 -1
  38. package/dist/screen-patterns.d.ts +100 -0
  39. package/dist/screen-patterns.d.ts.map +1 -1
  40. package/dist/screen-patterns.js +98 -3
  41. package/dist/screen-patterns.js.map +1 -1
  42. package/dist/showcase.d.ts.map +1 -1
  43. package/dist/showcase.js +10 -0
  44. package/dist/showcase.js.map +1 -1
  45. package/dist/steps.d.ts +16 -7
  46. package/dist/steps.d.ts.map +1 -1
  47. package/dist/steps.js +6 -1
  48. package/dist/steps.js.map +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/dist/version.js.map +1 -1
  52. package/docs/architecture.md +18 -0
  53. package/docs/bottom-navigation.md +10 -0
  54. package/docs/carousel.md +8 -0
  55. package/docs/dialog.md +23 -0
  56. package/docs/generated/component-maturity.md +32 -29
  57. package/docs/generated/renderer-evidence.json +1551 -1118
  58. package/docs/generated/renderer-evidence.md +243 -237
  59. package/docs/generated/showcase-manifest.json +688 -69
  60. package/docs/icon.md +9 -0
  61. package/docs/screen-patterns.md +18 -0
  62. package/docs/stable-core.md +22 -0
  63. package/docs/steps.md +4 -3
  64. package/docs/usage/README.md +28 -25
  65. package/docs/usage/SELECTION.md +1 -1
  66. package/docs/usage/components/auth-screen-layout.md +15 -5
  67. package/docs/usage/components/bottom-navigation.md +65 -2
  68. package/docs/usage/components/card.md +36 -5
  69. package/docs/usage/components/carousel.md +1 -0
  70. package/docs/usage/components/chat-date-separator.md +86 -0
  71. package/docs/usage/components/chat-message.md +10 -1
  72. package/docs/usage/components/chat-screen.md +3 -2
  73. package/docs/usage/components/comment-thread-row.md +11 -3
  74. package/docs/usage/components/comment-thread-screen.md +1 -1
  75. package/docs/usage/components/dialog.md +69 -0
  76. package/docs/usage/components/editor-screen.md +6 -2
  77. package/docs/usage/components/field.md +13 -0
  78. package/docs/usage/components/icon-button.md +1 -0
  79. package/docs/usage/components/icon-tile.md +10 -1
  80. package/docs/usage/components/icon.md +45 -6
  81. package/docs/usage/components/image-choice-group.md +3 -1
  82. package/docs/usage/components/media-selection-screen.md +17 -0
  83. package/docs/usage/components/message-composer.md +4 -1
  84. package/docs/usage/components/moderation-screen.md +88 -2
  85. package/docs/usage/components/page-indicator.md +102 -0
  86. package/docs/usage/components/profile-screen.md +9 -3
  87. package/docs/usage/components/profile-summary.md +3 -3
  88. package/docs/usage/components/screen-layout.md +9 -0
  89. package/docs/usage/components/steps.md +14 -7
  90. package/docs/usage/components/tabs.md +21 -1
  91. package/docs/usage/components/text.md +30 -1
  92. package/docs/usage/components/typing-indicator.md +89 -0
  93. package/docs/usage/screens/common-login.md +3 -1
  94. package/docs/usage/screens/common-profile.md +3 -3
  95. package/docs/usage/screens/flow-editor.md +13 -2
  96. package/docs/usage/screens/flow-moderation.md +31 -3
  97. package/package.json +2 -1
@@ -0,0 +1,86 @@
1
+ # ChatDateSeparator
2
+
3
+ - ๋‹จ๊ณ„: ์ปดํฌ๋„ŒํŠธ
4
+ - ์ƒํƒœ: ์‹คํ—˜
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: ๋ฏธ๊ฒŒ์‹œ(1.30.0 ์ดํ›„)
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-18
8
+ - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md), `src/screen-patterns.ts`์˜ `chatDateSeparatorRecipe`; WebยทNative `src/screens.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `์‹คํ—˜/์ปดํฌ๋„ŒํŠธ/๋ฐ์ดํ„ฐ ํ‘œ์‹œ/๋Œ€ํ™” ๋‚ ์งœ ๊ตฌ๋ถ„`
10
+
11
+ ## ์–ธ์ œ ์“ฐ๋‚˜
12
+
13
+ ๋Œ€ํ™” ํƒ€์ž„๋ผ์ธ์—์„œ ๋‚ ์งœ(๋˜๋Š” ์˜ค๋ž˜ ์‰ฐ ๋’ค์˜ ์‹œ๊ฐ)๊ฐ€ ๋ฐ”๋€Œ๋Š” ์ž๋ฆฌ์— ๊ฐ€์šด๋ฐ ์ •๋ ฌ ๊ตฌ๋ถ„ ์ค„์„ ๋‘˜ ๋•Œ ์“ด๋‹ค. ๋ฌธ๊ตฌ ํฌ๋งท("์˜ค๋Š˜", "10์›” 18์ผ ํ† ์š”์ผ", "์˜คํ›„ 3:04")๊ณผ
14
+ ๊ตฌ๋ถ„์„ ๋„ฃ์„ ์œ„์น˜(๋‚ ์งœ ๋ณ€๊ฒฝยท๊ฐ„๊ฒฉ ๊ธฐ์ค€)๋Š” ์ œํ’ˆ์ด ์ •ํ•˜๊ณ , HJM์€ ์ค„์˜ ๋ฐฐ์น˜ยท๊ธ€์žยท๊ฐ„๊ฒฉ์„ ๋งก๋Š”๋‹ค.
15
+
16
+ ## ์“ฐ์ง€ ์•Š์„ ๋•Œ
17
+
18
+ | ์ƒํ™ฉ | ๋Œ€์‹  ์“ธ ๊ฒƒ |
19
+ | --- | --- |
20
+ | ์ผ๋ฐ˜ ๋ชฉ๋ก์˜ ๊ตฌํš ์ œ๋ชฉ | [Section](section.md), [List](list.md) ๋ฌถ์Œ ์ œ๋ชฉ |
21
+ | ์„ ๋งŒ ํ•„์š”ํ•œ ๊ตฌ๋ถ„ | [Divider](divider.md) |
22
+ | ๋ฉ”์‹œ์ง€๋งˆ๋‹ค ๋ถ™๋Š” ์‹œ๊ฐ | [ChatMessage](chat-message.md) `timestamp` |
23
+
24
+ ## ๊ณต๊ฐœ ์ด๋ฆ„๊ณผ import
25
+
26
+ | ์ด๋ฆ„ | ์—ญํ•  | Web | Native |
27
+ | --- | --- | --- | --- |
28
+ | `ChatDateSeparator` | ๊ธฐ๋ณธ | `@hjmds/react/screens` | `@hjmds/react-native/screens` |
29
+
30
+ ๋ฃจํŠธ barrel์—๋Š” ์—†๋‹ค.
31
+
32
+ ## ์ตœ์†Œ ์‚ฌ์šฉ ์˜ˆ
33
+
34
+ ```tsx
35
+ // Web
36
+ import { ChatDateSeparator, ChatMessage } from "@hjmds/react/screens";
37
+
38
+ {rows.map(row => <Fragment key={row.message.id}>
39
+ {row.dayChanged ? <ChatDateSeparator label={formatDay(row.message.sentAt)} dateTime={row.message.sentAt.slice(0, 10)} /> : null}
40
+ <ChatMessage {...toMessageProps(row.message)}>{row.message.text}</ChatMessage>
41
+ </Fragment>)}
42
+ ```
43
+
44
+ ```tsx
45
+ // Native
46
+ import { ChatDateSeparator, ChatMessage } from "@hjmds/react-native/screens";
47
+
48
+ const renderItem = ({ item }: { item: Row }) => <>
49
+ {item.dayChanged ? <ChatDateSeparator label={formatDay(item.message.sentAt)} /> : null}
50
+ <ChatMessage {...toMessageProps(item.message)}><Text>{item.message.text}</Text></ChatMessage>
51
+ </>;
52
+ ```
53
+
54
+ ## ์ถ•๊ณผ ๊ธฐ๋ณธ๊ฐ’
55
+
56
+ | prop | ๊ฐ’ | ๊ธฐ๋ณธ๊ฐ’ | ์„ค๋ช… |
57
+ | --- | --- | --- | --- |
58
+ | `label` | `string` | ํ•„์ˆ˜ | ์ œํ’ˆ์ด ํฌ๋งทํ•œ ๋‚ ์งœยท์‹œ๊ฐ. ๋นˆ ๋ฌธ์ž์—ด์€ `TypeError` |
59
+ | `dateTime` | `string` | ์—†์Œ | Web `<time dateTime>` ๊ฐ’(์˜ˆ: `2026-10-18`). Native๋Š” ์“ฐ์ง€ ์•Š๋Š”๋‹ค |
60
+ | `layoutStyle` | `HjmCompositionStyleProp` | ์—†์Œ | ํ–‰ ๋ฐฐ์น˜ ์ „์šฉ. ์ƒ‰ยท์—ฌ๋ฐฑ์„ ๋ฎ์ง€ ์•Š๋Š”๋‹ค |
61
+
62
+ ## ๋ฐฐ์น˜
63
+
64
+ | ํ•ญ๋ชฉ | ๊ฐ’ | ๊ทผ๊ฑฐ |
65
+ | --- | --- | --- |
66
+ | ํฌ๊ธฐ | ํƒ€์ž„๋ผ์ธ ํญ ์ „์ฒด ํ–‰, ๊ธ€์ž `textVariant` caption ยท `tone` muted | `chatDateSeparatorRecipe` |
67
+ | ๊ฐ„๊ฒฉ | ์œ„ `paddingTop`(`spacing.sm`) 12, ์•„๋ž˜ `paddingBottom`(`spacing.xxs`) 4. ๋‹ค์Œ ๋ฉ”์‹œ์ง€๋Š” ์ž๊ธฐ `groupPosition` ์œ„ ์—ฌ๋ฐฑ(์ƒˆ ๋ฌถ์Œ 12)์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ง„๋‹ค | `chatDateSeparatorRecipe`, `resolveChatMessageGroupDisplay` |
68
+ | ์ˆœ์„œยท์ •๋ ฌ | ๊ตฌ๋ถ„ํ•  ๋ฉ”์‹œ์ง€ ๋ฐ”๋กœ ์•ž, ๊ฐ€๋กœ ๊ฐ€์šด๋ฐ | WebยทNative `ChatDateSeparator` |
69
+ | ๊ณ ์ •ยท์Šคํฌ๋กค | ์ž์ฒด ๊ณ ์ • ์—†์Œ. ํƒ€์ž„๋ผ์ธ๊ณผ ํ•จ๊ป˜ ์Šคํฌ๋กคํ•œ๋‹ค | โ€” |
70
+ | ์ข์€ ํญยทํฐ ๊ธ€์ž | ๊ธ€์ž๋Š” ์ค„๋ฐ”๊ฟˆ๋˜๊ณ  ์ž๋ฅด์ง€ ์•Š๋Š”๋‹ค | WebยทNative `ChatDateSeparator` |
71
+
72
+ ## ๊ผญ ์ง€ํ‚ฌ ๊ฒƒ
73
+
74
+ - ๊ตฌ๋ถ„ ์ค„ ๋’ค์˜ ๋ฉ”์‹œ์ง€๋Š” ์ƒˆ ๋ฌถ์Œ์œผ๋กœ ์‹œ์ž‘ํ•œ๋‹ค(`resolveChatMessageGroups`๊ฐ€ ๊ฐ™์€ ์‹œ๊ฐ ๋ฌธ์ž์—ด์ด๋ฉด ๋ฌถ์œผ๋ฏ€๋กœ, ๋‚ ์งœ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์ œํ’ˆ์ด ๋ฌถ์Œ์„ ๋Š๋Š”๋‹ค).
75
+ - ๋ฌธ๊ตฌ๋Š” ์ œํ’ˆ์ด ์ง€์—ญํ™”ยทํฌ๋งทํ•œ๋‹ค. HJM์€ ๋‚ ์งœ๋ฅผ ํ•ด์„ํ•˜์ง€ ์•Š๋Š”๋‹ค.
76
+ - ์ฝ๊ธฐ ์ˆœ์„œ์ƒ ๋ฉ”์‹œ์ง€ ์‚ฌ์ด ์ผ๋ฐ˜ ๊ธ€์ž๋กœ ์ฝํžŒ๋‹ค. ๋”ฐ๋กœ live region์ด๋‚˜ heading ์—ญํ• ์„ ์ฃผ์ง€ ์•Š๋Š”๋‹ค.
77
+
78
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
79
+
80
+ | ํ•ญ๋ชฉ | Web | Native |
81
+ | --- | --- | --- |
82
+ | ์‹œ๊ฐ ์˜๋ฏธ | `dateTime`์ด ์žˆ์œผ๋ฉด `<time>` | ๊ธ€์ž๋งŒ |
83
+
84
+ ## ๊ทผ๊ฑฐ
85
+
86
+ - ๊ณตํ†ต ๋‚ ์งœ ๊ตฌ๋ถ„: 2026-10-18 ์œ ํ‹ธ๋ฒ„์Šค ์ฑ„ํŒ…. ChatScreen์— ์‹œ๊ฐ ๊ตฌ๋ถ„ ์ค„์ด ์—†์–ด ๊ฐ€์šด๋ฐ ํ๋ฆฐ caption์„ ์ œํ’ˆ์ด ์ง์ ‘ ๊ทธ๋ ธ๋‹ค(์œ„ 12ยท์•„๋ž˜ 8 ์—ฌ๋ฐฑ). ์•„๋ž˜ ์—ฌ๋ฐฑ์€ ๋‹ค์Œ ๋ฉ”์‹œ์ง€์˜ ๋ฌถ์Œ ์—ฌ๋ฐฑ 12์™€ ๊ฒน์น˜์ง€ ์•Š๋„๋ก 4๋กœ ์ •ํ–ˆ๋‹ค.
@@ -85,8 +85,11 @@ import { Text } from "@hjmds/react-native/primitives";
85
85
  | `replyQuote` | `{ title, excerpt }` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ๋งํ’์„  ๋งจ ์œ„์— HJM์ด ์ธ์šฉ์„ ๊ทธ๋ฆฐ๋‹ค: ์ œ๋ชฉ(์˜ˆ: "์„œ์—ฐ์—๊ฒŒ ๋‹ต์žฅ", ๋ณธ๋ฌธ ๊ธ€์ž) โ†’ ์›๋ฌธ ํ•œ ์ค„(ํ๋ฆฐ ๊ธ€์ž, ๋„˜์น˜๋ฉด ๋ง์ค„์ž„) โ†’ ๊ตฌ๋ถ„์„  1 โ†’ ๋‹ต์žฅ ๋ณธ๋ฌธ. ์ž์œ  ๋…ธ๋“œ `reply`๋ณด๋‹ค ์šฐ์„  ์‚ฌ์šฉํ•œ๋‹ค |
86
86
  | `replyLink` | `{ label, onPress() }` | ์—†์Œ | ์ธ์šฉ๊ณผ ๊ฐ™์ด ์ฃผ๋ฉด ๋งํ’์„  ์•ˆ ์ธ์šฉ์„ ๋ˆŒ๋Ÿฌ ์›๋ฌธ์œผ๋กœ ์ด๋™ํ•œ๋‹ค(Native๋Š” ๋งํ’์„ ์˜ "์›๋ฌธ์œผ๋กœ ์ด๋™" ์ ‘๊ทผ์„ฑ ๋™์ž‘๋„ ์ œ๊ณต). ์ด์ „์—๋Š” ๋ฒ„๋ธ” ๋ฐ– ghost ๋ฒ„ํŠผ์ด์—ˆ๋‹ค |
87
87
  | `media` | `{ id, label, preview, aspectRatio?, onPress? }[]` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ๋ณด๋‚ธ ์‚ฌ์ง„. ํญ `chatMessageRecipe.mediaWidth` 210, ํ•œ ์žฅ์€ `aspectRatio`(๊ธฐ๋ณธ 1), ์—ฌ๋Ÿฌ ์žฅ์€ ์ •์‚ฌ๊ฐ ์นธ(`resolveChatMediaColumns`: 2ยท4์žฅ 2์—ด, ๊ทธ ์™ธ 3์—ด, ๊ฐ„๊ฒฉ 2), ๋ฐ”๊นฅ radius 14. ๊ธ€ ์—†์ด ์‚ฌ์ง„๋งŒ ๋ณด๋‚ด๋ฉด ๋งํ’์„  ํ‘œ๋ฉดยท๊ผฌ๋ฆฌ๋ฅผ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. `onPress`๊ฐ€ ์žˆ์œผ๋ฉด ์‚ฌ์ง„๋งˆ๋‹ค ๋ฒ„ํŠผ(ํฌ๊ฒŒ ๋ณด๊ธฐ๋Š” ์ œํ’ˆ) |
88
+ | `mediaAlbum` | `{ count, renderPage(index), pageLabel(index, count), previousLabel, nextLabel, aspectRatio?, onPress?(index), index?ยทonIndexChange?ยทdefaultIndex? }` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ํ•œ ์žฅ์”ฉ ๋„˜๊ธฐ๋Š” ์‚ฌ์ง„ ๋ฌถ์Œ. `renderPage`๋Š” ํ™”๋ฉด์— ๋ณด์ด๋Š” ์žฅ์—๋งŒ ํ˜ธ์ถœ๋˜์–ด ์ œํ’ˆ์ด ๊ทธ ์‚ฌ์ง„๋งŒ ์กฐํšŒยท๋””์ฝ”๋”ฉํ•œ๋‹ค(์ด๋ฏธ์ง€ ์›๋ณธยท๊ถŒํ•œยท์บ์‹œ๋Š” ์ œํ’ˆ). ํญ `mediaWidth` 210, ์‚ฌ์ง„ ์นธ `aspectRatio`(๊ธฐ๋ณธ 1)ยทradius 14, ๊ทธ ์•„๋ž˜ ์ด์ „ ์•„์ด์ฝ˜ ยท ์œ„์น˜(์˜ˆ: 2/5) ยท ๋‹ค์Œ ์•„์ด์ฝ˜ ์ค„(์ž‘์€ ghost IconButton, `mediaAlbumRecipe`). ๊ธ€ ์—†์ด ์•จ๋ฒ”๋งŒ ๋ณด๋‚ด๋ฉด `media`์ฒ˜๋Ÿผ ๋งํ’์„  ํ‘œ๋ฉดยท๊ผฌ๋ฆฌ๋ฅผ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋„˜๊น€ ๋ฒ„ํŠผ์ด ์žˆ์–ด ๋งํ’์„ ์€ `interactiveContent`๋กœ ๋‹ค๋ค„์ง„๋‹ค. ๋„˜๊ธด ์žฅ์€ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ๋„ ๊ฐ™๋‹ค. `media`์™€ ํ•จ๊ป˜ ์ฃผ๋ฉด `TypeError` |
88
89
  | `replyAction` | `{ label, onPress(), disabled?, icon? }` | ์—†์Œ | `replySwipeDistance` 60px ์ด์ƒ์ด๊ณ  ์„ธ๋กœ ์ด๋™์˜ 2๋ฐฐ๋ฅผ ๋„˜๋Š” ๊ฐ€๋กœ ์Šค์™€์ดํ”„๋กœ ์‹คํ–‰(`isReplySwipe`). `reactions`๊ฐ€ ์žˆ์œผ๋ฉด ๊ธธ๊ฒŒ ๋ˆ„๋ฅธ ๋ฉ”๋‰ด์˜ ์ฒซ ํ•ญ๋ชฉ์œผ๋กœ๋„ ๋ณด์ธ๋‹ค(๋ฏธ๊ฒŒ์‹œ) |
89
90
  | `reactions` | `ReactionPickerProps & { closeLabel, menuActions?, reportAction?, menuAction? }` | ์—†์Œ | `reactionHoldMs` 450ms ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๋ฉด ๋ฉ”๋‰ด๊ฐ€ ์—ด๋ฆฐ๋‹ค: ๋ฐ˜์‘ ์ค„(์ด๋ชจ์ง€ `typography.title` 18/26, `more`๋ฉด +) โ†’ ๋ฉ”์‹œ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ โ†’ ์นด๋“œ(๋งจ ์œ„ `timestamp` ์ •ํ™•ํ•œ ์‹œ๊ฐ โ†’ `replyAction` โ†’ `menuActions` โ†’ `menuAction` โ†’ `reportAction` ๋นจ๊ฐ„ ๊ธ€์ž). ํ•ญ๋ชฉ์€ ๋†’์ด 44, ์•„์ด์ฝ˜ 24 ์นธ. ๋ฐฐ์น˜๋Š” `holdMenuRecipe`(์ธ์Šคํƒ€๊ทธ๋žจ DM ์‹ค์ธก): ํ™”๋ฉด ์ขŒ์šฐ 10 ์•ˆ์ชฝ ์ „์ฒด ํญ ๋ฐ˜์‘ ์ค„(๋†’์ด 60, 48 ์›ํ˜• ์นธ, ์ด๋ชจ์ง€ 28, ๋„˜์น  ๋•Œ๋งŒ ๊ฐ€๋กœ ์Šคํฌ๋กค) โ†’ 12 โ†’ ๋ˆ„๋ฅธ ๋งํ’์„  ๊ทธ๋Œ€๋กœ ๋ฏธ๋ฆฌ๋ณด๊ธฐ(์ œ ์ชฝ ์ •๋ ฌ) โ†’ 12 โ†’ ํญ 200ยทradius 18 ๋™์ž‘ ์นด๋“œ(ํ–‰ 56, ์•„์ด์ฝ˜ 24, `submenu` ํ–‰์€ โ€บ)๋ฅผ ๋งํ’์„  ์ชฝ์— ๋ถ™์ธ๋‹ค. ๋ณด์ด๋Š” ร—๋Š” ์—†๊ณ  ๋ฐฐ๊ฒฝ ํƒญยทEscapeยทVoiceOver ๋‹ซ๊ธฐ๋กœ ๋‹ซ๋Š”๋‹ค(๋ณด์กฐ๊ธฐ์ˆ  ์ฒซ ์ดˆ์ ์šฉ ๋‹ซ๊ธฐ ๋Œ€์ƒ๋งŒ ๋‘”๋‹ค). ๊ฐ™์€ ๋ฐ˜์‘ ์žฌ์„ ํƒ์€ `null`. `label`์€ Web์—์„œ ๋งํ’์„  ์ด๋ฆ„ยทPopover ์ œ๋ชฉ, Native์—์„œ ์ ‘๊ทผ์„ฑ ํžŒํŠธยท๋™์ž‘ ์ด๋ฆ„. ์‹œ๊ฐยท๋™์ž‘ ์นด๋“œ์™€ `reportAction`ยท`menuActions`๋Š” ๋ฏธ๊ฒŒ์‹œ(2026-10-16 ์ธ์Šคํƒ€๊ทธ๋žจ DM ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ์‹ค์ธก ๋ฐ˜์˜) |
91
+ | `reactions.menuActions[].tone` | `"neutral"` ยท `"danger"` | `"neutral"` | ๋ฏธ๊ฒŒ์‹œ(1.30.0 ์ดํ›„). ๊ธธ๊ฒŒ ๋ˆ„๋ฅธ ๋ฉ”๋‰ด ํ–‰์˜ ์ƒ‰. ์‚ญ์ œ์ฒ˜๋Ÿผ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ํ–‰์€ `danger`(๋นจ๊ฐ„ ๊ธ€์ž). [Menu](menu.md) ํ•ญ๋ชฉ `tone`๊ณผ ๊ฐ™์€ ์–ดํœ˜(`MenuItemTone`). `reportAction`์€ ํ•ญ์ƒ `danger`์ด๊ณ  ๋งจ ๋์ด๋‹ค. ๋Œ“๊ธ€(CommentThreadRow) ๋ฉ”๋‰ด๋„ ๊ฐ™๋‹ค |
92
+ | `reactions.summary` | `{ label, items: { id, emoji, count }[] }` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ํ˜„์žฌ ๋ฐ˜์‘ ์ง‘๊ณ„๋ฅผ ์‹œ๊ฐ ์˜† ์•Œ์•ฝ ๋ฒ„ํŠผ ํ•˜๋‚˜๋กœ ๊ทธ๋ฆฌ๊ณ , ๋ˆ„๋ฅด๋ฉด ๊ธธ๊ฒŒ ๋ˆ„๋ฅธ ๊ฒƒ๊ณผ ๊ฐ™์€ ๋ฉ”๋‰ด๋ฅผ ์—ฐ๋‹ค. `label`์€ ๋ฒ„ํŠผ์˜ ์ง€์—ญํ™” ์ ‘๊ทผ์„ฑ ์ด๋ฆ„(์˜ˆ: "๋ฐ˜์‘ โค๏ธ 2๊ฐœ, ๐Ÿ˜‚ 1๊ฐœ. ๋ฐ˜์‘ ๋ฉ”๋‰ด ์—ด๊ธฐ"), `items`๋Š” ํ‘œ์‹œ ์ˆœ์„œ. `count`๊ฐ€ `chatMessageRecipe.reactionSummary.minCount` 2 ๋ฏธ๋งŒ์ด๋ฉด ์ด๋ชจ์ง€๋งŒ ๋ณด์ธ๋‹ค. ๋‚ด ๋ฐ˜์‘(`value`)์ด ๋“ค์–ด ์žˆ์œผ๋ฉด brand ํ…Œ๋‘๋ฆฌ. ๋†’์ด 24(Native๋Š” hitSlop์œผ๋กœ 44 ํ„ฐ์น˜), ์ขŒ์šฐ 8, ํ•ญ๋ชฉ ์‚ฌ์ด 4. ๋นˆ `label`ยท์ค‘๋ณต idยท๋นˆ ์ด๋ชจ์ง€๋Š” `TypeError`, ์–‘์˜ ์ •์ˆ˜๊ฐ€ ์•„๋‹Œ count๋Š” `RangeError`(`resolveChatReactionSummary`). `reactions.disabled`๋ฉด ๋ฒ„ํŠผ๋„ ๋น„ํ™œ์„ฑ. ๋ฉ”๋‰ด๊ฐ€ ๋‹ซํžˆ๋ฉด Web ์ดˆ์ ์€ ๋งํ’์„ ์œผ๋กœ ๋Œ์•„๊ฐ„๋‹ค |
90
93
  | `interactiveContent` | `boolean` | `false` | ์‚ฌ์ง„ ์•จ๋ฒ”ยท๋งํฌ์ฒ˜๋Ÿผ ์ž์‹์ด ๋ฒ„ํŠผ์„ ๊ฐ€์ง€๋ฉด `true`. ์•ˆ์ชฝ ๋ฒ„ํŠผยท๋งํฌ์˜ ๋ˆ„๋ฆ„๊ณผ Web Enter/Space๋Š” ๊ทธ ์ปจํŠธ๋กค์ด ๋ฐ›๋Š”๋‹ค |
91
94
  | `actions` | `ReactNode` | ์—†์Œ | ์‹œ๊ฐ ์˜† ์Šฌ๋กฏ. ๋ฐ˜์‘ ์ง‘๊ณ„ ๋ฐฐ์ง€ยท์žฌ์‹œ๋„๋Š” ์ œํ’ˆ์ด ๋„ฃ๋Š”๋‹ค |
92
95
  | `children` | `ReactNode` | ํ•„์ˆ˜ | ๋ฉ”์‹œ์ง€ ๋ณธ๋ฌธ. Native๋Š” ๋งํ’์„ ์ด ์ด ํ…์ŠคํŠธ๋กœ ์ฝํžŒ๋‹ค |
@@ -111,7 +114,8 @@ import { Text } from "@hjmds/react-native/primitives";
111
114
  ๋‹ต์žฅยท๋ฐ˜์‘์€ ์ ‘๊ทผ์„ฑ ๋™์ž‘(`reply`ยท`activate`)์œผ๋กœ ๋‚จ๋Š”๋‹ค. 2026-10-06 ๋ฆฌ๋ทฐ์—์„œ ํ–‰ ์ด๋ฆ„(`author`)๊ณผ ๋ฐ˜์‘ ์ด๋ฆ„(`picker.label`)์ด
112
115
  ๋ณธ๋ฌธ์„ ๊ฐ€๋ ค ํ™”๋ฉด ๋‚ญ๋…๊ธฐ๊ฐ€ ๋ฉ”์‹œ์ง€๋ฅผ ์ฝ์ง€ ๋ชปํ–ˆ๋‹ค.
113
116
  - ํƒ€์ž„๋ผ์ธ ์ „์ฒด๋ฅผ live region์œผ๋กœ ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค(๊ณผ๊ฑฐ ๋ฉ”์‹œ์ง€ ๋กœ๋”ฉ ๋•Œ ์ฝ๊ธฐ ์ˆœ์„œ๋ฅผ ๊ฐ€๋กœ์ฑˆ๋‹ค).
114
- - ๋ฐ˜์‘ **์„ ํƒ ๋ฉ”๋‰ด**์™€ ํ˜„์žฌ ๋ฐ˜์‘ **์ง‘๊ณ„ ๋ฒ„ํŠผ**์€ ๋‹ค๋ฅธ ํ–‰๋™์ด๋‹ค. ์ง‘๊ณ„๋ฅผ ๋ˆŒ๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ฉ”๋‰ด๋ฅผ ์—ด ๋•Œ๋Š”
117
+ - ๋ฐ˜์‘ **์„ ํƒ ๋ฉ”๋‰ด**์™€ ํ˜„์žฌ ๋ฐ˜์‘ **์ง‘๊ณ„ ๋ฒ„ํŠผ**์€ ๋‹ค๋ฅธ ํ–‰๋™์ด๋‹ค. ์ง‘๊ณ„๋ฅผ ๋ˆŒ๋Ÿฌ ๊ฐ™์€ ๋ฉ”๋‰ด๋ฅผ ์—ด ๋•Œ๋Š”
118
+ `reactions.summary`๋ฅผ ์“ด๋‹ค(๋ฏธ๊ฒŒ์‹œ). ์ง‘๊ณ„๋ฅผ ์ƒํƒœ ํ‘œ์‹œ์šฉ Tag๋กœ `actions`์— ์ง์ ‘ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๋‹ค๋ฅธ ํ–‰๋™(์˜ˆ: ๋ฐ˜์‘ํ•œ ์‚ฌ๋žŒ ๋ชฉ๋ก)์„ ์—ฌ๋Š” ๋ฒ„ํŠผ์€
115
119
  `actions` ์Šฌ๋กฏ์— Button์„ ๊ณต๊ธ‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ณธ๋ฌธ ์•ˆ์— ์ง‘๊ณ„ ๋ฒ„ํŠผ์„ ๋„ฃ๋Š”๋‹ค๋ฉด ์‚ฌ์ง„ยท๋งํฌ๊ฐ€ ์—†์–ด๋„
116
120
  `interactiveContent=true`๊ฐ€ ํ•„์š”ํ•˜๋‹ค. 2026-10-07 Utilverse ์†Œ์Šค ๋Œ€์กฐ์—์„œ ์‚ฌ์ง„/๋„๊ตฌ๋งŒ ๊ฒ€์‚ฌํ•ด
117
121
  ์ง‘๊ณ„ ๋ฒ„ํŠผ์ด ๋ถ€๋ชจ ์ ‘๊ทผ์„ฑ ๊ทธ๋ฃน์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ๋ฅผ ๋ฐœ๊ฒฌํ–ˆ๋‹ค. `actions`์— ๋‘˜ ๋•Œ๋„ ๊ธด ์ง‘๊ณ„์™€
@@ -129,6 +133,8 @@ import { Text } from "@hjmds/react-native/primitives";
129
133
 
130
134
  ## ํ•จ์ •
131
135
 
136
+ - ์‚ฌ์ง„์ด ์—ฌ๋Ÿฌ ์žฅ์ด๊ณ  ์žฅ๋งˆ๋‹ค ๊ถŒํ•œ ํ™•์ธยท์กฐํšŒ๊ฐ€ ํ•„์š”ํ•˜๋ฉด `media`์— ๋ชจ๋“  `preview`๋ฅผ ๋„˜๊ธฐ์ง€ ๋ง๊ณ  `mediaAlbum`์„ ์“ด๋‹ค. `media`์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” ํ•œ ๋ฒˆ์— ๋ชจ๋‘ ๋งˆ์šดํŠธ๋œ๋‹ค(2026-10-18 Utilverse ์†Œ์…œ ์‚ฌ์ง„: ๋Œ€ํ™” ํ–‰๋งˆ๋‹ค ์›๋ณธ 10์žฅ์„ ๋ฐ›์ง€ ์•Š์œผ๋ ค๊ณ  ์ œํ’ˆ ์•จ๋ฒ”์„ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค).
137
+
132
138
  - Native๋Š” ๋ฐ˜์‘ ๋ชจ๋‹ฌ ์•ˆ์—์„œ `children`์„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋กœ ํ•œ ๋ฒˆ ๋” ๋ Œ๋”ํ•œ๋‹ค. ์ž์‹์€ ํ‘œ์‹œ์šฉ ์ฝ˜ํ…์ธ ๋กœ ์ค€๋‹ค
133
139
  (๋ถ€์ž‘์šฉยท๊ณ ์œ  ref๋ฅผ ๋‘์ง€ ์•Š๋Š”๋‹ค).
134
140
  - `reactions`์˜ `options`์™€ `more.options`๋Š” id๊ฐ€ ํ•ฉ์ณ์„œ ์œ ์ผํ•˜๊ณ  emojiยทlabel์ด ๋น„์–ด ์žˆ์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. `value`๊ฐ€
@@ -147,4 +153,7 @@ import { Text } from "@hjmds/react-native/primitives";
147
153
 
148
154
  ## ๊ทผ๊ฑฐ
149
155
 
156
+ - `reactions.summary`: 2026-10-18 ๋ฒˆ๋š DM. ๋ฉ”๋‰ด๋ฅผ ๋ฐ–์—์„œ ์—ด ์ˆ˜ ์—†์–ด ์ง‘๊ณ„๋ฅผ ์ƒํƒœ์šฉ Tag๋กœ๋งŒ `actions`์— ๊ทธ๋ ธ๋‹ค(์ œํ’ˆ docs/DESIGN.md์— ๊ฒฉ์ฐจ๋กœ ๊ธฐ๋ก). ์ œ์–ดํ˜• `open` ๋Œ€์‹  HJM์ด ์ง‘๊ณ„ ๋ฒ„ํŠผ๊นŒ์ง€ ๊ทธ๋ ค ์œ„์น˜ยท์ ‘๊ทผ์„ฑ ์ด๋ฆ„ยท๋ฉ”๋‰ด ์—ฐ๊ฒฐ์„ ๊ณตํ†ต์œผ๋กœ ๋งก๋Š”๋‹ค.
157
+ - `menuActions[].tone`: 2026-10-18 ์œ ํ‹ธ๋ฒ„์Šค ์ฑ„ํŒ… ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋ฉ”๋‰ด์˜ ์‚ญ์ œ ํ–‰. ๋นจ๊ฐ„ ๊ธ€์ž๊ฐ€ `reportAction`์—๋งŒ ์žˆ์–ด ์‚ญ์ œ๊ฐ€ ์ผ๋ฐ˜ ํ–‰์œผ๋กœ ๋ณด์˜€๋‹ค.
158
+
150
159
  - ์•„๋ฐ”ํƒ€ 32: 2026-10-17 ์ •๋ ฌ ์‹ค์ธก. recipeยท๋ฌธ์„œ๋Š” 36์ด์—ˆ์ง€๋งŒ Web Avatar ๋‹จ๊ณ„(32/40/48/64)์— 36์ด ์—†์–ด Web์€ 36 ์นธ์— 32 ์•„๋ฐ”ํƒ€๋ฅผ, Native๋Š” 36 ์•„๋ฐ”ํƒ€๋ฅผ ๊ทธ๋ ธ๋‹ค. ์นด์นด์˜คํ†ก ์ฐธ๊ณ ๊ฐ’ ์•ฝ 34์— ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด Avatar ๋‹จ๊ณ„ `small` 32๋ฅผ recipe๊ฐ€ ์ง์ ‘ ์ฝ๊ฒŒ ํ•ด ๋‘ ํ”Œ๋žซํผ์ด ๊ฐ™์€ ๊ฐ’์„ ์“ด๋‹ค. ์ž‘์„ฑ์ž ์ฒซ ์ค„๊ณผ์˜ ์œ„์ชฝ ๋งž์ถค(์นด์นด์˜คํ†ก ๋ฐฉ์‹)์€ ์œ ์ง€ํ•œ๋‹ค.
@@ -29,6 +29,7 @@ DMยท๋Œ€ํ™”๋ฐฉ์ฒ˜๋Ÿผ ํ—ค๋”, ๋ฉ”์‹œ์ง€ ํƒ€์ž„๋ผ์ธ, ํ•˜๋‹จ ์ž‘์„ฑ์ฐฝ์œผ๋กœ ์ด
29
29
  | --- | --- | --- | --- |
30
30
  | `ChatScreen` | ํ™”๋ฉด ์กฐํ•ฉ | `/screens` | `/screens` |
31
31
  | `ChatMessage`, `MessageComposer`, `ScreenLayout` | ํ•จ๊ป˜ ์“ฐ๋Š” ์กฐ๊ฐ | `/screens` | `/screens` |
32
+ | [`TypingIndicator`](typing-indicator.md), [`ChatDateSeparator`](chat-date-separator.md) | ํƒ€์ž„๋ผ์ธ ์กฐ๊ฐ(์ž…๋ ฅ ์ค‘ ํ‘œ์‹œ, ๋‚ ์งœ ๊ตฌ๋ถ„). ๋ฏธ๊ฒŒ์‹œ(1.30.0 ์ดํ›„) | `/screens` | `/screens` |
32
33
 
33
34
  ๋ฃจํŠธ barrel์—๋Š” ์—†๋‹ค. `/screens`๋Š” optional native peer๋ฅผ ์š”๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
34
35
 
@@ -68,11 +69,11 @@ import { ChatScreen, MessageComposer } from "@hjmds/react-native/screens";
68
69
  | --- | --- | --- |
69
70
  | `title`(ํ•„์ˆ˜) | ์ง€์—ญํ™”ํ•œ ๋Œ€ํ™” ์ œ๋ชฉ | `header`๋ฅผ ์ฃผ๋ฉด Web์€ `aria-label`๋กœ๋งŒ ์“ด๋‹ค |
70
71
  | `header` / `exit` / `actions` / `description` | ๊ธฐ์กด navigation ํ—ค๋”, ๋˜๋Š” ํ—ค๋” ์‹œ์ž‘ ์ชฝ ํ™”๋ฉด ๋‚˜๊ฐ€๊ธฐ ์•„์ด์ฝ˜(`exit`: ๋’ค๋กœยท๋‹ซ๊ธฐ)ยท๋„๊ตฌ ์Šฌ๋กฏ | `header`๋ฅผ ์ฃผ๋ฉด ๊ธฐ๋ณธ ํ—ค๋”๋ฅผ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค |
71
- | `children` | ํƒ€์ž„๋ผ์ธ(๊ฐ€์ƒํ™” ๋ชฉ๋ก + `ChatMessage`) | ์ž๋™ ์Šคํฌ๋กคยท์ƒˆ ๋ฉ”์‹œ์ง€ ๋ฐฐ์ง€ยท์ด์ „ ๋ฉ”์‹œ์ง€ ์œ„์น˜๋Š” ์ œํ’ˆ |
72
+ | `children` | ํƒ€์ž„๋ผ์ธ(๊ฐ€์ƒํ™” ๋ชฉ๋ก + `ChatMessage`) | ์ž๋™ ์Šคํฌ๋กคยท์ƒˆ ๋ฉ”์‹œ์ง€ ๋ฐฐ์ง€ยท์ด์ „ ๋ฉ”์‹œ์ง€ ์œ„์น˜๋Š” ์ œํ’ˆ. ๋‚ ์งœยท์‹œ๊ฐ์ด ๋ฐ”๋€Œ๋Š” ์ž๋ฆฌ์—๋Š” `ChatDateSeparator`, ์ƒ๋Œ€๊ฐ€ ์ž…๋ ฅ ์ค‘์ด๋ฉด ๋งˆ์ง€๋ง‰ ๋ฉ”์‹œ์ง€ ๋’ค์— `TypingIndicator`๋ฅผ ๋‘”๋‹ค. ๋‘˜์„ ์ œํ’ˆ captionยท์  ํ‘œ์‹œ๋กœ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค |
72
73
  | `composer`(ํ•„์ˆ˜) | `MessageComposer` ๋“ฑ ์ž‘์„ฑ์ฐฝ | `ready`์™€ `empty`์—์„œ ๋ณด์ธ๋‹ค(๋นˆ ๋Œ€ํ™”์˜ ์ฒซ ๋ฉ”์‹œ์ง€ ์ž…๋ ฅ) |
73
74
  | `state` / `stateAction` | `loading`ยท`empty`ยท`error`ยท`restricted` + ์ง€์—ญํ™” `title` | ready ์™ธ์—๋Š” ๋ณธ๋ฌธ์„ ๊ต์ฒดํ•œ๋‹ค |
74
75
  | `notice` | ์ƒˆ๋กœ๊ณ ์นจ ์‹คํŒจ ๊ฐ™์€ ๋น„์ฐจ๋‹จ ์•ˆ๋‚ด | ์ดˆ์•ˆ ์œ ์ง€๊ฐ€ ํ•„์š”ํ•˜๋ฉด `state` ๋Œ€์‹  ์ด๊ฒƒ |
75
- | `avatar` | ์ƒ๋Œ€ยท๋ฐฉ ์‚ฌ์ง„ ๋…ธ๋“œ | ๋ฏธ๊ฒŒ์‹œ. ๊ฐ€์šด๋ฐ ์ •๋ ฌ ์ œ๋ชฉ ์•ž์— ๋†“๋Š”๋‹ค. ๋‹จํ†ก๋ฐฉ์€ ์ธ์› ์ˆ˜๋ฅผ `titleBadge`(ํ๋ฆฐ ๊ธ€์ž)๋กœ ๋„˜๊ธด๋‹ค |
76
+ | `avatar` | ์ƒ๋Œ€ยท๋ฐฉ ์‚ฌ์ง„ ๋…ธ๋“œ | ๋ฏธ๊ฒŒ์‹œ. ๊ฐ€์šด๋ฐ ์ •๋ ฌ ์ œ๋ชฉ ์•ž์— ๋†“๋Š”๋‹ค. ๋‹จํ†ก๋ฐฉ์€ ์ธ์› ์ˆ˜๋ฅผ `titleBadge`(ํ๋ฆฐ ๊ธ€์ž)๋กœ ๋„˜๊ธด๋‹ค. ScreenLayout `titleLeading`๊ณผ ๊ฐ™์€ ์ž๋ฆฌ๋ผ ChatScreen์€ `titleLeading`์„ ๋”ฐ๋กœ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค. ์žฅ์‹์ด๋ผ ๋ณด์กฐ๊ธฐ์ˆ ์—์„œ ์ˆจ๋Š”๋‹ค(์ œ๋ชฉ์ด ์ด๋ฆ„) |
76
77
  | `jumpToLatest` | `{ label, icon, onPress() }` | ๋ฏธ๊ฒŒ์‹œ. ๋…์ž๊ฐ€ ์ตœ์‹  ๋ฉ”์‹œ์ง€์—์„œ ๋ฒ—์–ด๋‚ฌ์„ ๋•Œ๋งŒ ๋„˜๊ธด๋‹ค. ์ž‘์„ฑ์ฐฝ ์œ„ ๋ ์ชฝ์— ์›ํ˜• `IconButton size="small"`(secondary)๋กœ ๋–  ์žˆ๊ณ , ์Šคํฌ๋กค ์œ„์น˜ ํŒ๋‹จ์€ ์ œํ’ˆ์ด ํ•œ๋‹ค |
77
78
 
78
79
  ## ์ถ•๊ณผ ๊ธฐ๋ณธ๊ฐ’
@@ -64,6 +64,9 @@ import { CommentThreadRow } from "@hjmds/react-native/screen-flows";
64
64
  | --- | --- | --- |
65
65
  | `id`ยท`parentId`ยท`author`ยท`body`ยท`timeLabel`ยท`likeCountLabel`ยท`likeIcon`ยท`likeLabel` | ํ•„์ˆ˜ | `parentId`๊ฐ€ `null`์ด๋ฉด ์ตœ์ƒ์œ„ ๋Œ“๊ธ€ |
66
66
  | `bodyText` | ์—†์Œ | ์ฃผ๋ฉด `author`์™€ ๋ณธ๋ฌธ์„ ํ•œ ๋ฌธ์žฅ์œผ๋กœ, ์‹œ๊ฐ์€ ์•„๋ž˜ ํ–‰๋™ ์ค„๋กœ ์˜ฎ๊ธด๋‹ค |
67
+ | `timePlacement` | `"auto"` | ๋ฏธ๊ฒŒ์‹œ. `"auto"`๋Š” `bodyText`๊ฐ€ ์—†์œผ๋ฉด ์‹œ๊ฐ์„ ์ž‘์„ฑ์ž ์˜†์— ๋‘”๋‹ค. `"actions"`๋Š” ๊ธ€์ด ์•„๋‹Œ ๋ณธ๋ฌธ(๊ฝƒ ๋„์žฅยท์นด๋“œ)์—์„œ๋„ ์‹œ๊ฐ์„ ๋ณธ๋ฌธ ์•„๋ž˜ ๋ฐ˜์‘ ์š”์•ฝยท๋‹ต๊ธ€๊ณผ ๊ฐ™์€ ์ค„ ๋งจ ์•ž์œผ๋กœ ์˜ฎ๊ธด๋‹ค |
68
+ | `panel` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ๋Œ“๊ธ€๋ณ„ ์ธ๋ผ์ธ ํŒจ๋„(์ˆ˜์ •ยท์‹ ๊ณ ยท์ฐจ๋‹จ ์–‘์‹). ๋‹ต๊ธ€ ์ค„ ์•„๋ž˜, ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋Œ€์ƒ ๋ฐ–์— `panelGap` 8 ๊ฐ„๊ฒฉ์œผ๋กœ ๋†“์ธ๋‹ค. ์—ด๋ฆผ ์ƒํƒœยท์ดˆ์ ยท์ œ์ถœยท๋‹ซ๊ธฐ๋Š” ์ œํ’ˆ์ด ์†Œ์œ ํ•œ๋‹ค |
69
+ | `mediaAlbum` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ํ•œ ์žฅ์”ฉ ๋„˜๊ธฐ๋Š” ์‚ฌ์ง„ ๋ฌถ์Œ `{ count, renderPage(index), pageLabel(index, count), previousLabel, nextLabel, aspectRatio?, onPress?(index), index?ยทonIndexChange?ยทdefaultIndex? }`. `renderPage`๋Š” ํ™”๋ฉด์— ๋ณด์ด๋Š” ์žฅ์—๋งŒ ํ˜ธ์ถœ๋˜์–ด ์ œํ’ˆ์ด ๊ทธ ์‚ฌ์ง„๋งŒ ์กฐํšŒยท๋””์ฝ”๋”ฉํ•œ๋‹ค. `media`์™€ ํ•จ๊ป˜ ์ฃผ๋ฉด `TypeError`. ๋ณธ๋ฌธ ์•„๋ž˜, ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋Œ€์ƒ ๋ฐ–์— ๋†“์ธ๋‹ค |
67
70
  | `avatar` | ์—†์Œ | ์ œํ’ˆ ์•„๋ฐ”ํƒ€. ์ตœ์ƒ์œ„ 32ยท๋‹ต๊ธ€ 24 ํญ ์นธ์— ๋‘”๋‹ค |
68
71
  | `canReply` | `parentId === null` | ๋‹ต๊ธ€ ํ–‰๋™ ํ‘œ์‹œ ์—ฌ๋ถ€ |
69
72
  | `replyDisabled` | false | ๋‹ต๊ธ€ ํ–‰๋™ ๋น„ํ™œ์„ฑ |
@@ -75,9 +78,9 @@ import { CommentThreadRow } from "@hjmds/react-native/screen-flows";
75
78
 
76
79
  | ํ•ญ๋ชฉ | ๊ฐ’ | ๊ทผ๊ฑฐ |
77
80
  | --- | --- | --- |
78
- | ํฌ๊ธฐ | ์•„๋ฐ”ํƒ€ ์นธ ์ตœ์ƒ์œ„ 32ยท๋‹ต๊ธ€ 24, ์ข‹์•„์š” ์ˆ˜ ์นธ ํญ 44, `media` ์ตœ๋Œ€ ํญ 160ยทradius `radius.md` | `commentThreadRecipe` |
81
+ | ํฌ๊ธฐ | ์•„๋ฐ”ํƒ€ ์นธ ์ตœ์ƒ์œ„ 32ยท๋‹ต๊ธ€ 24, ์ข‹์•„์š” ์ˆ˜ ์นธ ํญ 44, `media`ยท`mediaAlbum` ์ตœ๋Œ€ ํญ 160ยทradius `radius.md`(์•จ๋ฒ”์€ ์‚ฌ์ง„ ์นธ์—๋งŒ radius, ๊ทธ ์•„๋ž˜ ์ด์ „ ์•„์ด์ฝ˜ ยท ์œ„์น˜(์˜ˆ: 2/5) ยท ๋‹ค์Œ ์•„์ด์ฝ˜ ์ค„, ์ž‘์€ ghost IconButton) | `commentThreadRecipe`, `mediaAlbumRecipe` |
79
82
  | ๊ฐ„๊ฒฉ | ์•„๋ฐ”ํƒ€ยท๋ณธ๋ฌธยท์ข‹์•„์š” ์—ด ์‚ฌ์ด `spacing.xs` 8, ๋ณธ๋ฌธ ์ค„ ์‚ฌ์ด 2, ํ–‰๋™ ์ค„ ํ•ญ๋ชฉ ์‚ฌ์ด `spacing.sm` 12, ์ž‘์„ฑ์žโ€“์‹œ๊ฐ `spacing.xxs` 4, `media` ์œ„ 4 | `commentThreadRecipe.lineGap`, `Stack gap` |
80
- | ์ˆœ์„œยท์ •๋ ฌ | ์•„๋ฐ”ํƒ€ โ†’ (์ž‘์„ฑ์žยท์‹œ๊ฐ โ†’ ๋ณธ๋ฌธ โ†’ media โ†’ ์‹œ๊ฐ/๋ฐ˜์‘ ์š”์•ฝ/๋‹ต๊ธ€ ํ–‰๋™) โ†’ ์ข‹์•„์š” ๋ฒ„ํŠผยท`actions` ์•„๋ž˜ ์ข‹์•„์š” ์ˆ˜. ์œ„ ์ •๋ ฌ ๋‹จ, ์˜ค๋ฅธ์ชฝ ์ข‹์•„์š”ยท`actions`ยท๋”๋ณด๊ธฐ ์—ด์€ 44 ํ„ฐ์น˜ ์˜์—ญ์„ ์ฒซ ์ค„(์ž‘์„ฑ์ž caption, `bodyText`๋ฉด ๋ณธ๋ฌธ ์ค„)์˜ ๊ฐ€์šด๋ฐ์— ๋งž์ถ˜๋‹ค: ์—ด์„ (์ฒซ ์ค„ ๋†’์ด โˆ’ 44) / 2๋งŒํผ ์˜ฌ๋ฆฐ๋‹ค(`commentThreadRecipe.actionAnchor`). HJM์˜ ์ข‹์•„์š”ยท๋”๋ณด๊ธฐ๊ฐ€ ๋งˆ์ง€๋ง‰์ด๋ฉด ์—ด ์ „์ฒด๊ฐ€ IconButton ๊ฐ€์žฅ์ž๋ฆฌ ์˜ค๋ฒ„ํ–‰(10)๋งŒํผ ๋์œผ๋กœ ๋‚˜๊ฐ€ ์•„์ด์ฝ˜ ์นธ์ด ํ–‰ ๋ ์—ฌ๋ฐฑ ์„ (์•„๋ฐ”ํƒ€ ์‹œ์ž‘๊ณผ ๊ฐ™์€ ์„ )์— ์˜จ๋‹ค. ์ œํ’ˆ `actions`๊ฐ€ ๋งˆ์ง€๋ง‰์ด๋ฉด ๊ทธ ๋ฒ„ํŠผ์— `edge="end"`๋ฅผ ์ค€๋‹ค. ์ธก์ • ์ „ํ›„(Web 402): ์•„๋ฐ”ํƒ€ 168 / ์ž‘์„ฑ์ž ์ค„ 162 / ์•„์ด์ฝ˜ 174 โ†’ ์•„์ด์ฝ˜ 160 = ์ž‘์„ฑ์ž ์ค„, ๋”๋ณด๊ธฐ ์นธ ๋ 28 โ†’ 16(2026-10-17 ์‹ค์ธก ๊ฐ์‚ฌ, [IconButton `edge`](icon-button.md#๋ฐฐ์น˜)) | ์–‘ renderer |
83
+ | ์ˆœ์„œยท์ •๋ ฌ | ์•„๋ฐ”ํƒ€ โ†’ (์ž‘์„ฑ์žยท์‹œ๊ฐ โ†’ ๋ณธ๋ฌธ โ†’ media ๋˜๋Š” mediaAlbum โ†’ ์‹œ๊ฐ/๋ฐ˜์‘ ์š”์•ฝ/๋‹ต๊ธ€ ํ–‰๋™ โ†’ panel) โ†’ ์ข‹์•„์š” ๋ฒ„ํŠผยท`actions` ์•„๋ž˜ ์ข‹์•„์š” ์ˆ˜. ์œ„ ์ •๋ ฌ ๋‹จ, ์˜ค๋ฅธ์ชฝ ์ข‹์•„์š”ยท`actions`ยท๋”๋ณด๊ธฐ ์—ด์€ 44 ํ„ฐ์น˜ ์˜์—ญ์„ ์ฒซ ์ค„(์ž‘์„ฑ์ž caption, `bodyText`๋ฉด ๋ณธ๋ฌธ ์ค„)์˜ ๊ฐ€์šด๋ฐ์— ๋งž์ถ˜๋‹ค: ์—ด์„ (์ฒซ ์ค„ ๋†’์ด โˆ’ 44) / 2๋งŒํผ ์˜ฌ๋ฆฐ๋‹ค(`commentThreadRecipe.actionAnchor`). HJM์˜ ์ข‹์•„์š”ยท๋”๋ณด๊ธฐ๊ฐ€ ๋งˆ์ง€๋ง‰์ด๋ฉด ์—ด ์ „์ฒด๊ฐ€ IconButton ๊ฐ€์žฅ์ž๋ฆฌ ์˜ค๋ฒ„ํ–‰(10)๋งŒํผ ๋์œผ๋กœ ๋‚˜๊ฐ€ ์•„์ด์ฝ˜ ์นธ์ด ํ–‰ ๋ ์—ฌ๋ฐฑ ์„ (์•„๋ฐ”ํƒ€ ์‹œ์ž‘๊ณผ ๊ฐ™์€ ์„ )์— ์˜จ๋‹ค. ์ œํ’ˆ `actions`๊ฐ€ ๋งˆ์ง€๋ง‰์ด๋ฉด ๊ทธ ๋ฒ„ํŠผ์— `edge="end"`๋ฅผ ์ค€๋‹ค. ์ธก์ • ์ „ํ›„(Web 402): ์•„๋ฐ”ํƒ€ 168 / ์ž‘์„ฑ์ž ์ค„ 162 / ์•„์ด์ฝ˜ 174 โ†’ ์•„์ด์ฝ˜ 160 = ์ž‘์„ฑ์ž ์ค„, ๋”๋ณด๊ธฐ ์นธ ๋ 28 โ†’ 16(2026-10-17 ์‹ค์ธก ๊ฐ์‚ฌ, [IconButton `edge`](icon-button.md#๋ฐฐ์น˜)) | ์–‘ renderer |
81
84
  | ๊ณ ์ •ยท์Šคํฌ๋กค | ํ–‰์€ ์Šคํฌ๋กคํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ชฉ๋ก ์Šคํฌ๋กคยทํ–‰ ๊ฐ„๊ฒฉ(`rowGap` 12)ยท๋‹ต๊ธ€ ๋“ค์—ฌ์“ฐ๊ธฐ(`replyIndent` 40)๋Š” host๊ฐ€ ์ค€๋‹ค | `CommentThreadScreen` |
82
85
  | ์ข์€ ํญยทํฐ ๊ธ€์ž | ๋ณธ๋ฌธ ์—ด์ด `min-width: 0`์œผ๋กœ ์ค„๋ฉฐ ์ค„๋ฐ”๊ฟˆ๋˜๊ณ  ์ž‘์„ฑ์žยทํ–‰๋™ ์ค„์€ `flexWrap`. ์ข‹์•„์š” ์—ด์€ ์ค„์ง€ ์•Š๋Š”๋‹ค | `.hjm-comment-row__text`, `flexShrink: 0` |
83
86
 
@@ -85,7 +88,8 @@ import { CommentThreadRow } from "@hjmds/react-native/screen-flows";
85
88
 
86
89
  - ๊ฐ€์ƒํ™” host์—์„œ ํ–‰ ์‚ฌ์ด ๊ฐ„๊ฒฉ(`commentThreadRecipe.rowGap`)๊ณผ ๋‹ต๊ธ€ ๋“ค์—ฌ์“ฐ๊ธฐ(`replyIndent`)๋ฅผ ์ง์ ‘ ์ค„ ๋•Œ๋„ recipe ๊ฐ’์„ ์“ด๋‹ค. ์ œํ’ˆ ์ˆซ์ž๋กœ ๋‹ค์‹œ ์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค.
87
90
  - ๋‹ต๊ธ€ ํŽผ์น˜๊ธฐยท์ •๋ ฌยท๊ถŒํ•œยท์ข‹์•„์š” ์ €์žฅ์€ ์ œํ’ˆ์ด ์†Œ์œ ํ•œ๋‹ค. ํ–‰์€ ์ฝœ๋ฐฑ๋งŒ ํ˜ธ์ถœํ•œ๋‹ค.
88
- - ํ–‰ ์•ˆ์— ๋‘ ๋ฒˆ์งธ ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“ค์ง€ ๋ง๊ณ  `actions`ยท`likeAction`ยท`media` ์Šฌ๋กฏ์œผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค.
91
+ - ํ–‰ ์•ˆ์— ๋‘ ๋ฒˆ์งธ ๋ ˆ์ด์•„์›ƒ์„ ๋งŒ๋“ค์ง€ ๋ง๊ณ  `actions`ยท`likeAction`ยท`media`ยท`mediaAlbum`ยท`panel` ์Šฌ๋กฏ์œผ๋กœ ์ถ”๊ฐ€ํ•œ๋‹ค. ์‹œ๊ฐยท๋‹ต๊ธ€ ์ค„์„ ๋ณธ๋ฌธ ๋…ธ๋“œ ์•ˆ์— ๋‹ค์‹œ ๊ทธ๋ฆฌ์ง€ ๋ง๊ณ  `timePlacement: "actions"`์™€ `canReply`๋ฅผ ์“ด๋‹ค.
92
+ - `mediaAlbum`์˜ ์‚ฌ์ง„ ์กฐํšŒยท๊ถŒํ•œ ํ™•์ธยท์บ์‹œ ์ˆ˜๋ช…์€ ์ œํ’ˆ์ด `renderPage` ์•ˆ์—์„œ ์†Œ์œ ํ•œ๋‹ค. HJM์€ ๋ณด์ด๋Š” ์žฅ๋งŒ ๋งˆ์šดํŠธํ•˜๊ณ  ๋„˜๊น€ ์ƒํƒœ๋ฅผ ํ–‰ ์•ˆ์— ๋‘”๋‹ค(์ œ์–ดํ˜•์€ `index`+`onIndexChange`).
89
93
 
90
94
  ## ์ƒํƒœ์™€ ์ƒํ˜ธ์ž‘์šฉ
91
95
 
@@ -93,6 +97,10 @@ import { CommentThreadRow } from "@hjmds/react-native/screen-flows";
93
97
  `reactions`๋ฅผ ์ฃผ๋ฉด ์ž‘์„ฑ์žยท๋ณธ๋ฌธ๋งŒ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋Œ€์ƒ์ด ๋˜๊ณ , ๋‹ต๊ธ€ ๊ฐ€๋Šฅํ•˜๋ฉด ๋ฉ”๋‰ด์— ๋‹ต๊ธ€ ํ•ญ๋ชฉ์ด ์ž๋™์œผ๋กœ ๋ถ™๋Š”๋‹ค.
94
98
  ๋‹ต๊ธ€ ํ–‰๋™ ๋ฒ„ํŠผ์€ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋Œ€์ƒ ๋ฐ–์— ๋‚จ์•„ ์ž๊ธฐ ๋ฒ„ํŠผ ์˜๋ฏธ๋ฅผ ์œ ์ง€ํ•œ๋‹ค.
95
99
 
100
+ ## ๊ทผ๊ฑฐ
101
+
102
+ - `timePlacement`ยท`panel`ยท`mediaAlbum`: 2026-10-18 HJM 1.30.0 ์ฑ„ํƒ ๊ฐ์‚ฌ. Spint๋Š” ๊ฝƒ ๋„์žฅ ๋ณธ๋ฌธ์—์„œ ์‹œ๊ฐ์„ ๋‹ต๊ธ€ ์ค„์— ๋‘๋ ค๊ณ  ์‹œ๊ฐยท๋‹ต๊ธ€ ํ–‰์„ ๋ณธ๋ฌธ ์•ˆ์— ๋‹ค์‹œ ๊ทธ๋ ธ๊ณ , Utilverse๋Š” ์ˆ˜์ •ยท์‹ ๊ณ ยท์ฐจ๋‹จ ์–‘์‹์„ ๋ณธ๋ฌธ ๋…ธ๋“œ ์•ˆ(๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ๋Œ€์ƒ ์•ˆ)์— ๋„ฃ์—ˆ์œผ๋ฉฐ, ์‚ฌ์ง„ ๋ฌถ์Œ์€ ์„ ํƒํ•œ ์žฅ๋งŒ ์กฐํšŒํ•˜๋Š” ์ œํ’ˆ ์•จ๋ฒ”์„ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.
103
+
96
104
  ## ํ”Œ๋žซํผ ์ฐจ์ด
97
105
 
98
106
  | ํ•ญ๋ชฉ | Web | Native |
@@ -93,7 +93,7 @@ import { MessageComposer } from "@hjmds/react-native/screens";
93
93
  `likeIcon`, `likeLabel`์€ ํ•„์ˆ˜. ์„ ํƒ์€ `bodyText`(์ž‘์„ฑ์ž์™€ ๋ณธ๋ฌธ์„ ํ•œ ์ค„ ํ๋ฆ„์œผ๋กœ), `avatar`, `likeAction`(๊ธฐ๋ณธ
94
94
  ์ข‹์•„์š” ๋ฒ„ํŠผ ๊ต์ฒด, `null`์ด๋ฉด ์ƒ๋žต), `actions`(ํ•˜ํŠธ ๋ฐ”๋กœ ์˜†์˜ ๋”๋ณด๊ธฐ ๋ฉ”๋‰ด ๋“ฑ), `canReply`(๊ธฐ๋ณธ: ์ตœ์ƒ์œ„๋งŒ true), `replyDisabled`.
95
95
  `reactions`(๋ฏธ๊ฒŒ์‹œ)๋ฅผ ์ฃผ๋ฉด ์ž‘์„ฑ์žยท๋ณธ๋ฌธ์„ ๊ธธ๊ฒŒ ๋ˆŒ๋Ÿฌ ChatMessage์™€ ๊ฐ™์€ ๋ฉ”๋‰ด(๋ฐ˜์‘ ์ค„ โ†’ ๋ฏธ๋ฆฌ๋ณด๊ธฐ โ†’ ์‹œ๊ฐ โ†’ ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ โ†’ `menuActions` โ†’ `reportAction` ์‹ ๊ณ )๊ฐ€ ์—ด๋ฆฐ๋‹ค. ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ๋Š” `canReply`์ผ ๋•Œ `onReply`๋กœ ์ž๋™ ์—ฐ๊ฒฐ๋˜๊ณ , ์•„์ด์ฝ˜์€ `reactions.replyIcon`์œผ๋กœ ๋„˜๊ธด๋‹ค. `reactionSummary`๋Š” ๋‹ต๊ธ€ ๋‹ฌ๊ธฐ ์•ž์— ๋†“๋Š” ์„ ํƒ๋œ ๋ฐ˜์‘ ํ‘œ์‹œ ์Šฌ๋กฏ์ด๋‹ค. ๋Œ“๊ธ€์˜ ๊ธฐ๋ณธ์€ ๋ฐ˜์‘ ์˜ต์…˜์„ ๋น„์šด(`options: []`) ๋™์ž‘ ๋ชฉ๋ก ์ „์šฉ ๋ฉ”๋‰ด๋‹ค(๋‹ต๊ธ€ ๋‹ฌ๊ธฐ โ†’ `menuActions` โ†’ ์‹ ๊ณ , ์ธ์Šคํƒ€๊ทธ๋žจ ๋Œ“๊ธ€ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ์‹ค์ธก). ๋Œ“๊ธ€ ์ด๋ชจ์ง€๋Š” ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์ž‘์„ฑ์ฐฝ์˜ `quickInserts` ์ด๋ชจ์ง€ ์ค„๋กœ ์ž…๋ ฅํ•œ๋‹ค. ๋ฐ˜์‘ ์ค„์€ ์ œํ’ˆ์ด ๋ช…์‹œ์ ์œผ๋กœ ๋Œ“๊ธ€ ๋ฐ˜์‘์„ ์šด์˜ํ•  ๋•Œ๋งŒ ์˜ต์…˜์„ ๋„ฃ๋Š”๋‹ค. ๋Œ“๊ธ€ ๋ฉ”๋‰ด์—๋Š” ์‹œ๊ฐ ์ œ๋ชฉ์„ ๋‘์ง€ ์•Š๋Š”๋‹ค(ํ–‰์— ์ด๋ฏธ ๋ณด์ธ๋‹ค).
96
- `media`(๋ฏธ๊ฒŒ์‹œ, ์„ ํƒ)๋Š” ๋ณธ๋ฌธ ์•„๋ž˜ ์‚ฌ์ง„ยท์Šคํ‹ฐ์ปค์ด๋ฉฐ ์ตœ๋Œ€ ํญ `commentThreadRecipe.mediaMaxWidth` 160, radius 12๋กœ ๊ฐ์‹ผ๋‹ค. `likeCountLabel`์€ ํ•˜ํŠธ ์•„๋ž˜ ์ˆซ์ž๋งŒ(์˜ˆ: "15") ๋„ฃ๊ณ  0์ด๋ฉด ๋นˆ ๋ฌธ์ž์—ด๋กœ ์ˆจ๊ธด๋‹ค. ๊ฐœ์ˆ˜๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๋ฌธ์žฅ์€ `likeLabel`์— ๋‘”๋‹ค.
96
+ `media`(๋ฏธ๊ฒŒ์‹œ, ์„ ํƒ)๋Š” ๋ณธ๋ฌธ ์•„๋ž˜ ์‚ฌ์ง„ยท์Šคํ‹ฐ์ปค์ด๋ฉฐ ์ตœ๋Œ€ ํญ `commentThreadRecipe.mediaMaxWidth` 160, radius 12๋กœ ๊ฐ์‹ผ๋‹ค. ์‚ฌ์ง„ ์—ฌ๋Ÿฌ ์žฅ์„ ํ•œ ์žฅ์”ฉ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•˜๋ฉด `media` ๋Œ€์‹  `mediaAlbum`, ์‹œ๊ฐ์„ ๋‹ต๊ธ€ ์ค„๋กœ ์˜ฎ๊ธฐ๋ ค๋ฉด `timePlacement: "actions"`, ์ˆ˜์ •ยท์‹ ๊ณ ยท์ฐจ๋‹จ ์–‘์‹์€ `panel`์„ ์“ด๋‹ค(๋ฏธ๊ฒŒ์‹œ, [CommentThreadRow](comment-thread-row.md#์ถ•๊ณผ-๊ธฐ๋ณธ๊ฐ’)). `likeCountLabel`์€ ํ•˜ํŠธ ์•„๋ž˜ ์ˆซ์ž๋งŒ(์˜ˆ: "15") ๋„ฃ๊ณ  0์ด๋ฉด ๋นˆ ๋ฌธ์ž์—ด๋กœ ์ˆจ๊ธด๋‹ค. ๊ฐœ์ˆ˜๋ฅผ ํฌํ•จํ•œ ์ „์ฒด ๋ฌธ์žฅ์€ `likeLabel`์— ๋‘”๋‹ค.
97
97
 
98
98
  ## ๋ฐฐ์น˜
99
99
 
@@ -38,6 +38,7 @@
38
38
  | --- | --- | --- | --- |
39
39
  | `Dialog` | ๊ธฐ๋ณธ | `@hjmds/react`, `/overlays` | `@hjmds/react-native`, `/overlays` |
40
40
  | `OverlayStackProvider` | ํ™•์žฅ โ€” ๋ช…๋ นํ˜• ์—ด๊ธฐ(`useDialog`ยท`useSheet`ยท`useOverlayStack`) | `@hjmds/react`, `/overlay-stack` | ์—†์Œ |
41
+ | `ImmersiveOverlay` | ํ™•์žฅ โ€” ์นด๋“œยทscrimยท๋จธ๋ฆฌ ์—†์ด ํ™”๋ฉด ์ „์ฒด๋ฅผ ์ฑ„์šฐ๋Š” ํ‘œํ˜„(1.31.0). ์•„๋ž˜ [์ „์ฒด ํ™”๋ฉด ํ‘œํ˜„](#์ „์ฒด-ํ™”๋ฉด-ํ‘œํ˜„-immersiveoverlay) | `@hjmds/react`, `/overlays` | `@hjmds/react-native`, `/overlays` |
41
42
 
42
43
  ## ์ตœ์†Œ ์‚ฌ์šฉ ์˜ˆ
43
44
 
@@ -127,6 +128,8 @@ await handle.closed; // portal ์ œ๊ฑฐยท์ดˆ์  ๋ณต๊ตฌ ๋’ค. ๋‹ค์Œ ์˜ค๋ฒ„๋ ˆ์ด๋Š”
127
128
  | `onActionError`(Native) | `(error: unknown) => void` | โ€” | action ์‹คํŒจ๋ฅผ ๋ณธ๋ฌธ ์˜ค๋ฅ˜ยท์žฌ์‹œ๋„๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค. ์˜ค๋ฅ˜๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ ์ดˆ์•ˆ์„ ๋ณด์กดํ•œ๋‹ค. ์—†์œผ๋ฉด ๊ฐœ๋ฐœ ๋นŒ๋“œ์—์„œ ์‹คํŒจ๋งˆ๋‹ค `console.error`๋ฅผ ๋‚จ๊ธด๋‹ค(์ค‘๋ณต ์ œ๊ฑฐ ์—†์Œ). 1.13.0 ๋ณ€๊ฒฝ |
128
129
  | `children`(Native) | `ReactNode` | โ€” | ๊ณ ์ •๋œ ์ œ๋ชฉ ์ค„ ์•„๋ž˜ ์Šคํฌ๋กค ๋ณธ๋ฌธ(`ScrollView`) ์•ˆ์— ๋“ค์–ด๊ฐ„๋‹ค. `FlatList`ยท`SectionList`๋ฅผ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค |
129
130
  | `contentStyle`(Native) | ๋ฐฐ์น˜ key๋งŒ(`HjmCompositionStyleProp`) | โ€” | ๋Œ€ํ™”์ƒ์ž ์ƒ์ž์˜ ๋ฐฐ์น˜. ๋†’์ด๋Š” `size`๋กœ |
131
+ | `overlayLayer` | `ReactNode` | ์—†์Œ | 1.31.0. ์นด๋“œ **๋ฐ–** ํ™”๋ฉด ์ „์ฒด ์ธต์— ๊ทธ๋ฆฌ๋Š” ์žฅ์‹(๋Œ๊ธฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐยทํŠน์ • ์œ„์น˜ ๊ฐ•์กฐ). ์นด๋“œ๋ณด๋‹ค ์œ„์— ๊ทธ๋ ค์ง€๊ณ  Web viewport/Native window ์ขŒํ‘œ(`clientX`ยท`pageX`ยท`measureInWindow`)๋ฅผ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค. ๋ˆ„๋ฆ„์„ ๋ฐ›์ง€ ์•Š๊ณ  ๋ณด์กฐ๊ธฐ์ˆ ์—์„œ ์ˆจ๊ธด๋‹ค(Web `inert`ยท`aria-hidden`, Native `pointerEvents="none"`ยท`no-hide-descendants`). ์กฐ์ž‘ํ•  ๋‚ด์šฉ์€ `children`์— ๋‘”๋‹ค |
132
+ | `scrollEnabled` | `boolean` | `true` | 1.31.0. `false`๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๋Œ€ํ™”์ƒ์ž๋ฅผ ์Šคํฌ๋กคํ•˜์ง€ ๋ชปํ•œ๋‹ค(ํ˜„์žฌ ์œ„์น˜ ์œ ์ง€). Web์€ ์นด๋“œ `overflow: hidden`, Native๋Š” ๋ณธ๋ฌธ ScrollView `scrollEnabled`. ๋ณธ๋ฌธ ์•ˆ ๋Œ๊ธฐ ์ œ์Šค์ฒ˜๊ฐ€ ์ง„ํ–‰๋˜๋Š” ๋™์•ˆ๋งŒ ๋„๊ณ  ๋๋‚˜๋ฉด ๋˜๋Œ๋ฆฐ๋‹ค. ๋‚ด์šฉ์„ ์ž๋ฅด๋Š” ์šฉ๋„๋กœ ์“ฐ์ง€ ์•Š๋Š”๋‹ค |
130
133
  | `useDialog()`(Web) | `(request) => { closed: Promise<void>, close(): void }` | โ€” | request๋Š” `open`ยท`defaultOpen`ยท`onOpenChange`ยท`trigger`๋ฅผ ๋บ€ DialogProps. `OverlayStackProvider` ์•ˆ์—์„œ๋งŒ |
131
134
 
132
135
  - Dialog๋Š” `layoutStyle`์„ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค(Web ์ œ์™ธ 15๊ฐœ ์ค‘ ํ•˜๋‚˜ โ€” ํฌํ„ธ๋กœ ๊ฐ€์šด๋ฐ ๊ทธ๋ ค์ ธ ๋ฐฐ์น˜ํ•  ํ๋ฆ„ ์•ˆ ๋ฃจํŠธ๊ฐ€ ์—†๋‹ค).
@@ -178,6 +181,66 @@ Native, ํฐ ๊ธ€์ž ๋˜๋Š” ํญ < 480: [ ์ทจ์†Œ ]
178
181
  - Native์—์„œ ์ €์žฅ์ฒ˜๋Ÿผ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋Š” action์€ `onActionError`๋ฅผ ๋„˜๊ธด๋‹ค. ๋‹จ๋ฐœ์„ฑ ์ž‘์—…์— Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ์™„๋ฃŒ๊นŒ์ง€ ๋‹ซํžˆ์ง€ ์•Š์œผ๋ฏ€๋กœ, ์ฆ‰์‹œ ๋‹ซ์œผ๋ ค๋ฉด ์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.
179
182
  - ๋ฐฐ์น˜ ๋ณ€๊ฒฝ์€ Native `contentStyle`(layout ์ „์šฉ)๋งŒ ์“ด๋‹ค. ๋†’์ด๋Š” `size`๋กœ ์ •ํ•œ๋‹ค. Web์€ `className`๋งŒ ๋ฐ›์œผ๋ฉฐ ์‹œ๊ฐ override์— ์“ฐ์ง€ ์•Š๋Š”๋‹ค.
180
183
 
184
+ ### ์นด๋“œ ๋ฐ– ๋ ˆ์ด์–ด (`overlayLayer`)
185
+
186
+ ํด๋” ์•ˆ ์•„์ด์ฝ˜์„ ๋Œ ๋•Œ์ฒ˜๋Ÿผ ์†๊ฐ€๋ฝยทํฌ์ธํ„ฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋Š” ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋Š” ์นด๋“œ ์•ˆ์— ๊ทธ๋ฆฌ๋ฉด ์นด๋“œ์˜ ์•ˆ์ชฝ ์—ฌ๋ฐฑยท์Šคํฌ๋กคยท์ž˜๋ฆผยท์—ด๋ฆผ
187
+ ๋ณ€ํ˜• ๋•Œ๋ฌธ์— ์ขŒํ‘œ๊ฐ€ ์–ด๊ธ‹๋‚˜๊ณ  ์นด๋“œ์— ๊ฐ‡ํžŒ๋‹ค. `overlayLayer`๋Š” ์นด๋“œ์™€ ๊ฐ™์€ ๋ชจ๋‹ฌ ์•ˆ์ด์ง€๋งŒ ์นด๋“œ์˜ ํ˜•์ œ ์ธต์ด๋ผ ์ด ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค.
188
+ ๋Œ๊ธฐ ์ œ์Šค์ฒ˜์™€ ๋“œ๋กญ ๋Œ€์ƒ ์ธก์ •์€ ์นด๋“œ ์•ˆ `children`์ด ์†Œ์œ ํ•˜๊ณ , ์ธต์—๋Š” ํ˜„์žฌ ์ขŒํ‘œ์˜ ๊ทธ๋ฆผ๋งŒ ๋„˜๊ธด๋‹ค.
189
+ Native ์ž…๋ ฅ ๋ณธ๋ฌธ์˜ ํ‚ค๋ณด๋“œ ํšŒํ”ผ๋Š” Dialog๊ฐ€ ์ด๋ฏธ ๋งก๋Š”๋‹ค(์œ„ ํ‚ค๋ณด๋“œ ์„ค๋ช…). ์ œํ’ˆ์ด `KeyboardAvoidingView`๋ฅผ ๋‹ค์‹œ ๊ฐ์‹ธ์ง€ ์•Š๋Š”๋‹ค.
190
+ ๋Œ๊ธฐ ์ค‘์—๋Š” `scrollEnabled={!dragging}`์œผ๋กœ ๋ณธ๋ฌธ ์Šคํฌ๋กค์„ ์ž ๊ทผ๋‹ค. ์ž ๊ทธ์ง€ ์•Š์œผ๋ฉด Native ScrollView๊ฐ€ ์„ธ๋กœ ์›€์ง์ž„์„ ๊ฐ€์ ธ๊ฐ€ ๋Œ๊ธฐ๊ฐ€ ๋Š๊ธฐ๊ณ , Web์€ ์นด๋“œ๊ฐ€ ํ•จ๊ป˜ ์Šคํฌ๋กค๋œ๋‹ค.
191
+
192
+ ```tsx
193
+ // Native
194
+ <Dialog open={open} onOpenChange={setOpen} title={folder.name} closeLabel={t("common.close")}
195
+ scrollEnabled={drag === null}
196
+ overlayLayer={drag ? <DragGhost x={drag.pageX} y={drag.pageY} item={drag.item} /> : null}>
197
+ {grid /* ๋Œ๊ธฐ responder์™€ measureInWindow ๋“ฑ๋ก์€ ์—ฌ๊ธฐ */}
198
+ </Dialog>
199
+ ```
200
+
201
+ <!-- usage-example: Web ../../../../../showcase/web/src/components/dialog-overlay-examples.tsx -->
202
+ - [Web ์‹คํ–‰ ์˜ˆ์ œ](https://github.com/jim1286/hjm-design-system/blob/8afe62bcf44b4ced063998bc9669b55704265f2d/showcase/web/src/components/dialog-overlay-examples.tsx): `DialogOverlayLayerPreview`ยท`ImmersiveOverlayPreview`(`๋ฐฐํฌ/์ปดํฌ๋„ŒํŠธ/์˜ค๋ฒ„๋ ˆ์ด/๋Œ€ํ™”์ƒ์ž`์˜ ์นด๋“œ ๋ฐ– ๋ ˆ์ด์–ดยท์ „์ฒด ํ™”๋ฉด ํ‘œํ˜„).
203
+ <!-- usage-example: Native ../../../../../showcase/native/src/dialog-overlay-preview.tsx -->
204
+ - [Native ์‹คํ–‰ ์˜ˆ์ œ](https://github.com/jim1286/hjm-design-system/blob/8afe62bcf44b4ced063998bc9669b55704265f2d/showcase/native/src/dialog-overlay-preview.tsx): ๊ฐ™์€ ์ด๋ฆ„์˜ Native preview.
205
+
206
+ ### ์ „์ฒด ํ™”๋ฉด ํ‘œํ˜„ (`ImmersiveOverlay`)
207
+
208
+ ์ „๊ด‘ํŒยท๋ฐœํ‘œ ํ™”๋ฉด์ฒ˜๋Ÿผ **ํ™”๋ฉด ์ž์ฒด๊ฐ€ ๋‚ด์šฉ**์ธ ํ‘œํ˜„์— ์“ด๋‹ค. ์นด๋“œยทscrimยท๋จธ๋ฆฌยท์•ˆ์ชฝ ์—ฌ๋ฐฑ์ด ์—†๊ณ , ์ œํ’ˆ์ด ๊ทธ๋ฆฐ ๋ฌด๋Œ€๊ฐ€ ํ™”๋ฉด ๋๊นŒ์ง€
209
+ ์ฑ„์šด๋‹ค. ์•„๋ž˜์—๋Š” HJM ํ–‰๋™ ์ค„(์ƒํƒœ ๋ฌธ๊ตฌ โ†’ ๋„๊ตฌ ํ–‰๋™ โ†’ ๋‚˜๊ฐ€๊ธฐ ํ–‰๋™)์ด safe area ์•ˆ์— ๋ถ™๋Š”๋‹ค. ๋ชจ๋‹ฌ ์Šคํƒยทํฌ์ปค์Šค ๊ฐ€๋‘๊ธฐยท
210
+ ๋ฐฐ๊ฒฝ ๊ฒฉ๋ฆฌยท๋‹ซํžŒ ๋’ค ํฌ์ปค์Šค ๋ณต๊ท€ยท์•„๋ž˜ ํ™”๋ฉด์˜ ์ง„์ž… ํฌ์ปค์Šค ๋Œ€๊ธฐ๋Š” Dialog์™€ ๊ฐ™๋‹ค.
211
+
212
+ | prop | ๊ฐ’ | ๊ธฐ๋ณธ๊ฐ’ | ์„ค๋ช… |
213
+ | --- | --- | --- | --- |
214
+ | `open`ยท`onOpenChange` | `boolean`, `(open, { reason }) => void` | โ€” | ์ œ์–ดํ˜•๋งŒ. Web `reason`: `"exit-action"`ยท`"escape"`. Native: `"exit-action"`ยท`"back"` |
215
+ | `label` | ํ˜„์ง€ํ™” ๋ฌธ์ž์—ด | โ€” | ํ‘œํ˜„ ์ „์ฒด์˜ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„ |
216
+ | `exitLabel` | ํ˜„์ง€ํ™” ๋ฌธ์ž์—ด | โ€” | ๋‚˜๊ฐ€๊ธฐ ๋ฒ„ํŠผ์— ๋ณด์ด๋Š” ๋ฌธ๊ตฌ(์ œํ’ˆ ๋ฌธ๊ตฌ) |
217
+ | `children` | `ReactNode` | โ€” | ๋ฌด๋Œ€. ๋‚จ์€ ๋†’์ด ์ „์ฒด๋ฅผ ์ฑ„์šฐ๊ณ  ๋„˜์น˜๋Š” ๋ถ€๋ถ„์€ ์ž˜๋ฆฐ๋‹ค |
218
+ | `tools` | `ReactNode` | ์—†์Œ | ๋‚˜๊ฐ€๊ธฐ ์•ž์— ๋†“๋Š” ๋„๊ตฌ ํ–‰๋™(๋ฉˆ์ถค ๋“ฑ `IconButton`) |
219
+ | `status` | `ReactNode` | ์—†์Œ | ํ–‰๋™ ์ค„ ์œ„ ์ƒํƒœ ๋ฌธ๊ตฌยท`Notice` |
220
+ | `returnFocusRef` | ref | ์—†์Œ | ๋‹ซํžŒ ๋’ค ๋Œ์•„๊ฐˆ ๋Œ€์ƒ |
221
+ | `initialFocusRef`(Web) | ref | ๋‚˜๊ฐ€๊ธฐ ๋ฒ„ํŠผ | ์—ด๋ฆด ๋•Œ ํฌ์ปค์Šค |
222
+ | `modalPriority`ยท`portalContainer`ยท`className`(Web) | โ€” | โ€” | Dialog์™€ ๊ฐ™๋‹ค |
223
+ | `onShow`(Native) | RN `Modal` `onShow` | โ€” | ํ‘œ์‹œ ์™„๋ฃŒ |
224
+
225
+ - Native๋Š” `presentationStyle="fullScreen"` Modal์—์„œ ์ƒํƒœ ํ‘œ์‹œ์ค„์„ ์ˆจ๊ธฐ๊ณ  ์„ธ๋กœยท๊ฐ€๋กœ ํšŒ์ „์„ ํ—ˆ์šฉํ•œ๋‹ค(`dialogRecipe.immersive.orientations`, ์•ฑ์ด ์„ธ๋กœ ๊ณ ์ •์ด์–ด๋„ ์ด ํ‘œํ˜„์€ ํšŒ์ „ํ•œ๋‹ค). ๋ฌด๋Œ€๋Š” ํ™”๋ฉด ๋๊นŒ์ง€ ๊ทธ๋ ค์ง€๋ฏ€๋กœ ์ฝ์–ด์•ผ ํ•  ๋‚ด์šฉ์€ ๊ฐ€์šด๋ฐ ๋‘๊ฑฐ๋‚˜ `useHjmNativeSafeAreaInsets`๋กœ ๊ฐ€์žฅ์ž๋ฆฌ ์ ˆ๊ฐœ๋ฅผ ํ”ผํ•œ๋‹ค.
226
+ - Web์€ viewport ์ „์ฒด๋ฅผ ๋ฎ๋Š” ๊ณ ์ • ์ธต์ด๋‹ค. ๋ธŒ๋ผ์šฐ์ € Fullscreen API๋Š” ์š”์ฒญํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ƒˆ ์‚ฌ์šฉ์ž ํ™œ์„ฑํ™”๊ฐ€ ํ•„์š”ํ•˜๊ณ , ๊ทธ Escape๋Š” ํŽ˜์ด์ง€๊ฐ€ ๋ฐ›๊ธฐ ์ „์— ์ „์ฒด ํ™”๋ฉด๋งŒ ๋๋‚ด ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์ฐฝ ์•ˆ์— ๋‚จ์œผ๋ฉฐ, iOS Safari๋Š” ์š”์†Œ ์ „์ฒด ํ™”๋ฉด์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹ค.
227
+ - ํ–‰๋™ ์ค„ ๊ฐ„๊ฒฉ: ์•ˆ์ชฝ `spacing.md` 16(์•„๋ž˜ยท์ขŒ์šฐ๋Š” safe area์™€ ํฐ ๊ฐ’), ์ƒํƒœโ€“ํ–‰๋™ `spacing.xs` 8, ํ–‰๋™ ์‚ฌ์ด `spacing.sm` 12. ๋‚˜๊ฐ€๊ธฐ ๋ฒ„ํŠผ์€ `size="large"`๋กœ ๋‚จ์€ ํญ์„ ์ฑ„์šด๋‹ค.
228
+ - ํ™”๋ฉด ๊บผ์ง ๋ฐฉ์ง€ยท์›€์ง์ž„ ๊ฐ™์€ ๋ฌด๋Œ€ ๋™์ž‘์€ ์ œํ’ˆ ์†Œ์œ ๋‹ค.
229
+
230
+ ```tsx
231
+ // Native
232
+ import { ImmersiveOverlay } from "@hjmds/react-native/overlays";
233
+
234
+ <ImmersiveOverlay open={presenting} onOpenChange={(next) => { if (!next) setPresenting(false); }}
235
+ label={t("display.title")} exitLabel={t("displayClose")}
236
+ status={<Text variant="caption" tone="muted">{t("displayWakeActive")}</Text>}
237
+ tools={<IconButton tone="secondary" size="large" label={t("displayPause")} onPress={togglePause}><PauseGlyph /></IconButton>}>
238
+ {sign /* ์ œํ’ˆ์ด ๊ทธ๋ฆฐ ์ „๊ด‘ํŒ */}
239
+ </ImmersiveOverlay>
240
+ ```
241
+
242
+ ์‹คํ–‰ ์˜ˆ์ œ: ์œ„ ๊ฐ™์€ ํŒŒ์ผ์˜ `ImmersiveOverlayPreview`(`๋ฐฐํฌ/์ปดํฌ๋„ŒํŠธ/์˜ค๋ฒ„๋ ˆ์ด/๋Œ€ํ™”์ƒ์ž` ์ „์ฒด ํ™”๋ฉด ํ‘œํ˜„).
243
+
181
244
  ## ํ”Œ๋žซํผ ์ฐจ์ด
182
245
 
183
246
  | ํ•ญ๋ชฉ | Web | Native |
@@ -255,3 +318,9 @@ function RecordDetail() {
255
318
  ์‹ค์ œ View์˜ `measureInWindow`๋กœ ํ™œ์„ฑํ™” ์‹œ์ ์˜ ์˜์—ญ์„ ์–ป๊ณ , ์ขŒํ‘œ๋ฅผ ์–ป์ง€ ๋ชปํ•ด๋„ ์ผ๋ฐ˜ Dialog๋ฅผ ์—ฐ๋‹ค.
256
319
  ์ธก์ • callback๋งŒ ๋ฌด๊ธฐํ•œ ๊ธฐ๋‹ค๋ฆฌ๋ฉด์„œ ์—ด๊ธฐ ํ–‰๋™์„ ๋ง‰์ง€ ์•Š๋Š”๋‹ค. ๋ฒ„ํŠผ์˜ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„๊ณผ ์ดˆ์  ๋ณต๊ท€ ๋Œ€์ƒ์„
257
320
  ์œ ์ง€ํ•˜๊ณ , ์นด๋“œ ์ „์ฒด๋ฅผ ๋ˆ„๋ฆ„ ๊ฐ€๋Šฅํ•œ ๋น„์˜๋ฏธ์  View/div๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
321
+
322
+ ## ๊ทผ๊ฑฐ
323
+
324
+ - `overlayLayer`: 2026-10-18 ๋ฒˆ๋š ํด๋” ํŒ์—…. ๋Œ๊ธฐ ์œ ๋ น ์•„์ด์ฝ˜์„ ์ฐฝ ์ขŒํ‘œ๋กœ ์นด๋“œ ๋ฐ–์— ๊ทธ๋ ค์•ผ ํ•ด์„œ Dialog ๋Œ€์‹  raw RN `Modal`๊ณผ `KeyboardAvoidingView`๋ฅผ ์ผ๋‹ค. ํ‚ค๋ณด๋“œ ํšŒํ”ผ๋Š” 2026-10-13๋ถ€ํ„ฐ Dialog๊ฐ€ ์ด๋ฏธ ๊ฐ–๊ณ  ์žˆ์—ˆ๊ณ , ์นด๋“œ ๋ฐ– ์ธต๋งŒ ์—†์—ˆ๋‹ค.
325
+ - `scrollEnabled`: 2026-10-18 ๋ฒˆ๋š ํด๋” ํŒ์—…. ์•„์ด์ฝ˜์„ ๋„๋Š” ๋™์•ˆ ๋ณธ๋ฌธ ์Šคํฌ๋กค์„ ๊บผ์•ผ ํ•ด์„œ(`scrollEnabled={!touchingIcon && !draggingId}`) raw `Modal`์˜ ScrollView๋ฅผ ์œ ์ง€ํ–ˆ๋‹ค.
326
+ - `ImmersiveOverlay`: 2026-10-18 utilverse ์ „๊ด‘ํŒ. DialogยทSheet๋Š” ํ•ญ์ƒ ์นด๋“œยทscrim(`full` Sheet๋„ ์œ„ safe areaยท๋ชจ์„œ๋ฆฌยท๋จธ๋ฆฌ)์„ ๊ทธ๋ ค ์ƒํƒœ ํ‘œ์‹œ์ค„์„ ์ˆจ๊ธด ๊ฐ€๋กœ ์ „๊ด‘ํŒ์„ ํ‘œํ˜„ํ•˜์ง€ ๋ชปํ•ด raw RN `Modal`์„ ์ผ๋‹ค. ํ‘œํ˜„๋งŒ ๋‹ค๋ฅด๊ณ  ๋ชจ๋‹ฌ ์Šคํƒยทํฌ์ปค์Šคยท๋‹ซ๊ธฐ ๊ณ„์•ฝ์€ Dialog์™€ ๊ฐ™์•„ Dialog์˜ ํ™•์žฅ์œผ๋กœ ๋‘”๋‹ค.
@@ -4,7 +4,7 @@
4
4
  - ์ƒํƒœ: ๋ฐฐํฌ
5
5
  - ์ง€์›: Web ยท Native
6
6
  - ์ ์šฉ: 1.17.1
7
- - ๊ฒ€ํ† ์ผ: 2026-10-17
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-18
8
8
  - ๊ทผ๊ฑฐ: [๋ฐ˜๋ณต ํ™”๋ฉด ์กฐํ•ฉ](../../screen-patterns.md), WebยทNative `src/screens.tsx`ยท`src/screen-flows.tsx`; ๊ธฐ์กด ๊ฐœ๋ณ„ ์ง€์นจ์„ ์ƒˆ ๊ทœ๊ฒฉ์œผ๋กœ ํ†ตํ•ฉ. ์˜ˆ์ œ ์Šคํ† ๋ฆฌ๋Š” 2026-10-06 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ([์Šน์ธ ๊ธฐ๋ก](../../../../../docs/STORYBOOK_NAVIGATION.md#21-2026-10-06-์ „์ฒด-์Šน๊ฒฉ๊ณผ-๊ทœ๊ฒฉ-ํ™•์ •)). ์Šคํ† ๋ฆฌ๋ถ ๋ฐฐํฌ๋Š” API ๊ฒŒ์‹œ๊ฐ€ ์•„๋‹ˆ๋‹ค(`์ ์šฉ` ์ฐธ๊ณ )
9
9
  - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/ํ™”๋ฉด/์ฝ˜ํ…์ธ /์ž‘์„ฑ๊ณผ ์ˆ˜์ •`, `๋ฐฐํฌ/ํ™”๋ฉด/๊ณ„์ •/ํ”„๋กœํ•„`
10
10
 
@@ -95,6 +95,7 @@ import { TextField } from "@hjmds/react-native/inputs";
95
95
  | `dirty` | `boolean` | ํ•„์ˆ˜ | `true`์—์„œ ๋‹ซ๊ธฐ๋ฅผ ๋ˆ„๋ฅด๋ฉด `discard` ํ™•์ธ์ฐฝ์„ ๋„์šฐ๊ณ , ํ™•์ธํ•˜๋ฉด `cancel.onAction`์„ ๋ถ€๋ฅธ๋‹ค. `false`๋ฉด ๋ฐ”๋กœ ๋ถ€๋ฅธ๋‹ค |
96
96
  | `submit` | `ScreenFlowAction`(`{ label, onAction(), disabled?, pending? }`) | ํ•„์ˆ˜ | `pending`์ด๋ฉด loadingยท๋น„ํ™œ์„ฑ. ๊ทธ๋™์•ˆ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ๋„ ๋น„ํ™œ์„ฑ. ๋ณธ๋ฌธ `state`๊ฐ€ `ready`๊ฐ€ ์•„๋‹ˆ๋ฉด ์ €์žฅ ๋น„ํ™œ์„ฑ |
97
97
  | `cancel` | `ScreenFlowAction` | ํ•„์ˆ˜ | ํ—ค๋” ์‹œ์ž‘ ์ชฝ ร— ์•„์ด์ฝ˜ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ(`ScreenLayout` `exit` `close`). `label`์€ ๋ณด์กฐ๊ธฐ์ˆ  ์ด๋ฆ„์ด๋‹ค. 2026-10-17๋ถ€ํ„ฐ ๊ธ€์ž ๋ฒ„ํŠผ ๋Œ€์‹  ์•„์ด์ฝ˜ |
98
+ | `stepBack` | `ScreenFlowAction` | ์—†์Œ | 1.31.0. ๋‹จ๊ณ„ํ˜• ์ž‘์„ฑ์—์„œ ์ฃผ๋Š” ๋™์•ˆ ํ—ค๋” ์‹œ์ž‘ ์ชฝ์ด ๋’ค๋กœ ํ™”์‚ดํ‘œ(`exit` `back`)๊ฐ€ ๋˜๊ณ  ๋ˆ„๋ฅด๋ฉด `stepBack.onAction`(์•ž ๋‹จ๊ณ„๋กœ)์„ ๋ถ€๋ฅธ๋‹ค. ๋‹จ๊ณ„๋ฅผ ๋˜๋Œ๋ฆฌ๋Š” ์ผ์ด๋ผ `dirty`์—ฌ๋„ `discard` ํ™•์ธ์„ ๋„์šฐ์ง€ ์•Š๊ณ , ์ด๋ฏธ ์—ด๋ฆฐ ๋ฒ„๋ฆฌ๊ธฐ ํ™•์ธ๋„ ๋‹ซ๋Š”๋‹ค. `disabled`ยท`pending` ๋˜๋Š” `submit.pending`์ด๋ฉด ์ž ๊ธด๋‹ค. ์ฒซ ๋‹จ๊ณ„์—์„œ๋Š” ๋นผ์„œ ร— ๋‹ซ๊ธฐ๋กœ ๋Œ์•„๊ฐ„๋‹ค |
98
99
  | `discard` | AlertDialog confirm ์š”์ฒญ์—์„œ `onConfirm`์„ ๋บ€ ๊ฒƒ + `fallbackErrorMessage: string` | ํ•„์ˆ˜ | `onConfirm`์€ EditorScreen์ด ์ฑ„์šด๋‹ค |
99
100
  | `submitPlacement` | `"footer"` ยท `"header"` | `"footer"` | `header`๋ฉด ์ €์žฅ์ด ์ƒ๋‹จ `actions` ์ž๋ฆฌ๋กœ ๊ฐ€๊ณ  footer์—๋Š” `draftStatus`๋งŒ ๋‚จ๋Š”๋‹ค |
100
101
  | `draftStatus` | `ReactNode` | ์—†์Œ | ์ดˆ์•ˆ ์ €์žฅ ์ƒํƒœ ์•ˆ๋‚ด. footer์— ๋†“์ธ๋‹ค |
@@ -107,7 +108,7 @@ import { TextField } from "@hjmds/react-native/inputs";
107
108
  | --- | --- | --- |
108
109
  | ํฌ๊ธฐ | ScreenLayout ํญ(์ตœ๋Œ€ 720) ์•ˆ์— ์ž…๋ ฅ ๋ณธ๋ฌธ; ์ €์žฅ์€ `Button` ๊ธฐ๋ณธ ํฌ๊ธฐ, ๋‹ซ๊ธฐ๋Š” `IconButton` | `ScreenLayout`, `screen-flows.tsx` `Action` |
109
110
  | ๊ฐ„๊ฒฉ | ํ™”๋ฉด padding `spacing.md` 16; footer ์•ˆ `draftStatus`โ€“์ €์žฅ `spacing.sm` 12(`Stack gap="sm"`); ๋ณธ๋ฌธ ์•ˆ ์ž…๋ ฅ ๊ฐ„๊ฒฉ์€ ์ œํ’ˆ(Form ๋“ฑ) ์†Œ์œ  | WebยทNative `EditorScreen` |
110
- | ์ˆœ์„œยท์ •๋ ฌ | ํ—ค๋”(๋‹ซ๊ธฐ โ†’ ์ œ๋ชฉ โ†’ [์ €์žฅ: header ๋ฐฐ์น˜]) โ†’ ๋ณธ๋ฌธ โ†’ footer(`draftStatus` โ†’ ์ €์žฅ) | `EditorScreen` ๋ Œ๋” ์ˆœ์„œ |
111
+ | ์ˆœ์„œยท์ •๋ ฌ | ํ—ค๋”(๋‹ซ๊ธฐ ๋˜๋Š” `stepBack` ๋’ค๋กœ โ†’ ์ œ๋ชฉ โ†’ [์ €์žฅ: header ๋ฐฐ์น˜]) โ†’ ๋ณธ๋ฌธ โ†’ footer(`draftStatus` โ†’ ์ €์žฅ) | `EditorScreen` ๋ Œ๋” ์ˆœ์„œ |
111
112
  | ๊ณ ์ •ยท์Šคํฌ๋กค | ํ—ค๋”ยทfooter ๊ณ ์ •, ๋ณธ๋ฌธ ์Šคํฌ๋กค(`scroll` ๊ธฐ๋ณธ `"screen"`); ์ดํƒˆ ํ™•์ธ์€ AlertDialog ์˜ค๋ฒ„๋ ˆ์ด | `ScreenLayout`, `AlertDialog` |
112
113
  | ์ข์€ ํญยทํฐ ๊ธ€์ž | ์ œ๋ชฉ ์—ด ์ตœ์†Œ ํญ 120 ร— ๊ธ€์ž ๋ฐฐ์œจ, ๋ชจ์ž๋ผ๋ฉด header ์ €์žฅ ๋ฒ„ํŠผ์ด ๋‹ค์Œ ์ค„๋กœ ๋‚ด๋ ค๊ฐ„๋‹ค; ํ‚ค๋ณด๋“œยทsafe area๋Š” host | `screenPatternRecipe.headerMinWidth` |
113
114
 
@@ -119,6 +120,8 @@ import { TextField } from "@hjmds/react-native/inputs";
119
120
  - ์ €์žฅ ์‹œ์ž‘ ๋˜๋Š” `cancel.disabled`/`cancel.pending` ์ „ํ™˜์€ ์ด๋ฏธ ์—ด๋ฆฐ ์ดˆ์•ˆ ๋ฒ„๋ฆฌ๊ธฐ ํ™•์ธ๋„ ์ข…๋ฃŒํ•œ๋‹ค. ์ž ๊ธˆ์ด ํ’€๋ ธ๋‹ค๊ณ  ํ™•์ธ์ฐฝ์ด ๋‹ค์‹œ ์—ด๋ฆฌ์ง€๋Š” ์•Š๋Š”๋‹ค.
120
121
  - ์ตœ์ดˆ ์กฐํšŒยท์˜ค๋ฅ˜ยท๊ถŒํ•œ ์ œํ•œ ๋“ฑ ๋ณธ๋ฌธ ๋Œ€์ฒด ์ƒํƒœ์—์„œ๋Š” ์ €์žฅ์„ ์ž ๊ทผ๋‹ค. ์ƒˆ ๋ฌธ์„œ์˜ ๋นˆ ์ดˆ์•ˆ์€ `state="empty"`๊ฐ€ ์•„๋‹ˆ๋ผ `ready`์™€ ๋นˆ ์ž…๋ ฅ์œผ๋กœ ํ‘œํ˜„ํ•˜๊ณ  ์ œํ’ˆ validation์€ `submit.disabled`์— ์—ฐ๊ฒฐํ•œ๋‹ค.
121
122
  - ๋ผ์šฐํ„ฐ ๋’ค๋กœ๊ฐ€๊ธฐยทAndroid backยท๋ธŒ๋ผ์šฐ์ € ์ดํƒˆ์€ ์ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ง‰์ง€ ์•Š๋Š”๋‹ค. ์ œํ’ˆ์ด `dirty`๋กœ guard๋ฅผ ๊ฑด๋‹ค.
123
+ - ๋‹จ๊ณ„ํ˜• ์ž‘์„ฑ์˜ ํ—ค๋” ๋’ค๋กœ๋Š” `header`์— TopBar๋ฅผ ๋„ฃ์–ด ๋งŒ๋“ค์ง€ ์•Š๊ณ  `stepBack`์„ ์“ด๋‹ค. ์ง€๋‚œ ๋‹จ๊ณ„๋ฅผ ๋ˆŒ๋Ÿฌ ๋Œ์•„๊ฐ€๋Š” ํ‘œ์‹œ๋Š” [Steps](steps.md) `onStepPress`๋‹ค([๋‹จ๊ณ„ํ˜• ์ž‘์„ฑ](../screens/flow-editor.md#๋‹จ๊ณ„ํ˜•-์ž‘์„ฑ1310)).
124
+ - ์ฒซ ๋‹จ๊ณ„์—์„œ ์ดํƒˆํ•˜๋Š” ๊ธธ์€ `cancel`์ด๋‹ค. `stepBack`์œผ๋กœ ํ™”๋ฉด์„ ๋– ๋‚˜๊ฒŒ ๋งŒ๋“ค๋ฉด `discard` ํ™•์ธ์„ ๊ฑด๋„ˆ๋›ด๋‹ค.
122
125
  - ์ €์žฅ ์„ฑ๊ณต ํŒ์ •ยท์˜ค๋ฅ˜ ํ‘œ์‹œ๋Š” ์ œํ’ˆ ์ƒํƒœ(`notice`, Toast ๋“ฑ)๋กœ ํ•œ๋‹ค.
123
126
 
124
127
  ## ํ”Œ๋žซํผ ์ฐจ์ด
@@ -130,4 +133,5 @@ import { TextField } from "@hjmds/react-native/inputs";
130
133
 
131
134
  ## ๊ทผ๊ฑฐ
132
135
 
136
+ - `stepBack`(1.31.0): 2026-10-18 spint ์ž‘์„ฑ(์ƒ‰ โ†’ ์ด๋ชจ์ง€ โ†’ ๊ธ€)์€ ํ—ค๋” ์‹œ์ž‘ ์ชฝ์ด ์•ž ๋‹จ๊ณ„๋กœ ๊ฐ€์•ผ ํ–ˆ๋Š”๋ฐ EditorScreen์€ ร— ๋‹ซ๊ธฐ๋งŒ ์žˆ๊ณ  `exit`๋ฅผ ๋ฐ›์ง€ ์•Š์•„, ์ œํ’ˆ์ด `header`์— TopBar๋ฅผ ๋„ฃ์–ด ๋จธ๋ฆฌ๋ฅผ ํ†ต์งธ๋กœ ๋Œ€์ฒดํ–ˆ๋‹ค.
133
137
  - ์ €์žฅ ์‹œ์ž‘ ์‹œ ์—ด๋ฆฐ ๋ฒ„๋ฆฌ๊ธฐ ํ™•์ธ ์ข…๋ฃŒ: 2026-10-13 ์ด์ „ ํ™•์ธ์ฐฝ์œผ๋กœ ์ €์žฅ ์ค‘ ์ดํƒˆ ์ž ๊ธˆ์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ๋˜ ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ–ˆ๋‹ค.
@@ -81,6 +81,19 @@ import { Field } from "@hjmds/react-native/forms";
81
81
  - ์ƒํƒœ ํ…Œ๋‘๋ฆฌ๋Š” recipe๊ฐ€ ์ •ํ•œ๋‹ค: ๊ธฐ๋ณธ `borderControl`, ํฌ์ปค์Šค `contentBrand`, ์˜ค๋ฅ˜ `danger`.
82
82
  - ๋„์›€๋ง์€ ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ฒจ๋„ ์‚ฌ๋ผ์ง€์ง€ ์•Š๊ณ  ๋‘˜ ๋‹ค ๋ณด์ธ๋‹ค(Web์€ `aria-describedby`๋กœ ๋‘˜ ๋‹ค ์—ฐ๊ฒฐ).
83
83
  - ๋น„ํ™œ์„ฑ(`disabled`)์€ ๋ผ๋ฒจ๊ณผ ์ปจํŠธ๋กค๋งŒ ํ๋ฆฌ๊ณ  ๋„์›€๋งยท์˜ค๋ฅ˜๋Š” ์›๋ž˜ ๋Œ€๋น„ ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค(`fieldRecipe.disabledScope`).
84
+
85
+ ### ์˜ค๋ฅ˜ ์•Œ๋ฆผ
86
+
87
+ `error`๊ฐ€ ๋‚˜ํƒ€๋‚˜๊ฑฐ๋‚˜ ๋ฐ”๋€Œ๋ฉด ์ฆ‰์‹œ(assertive) ํ•œ ๋ฒˆ ์•Œ๋ฆฐ๋‹ค. FieldยทTextFieldยทTextAreaยทSelectยทComboboxยทNumberFieldยท
88
+ CheckboxGroupยทRadioGroup๊ณผ ๊ทธ ์œ„์— ๋งŒ๋“  ์ปจํŠธ๋กค(MessageComposer ๋“ฑ)์ด ๊ฐ™์€ ๊ณ„์•ฝ์„ ์“ด๋‹ค(1.30.0 ์ดํ›„).
89
+
90
+ - Web: ์˜ค๋ฅ˜ ์ „์— ๋ฏธ๋ฆฌ mount๋œ `role="alert"` ์˜์—ญ ํ•˜๋‚˜๊ฐ€ ์˜ค๋ฅ˜ id๋ฅผ ๊ฐ–๊ณ , ์ปจํŠธ๋กค์˜ `aria-describedby`๊ฐ€ ๊ทธ ์˜์—ญ์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค.
91
+ ๋ณด์ด๋Š” ์˜ค๋ฅ˜ ๋ฌธ๊ตฌ๋Š” `aria-hidden`์ด๋ผ ํƒ์ƒ‰ ๋ชจ๋“œ์—์„œ๋„ ํ•œ ๋ฒˆ๋งŒ ์ฝํžŒ๋‹ค. ์˜์—ญ์€ ์‹œ๊ฐ์ ์œผ๋กœ ์ˆจ๊ฒจ์ ธ ์นธ ๊ฐ„๊ฒฉ์„ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
92
+ - Native: ์˜ค๋ฅ˜ ๋ฌธ๊ตฌ๊ฐ€ assertive live region(Android)์ด๊ณ , live region์„ ์ฝ์ง€ ์•Š๋Š” iOS๋Š” ์ƒˆ ์˜ค๋ฅ˜๋งˆ๋‹ค
93
+ `announceForAccessibility`๋กœ ํ•œ ๋ฒˆ ์•Œ๋ฆฐ๋‹ค.
94
+ - ์ œํ’ˆ์€ ๊ฐ™์€ ๋ฌธ๊ตฌ๋ฅผ Notice๋‚˜ ๋ณ„๋„ `role="alert"`๋กœ ๋‹ค์‹œ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
95
+
96
+ ๊ทผ๊ฑฐ: 2026-10-18 ๋ฒˆ๋š DM์—์„œ Web ํ•„๋“œ ์˜ค๋ฅ˜๊ฐ€ describedby๋กœ๋งŒ ์—ฐ๊ฒฐ๋ผ ๋‚˜ํƒ€๋‚  ๋•Œ ๋‚ญ๋…๋˜์ง€ ์•Š์•˜๊ณ , Native iOS๋„ ์ž‘์„ฑ์ฐฝยทFieldGroup ์™ธ ํ•„๋“œ ์˜ค๋ฅ˜๋ฅผ ์•Œ๋ฆฌ์ง€ ์•Š์•˜๋‹ค. ์ž‘์„ฑ์ฐฝ์—๋งŒ ์ˆจ๊ธด alert๋ฅผ ๋”ํ•œ ์ฒซ ์ˆ˜์ •์€ ๊ฐ™์€ ๋ฌธ๊ตฌ๋ฅผ ๋‘ ๋ฒˆ ์ฝ๊ฒŒ ํ•ด ๊ณต์šฉ ๊ณ„์•ฝ(`react/src/internal/field-error.tsx`, `react-native/src/internal/field-frame.tsx`)์œผ๋กœ ์˜ฎ๊ฒผ๋‹ค.
84
97
  ํ๋ฆผ ์ •๋„๋Š” ์ปดํฌ๋„ŒํŠธ recipe์— `states.disabledOpacity`๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ๊ฐ’(SearchFieldยทPasswordFieldยทOtpFieldยทNumberFieldยทSelect
85
98
  0.5), ์—†์œผ๋ฉด `fieldRecipe.disabledOpacity` 0.6(TextFieldยทTextAreaยทMentionsยทFieldยทNativeSelectยทComboboxยทDatePickerยทTagsInput)์ด๋‹ค.
86
99
  2026-10-06 ์ „์—๋Š” ํ‹€ ์ „์ฒด๊ฐ€ ํ๋ ค์ ธ ์ž ๊ธด ์ด์œ ๋ฅผ ์ ์€ ๋„์›€๋ง๊นŒ์ง€ ๋Œ€๋น„๊ฐ€ ๋–จ์–ด์กŒ๋‹ค.
@@ -39,6 +39,7 @@ DialogยทSheet footer ํ–‰๋™, ๋ชฉ๋ก ๋ "๋” ๋ณด๊ธฐ", ์˜จ๋ณด๋”ฉ ๋‹ค์Œยท์ด์ „,
39
39
  | --- | --- | --- | --- |
40
40
  | `IconButton` | ๊ธฐ๋ณธ | `@hjmds/react`, `/actions` | `@hjmds/react-native`, `/actions`, `/bottom-cta` |
41
41
  | `NotificationBell` | ํ™•์žฅ โ€” ๋ฏธํ™•์ธ ์ˆ˜ ๋ฐฐ์ง€ + ์ฆ๊ฐ€ ์‹œ ํ•œ ๋ฒˆ ํ”๋“ค๋ฆผ | `/notification-bell` | `/notification-bell` |
42
+ | `ClipboardIconButton` | ๋™๋ฐ˜(๋ณต์‚ฌ) โ€” ์•„์ด์ฝ˜๋งŒ ์žˆ๋Š” ๋ณต์‚ฌ ๋„๊ตฌ ํ–‰๋™. `value`, `labels: { idle; copied }`(๋‘ ์ƒํƒœ์˜ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„), `onCopy?`, `onCopyError?`, `feedbackDuration?`(ms), `tone` ๊ธฐ๋ณธ `ghost` | `@hjmds/react`, `/clipboard` | โ€” |
42
43
 
43
44
  `NotificationBell`์€ granular subpath๋กœ๋งŒ import ๋œ๋‹ค. ์ถ”๊ฐ€ peer๋Š” ์—†๋‹ค.
44
45
 
@@ -54,6 +54,9 @@ import { IconTile } from "@hjmds/react-native/screens";
54
54
  | overlay | ReactNode | ์—†์Œ | ์ œํ’ˆ ๋ฐฐ์ง€ |
55
55
  | labelLines | 1 ๋˜๋Š” 2 | 1 | ์ œ๋ชฉ ์ค„ ์ˆ˜ |
56
56
  | ghost | boolean | false | ์ด๋™ ๋Œ€์ƒ ํ‘œ์‹œ |
57
+ | frame | `"surface"` ยท `"none"` | `"surface"` | ๋ฏธ๊ฒŒ์‹œ(1.30.0 ์ดํ›„). `surface`๋Š” ๊ณตํ†ต ํ‹€(`surfaceAlt` ์ฑ„์›€ + ํ…Œ๋‘๋ฆฌ 1), `none`์€ ํ‹€์„ ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ์•„์ด์ฝ˜ ์›๋ณธ์ด ์ด๋ฏธ ์™„์„ฑ๋œ ๋Ÿฐ์ฒ˜ ์•„์ด์ฝ˜(๋ชจ์–‘ยท๋ฐฐ๊ฒฝ ํฌํ•จ)์ผ ๋•Œ `none` |
58
+ | shape | `"squircle"` ยท `"square"` ยท `"circle"` | `"squircle"` | ๋ฏธ๊ฒŒ์‹œ. ํ‹€(๊ณผ ghost ์ ์„ )์˜ ๋ชจ์„œ๋ฆฌ. ํฌ๊ธฐ ๋Œ€๋น„ ๋น„์œจ `iconTileRecipe.shapes`(squircle 0.22 ยท square 0 ยท circle 0.5). ์ œํ’ˆ ์•„์ด์ฝ˜ ํ…Œ๋งˆ(์•„์ดํฐ ์…ธ squircle, WindowsยทLinux ์…ธ square)๋ฅผ ๋”ฐ๋ฅธ๋‹ค |
59
+ | size | `"fixed"` ยท `"fill"` | `"fixed"` | ๋ฏธ๊ฒŒ์‹œ. `fixed`๋Š” 64ร—64, `fill`์€ ๊ฒฉ์ž ์นธ ํญ์„ ์ฑ„์šฐ๋˜ `fillMinSize` 48 ~ `fillMaxSize` 68 ์‚ฌ์ด๋กœ ์ œํ•œํ•œ ์ •์‚ฌ๊ฐํ˜•. ์—ด ์ˆ˜ยท์นธ ํญ์€ host Grid๊ฐ€ ์ •ํ•˜๊ณ  ์ œํ’ˆ์ด ์นธ ํฌ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•ด ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค |
57
60
 
58
61
  `icon: ReactNode`, `label: string`, `overlay?: ReactNode`, `labelLines?: 1 | 2`(๊ธฐ๋ณธ1), `ghost?: boolean`(๊ธฐ๋ณธfalse). ๋‘ ์ค„์€ ๋Ÿฐ์ฒ˜ ์ œ๋ชฉ ๋†’์ด44๋ฅผ ์˜ˆ์•ฝํ•˜๊ณ  ghost๋Š” ์ด๋™ ๋Œ€์ƒ์˜ ์ ์„ /๋ฐ˜ํˆฌ๋ช… ํ‘œํ˜„์ด๋‹ค. ์•„์ด์ฝ˜ 64, ์ œ๋ชฉ ์ตœ๋Œ€ ํญ72, ๊ฐ„๊ฒฉ6์„ ๊ณต์œ  recipe๊ฐ€ ์ •ํ•œ๋‹ค. overlay๋Š” ์•„์ด์ฝ˜ ๊ธฐ์ค€ ๋ฐฐ์ง€๋ฅผ ์œ„ํ•œ ์Šฌ๋กฏ์ด๋ฉฐ ๋„˜์นจ์„ ์ž๋ฅด์ง€ ์•Š๋Š”๋‹ค.
59
62
 
@@ -61,7 +64,7 @@ import { IconTile } from "@hjmds/react-native/screens";
61
64
 
62
65
  | ํ•ญ๋ชฉ | ๊ฐ’ | ๊ทผ๊ฑฐ |
63
66
  | --- | --- | --- |
64
- | ํฌ๊ธฐ | ์•„์ด์ฝ˜ 64ร—64(radius 22%), ์ œ๋ชฉ ์ตœ๋Œ€ ํญ 72 ยท ๋‘ ์ค„์ด๋ฉด 84์™€ ์ตœ์†Œ ๋†’์ด 44 | `iconTileRecipe` |
67
+ | ํฌ๊ธฐ | ์•„์ด์ฝ˜ 64ร—64(radius 22%, `shape`์— ๋”ฐ๋ผ 0%ยท50%), `size="fill"`์ด๋ฉด ์นธ ํญ 48~68 ์ •์‚ฌ๊ฐํ˜•; ์ œ๋ชฉ ์ตœ๋Œ€ ํญ 72 ยท ๋‘ ์ค„์ด๋ฉด 84์™€ ์ตœ์†Œ ๋†’์ด 44 | `iconTileRecipe` |
65
68
  | ๊ฐ„๊ฒฉ | ์•„์ด์ฝ˜๊ณผ ์ œ๋ชฉ ์‚ฌ์ด 6 | `iconTileRecipe.gap` |
66
69
  | ์ˆœ์„œยท์ •๋ ฌ | ์•„์ด์ฝ˜(overlay ํฌํ•จ) ์œ„ โ†’ ์ œ๋ชฉ ์•„๋ž˜, ๊ฐ€๋กœ ๊ฐ€์šด๋ฐ, ์ œ๋ชฉ ๊ฐ€์šด๋ฐ ์ •๋ ฌ | ์–‘ renderer |
67
70
  | ๊ณ ์ •ยท์Šคํฌ๋กค | Grid๋‚˜ ๊ฐ€์ƒ ๋ชฉ๋ก์˜ host๊ฐ€ ์—ด ์ˆ˜ยท์™ธ๋ถ€ ์—ฌ๋ฐฑยท์Šคํฌ๋กค์„ ์ •ํ•œ๋‹ค | ๋ณ„๋„ scroller ์—†์Œ |
@@ -73,6 +76,8 @@ import { IconTile } from "@hjmds/react-native/screens";
73
76
 
74
77
  ์•„์ด์ฝ˜ ์›๋ณธ๊ณผ ์ œํ’ˆ ๋ฐฐ์ง€๋งŒ ์ „๋‹ฌํ•œ๋‹ค. ๊ฐ™์€ ๋ฐฐ์น˜๋ฅผ ๋ณ„๋„ View/div๋กœ ๋‹ค์‹œ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค.
75
78
 
79
+ - ์™„์„ฑ๋œ ์•„์ด์ฝ˜ ์•„ํŠธ์›Œํฌ ์œ„์— ํ‹€์ด ๋‘ ๊ฒน์œผ๋กœ ๊ทธ๋ ค์ง€๋ฉด `frame="none"`, ์•„์ด์ฝ˜ ๋ชจ์–‘์ด ํ…Œ๋งˆ๋งˆ๋‹ค ๋‹ค๋ฅด๋ฉด `shape`, ์นธ ํฌ๊ธฐ๋ฅผ ๋”ฐ๋ผ ์ปค์ ธ์•ผ ํ•˜๋ฉด `size="fill"`์„ ์“ด๋‹ค. ์ด ์ด์œ ๋กœ ๋Ÿฐ์ฒ˜ ํ•ญ๋ชฉ์„ ์ง์ ‘ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค.
80
+
76
81
  ## ์ƒํƒœ์™€ ์ƒํ˜ธ์ž‘์šฉ
77
82
 
78
83
  ์ด ๊ตฌ์„ฑ์€ ํ‘œ์‹œ ์ „์šฉ์ด๋‹ค. ๋งํฌ/๋ฒ„ํŠผ/๋“œ๋ž˜๊ทธ host๊ฐ€ disabledยทํฌ์ปค์Šคยทํ‚ค๋ณด๋“œยท๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ์™€ ๋ผ์šฐํŒ…์„ ์†Œ์œ ํ•˜๋ฏ€๋กœ ๊ธฐ์กด ์•ฑ์˜ drag engine์„ ๊ต์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค.
@@ -88,3 +93,7 @@ host์— ์ „์ฒด ํ–‰๋™์˜ ํ˜„์ง€ํ™” ์ด๋ฆ„์„ ์ œ๊ณตํ•œ๋‹ค. ์•„์ด์ฝ˜ ๋‚ด๋ถ€ ์žฅ
88
93
  ## ๊ด€๋ จ ์ง€์นจ
89
94
 
90
95
  [์„ ํƒ ๊ธฐ์ค€](../SELECTION.md), [Grid](grid.md), [Card](card.md).
96
+
97
+ ## ๊ทผ๊ฑฐ
98
+
99
+ - `frame`ยท`shape`ยท`size`: 2026-10-18 HJM 1.30.0 ๊ฐ์‚ฌ. ์œ ํ‹ธ๋ฒ„์Šค ๋Ÿฐ์ฒ˜๋Š” ๋„๊ตฌ ์•„ํŠธ์›Œํฌ๊ฐ€ ์™„์„ฑ๋œ ์•„์ด์ฝ˜(์…ธ์— ๋”ฐ๋ผ iPhone squircle, WindowsยทLinux square)์ด๊ณ  ํฌ๊ธฐ๊ฐ€ ๊ฒฉ์ž๋ฅผ ๋”ฐ๋ผ 48~68๋กœ ๋ณ€ํ•ด, ๊ณ ์ • 64 ํ‹€์„ ๊ทธ๋ฆฌ๋Š” IconTile ๋Œ€์‹  ์ž์ฒด ํƒ€์ผ์„ ์œ ์ง€ํ–ˆ๋‹ค. ๋ฒ”์œ„ 48~68์€ ๊ทธ ๋Ÿฐ์ฒ˜์˜ ์‹ค์ œ ์นธ ๊ณ„์‚ฐ๊ฐ’์ด๋‹ค.
@@ -10,8 +10,8 @@
10
10
 
11
11
  ## ์–ธ์ œ ์“ฐ๋‚˜
12
12
 
13
- HJM semantic ์ด๋ฆ„(`search`, `back`, `chevronEnd`, `notifications` ๋“ฑ 43๊ฐœ)์œผ๋กœ ๊ณ ๋ฅด๋Š” ๊ทธ๋ฆผ ๊ธฐํ˜ธ์— ์“ด๋‹ค.
14
- ํฌ๊ธฐยท์ƒ‰ยท์„  ๊ตต๊ธฐยทRTL ๋ฐ˜์ „์€ HJM์ด ์ •ํ•˜๊ณ , ๊ทธ๋ฆผ ์ž์ฒด๋Š” ๋‚ด์žฅ ๊ฒฝ๋กœ(Web)๋‚˜ ์ œํ’ˆ์ด ๋„˜๊ธด glyph๊ฐ€ ๊ทธ๋ฆฐ๋‹ค.
13
+ HJM semantic ์ด๋ฆ„(`search`, `back`, `chevronEnd`, `notifications` ๋“ฑ 47๊ฐœ)์œผ๋กœ ๊ณ ๋ฅด๋Š” ๊ทธ๋ฆผ ๊ธฐํ˜ธ์— ์“ด๋‹ค.
14
+ ํฌ๊ธฐยท์ƒ‰ยท์„  ๊ตต๊ธฐยทRTL ๋ฐ˜์ „์€ HJM์ด ์ •ํ•˜๊ณ , ๊ทธ๋ฆผ์€ ๊ณ„์•ฝ์˜ ๊ณต์šฉ ๊ฒฝ๋กœ(Web ๋‚ด์žฅ, Native `/icon-svg`)๋‚˜ ์ œํ’ˆ์ด ๋„˜๊ธด glyph๊ฐ€ ๊ทธ๋ฆฐ๋‹ค.
15
15
 
16
16
  ## ์“ฐ์ง€ ์•Š์„ ๋•Œ
17
17
 
@@ -29,14 +29,17 @@ HJM semantic ์ด๋ฆ„(`search`, `back`, `chevronEnd`, `notifications` ๋“ฑ 43๊ฐœ)
29
29
  | --- | --- | --- | --- |
30
30
  | `Icon` | ๊ธฐ๋ณธ | `@hjmds/react`, `/display` | `@hjmds/react-native`, `/primitives` |
31
31
  | `createLucideGlyph` | ๋ณด์กฐ โ€” Lucide glyph ์—ฐ๊ฒฐ helper(์ปดํฌ๋„ŒํŠธ ์•„๋‹˜) | `/icon-lucide` | `/icon-lucide` |
32
+ | `renderSemanticGlyph` | ๋ณด์กฐ โ€” HJM ๊ณต์šฉ ๊ฒฝ๋กœ๋ฅผ ๊ทธ๋ฆฌ๋Š” `renderGlyph`(์ปดํฌ๋„ŒํŠธ ์•„๋‹˜, 1.31.0) | โ€” (๋‚ด์žฅ) | `/icon-svg` |
33
+ | `semanticIconPaths` ยท `isSemanticIconName` | ๊ณต์šฉ 24 ๊ฒฉ์ž ๊ฒฝ๋กœ ๋ฐ์ดํ„ฐ์™€ ์ด๋ฆ„ ํŒ๋ณ„ | `@hjmds/design-contracts/components/icon` | ๊ฐ™์Œ |
32
34
 
33
35
  `/icon-lucide`๋Š” optional peer๊ฐ€ ํ•„์š”ํ•˜๋‹ค. Web `lucide-react` 1.49.0,
34
- Native `lucide-react-native` 1.49.0. ๋ฃจํŠธ entry์—์„œ๋Š” ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค.
36
+ Native `lucide-react-native` 1.49.0. Native `/icon-svg`๋Š” optional peer `react-native-svg` 15.15.5๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
37
+ ๋‘˜ ๋‹ค ๋ฃจํŠธ entry์—์„œ๋Š” ๋‚˜์˜ค์ง€ ์•Š๋Š”๋‹ค.
35
38
 
36
39
  ## ์ตœ์†Œ ์‚ฌ์šฉ ์˜ˆ
37
40
 
38
41
  ```tsx
39
- // Web โ€” ๋‚ด์žฅ glyph
42
+ // Web โ€” built-in glyph
40
43
  import { Icon } from "@hjmds/react/display";
41
44
 
42
45
  <Icon name="search" />
@@ -54,6 +57,42 @@ const renderGlyph = createLucideGlyph({ search: Search, back: ArrowLeft });
54
57
  <Icon descriptor={{ name: "search" }} renderGlyph={renderGlyph} />
55
58
  ```
56
59
 
60
+ ```tsx
61
+ // Native โ€” Web๊ณผ ๊ฐ™์€ HJM ๊ณต์šฉ ๊ทธ๋ฆผ(react-native-svg ํ•„์š”)
62
+ import { renderSemanticGlyph } from "@hjmds/react-native/icon-svg";
63
+
64
+ <Icon descriptor={{ name: "arrowUp" }} renderGlyph={renderSemanticGlyph} />
65
+ ```
66
+
67
+ ### ๊ณต์šฉ ๊ทธ๋ฆผ์˜ Native ์ œ๊ณต ๋ฐฉ์‹(1.31.0 ์ถ”๊ฐ€)
68
+
69
+ 2026-10-18 utilverse๊ฐ€ ์‚ฌ์ง„ ์ˆœ์„œ ๋ณ€๊ฒฝยทํšŒ์ „ IconButton์„ ์œ„ํ•ด `arrowUp`ยท`arrowDown`ยท`rotate` ๊ฒฝ๋กœ๋ฅผ ์•ฑ adapter์— ์ง์ ‘
70
+ ๊ทธ๋ ธ๋‹ค. Web๋งŒ HJM ๊ฒฝ๋กœ๋ฅผ ๋‚ด์žฅํ•˜๊ณ  ๊ทธ ๊ฒฝ๋กœ๊ฐ€ renderer ๋‚ด๋ถ€์— ์žˆ์–ด Native๋Š” ๊ฐ™์€ ๊ทธ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
71
+ ๊ฒฝ๋กœ๋Š” ์™ธํ˜• ๊ฐ’์ด๋ฏ€๋กœ ๊ณ„์•ฝ `semanticIconPaths`๋กœ ์˜ฎ๊ฒจ ๋‘ ํ”Œ๋žซํผ์ด ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๋Š”๋‹ค.
72
+
73
+ Native Icon์€ `renderGlyph`๋ฅผ ํ•„์ˆ˜๋กœ ์œ ์ง€ํ•œ๋‹ค. core ์ปจํŠธ๋กค์ด `react-native-svg` ์—†์ด ๋™์ž‘ํ•˜๋„๋ก SVG๋Š” optional peer์ด๊ณ ,
74
+ ๋‚ด์žฅ ์ปจํŠธ๋กค์˜ ๋ช‡ ๊ธฐํ˜ธ(์…ฐ๋ธŒ๋Ÿฐยท๋‹ฌ๋ ฅยท์‚ญ์ œ ๋“ฑ)๋Š” ๋‚ด๋ถ€์—์„œ View๋กœ ๊ทธ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ๊ณต์šฉ ๊ทธ๋ฆผ์€ `/icon-lucide`์™€ ๊ฐ™์€
75
+ ์„ ํƒ entry `/icon-svg`์˜ `renderSemanticGlyph`๋กœ ์—ฐ๋‹ค. ์•ฑ ๊ณ ์œ  ์ด๋ฆ„์„ ํ•จ๊ป˜ ์“ฐ๋ฉด ํŒ๋ณ„ ํ›„ ๋„˜๊ธด๋‹ค.
76
+
77
+ ```tsx
78
+ // Native โ€” app glyph names mixed with HJM names
79
+ import { isSemanticIconName } from "@hjmds/design-contracts/components/icon";
80
+
81
+ const renderGlyph = (props: NativeIconRenderProps<AppGlyphName>) =>
82
+ isSemanticIconName(props.name) ? renderSemanticGlyph(props) : renderAppGlyph(props);
83
+ ```
84
+
85
+ `react-native-svg`๋ฅผ ์„ค์น˜ํ•˜์ง€ ์•Š๋Š” ์•ฑ์€ ์ž๊ธฐ adapter์—์„œ `semanticIconPaths[name]`์„ ์ฝ์–ด ๊ฐ™์€ ๊ฒฝ๋กœ๋ฅผ ๊ทธ๋ฆฐ๋‹ค.
86
+
87
+ ### ์ด๋ฆ„ ๊ณ ๋ฅด๊ธฐ
88
+
89
+ | ๋ชฉ์  | ์ด๋ฆ„ | ๋น„๊ณ  |
90
+ | --- | --- | --- |
91
+ | ์ˆœ์„œ๋ฅผ ์œ„/์•„๋ž˜๋กœ ์˜ฎ๊ธฐ๊ธฐ | `arrowUp` ยท `arrowDown` | 1.31.0 ์ถ”๊ฐ€. ์œ„์•„๋ž˜๋Š” ๋ฌผ๋ฆฌ ๋ฐฉํ–ฅ์ด๋ผ RTL์—์„œ ๋ฐ˜์ „ํ•˜์ง€ ์•Š๋Š”๋‹ค |
92
+ | ์ด๋ฏธ์ง€ยทํŽ˜์ด์ง€ 90ยฐ ํšŒ์ „ | `rotate` | 1.31.0 ์ถ”๊ฐ€. ์‹œ๊ณ„ ๋ฐฉํ–ฅ. RTL์—์„œ ๋ฐ˜์ „ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ๋Š” `refresh` |
93
+ | ์ฐจ๋‹จยท์ž ๊ธˆ ํ•ด์ œ | `unblock` | 1.31.0 ์ถ”๊ฐ€. `lock`์˜ ๊ณ ๋ฆฌ๊ฐ€ ์—ด๋ฆฐ ๋ชจ์–‘ |
94
+ | ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณด๊ธฐ/์ˆจ๊ธฐ๊ธฐ(๋ˆˆ/๊ฐ€๋ฆฐ ๋ˆˆ) | `visibility` ยท `visibilityOff` | ์ด๋ฏธ ์žˆ๋Š” ์ด๋ฆ„์ด๋‹ค. `eye`๋ผ๋Š” ์ด๋ฆ„์€ ์—†๋‹ค. ์ˆจ๊น€ ์ƒํƒœ์—์„œ `visibility`, ๋ณด์ž„ ์ƒํƒœ์—์„œ `visibilityOff`(๋ˆ„๋ฅด๋ฉด ๋  ์ƒํƒœ). ๋น„๋ฐ€๋ฒˆํ˜ธ ์ž…๋ ฅ ์ž์ฒด๋Š” ์ด ์ง์„ ์ด๋ฏธ ๋‚ด์žฅํ•œ [PasswordField](password-field.md)๋ฅผ ์“ด๋‹ค |
95
+
57
96
  ## ์ถ•๊ณผ ๊ธฐ๋ณธ๊ฐ’
58
97
 
59
98
  | prop | ๊ฐ’ | ๊ธฐ๋ณธ๊ฐ’ | ์„ค๋ช… |
@@ -93,7 +132,7 @@ const renderGlyph = createLucideGlyph({ search: Search, back: ArrowLeft });
93
132
  | ํ•ญ๋ชฉ | Web | Native |
94
133
  | --- | --- | --- |
95
134
  | API ๋ชจ์–‘ | ํ‰ํ‰ํ•œ prop(`name`, `size`, `tone` โ€ฆ) | `descriptor` ๊ฐ์ฒด ํ•˜๋‚˜ |
96
- | glyph | ๋‚ด์žฅ SVG ๊ฒฝ๋กœ, `renderGlyph`๋Š” ์„ ํƒ | `renderGlyph` ํ•„์ˆ˜(๋‚ด์žฅ ๊ทธ๋ฆผ ์—†์Œ) |
135
+ | glyph | ๋‚ด์žฅ SVG ๊ฒฝ๋กœ(`semanticIconPaths`), `renderGlyph`๋Š” ์„ ํƒ | `renderGlyph` ํ•„์ˆ˜. ๊ณต์šฉ ๊ฒฝ๋กœ๋Š” `/icon-svg`์˜ `renderSemanticGlyph` |
97
136
  | ์‚ฌ์šฉ์ž ์ •์˜ ์ด๋ฆ„ | `SemanticIconName`๋งŒ | `Icon<Name>` ์ œ๋„ค๋ฆญ์œผ๋กœ ์ œํ’ˆ ์ด๋ฆ„ ํ—ˆ์šฉ |
98
137
  | ์ถœ๋ ฅ | `<svg>`(์žฅ์‹์ด๋ฉด `aria-hidden`) | `View` ํ”„๋ ˆ์ž„(์žฅ์‹์ด๋ฉด `accessible={false}`) |
99
138
  | ๋ฐฐ์น˜ | `layoutStyle` | `layoutStyle`. `style`์€ ์ œ๊ฑฐ๋œ ์†์„ฑ โ€” `layoutStyle` ๋˜๋Š” descriptor `size`ยท`tone` |
@@ -102,4 +141,4 @@ const renderGlyph = createLucideGlyph({ search: Search, back: ArrowLeft });
102
141
 
103
142
  - ์•„์ด์ฝ˜์€ ์ž๊ธฐ `tone` ์ƒ‰์„ ์ง์ ‘ ์น ํ•œ๋‹ค. Web CSS๊ฐ€ `data-tone`์œผ๋กœ ์ƒ‰์„ ์ง€์ •ํ•˜๋ฏ€๋กœ ๋ถ€๋ชจ ์ƒ‰์„ ์ƒ์†ํ•˜์ง€ ์•Š๋Š”๋‹ค.
104
143
  primaryยทdanger IconButton ์•ˆ์—์„œ ๊ธฐ๋ณธ tone(`secondary`)์„ ์“ฐ๋ฉด ์ฑ„์›Œ์ง„ ๋ฉด ์œ„์— ํšŒ์ƒ‰์ด ๋œ๋‹ค. ์ด๋•Œ๋Š” `tone="inverse"`.
105
- - `createLucideGlyph` ๋งต์— ์—†๋Š” ์ด๋ฆ„์„ ๊ทธ๋ฆฌ๋ฉด ์‹คํ–‰ ์ค‘ `TypeError`๊ฐ€ ๋‚œ๋‹ค.
144
+ - `createLucideGlyph` ๋งต์— ์—†๋Š” ์ด๋ฆ„์„ ๊ทธ๋ฆฌ๋ฉด ์‹คํ–‰ ์ค‘ `TypeError`๊ฐ€ ๋‚œ๋‹ค. `renderSemanticGlyph`๋„ HJM ์ด๋ฆ„์ด ์•„๋‹ˆ๋ฉด `TypeError`๋ฅผ ๋‚ธ๋‹ค.
@@ -52,6 +52,7 @@ import { ImageChoiceGroup } from "@hjmds/react-native/image-choice";
52
52
  | --- | --- | --- | --- |
53
53
  | `label` | `string` | ํ•„์ˆ˜ | ๋ฌถ์Œ์˜ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„ |
54
54
  | `showLabel` | `boolean` | `false` | ํƒ€์ผ ์œ„์— `label`์„ ๋ณด์ด๋Š” ๊ธ€์ž๋กœ ๋‘”๋‹ค |
55
+ | `showItemLabels` | `boolean` | `false` | ๋ฏธ๊ฒŒ์‹œ. ํƒ€์ผ๋งˆ๋‹ค ํ•ญ๋ชฉ `label`์„ ๊ทธ๋ฆผ ์•„๋ž˜ ํ•œ ์ค„(caption, ๋„˜์น˜๋ฉด ๋ง์ค„์ž„)๋กœ ๋ณด์ธ๋‹ค. ๊ฐ™์€ ๋ˆ„๋ฆ„ ์˜์—ญ ์•ˆ์— ์žˆ๊ณ  ๋ณด์กฐ๊ธฐ์ˆ ์€ ๊ธฐ์กด์ฒ˜๋Ÿผ ํƒ€์ผ ์ด๋ฆ„ ํ•˜๋‚˜๋งŒ ์ฝ๋Š”๋‹ค. ์‚ฌ๋žŒ์„ ์ด๋ฆ„์œผ๋กœ ์•Œ์•„๋ด์•ผ ํ•˜๋Š” ๋ฐ›๋Š” ์‚ฌ๋žŒ ์„ ํƒ์— ์“ด๋‹ค |
55
56
  | `items` | `{ id, label, image, disabled? }[]` | ํ•„์ˆ˜ | id ์ค‘๋ณตยท๋นˆ ์ด๋ฆ„์ด๋ฉด `TypeError`. `image`๋Š” ์นธ์„ ์ฑ„์šฐ๋Š” ๊ทธ๋ฆผ ๋…ธ๋“œ |
56
57
  | `selectionMode` | `"single"` ยท `"multiple"` | ํ•„์ˆ˜ | ๋‹จ์ผ์€ radio, ๋‹ค์ค‘์€ checkbox๋กœ ์ฝํžŒ๋‹ค |
57
58
  | `value`ยท`onValueChange` | ๋‹จ์ผ `string \| null` / ๋‹ค์ค‘ `string[]` | ํ•„์ˆ˜ | ์ œ์–ดํ˜•. ๋‹ค์ค‘์€ ๋ˆ„๋ฅธ ์ˆœ์„œ๋Œ€๋กœ ์ถ”๊ฐ€ยท์ œ๊ฑฐ |
@@ -62,7 +63,7 @@ import { ImageChoiceGroup } from "@hjmds/react-native/image-choice";
62
63
  | ํ•ญ๋ชฉ | ๊ฐ’ | ๊ทผ๊ฑฐ |
63
64
  | --- | --- | --- |
64
65
  | ํฌ๊ธฐ | ๊ทธ๋ฆผ 72 ์ •์‚ฌ๊ฐ, ๋ฐ”๊นฅ ๊ณ ๋ฆฌ 2 + ๊ฐ„๊ฒฉ 2(ํ„ฐ์น˜ ์˜์—ญ 80), radius `radius.md` 12, ์ฒดํฌ ์› 20 | `imageChoiceRecipe` |
65
- | ๊ฐ„๊ฒฉ | ํƒ€์ผ ์‚ฌ์ด `spacing.xs` 8, ๋ผ๋ฒจโ€“ํƒ€์ผ 8 | `imageChoiceRecipe.gap` |
66
+ | ๊ฐ„๊ฒฉ | ํƒ€์ผ ์‚ฌ์ด `spacing.xs` 8, ๋ผ๋ฒจโ€“ํƒ€์ผ 8, ๊ทธ๋ฆผโ€“ํ•ญ๋ชฉ ๋ผ๋ฒจ `spacing.xxs` 4 | `imageChoiceRecipe.gap`, `itemLabelGap` |
66
67
  | ์ˆœ์„œยท์ •๋ ฌ | `items` ์ˆœ์„œ๋Œ€๋กœ ์‹œ์ž‘ ์ชฝ๋ถ€ํ„ฐ ์ค„๋ฐ”๊ฟˆ ๋ฐฐ์น˜, ์ฒดํฌ๋Š” ํƒ€์ผ ๋ ์ชฝ ์œ„ ๋ชจ์„œ๋ฆฌ | ๋‘ ๋ Œ๋”๋Ÿฌ |
67
68
  | ๊ณ ์ •ยท์Šคํฌ๋กค | ์ž์ฒด ์Šคํฌ๋กค ์—†์Œ. ๋ฐ”๊นฅ ๋ชฉ๋กยทํ™”๋ฉด์ด ์Šคํฌ๋กค | ๋‘ ๋ Œ๋”๋Ÿฌ |
68
69
  | ์ข์€ ํญยทํฐ ๊ธ€์ž | ๋‚จ์€ ํญ์—์„œ ์ค„๋ฐ”๊ฟˆ, ํƒ€์ผ ํฌ๊ธฐ๋Š” ์œ ์ง€ | flex wrap |
@@ -78,4 +79,5 @@ import { ImageChoiceGroup } from "@hjmds/react-native/image-choice";
78
79
 
79
80
  ## ๊ทผ๊ฑฐ
80
81
 
82
+ - `showItemLabels`: 2026-10-18 ๋ฒˆ๋š ๊ณต์œ  ๋ฐ›๋Š” ์‚ฌ๋žŒ ์„ ํƒ์ด ์ด๋ฆ„์„ ๋ณด์ด๋ ค๊ณ  ๋งํฌ ๋ฒ„ํŠผ์— ์•„๋ฐ”ํƒ€ยท์ฒดํฌยท์ด๋ฆ„์„ ์ง์ ‘ ์กฐ๋ฆฝํ–ˆ๋‹ค.
81
83
  - ๋„์ž… ๊ณ„๊ธฐ: 2026-10-16 ๊ตฌ์„ฑ ์˜ˆ์ œ ์ ๊ฒ€ โ€” ์ œํ’ˆ๋“ค์ด `selected`๋ฅผ ์ค€ ghost ๋ฒ„ํŠผ์œผ๋กœ ๊ทธ๋ฆผ ์„ ํƒ์„ ํ‰๋‚ด ๋‚ด ์„ ํƒ ์ƒํƒœ๊ฐ€ ์ผ๊ด€๋˜์ง€ ์•Š์•˜๋‹ค.
@@ -30,6 +30,7 @@
30
30
  | ์ด๋ฆ„ | ์—ญํ•  | Web | Native |
31
31
  | --- | --- | --- | --- |
32
32
  | `MediaSelectionScreen` | ๋ฏธ๋””์–ด ์„ ํƒ ๊ฒ€ํ†  ํ™”๋ฉด | `/screen-flows` | `/screen-flows` |
33
+ | `MediaSelectionCell` | ํ™”๋ฉด ๊ฒฉ์ž๊ฐ€ ๊ทธ๋ฆฌ๋Š” ํ•ญ๋ชฉ ์นธ(๋ฏธ๋ฆฌ๋ณด๊ธฐ โ†’ UploadItem โ†’ ์œ„๋กœยท์•„๋ž˜๋กœยทํšŒ์ „ยท์‚ญ์ œ). ๊ฐ€์ƒํ™” ๋ชฉ๋ก์—์„œ ๊ฐ™์€ ์นธ์„ ๊ทธ๋ฆด ๋•Œ ์“ด๋‹ค. ๋ฏธ๊ฒŒ์‹œ | `/screen-flows` | `/screen-flows` |
33
34
 
34
35
  granular subpath๋กœ๋งŒ import ๋œ๋‹ค(๋ฃจํŠธ entry์— ์—†์Œ). ์ถ”๊ฐ€ peer๋Š” ์—†๋‹ค.
35
36
 
@@ -96,6 +97,7 @@ Web์˜ `<img alt="">`๋Š” ์žฅ์‹์šฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋‹ค. ์‚ฌ์ง„ ์ด๋ฆ„์€ `removeLabe
96
97
  | `removeLabel` ยท `moveUpLabel` ยท `moveDownLabel` | `(item: MediaSelectionItem) => string` | ํ•„์ˆ˜ | ์‚ฌ์ง„ ์ด๋ฆ„์„ ํฌํ•จํ•œ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„ |
97
98
  | `onMove` | `(id: string, direction: -1 \| 1) => void` | ํ•„์ˆ˜ | ์ฒซ ํ•ญ๋ชฉ์˜ ์œ„๋กœยท๋งˆ์ง€๋ง‰ ํ•ญ๋ชฉ์˜ ์•„๋ž˜๋กœ๋Š” ๋น„ํ™œ์„ฑ |
98
99
  | `onRemove` ยท `onRetry` ยท `onCancel` | `(id: string) => void` | ํ•„์ˆ˜ | ์‚ญ์ œ, UploadItem ์žฌ์‹œ๋„ยท์ทจ์†Œ |
100
+ | `rotate` | `{ label, itemLabel(item), onRotate(id) }` | ์—†์Œ | ๋ฏธ๊ฒŒ์‹œ. ์ฃผ๋ฉด ์•„๋ž˜๋กœ์™€ ์‚ญ์ œ ์‚ฌ์ด์— ํšŒ์ „ ๋ฒ„ํŠผ(`label` ํ‘œ์‹œ ๋ฌธ๊ตฌ, `itemLabel` ์‚ฌ์ง„ ์ด๋ฆ„์„ ํฌํ•จํ•œ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„). ํšŒ์ „ ๊ฐ’ ์ €์žฅ๊ณผ `preview`ยท๊ฒฐ๊ณผ๋ฌผ ๋ฐ˜์˜์€ ์ œํ’ˆ์ด ํ•œ๋‹ค |
99
101
  | ๋‚˜๋จธ์ง€ | `ScreenLayout`๊ณผ ๊ฐ™์Œ(`children`ยท`footer` ์ œ์™ธ) | โ€” | `actions`๋ฅผ ์ฃผ๋ฉด `add` ์•ž์— ๊ฐ™์€ ํ–‰์œผ๋กœ ๋†“์ธ๋‹ค |
100
102
 
101
103
  ## ๋ฐฐ์น˜
@@ -108,6 +110,21 @@ Web์˜ `<img alt="">`๋Š” ์žฅ์‹์šฉ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋‹ค. ์‚ฌ์ง„ ์ด๋ฆ„์€ `removeLabe
108
110
  | ๊ณ ์ •ยท์Šคํฌ๋กค | ํ—ค๋”ยทfooter ๊ณ ์ •, ๊ฒฉ์ž๋Š” ๋ณธ๋ฌธ ์Šคํฌ๋กค(`scroll` ๊ธฐ๋ณธ `"screen"`) | `ScreenLayout` |
109
111
  | ์ข์€ ํญ | ์ข์€ ํญ์—์„œ๋„ 2์—ด ์œ ์ง€; ํ•ญ๋ชฉ ๋ฒ„ํŠผ ์ค„์€ ์ค„๋ฐ”๊ฟˆ(`flexWrap: "wrap"`)ํ•ด ๋ฒ„ํŠผ์„ ์ž๋ฅด์ง€ ์•Š๋Š”๋‹ค | `screen-flows.tsx` |
110
112
 
113
+ ## ๊ฐ€์ƒํ™” ๋ชฉ๋ก์˜ ํ•ญ๋ชฉ ์นธ
114
+
115
+ ์‚ฌ์ง„์ด ๋งŽ์•„ ์ œํ’ˆ์ด `VirtualList`ยท`FlatList`๋กœ ๋ชฉ๋ก์„ ์ง์ ‘ ๊ทธ๋ฆด ๋•Œ๋Š” ๊ฐ™์€ ์นธ `MediaSelectionCell`์„ `renderItem`์—์„œ ๊ทธ๋ฆฐ๋‹ค. ๋ฏธ๋ฆฌ๋ณด๊ธฐ์™€ ํ–‰๋™ ์ค„์„ ์ œํ’ˆ์ด ๋‹ค์‹œ ์กฐ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค(2026-10-18 Utilverse ์‚ฌ์ง„โ†’PDFยท์‚ฌ์ง„ ๋  ๋„๊ตฌ๊ฐ€ ์ธ๋„ค์ผยท์ˆœ์„œยทํšŒ์ „ยท์‚ญ์ œ ์นธ์„ ์ง์ ‘ ๋งŒ๋“ค์—ˆ๋‹ค).
116
+
117
+ ```tsx
118
+ // Native
119
+ import { MediaSelectionCell } from "@hjmds/react-native/screen-flows";
120
+
121
+ <FlatList data={photos} keyExtractor={photo => photo.id} renderItem={({ item, index }) =>
122
+ <MediaSelectionCell item={toMediaItem(item)} index={index} count={photos.length} {...itemActions}
123
+ rotate={{ label: t("photo.rotate"), itemLabel: entry => t("photo.rotateNamed", { name: entry.descriptor.name }), onRotate: rotatePhoto }} />} />
124
+ ```
125
+
126
+ `index`ยท`count`๋Š” ์ „์ฒด ๋ชฉ๋ก ๊ธฐ์ค€์ด๋‹ค(์ฒซ ํ•ญ๋ชฉ ์œ„๋กœยท๋งˆ์ง€๋ง‰ ํ•ญ๋ชฉ ์•„๋ž˜๋กœ ๋น„ํ™œ์„ฑ). `itemActions`๋Š” ํ™”๋ฉด๊ณผ ๊ฐ™์€ `labels`ยท`actionLabels`ยท`removeLabel`ยท`moveUpLabel`ยท`moveDownLabel`ยท`onRemove`ยท`onMove`ยท`onRetry`ยท`onCancel`์ด๋‹ค. ์นธ ์‚ฌ์ด ๊ฐ„๊ฒฉยท์—ด ์ˆ˜๋Š” host๊ฐ€ ์ค€๋‹ค. ์‚ญ์ œ ํ›„ Web ํ‚ค๋ณด๋“œ ์ดˆ์  ๋ณต๊ตฌ๋Š” ํ™”๋ฉด ๊ฒฉ์ž์˜ ๋™์ž‘์ด๋ฏ€๋กœ ๊ฐ€์ƒํ™” host๋Š” ์ž๊ธฐ ์ดˆ์ ์„ ์†Œ์œ ํ•œ๋‹ค.
127
+
111
128
  ## ๊ผญ ์ง€ํ‚ฌ ๊ฒƒ
112
129
 
113
130
  - `actionLabels`๋Š” ์งง์€ ํ‘œ์‹œ ๋ฌธ๊ตฌ, `removeLabel`ยท`moveUpLabel`ยท`moveDownLabel`์€ ์‚ฌ์ง„ ์ด๋ฆ„์„ ํฌํ•จํ•œ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„์ด๋‹ค.
@@ -76,7 +76,7 @@ import { Image } from "react-native";
76
76
  | `value` | `string` | ํ•„์ˆ˜ | ์™„์ „ ์ œ์–ดํ˜•. HJM์€ ์ดˆ์•ˆ์„ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค |
77
77
  | `label` | `string` | ํ•„์ˆ˜ | ์ ‘๊ทผ์„ฑ ์ด๋ฆ„; placeholder ์ƒ๋žต ์‹œ ๊ฐ™์€ ๋ฌธ๊ตฌ |
78
78
  | `placeholder` | `string` | `label` | ์งง์€ ์‹œ๊ฐ ์•ˆ๋‚ด์™€ ์ „์ฒด ์ ‘๊ทผ์„ฑ ์ด๋ฆ„์„ ๋ถ„๋ฆฌ |
79
- | `description` ยท `error` ยท `invalid` | `string` ยท `string` ยท `boolean` | ์—†์Œ | TextArea์˜ ์•ˆ๋‚ดยท์˜ค๋ฅ˜ ์—ฐ๊ฒฐ. Web describedby/invalid, Native hint/์˜ค๋ฅ˜ ํ‘œ๋ฉด |
79
+ | `description` ยท `error` ยท `invalid` | `string` ยท `string` ยท `boolean` | ์—†์Œ | TextArea์˜ ์•ˆ๋‚ดยท์˜ค๋ฅ˜ ์—ฐ๊ฒฐ. Web describedby/invalid, Native hint/์˜ค๋ฅ˜ ํ‘œ๋ฉด. `error`๋Š” ๋‚˜ํƒ€๋‚˜๊ฑฐ๋‚˜ ๋ฐ”๋€” ๋•Œ ์ฆ‰์‹œ(assertive) ์•Œ๋ฆฐ๋‹ค(1.30.0 ์ดํ›„ ์ˆ˜์ •). ์ž‘์„ฑ์ฐฝ ์ „์šฉ ์•Œ๋ฆผ์€ ์—†๊ณ  TextArea์˜ ๊ณต์šฉ Field ์˜ค๋ฅ˜ ๊ณ„์•ฝ([Field](field.md#์˜ค๋ฅ˜-์•Œ๋ฆผ))์„ ๊ทธ๋Œ€๋กœ ์“ด๋‹ค |
80
80
  | `onBlur` | `() => void` | ์—†์Œ | host์˜ ์ž…๋ ฅ ์ค‘ ์ƒํƒœ ํ•ด์ œ |
81
81
  | `submitMode` | `"newline"` ยท `"send"` | `"newline"` | Web Enter ์ „์†ก์€ Shift/IME/229 ๋ณดํ˜ธ. Native send๋Š” submit, newline์€ ๊ธฐ์กด ์ค„๋ฐ”๊ฟˆ |
82
82
  | `sendLabel` | `string` | ํ•„์ˆ˜ | ์ „์†ก ๋ฒ„ํŠผ ๋ฌธ๊ตฌยท์ ‘๊ทผ์„ฑ ์ด๋ฆ„ |
@@ -123,6 +123,7 @@ import { Image } from "react-native";
123
123
  - ์ž…๋ ฅ ์—ฐ๊ฒฐ(blur/IME/๊ฒ€์ฆ)์ด ๋” ํ•„์š”ํ•˜๋ฉด ์ œํ’ˆ์ด ๋‚ด๋ถ€ TextArea๋ฅผ ๋”ฐ๋กœ ์กฐ๋ฆฝํ•˜์ง€ ์•Š๊ณ  ๊ธฐ์กด MessageComposer ์—”์ง„์˜ ๊ณต๊ฐœ ์ถ•์„ ํ™•์žฅํ•œ๋‹ค.
124
124
  - ๋กœ์ปฌ ์ค€๋น„ ์‚ฌ์ง„์€ `attachments`๋กœ ํ‘œ์‹œํ•˜๋ฉฐ UploadItem์˜ ์„œ๋ฒ„ ์—…๋กœ๋“œ ์™„๋ฃŒ๋กœ ๊ฐ€์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ค€๋น„ ์ค‘ ํ•ด๋‹น ํ•ญ๋ชฉ์˜ `disabled`์™€ attachmentAction.disabled๋ฅผ ์—ฐ๊ฒฐํ•ด ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ๋ณธ๋ฌธ๊ณผ ์‚ฌ์ง„ ์ œ๊ฑฐ๋ฅผ ๋ถ„๋ฆฌํ•œ๋‹ค. ์„œ๋ฒ„ ์„ฑ๊ณต ์ „ ์ดˆ์•ˆ์„ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค.
125
125
  - Web ๋ฐฐ์น˜๋Š” `layoutStyle`๋กœ ํ•œ๋‹ค. Native๋Š” ์Šคํƒ€์ผ ํ†ต๋กœ๊ฐ€ ์—†์–ด ๊ฐ์‹ธ๋Š” ๋ ˆ์ด์•„์›ƒ์—์„œ ๋ฐฐ์น˜ํ•œ๋‹ค.
126
+ - ์ „์†ก ์‹คํŒจ๋Š” `error`๋กœ ๋„˜๊ธด๋‹ค. ์•Œ๋ฆผ์„ ์œ„ํ•ด ๊ฐ™์€ ๋ฌธ๊ตฌ๋ฅผ ํ™”๋ฉด `notice`์˜ Notice๋‚˜ `context`์˜ `role="alert"` ๋ฌธ๊ตฌ๋กœ ํ•œ ๋ฒˆ ๋” ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
126
127
 
127
128
  ## ํ”Œ๋žซํผ ์ฐจ์ด
128
129
 
@@ -140,6 +141,8 @@ Native ๋‚ด์žฅ ์‚ญ์ œยท๋ฉ”๋‰ด ๊ธฐํ˜ธ๋Š” ๊ณ ์ • ์•„์ด์ฝ˜ ํ‹€์˜ ํฌ๊ธฐ๋ฅผ ์œ ์ง€
140
141
 
141
142
  ## ๊ทผ๊ฑฐ
142
143
 
144
+ - `error` ์ฆ‰์‹œ ์•Œ๋ฆผ: 2026-10-18 ๋ฒˆ๋š DM. ์ž‘์„ฑ์ฐฝ `error`๊ฐ€ ์ž…๋ ฅ ์•„๋ž˜์— ๋ณด์ด๊ธฐ๋งŒ ํ•˜๊ณ  ๋‚ญ๋…๋˜์ง€ ์•Š์•„(Web ์•Œ๋ฆผ ์—†์Œ, iOS live region ๋ฏธ์ง€์›) ์ „์†ก ์‹คํŒจ๋ฅผ ํ™”๋ฉด notice์˜ Notice๋กœ ๋”ฐ๋กœ ์•Œ๋ ธ๋‹ค. ์ฒซ ์ˆ˜์ •์€ ์ž‘์„ฑ์ฐฝ์—๋งŒ ์ˆจ๊ธด alert๋ฅผ ๋”ํ•ด Web ํƒ์ƒ‰ ๋ชจ๋“œ์—์„œ ๊ฐ™์€ ๋ฌธ๊ตฌ๋ฅผ ๋‘ ๋ฒˆ ์ฝ์—ˆ์œผ๋ฏ€๋กœ, ๊ฐ™์€ ๋‚  ๋ชจ๋“  Field ์ปจํŠธ๋กค์˜ ๊ณต์šฉ ์˜ค๋ฅ˜ ๊ณ„์•ฝ์œผ๋กœ ์˜ฎ๊ฒผ๋‹ค.
145
+
143
146
  - ๋‹ต์žฅ ์ค‘ ์นด๋“œ ๋ฐฐ์น˜: 2026-10-16 ์นด์นด์˜คํ†ก ์‹ค์ธก.
144
147
  - `submitMode` ๋“ฑ ์ž…๋ ฅ ๊ณต๊ฐœ ์ถ•: 2026-10-07 ๋ฒˆ๋š ์ฑ„ํƒ์—์„œ ๊ธฐ์กด blur/IME/๊ฒ€์ฆ ์—ฐ๊ฒฐ์ด ๋ˆ„๋ฝ๋๋‹ค.
145
148
  - Native ๋‚ด์žฅ ๊ธฐํ˜ธ ๊ณ ์ • ํฌ๊ธฐ: 2026-10-06 ์ตœ๊ทผ ๊ฒ€์ƒ‰ ์‚ญ์ œ ๊ธฐํ˜ธ๊ฐ€ ํฐ ๊ธ€์ž์—์„œ ์ž˜๋ฆฌ๋Š” ๊ฒƒ์„ ์žฌํ˜„ํ–ˆ๋‹ค.