@hjmds/design-contracts 1.13.1 โ†’ 1.14.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 (117) hide show
  1. package/dist/agreement.d.ts +7 -1
  2. package/dist/agreement.d.ts.map +1 -1
  3. package/dist/agreement.js +14 -3
  4. package/dist/agreement.js.map +1 -1
  5. package/dist/behaviors.d.ts +1 -1
  6. package/dist/catalog.d.ts +4 -0
  7. package/dist/catalog.d.ts.map +1 -1
  8. package/dist/content-transition.d.ts +17 -0
  9. package/dist/content-transition.d.ts.map +1 -1
  10. package/dist/content-transition.js +21 -0
  11. package/dist/content-transition.js.map +1 -1
  12. package/dist/date-entry.d.ts +71 -0
  13. package/dist/date-entry.d.ts.map +1 -0
  14. package/dist/date-entry.js +78 -0
  15. package/dist/date-entry.js.map +1 -0
  16. package/dist/document-resource.d.ts +104 -0
  17. package/dist/document-resource.d.ts.map +1 -0
  18. package/dist/document-resource.js +77 -0
  19. package/dist/document-resource.js.map +1 -0
  20. package/dist/effect-surface.d.ts +6 -1
  21. package/dist/effect-surface.d.ts.map +1 -1
  22. package/dist/effect-surface.js +5 -2
  23. package/dist/effect-surface.js.map +1 -1
  24. package/dist/field-group.d.ts +47 -0
  25. package/dist/field-group.d.ts.map +1 -0
  26. package/dist/field-group.js +92 -0
  27. package/dist/field-group.js.map +1 -0
  28. package/dist/image.d.ts +17 -0
  29. package/dist/image.d.ts.map +1 -1
  30. package/dist/image.js +19 -0
  31. package/dist/image.js.map +1 -1
  32. package/dist/internal/effect-noise.d.ts +2 -0
  33. package/dist/internal/effect-noise.d.ts.map +1 -0
  34. package/dist/internal/effect-noise.js +4 -0
  35. package/dist/internal/effect-noise.js.map +1 -0
  36. package/dist/progressive-blur.d.ts +32 -0
  37. package/dist/progressive-blur.d.ts.map +1 -0
  38. package/dist/progressive-blur.js +28 -0
  39. package/dist/progressive-blur.js.map +1 -0
  40. package/dist/reference-controls.d.ts +32 -0
  41. package/dist/reference-controls.d.ts.map +1 -0
  42. package/dist/reference-controls.js +28 -0
  43. package/dist/reference-controls.js.map +1 -0
  44. package/dist/scroll-progress.d.ts +7 -1
  45. package/dist/scroll-progress.d.ts.map +1 -1
  46. package/dist/scroll-progress.js +21 -2
  47. package/dist/scroll-progress.js.map +1 -1
  48. package/dist/text-annotation.d.ts +43 -0
  49. package/dist/text-annotation.d.ts.map +1 -0
  50. package/dist/text-annotation.js +137 -0
  51. package/dist/text-annotation.js.map +1 -0
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/dist/version.js.map +1 -1
  55. package/docs/agreement.md +14 -0
  56. package/docs/dialog.md +16 -1
  57. package/docs/effect-surface.md +19 -3
  58. package/docs/generated/component-maturity.md +1 -1
  59. package/docs/generated/renderer-evidence.json +3 -3
  60. package/docs/generated/renderer-evidence.md +1 -1
  61. package/docs/generated/showcase-manifest.json +1 -1
  62. package/docs/image.md +17 -0
  63. package/docs/optional-adapters.md +25 -0
  64. package/docs/popover.md +15 -0
  65. package/docs/rating.md +6 -1
  66. package/docs/reference-controls.md +40 -0
  67. package/docs/task-list.md +15 -1
  68. package/docs/text-annotation.md +85 -0
  69. package/docs/usage/README.md +18 -1
  70. package/docs/usage/components/agreement.md +13 -3
  71. package/docs/usage/components/alert-dialog.md +7 -1
  72. package/docs/usage/components/avatar.md +33 -1
  73. package/docs/usage/components/card.md +7 -1
  74. package/docs/usage/components/carousel.md +5 -1
  75. package/docs/usage/components/chat-message.md +11 -1
  76. package/docs/usage/components/chip.md +5 -0
  77. package/docs/usage/components/content-transition.md +28 -4
  78. package/docs/usage/components/dialog.md +18 -1
  79. package/docs/usage/components/effect-surface.md +4 -2
  80. package/docs/usage/components/empty-state.md +10 -2
  81. package/docs/usage/components/field.md +23 -0
  82. package/docs/usage/components/form.md +6 -1
  83. package/docs/usage/components/image-comparison.md +82 -0
  84. package/docs/usage/components/image.md +81 -2
  85. package/docs/usage/components/keyboard-avoiding.md +6 -1
  86. package/docs/usage/components/link.md +3 -1
  87. package/docs/usage/components/list-row.md +3 -1
  88. package/docs/usage/components/list.md +6 -1
  89. package/docs/usage/components/message-composer.md +5 -0
  90. package/docs/usage/components/popover.md +7 -1
  91. package/docs/usage/components/progress.md +28 -0
  92. package/docs/usage/components/progressive-blur.md +113 -0
  93. package/docs/usage/components/rating.md +74 -0
  94. package/docs/usage/components/search-field.md +10 -0
  95. package/docs/usage/components/search-screen.md +5 -0
  96. package/docs/usage/components/segmented-control.md +17 -0
  97. package/docs/usage/components/statistic.md +11 -0
  98. package/docs/usage/components/tags-input.md +5 -0
  99. package/docs/usage/components/toast.md +5 -0
  100. package/docs/usage/components/upload-item.md +3 -1
  101. package/docs/usage/compositions/action-feedback.md +77 -0
  102. package/docs/usage/compositions/adaptive-content.md +81 -0
  103. package/docs/usage/compositions/context-toolbar.md +85 -0
  104. package/docs/usage/compositions/date-entry.md +108 -0
  105. package/docs/usage/compositions/document-resource.md +124 -0
  106. package/docs/usage/compositions/field-group.md +104 -0
  107. package/docs/usage/compositions/illustrated-outcome.md +91 -0
  108. package/docs/usage/compositions/live-list.md +104 -0
  109. package/docs/usage/compositions/optional-adapters.md +1 -1
  110. package/docs/usage/compositions/origin-dialog.md +108 -0
  111. package/docs/usage/compositions/selection-motion.md +73 -0
  112. package/docs/usage/compositions/texture-comparison.md +86 -0
  113. package/docs/usage/compositions/upload-recovery.md +80 -0
  114. package/docs/usage/compositions/video-dialog.md +100 -0
  115. package/docs/usage/screens/flow-onboarding.md +5 -3
  116. package/docs/usage/screens/product-bento.md +117 -0
  117. package/package.json +37 -1
@@ -0,0 +1,104 @@
1
+ # ๊ด€๋ จ ์ž…๋ ฅ ๋ฌถ์Œ
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: [์ž…๋ ฅ ๊ทธ๋ฃน ์กฐ์‚ฌ](../../../../../docs/plans/field-group-experiment.md), `src/field-group.ts`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/์ž…๋ ฅ๊ณผ ์ž‘์„ฑ/๊ด€๋ จ ์ž…๋ ฅ ๋ฌถ์Œ`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ์ฃผ์†Œยท์—ฐ๋ฝ์ฒ˜์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์ž…๋ ฅ์ด ํ•˜๋‚˜์˜ ์งˆ๋ฌธ์— ๋‹ตํ•  ๋•Œ ์“ด๋‹ค. FieldGroup์€ ๊ด€๋ จ์„ฑยท๋„์›€๋งยท์˜ค๋ฅ˜ยท์ž ๊ธˆ์„
16
+ ์†Œ์œ ํ•˜๊ณ  ๊ฐ’ยท๊ฒ€์ฆ ์‹œ์ ยท์ œ์ถœ์€ ์ œํ’ˆ์ด ์†Œ์œ ํ•œ๋‹ค. Form ์•ˆ์— ์—ฌ๋Ÿฌ ๊ทธ๋ฃน์„ ๋‘˜ ์ˆ˜ ์žˆ๋‹ค. ์„ ํƒ๋งŒ ๋ฌถ์œผ๋ฉด
17
+ CheckboxGroup/RadioGroup, ๋‚ ์งœ ์กฐ๊ฐ์ด๋ฉด DateEntry๋ฅผ ๋จผ์ € ์‚ฌ์šฉํ•œ๋‹ค. ๊ณตํ†ต ์ œ์ถœ ์„ธ์…˜์€ Form์˜ ๋ชซ์ด๋‹ค.
18
+
19
+ ## ๊ตฌ์„ฑ ์š”์†Œ
20
+
21
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
22
+ | --- | --- | --- |
23
+ | FieldGroup | ๊ทธ๋ฃน ์ด๋ฆ„ยท์„ค๋ช…ยท์˜ค๋ฅ˜์™€ ์ž…๋ ฅ ์—ฐ๊ฒฐ | ์ด ๋ฌธ์„œ |
24
+ | TextField ๋“ฑ | renderField๋กœ ๊ณต๊ธ‰ํ•˜๋Š” ๊ฐœ๋ณ„ ์ž…๋ ฅ | [Field](../components/field.md) |
25
+ | Form | ์ œํ’ˆ ์ œ์ถœ ๊ฒฝ๊ณ„ | [Form](../components/form.md) |
26
+
27
+ ## ๋ฐฐ์น˜
28
+
29
+ ```text
30
+ ๊ทธ๋ฃน ์ด๋ฆ„
31
+ ๊ทธ๋ฃน ์„ค๋ช…(์„ ํƒ)
32
+ ๊ทธ๋ฃน ์˜ค๋ฅ˜(์„ ํƒ, ํ•œ ๋ฒˆ)
33
+ [์ž…๋ ฅ 1]
34
+ ํ•„๋“œ ์„ค๋ช… / ํ•„๋“œ ์˜ค๋ฅ˜
35
+ [์ž…๋ ฅ 2]
36
+ ํ•„๋“œ ์„ค๋ช… / ํ•„๋“œ ์˜ค๋ฅ˜
37
+ ```
38
+
39
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
40
+ | --- | --- | --- | --- |
41
+ | ๋ฐ”๊นฅ ํ‹€ | Web fieldset / Native View | ๊ธฐ์กด ํผ ์•ˆ | ํ…Œ๋‘๋ฆฌ ์—†์Œ, Web ์ตœ์†Œ ํญ 0 |
42
+ | ๊ทธ๋ฃน ์ด๋ฆ„ | Web legend / Native Text label | ๋งจ ์œ„ | Web ์•„๋ž˜ spacing.sm 12, Native ๊ทธ๋ฃน ๊ฐ„๊ฒฉ spacing.sm 12 |
43
+ | ์ž…๋ ฅ ๋ฌถ์Œ | renderField | descriptor.fields ์ˆœ์„œ | ์„ธ๋กœ ๊ฐ„๊ฒฉ spacing.md 16 |
44
+ | ๊ฐœ๋ณ„ ๋„์›€๋ง | FieldGroup | ํ•ด๋‹น ์ž…๋ ฅ ๋ฐ”๋กœ ์•„๋ž˜ | Native ๊ฐ„๊ฒฉ spacing.xs 8, ๊ธ€์ž ํฌ๊ธฐ๋Š” Text ๊ธฐ๋ณธ |
45
+
46
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
47
+
48
+ 1. descriptor์— label๊ณผ fields๋ฅผ ๋„ฃ๋Š”๋‹ค. ํ•„๋“œ id๋Š” ๊ทธ๋ฃน ์•ˆ์—์„œ ๊ณ ์œ ํ•˜๋ฉฐ ์žฌ์ •๋ ฌ์—๋„ ์œ ์ง€ํ•œ๋‹ค.
49
+ 2. descriptionยทerror๋Š” ์ด๋ฏธ ํ˜„์ง€ํ™”ํ•œ ๋ฌธ๊ตฌ๋‹ค. ๋นˆ ๋ฌธ์ž์—ด์€ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ‘œ์‹œํ•  ์˜ค๋ฅ˜๊ฐ€ ์—†์œผ๋ฉด ์ƒ๋žตํ•œ๋‹ค.
50
+ 3. ๊ทธ๋ฃน error๋Š” message์™€ fieldIds๋ฅผ ๋ฐ›๋Š”๋‹ค. ์˜ํ–ฅ์„ ๋ฐ›๋Š” ํ•„๋“œ๋งŒ invalid๊ฐ€ ๋˜๊ณ , ๋นˆ ๋ฐฐ์—ด์€ ๊ทธ๋ฃน ์ „์šฉ ์˜ค๋ฅ˜๋‹ค.
51
+ 4. renderField์˜ controlProps๋ฅผ ๊ฐœ๋ณ„ ์ž…๋ ฅ์— ์ „๋‹ฌํ•œ๋‹ค. FieldGroup์ด ๋„์›€๋งยท์˜ค๋ฅ˜๋ฅผ ์ด๋ฏธ ๊ทธ๋ฆฌ๋ฏ€๋กœ ๊ฐ™์€ ๋‚ด์šฉ์„
52
+ TextField description/error์— ๋‹ค์‹œ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค. Web idยทaria ์—ฐ๊ฒฐ์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š๋Š”๋‹ค.
53
+ 5. ๊ฐ’ ๋ณ€๊ฒฝ์—๋Š” guardChange๋กœ ๊ฐ์‹ผ callback์„ ์ „๋‹ฌํ•œ๋‹ค. ๊ทธ๋ฃน ์ž ๊ธˆยท๊ฐœ๋ณ„ ์ž ๊ธˆยทํ•„๋“œ ์ œ๊ฑฐยท์–ธ๋งˆ์šดํŠธ ํ›„
54
+ ๋‚จ์€ callback์„ ์ฐจ๋‹จํ•œ๋‹ค. ์ž ๊ธˆ ํ•ด์ œ ์‹œ ์›๋ž˜ disabled์ธ ํ•„๋“œ๋Š” ๊ณ„์† ์ž ๊ธด๋‹ค. ์ œ๊ฑฐ๊ฐ€ commit๋œ
55
+ ํ•„๋“œ๋ฅผ ๊ฐ™์€ id๋กœ ๋‹ค์‹œ ์ถ”๊ฐ€ํ•ด๋„ ์ด์ „ callback์€ ๋ณต๊ตฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ƒˆ renderField์˜ guardChange๋ฅผ
56
+ ์‚ฌ์šฉํ•œ๋‹ค. 2026-10-07 ํšŒ๊ท€์—์„œ enabled id๋งŒ ๊ฒ€์‚ฌํ•˜๋ฉด ์ด์ „ ์ด๋ฒคํŠธ๊ฐ€ ์ƒˆ ์ดˆ์•ˆ์„ ๋ฐ”๊พธ๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.
57
+ 6. dynamic ํ•„๋“œ ์ œ๊ฑฐ ์‹œ ํ•ด๋‹น ๊ทธ๋ฃน ์˜ค๋ฅ˜ ๋Œ€์ƒ๋„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•œ๋‹ค. ์‚ฌ๋ผ์ง„ id๋‚˜ ์ค‘๋ณต ์˜ค๋ฅ˜ ๋Œ€์ƒ์€ TypeError๋‹ค.
58
+ 7. ์ œํ’ˆ์ด ๊ฐ’๊ณผ ๊ฒ€์ฆ์„ ์œ ์ง€ํ•œ๋‹ค. ์ž…๋ ฅ ์ˆœ์„œยท๊ทธ๋ฃน ์ž ๊ธˆ ๋ณ€๊ฒฝ์€ ๊ฐ’์„ ์ดˆ๊ธฐํ™”ํ•˜์ง€ ์•Š๋Š”๋‹ค.
59
+
60
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
61
+ | --- | --- | --- |
62
+ | ๊ธฐ๋ณธ | ๊ทธ๋ฃน ์„ค๋ช…๊ณผ ๋…๋ฆฝ ์ž…๋ ฅ | ์ž๋™ ํฌ์ปค์Šค ์—†์Œ |
63
+ | ์ง„ํ–‰ ์ค‘ | ์ œํ’ˆ ๊ฐ’ ํŽธ์ง‘ | ์ผ๋ฐ˜ Tabยทํ„ฐ์น˜ ์ด๋™ |
64
+ | ์‹คํŒจ | ๊ทธ๋ฃน ์˜ค๋ฅ˜ ํ•œ ๋ฒˆ, ํ•ด๋‹น ์ž…๋ ฅ invalid, ๊ฐœ๋ณ„ ์˜ค๋ฅ˜ ๋ณด์กด | Web ์—ฐ๊ฒฐ๋œ ์„ค๋ช…, Native hint์— ๊ทธ๋ฃน/๊ฐœ๋ณ„ ์„ค๋ช…๊ณผ ์˜ค๋ฅ˜ |
65
+ | ๋น„ํ™œ์„ฑ | ๊ฐ ์ž…๋ ฅ disabled | guardChange๊ฐ€ ๋Šฆ์€ ๊ฐ’ ๋ณ€๊ฒฝ๋„ ์ฐจ๋‹จ |
66
+
67
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
68
+
69
+ ```tsx
70
+ // Web
71
+ import { FieldGroup } from "@hjmds/react/field-group";
72
+ import { TextField } from "@hjmds/react/forms";
73
+ <FieldGroup descriptor={group} renderField={({ id, controlProps, guardChange }) =>
74
+ <TextField {...controlProps} value={values[id] ?? ""}
75
+ onValueChange={guardChange((value: string) => updateField(id, value))} />} />
76
+ ```
77
+
78
+ ```tsx
79
+ // Native
80
+ import { FieldGroup } from "@hjmds/react-native/field-group";
81
+ import { TextField } from "@hjmds/react-native/inputs";
82
+ <FieldGroup descriptor={group} renderField={({ id, controlProps, guardChange }) =>
83
+ <TextField {...controlProps} value={values[id] ?? ""}
84
+ onValueChange={guardChange((value: string) => updateField(id, value))} />} />
85
+ ```
86
+
87
+ ๊ณตํ†ต descriptor/resolver/edit session์€ `@hjmds/design-contracts/field-group`์— ์žˆ๋‹ค.
88
+ ์‚ฌ์šฉ์ž ์ •์˜ ์ž…๋ ฅ์€ labelยทdisabledยทinvalidยท๋„์›€๋ง์„ ์ž์‹ ์˜ ์‹ค์ œ ์ž…๋ ฅ host์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค.
89
+ controlProps๋Š” TextField์— ๋ฐ”๋กœ ์—ฐ๊ฒฐ๋˜๋Š” ํ˜•ํƒœ์ด๋ฉฐ Checkbox/Select ๋“ฑ ๋‹ค๋ฅธ ๊ณต๊ฐœ API์™€ ํ˜ธํ™˜๋˜๋Š”์ง€
90
+ ํ™•์ธ ์—†์ด ๊ทธ๋Œ€๋กœ ํŽผ์น˜์ง€ ์•Š๋Š”๋‹ค. renderField์˜ ๋ฐ˜ํ™˜๊ฐ’ ์•ˆ์— ๋…๋ฆฝ ์ œ์ถœ ๋ฒ„ํŠผ์„ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค.
91
+
92
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
93
+
94
+ | ํ•ญ๋ชฉ | Web | Native |
95
+ | --- | --- | --- |
96
+ | ๊ทธ๋ฃน ์˜๋ฏธ | fieldset์˜ ์ฒซ legend | ๊ฐœ๋ณ„ ์ž…๋ ฅ accessibilityLabel์— ๊ทธ๋ฃน ์ด๋ฆ„ ํฌํ•จ, ๋ถ€๋ชจ accessible=false |
97
+ | ํ•„๋“œ ์—ฐ๊ฒฐ | idยทaria-invalidยทaria-describedby | labelยทinvalidยทaccessibilityLabelยทaccessibilityHint |
98
+ | ๊ทธ๋ฃน ์˜ค๋ฅ˜ | role=alert | assertive live region, iOS ๋ณ„๋„ announcement |
99
+ | ํ™”๋ฉด ์Šคํฌ๋กค | ์ œํ’ˆ host | ์ œํ’ˆ ScrollViewยทํ‚ค๋ณด๋“œ ํšŒํ”ผ host |
100
+
101
+ ## ํ•จ์ •
102
+
103
+ - ์‹ค์ œ ์Šคํฌ๋ฆฐ๋ฆฌ๋”ยทNative ๊ธฐ๊ธฐยท์ œํ’ˆ ํŒ”๋ ˆํŠธ ๊ฒ€์ฆ์€ ์•„์ง ๋‚จ์•˜๋‹ค. ์ž๋™ ๊ฒ€์‚ฌ ํ†ต๊ณผ๋ฅผ ์Šน๊ฒฉ ๊ทผ๊ฑฐ๋กœ ๋‹จ๋… ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.
104
+ - ๊ตญ๊ฐ€ยท์ฃผ์†Œยท์—ฐ๋ฝ์ฒ˜์˜ ํ•„๋“œ ์ˆœ์„œ์™€ autocomplete๋Š” ์ œํ’ˆ๋ณ„๋กœ ์ •ํ•œ๋‹ค. ์˜ˆ์ œ์˜ ํ•œ๊ตญ ์ฃผ์†Œ ์ˆœ์„œ๋ฅผ ๊ณตํ†ต ๊ทœ์น™์œผ๋กœ ๋ณต์‚ฌํ•˜์ง€ ์•Š๋Š”๋‹ค.
@@ -0,0 +1,91 @@
1
+ # ๊ทธ๋ฆผ๊ณผ ์‹œ์ž‘ ์•ˆ๋‚ด
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: `showcase/web/src/patterns/illustrated-outcome-preview.tsx`, `showcase/native/src/illustrated-outcome-preview.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/ํ”ผ๋“œ๋ฐฑ๊ณผ ๋ณต๊ตฌ/๊ทธ๋ฆผ๊ณผ ์‹œ์ž‘ ์•ˆ๋‚ด`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ๋นˆ ๋ชฉ๋ก์—์„œ ์‹œ์ž‘์„ ์•ˆ๋‚ดํ•˜๊ณ  ์งง์€ ์˜จ๋ณด๋”ฉ์„ ๊ฑฐ์ณ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ ์ค„ ๋•Œ ์“ด๋‹ค. 3D ๊ทธ๋ฆผ์€ ์ œํ’ˆ ์ž์‚ฐ์ด๋ฉฐ
16
+ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ์˜ ์˜๋ฏธยท์ดˆ์ ยท๋ฒ„ํŠผ์„ ๋Œ€์ฒดํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ณ„๋„ wrapper ์—†์ด ๊ธฐ์กด ๊ณต๊ฐœ API๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
17
+
18
+ ## ๊ตฌ์„ฑ ์š”์†Œ
19
+
20
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
21
+ | --- | --- | --- |
22
+ | EmptyState | ์ฑ„์šธ ์ˆ˜ ์žˆ๋Š” ๋นˆ ๋ชฉ๋ก๊ณผ ์ฒซ ํ–‰๋™ | [๋นˆ ์ƒํƒœ](../components/empty-state.md) |
23
+ | OnboardingScreen | ๋‹จ๊ณ„ยท์ด์ „ยท๋‹ค์Œยท์™„๋ฃŒ ๋ฐฐ์น˜ | [์‹œ์ž‘ ์•ˆ๋‚ด](../screens/flow-onboarding.md) |
24
+ | Result | ํ๋ฆ„์ด ๋๋‚œ ๊ฒฐ๊ณผ์™€ ๋‹ค์Œ ํ–‰๋™ | [๊ฒฐ๊ณผ](../components/result.md) |
25
+ | TextField | ์ œํ’ˆ์ด ์†Œ์œ ํ•˜๋Š” ์ž…๋ ฅ ์ดˆ์•ˆ | [ํ…์ŠคํŠธ ํ•„๋“œ](../components/field.md) |
26
+
27
+ ## ๋ฐฐ์น˜
28
+
29
+ ```text
30
+ [์žฅ์‹ ๊ทธ๋ฆผ]
31
+ [์ œ๋ชฉ + ์„ค๋ช…]
32
+ [๋‹จ๊ณ„ ๋ณธ๋ฌธ: ๊ทธ๋ฆผ ๋˜๋Š” ์ž…๋ ฅ]
33
+ [๋‹ค์Œ / ์™„๋ฃŒ]
34
+ [์ด์ „: ๋‘ ๋ฒˆ์งธ ๋‹จ๊ณ„๋ถ€ํ„ฐ]
35
+ ```
36
+
37
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
38
+ | --- | --- | --- | --- |
39
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ๋ณธ๋ฌธ ์ „์ฒด | gap lg=20px |
40
+ | ๋นˆ ์ƒํƒœ ๊ทธ๋ฆผ | EmptyState ์ œํ’ˆ ์Šฌ๋กฏ | ์ œ๋ชฉ ์œ„ | 120ร—120, Asset xlarge์™€ ๊ฐ™์€ ํฌ๊ธฐ |
41
+ | ๋‹จ๊ณ„ ๋ณธ๋ฌธ | OnboardingScreen | ์ œ๋ชฉ๊ณผ ํ•˜๋‹จ ํ–‰๋™ ์‚ฌ์ด | ๊ทธ๋ฆผ 120ร—120 ๋˜๋Š” TextField |
42
+ | ์™„๋ฃŒ | Result | ํ๋ฆ„ ์ข…๋ฃŒ ์ž๋ฆฌ | ์ฒดํฌ 32ร—32, ๊ณตํ†ต 56px ์•„์ด์ฝ˜ ์˜์—ญ ์•ˆ |
43
+ | ๋ฒ„ํŠผ | ๊ฐ ๊ณต๊ฐœ ์ปดํฌ๋„ŒํŠธ | ํ™”๋ฉด์˜ ๊ธฐ์กด footer ๋˜๋Š” action ์˜์—ญ | ๊ธฐ์กด recipe ์ตœ์†Œ ํ„ฐ์น˜ ํฌ๊ธฐ ์œ ์ง€ |
44
+
45
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
46
+
47
+ 1. EmptyState ํ–‰๋™์œผ๋กœ ์‹œ์ž‘ ์•ˆ๋‚ด๋ฅผ ์—ฐ๋‹ค. ํ™”๋ฉด์˜ ํ˜„์žฌ ๋‹จ๊ณ„๋Š” ์ œํ’ˆ์ด ์ œ์–ดํ•œ๋‹ค.
48
+ 2. ๋‹ค์Œโ†’์ž…๋ ฅโ†’์ด์ „โ†’๋‹ค์Œ์—์„œ๋„ ์ดˆ์•ˆ์€ ์ƒ์œ„ ์ œํ’ˆ ์ƒํƒœ์— ๋‚จ๊ธด๋‹ค.
49
+ 3. ์˜ˆ์ œ๋Š” ์ œ๋ชฉ์ด ๊ณต๋ฐฑ์ด๋ฉด ์™„๋ฃŒ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•œ๋‹ค. ์‹ค์ œ ์•ฑ์˜ ์ €์žฅ ๊ฒฐ๊ณผ๋Š” ์„œ๋ฒ„ ์‘๋‹ต์œผ๋กœ๋งŒ ํ™•์ •ํ•œ๋‹ค.
50
+ 4. ๊ทธ๋ฆผ ๋กœ๋”ฉ ์‹คํŒจ๋Š” ๊ทธ๋ฆผ๋งŒ ์ œ๊ฑฐํ•œ๋‹ค. ์„ค๋ช…ยท์ž…๋ ฅยท๋ฒ„ํŠผ์€ ์œ ์ง€ํ•˜๋ฉฐ ์˜ค๋ฅ˜๋ฅผ ์„ฑ๊ณต์œผ๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
51
+ 5. ์™„๋ฃŒ ํ›„ ๋‹ค์‹œ ์‹œ์ž‘ํ•ด๋„ ์ดˆ์•ˆ์„ ์œ ์ง€ํ•œ๋‹ค. ์ดˆ์•ˆ ์‚ญ์ œ๋Š” ์ œํ’ˆ์˜ ๋ช…์‹œ์ ์ธ ์ •์ฑ…์ด ์žˆ์„ ๋•Œ๋งŒ ํ•œ๋‹ค.
52
+
53
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
54
+ | --- | --- | --- |
55
+ | ๊ธฐ๋ณธ | ๋นˆ ์ƒํƒœ ์ œ๋ชฉยท์žฅ์‹ ๊ทธ๋ฆผยท์ฒซ ํ–‰๋™ | ์ œ๋ชฉ์œผ๋กœ ์˜๋ฏธ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๊ทธ๋ฆผ์€ ์žฅ์‹์œผ๋กœ ์ˆจ๊น€ |
56
+ | ์ง„ํ–‰ ์ค‘ | ๋‹จ๊ณ„ ์•ˆ๋‚ดยท์ž…๋ ฅยท์ด์ „/๋‹ค์Œ ํ–‰๋™ | ์ดˆ์•ˆ์„ ์œ ์ง€ํ•˜๋ฉฐ ์‹ค์ œ ์ €์žฅ ์ค‘์—๋Š” ์ œํ’ˆ pending ์—ฐ๊ฒฐ |
57
+ | ์‹คํŒจ | ๊ทธ๋ฆผ ์‹คํŒจ ์‹œ ๋ณธ๋ฌธยทํ–‰๋™ ์œ ์ง€ | ์žฅ์‹ ์‹คํŒจ๋ฅผ ์ €์žฅ ์‹คํŒจ์™€ ํ˜ผ๋™ํ•˜์ง€ ์•Š์Œ |
58
+ | ์™„๋ฃŒ | ๊ฒฐ๊ณผ ์ œ๋ชฉยท์ž…๋ ฅ ๋‚ด์šฉยท๋‹ค์‹œ ์‹œ์ž‘ | Result์˜ ๊ธฐ๋ณธ status ์˜๋ฏธ ์œ ์ง€ |
59
+
60
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
61
+
62
+ ```tsx
63
+ // Web
64
+ import { EmptyState, Result } from "@hjmds/react/feedback";
65
+ import { OnboardingScreen } from "@hjmds/react/screen-flows";
66
+ <EmptyState title={title} description={description} icon={decorativeArtwork} action={startButton} />
67
+ <Result status="success" title={completedTitle} icon={decorativeCheck} actions={actions} />
68
+ ```
69
+
70
+ ```tsx
71
+ // Native
72
+ import { EmptyState, Result } from "@hjmds/react-native/feedback";
73
+ import { OnboardingScreen } from "@hjmds/react-native/screen-flows";
74
+ <EmptyState title={title} description={description} illustration={decorativeArtwork} action={startButton} />
75
+ <Result status="success" title={completedTitle} renderIcon={() => decorativeCheck} actions={actions} />
76
+ ```
77
+
78
+ OnboardingScreen์—๋Š” `steps`, `index`, `onIndexChange`, `nextLabel`, `backLabel`, `complete`,
79
+ `progressLabel`์„ ์ „๋‹ฌํ•œ๋‹ค. ๋‹จ๊ณ„ ๊ตฌ์กฐยท๋ฒ„ํŠผ ์œ„์น˜๋Š” ๊ธฐ์กด ํ™”๋ฉด ์ง€์นจ์„ ๋”ฐ๋ฅธ๋‹ค. ์ œํ’ˆ ์ฝ”๋“œ์—์„œ
80
+ Showcase๋ฅผ importํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ฆผ ํ˜ธ์ŠคํŠธ๋Š” Web img์˜ ๋นˆ alt, Native Image์˜ accessible=false๋กœ ์žฅ์‹์ž„์„ ๋ช…์‹œํ•œ๋‹ค.
81
+
82
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
83
+
84
+ | ํ•ญ๋ชฉ | Web | Native |
85
+ | --- | --- | --- |
86
+ | ํŒŒ์ผ | ๋กœ์ปฌ ํŒŒ์ผ URL | Metro ์ •์  require |
87
+ | ๊ทธ๋ฆผ ์ ‘๊ทผ์„ฑ | ๋นˆ alt | accessible=false |
88
+ | ์žฅ์‹ ์Šฌ๋กฏ | EmptyState.icon / Result.icon | EmptyState.illustration / Result.renderIcon |
89
+ | ์‹คํŒจ | ๊ทธ๋ฆผ๋งŒ ์ˆจ๊น€ | ๊ทธ๋ฆผ๋งŒ ์ˆจ๊น€ |
90
+
91
+ ์‹ค์ œ ๊ธฐ๊ธฐ์˜ ์ž์‚ฐ ๋””์ฝ”๋”ฉยทํฐ ๊ธ€์žยท์ ‘๊ทผ์„ฑ ๊ฒ€์ฆ์€ ํƒ€์ž… ๊ฒ€์‚ฌ์™€ ๋ณ„๋„๋‹ค.
@@ -0,0 +1,104 @@
1
+ # ์ถ”๊ฐ€ํ•ด๋„ ์œ ์ง€๋˜๋Š” ๋ชฉ๋ก
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: `showcase/web/src/patterns/live-list-preview.tsx`, `showcase/native/src/live-list-preview.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/์ •๋ณด ํ‘œ์‹œ/์ถ”๊ฐ€ํ•ด๋„ ์œ ์ง€๋˜๋Š” ๋ชฉ๋ก`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ์ž…๋ ฅ ์ค‘์ธ ๋ชฉ๋ก์— ์ƒˆ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์ˆœ์„œ๊ฐ€ ๋ฐ”๋€Œ์–ด๋„ ๊ธฐ์กด ์ดˆ์•ˆ๊ณผ ํ•ญ๋ชฉ์˜ ์ •์ฒด์„ฑ์„ ์œ ์ง€ํ•  ๋•Œ ์“ด๋‹ค.
16
+ Magic UI Animated List์˜ ์†Œ๊ฐœ ํŽ˜์ด์ง€ ์ˆœ์ฐจ ๋…ธ์ถœ์—์„œ ์ƒˆ ํ•ญ๋ชฉ์˜ ๋“ฑ์žฅ ํ‘œํ˜„๋งŒ ํก์ˆ˜ํ•œ๋‹ค. ์ด๋ฏธ ๋„์ฐฉํ•œ
17
+ ๋ฐ์ดํ„ฐ๋ฅผ ํƒ€์ด๋จธ๋กœ ๋Šฆ์ถ”์ง€ ์•Š๊ณ , ์‹ค์ œ ์•Œ๋ฆผ ์ˆ˜์‹ ๊ณผ ๋ฐ๋ชจ ์žฌ์ƒ์„ ํ˜ผ๋™ํ•˜์ง€ ์•Š๋Š”๋‹ค.
18
+
19
+ ## ๊ตฌ์„ฑ ์š”์†Œ
20
+
21
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
22
+ | --- | --- | --- |
23
+ | List ยท ListRow | stable key ํ•ญ๋ชฉ์˜ ๋ชฉ๋ก ์˜๋ฏธยท์ œ๋ชฉยท๊ตฌ๋ถ„์„  | [๋ชฉ๋ก](../components/list.md), [ํ–‰](../components/list-row.md) |
24
+ | ContentTransition | ์ƒˆ ํ•ญ๋ชฉ ๋“ฑ์žฅ๊ณผ ๋ฐ”๊นฅ ๋†’์ด ์ „ํ™˜ | [๋‚ด์šฉ ์ „ํ™˜](../components/content-transition.md) |
25
+ | TextField | ์ œํ’ˆ์ด ํ•ญ๋ชฉ id๋ณ„๋กœ ๋ณด์กดํ•˜๋Š” ๋ฉ”๋ชจ | [ํ•„๋“œ](../components/field.md) |
26
+ | Button ยท Text | ๋ช…์‹œ์  ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ๊ณผ ๋ณ€๊ฒฝ ๊ฒฐ๊ณผ | [๋ฒ„ํŠผ](../components/button.md), [๊ธ€์ž](../components/text.md) |
27
+
28
+ ## ๋ฐฐ์น˜
29
+
30
+ ```text
31
+ [์•ˆ๋‚ด]
32
+ [๊ธฐ๋ก ์ถ”๊ฐ€] [3๊ฐœ ํ•จ๊ป˜ ์ถ”๊ฐ€]
33
+ [์ˆœ์„œ ๋’ค์ง‘๊ธฐ] [์ฒซ ๊ธฐ๋ก ์‚ญ์ œ]
34
+ [์›€์ง์ž„ ๋ฉˆ์ถ”๊ธฐ]
35
+ [๋ณ€๊ฒฝ ๊ฒฐ๊ณผ]
36
+ โ”Œ ๊ธฐ๋ก ๋ชฉ๋ก โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
37
+ โ”‚ ์ œ๋ชฉยท์„ค๋ช… โ”‚
38
+ โ”‚ ๋ฉ”๋ชจ ์ž…๋ ฅ โ”‚
39
+ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
40
+ โ”‚ ๋‹ค์Œ ๊ธฐ๋กยท๋ฉ”๋ชจ โ”‚
41
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
42
+ ```
43
+
44
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
45
+ | --- | --- | --- | --- |
46
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ์œ„์—์„œ ์•„๋ž˜ | gap lg=20px |
47
+ | ๋ณ€๊ฒฝ ํ–‰๋™ | inline Stack ยท Button | ๋ชฉ๋ก ์œ„, ์ข์œผ๋ฉด ๊ฐ๊น€ | ๊ธฐ๋ณธ Stack/Button recipe |
48
+ | ๋ฐ์ดํ„ฐ | List | ๋ณ€๊ฒฝ ๊ฒฐ๊ณผ ์•„๋ž˜ | separator full, ๊ณตํ†ต ListRow/TextField ํฌ๊ธฐ |
49
+ | ๋“ฑ์žฅ | ContentTransition | ๊ฐ stable id ํ•ญ๋ชฉ | rise=12px, motion.normal, easing.enter |
50
+ | ๋†’์ด | ContentTransition animateHeight | ๋ชฉ๋ก ๋ฐ”๊นฅ | ๊ฐ™์€ stateKey๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ ์‹ค์ œ ์ธก์ • ๋†’์ด๋งŒ ์ „ํ™˜ |
51
+
52
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
53
+
54
+ 1. ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ”๋กœ ํ‘œ์‹œํ•˜๊ณ  ์ตœ์ดˆ ๋“ฑ์žฅ ๋ชจ์…˜์€ ๋ˆ๋‹ค.
55
+ 2. ์ถ”๊ฐ€๋œ ํ•ญ๋ชฉ์€ ๊ณ ์œ  id๋ฅผ ๋ถ€์—ฌํ•ด ์ฆ‰์‹œ ๋ฐฐ์—ด์— ๋ฐ˜์˜ํ•œ๋‹ค. ํ•ด๋‹น ํ•ญ๋ชฉ๋งŒ enterOnMount๋ฅผ ์ผ ๋‹ค.
56
+ 3. ๊ธฐ์กด id์˜ React key์™€ stateKey๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ์ „์ฒด ๋ชฉ๋ก stateKey๋ฅผ ํ•ญ๋ชฉ ๊ฐœ์ˆ˜๋กœ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
57
+ 4. ์ˆœ์„œ ๋ณ€๊ฒฝ์€ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด ์ˆœ์„œ๋งŒ ๋ฐ”๊พธ๋ฉฐ ์ดˆ์•ˆ์€ id์— ์—ฐ๊ฒฐํ•œ๋‹ค. ์ˆœ์„œ ๋ณ€๊ฒฝ ์ž์ฒด์˜ ์ด๋™ ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค.
58
+ 5. ์‚ญ์ œ๋Š” ์ฆ‰์‹œ ๋ฐ˜์˜ํ•œ๋‹ค. ์‚ญ์ œ๋œ ์ž…๋ ฅยท๋ฒ„ํŠผ์„ ๋‹ด์€ ํ‡ด์žฅ ์‚ฌ๋ณธ์„ ๋‚จ๊ธฐ์ง€ ์•Š๋Š”๋‹ค.
59
+ 6. ์›€์ง์ž„ ๋ฉˆ์ถ”๊ธฐ๋Š” ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ๋ฉˆ์ถ”์ง€ ์•Š๋Š”๋‹ค. ๋‹ค์‹œ ์ผœ๋„ ๊ธฐ์กด ํ•ญ๋ชฉ์€ ์žฌ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
60
+
61
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
62
+ | --- | --- | --- |
63
+ | ๊ธฐ๋ณธ | ๊ธฐ์กด ๋ชฉ๋ก๊ณผ ์ž…๋ ฅ | ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ฆ‰์‹œ ํ‘œ์‹œ |
64
+ | ์ง„ํ–‰ ์ค‘ | ์ƒˆ ํ–‰๋งŒ ์งง๊ฒŒ ๋“ฑ์žฅ | ๊ธฐ์กด ์ž…๋ ฅ ๋…ธ๋“œ์™€ ์ดˆ์•ˆ ์œ ์ง€ |
65
+ | ์‹คํŒจ | ์„œ๋ฒ„ ์‹คํŒจ๊ฐ€ ์žˆ๋Š” ์ œํ’ˆ์€ ๊ธฐ์กด ๋ชฉ๋กยท์ดˆ์•ˆ์„ ๋ณด์กดํ•˜๊ณ  ์˜ค๋ฅ˜๋ฅผ ๋ณ„๋„ ํ‘œ์‹œ | ์ด ๋กœ์ปฌ ์‹คํ—˜์—๋Š” ๋„คํŠธ์›Œํฌ ์š”์ฒญ ์—†์Œ |
66
+ | ์‚ญ์ œ | ํ•ด๋‹น ํ–‰ ์ฆ‰์‹œ ์ œ๊ฑฐ | ์‚ญ์ œ ํ–‰๋™์€ ๋ชฉ๋ก ๋ฐ–์— ๋‘์–ด ์ดˆ์  ์œ ์‹ค ๋ฐฉ์ง€ |
67
+ | ๋น„์–ด ์žˆ์Œ | ๊ธฐ๋ก ์—†์Œ๊ณผ ์ถ”๊ฐ€ ์•ˆ๋‚ด | ์ถ”๊ฐ€ ๋ฒ„ํŠผ ์œ ์ง€ |
68
+ | ๋ชจ์…˜ ๊ฐ์†Œยท์ •์ง€ | ๊ฐ™์€ ๋ฐ์ดํ„ฐ, ๊ณต๊ฐ„ ์ „ํ™˜ ์—†์Œ | ์˜๋ฏธยท์ž…๋ ฅ ์ˆœ์„œ ๋™์ผ |
69
+
70
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
71
+
72
+ ```tsx
73
+ // Web
74
+ import { ContentTransition } from "@hjmds/react/content-transition";
75
+ <ContentTransition stateKey="records" animateHeight motion={paused ? 'none' : 'system'}>
76
+ <List label={listLabel}>
77
+ {items.map(item => <ContentTransition key={item.id} stateKey={item.id}
78
+ enterOnMount={item.justAdded} preset="rise" motion={paused ? 'none' : 'system'}>
79
+ <RecordEditor item={item} onChange={changeById} />
80
+ </ContentTransition>)}
81
+ </List>
82
+ </ContentTransition>
83
+ ```
84
+
85
+ ```tsx
86
+ // Native
87
+ import { ContentTransition } from "@hjmds/react-native/content-transition";
88
+
89
+ <ContentTransition stateKey={item.id} enterOnMount={item.justAdded} preset="rise">
90
+ <RecordEditor item={item} onChange={changeById} />
91
+ </ContentTransition>
92
+ ```
93
+
94
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
95
+
96
+ | ํ•ญ๋ชฉ | Web | Native |
97
+ | --- | --- | --- |
98
+ | ๋“ฑ์žฅ | ๊ธฐ์กด framer-motion optional peer | ๊ธฐ์กด RN Animated native driver |
99
+ | ๋†’์ด | ResizeObserver + WAAPI | onLayout + JS driver |
100
+ | ๋ณ€๊ฒฝ ์•Œ๋ฆผ | status | accessibilityLiveRegion polite |
101
+ | ์ ‘๊ทผ์„ฑ | ๊ธฐ์กด ์ž…๋ ฅ ์ดˆ์ ยทlist ์˜๋ฏธ | ๊ธฐ์กด ์ž…๋ ฅ๊ณผ Native list ์˜๋ฏธ |
102
+
103
+ ๋ฐ์ดํ„ฐยท์ค‘๋ณต ์ œ๊ฑฐยท์ •๋ ฌยท์„œ๋ฒ„ ํ™•์ •์€ ์ œํ’ˆ ์†Œ์œ ๋‹ค. ๊ธด ๋ชฉ๋ก์€ VirtualList ์„ ํƒ์„ ๋จผ์ € ๊ฒ€ํ† ํ•˜๊ณ 
104
+ ์žฌํ™œ์šฉ mount๋ฅผ ์ƒˆ ๋ฐ์ดํ„ฐ๋กœ ์˜ค์ธํ•ด enterOnMount๋ฅผ ์ผœ์ง€ ์•Š๋Š”๋‹ค. ํฐ ๊ธ€์žยท์ œํ’ˆ ํŒ”๋ ˆํŠธยท์„ฑ๋Šฅ์€ ์Šน๊ฒฉ ์ „ ๊ฒ€์ฆํ•œ๋‹ค.
@@ -165,5 +165,5 @@ const gutter = resolveWindowClass(useWindowDimensions().width) === "compact" ? "
165
165
  - KeyboardDock์€ window ๊ธฐ์ค€ ์ขŒํ‘œ๋ฅผ ์“ด๋‹ค. Storybook ์บ”๋ฒ„์Šค์ฒ˜๋Ÿผ ์•„๋ž˜์— ๋‹ค๋ฅธ ์˜์—ญ์ด ๋‚จ๋Š” host์—์„œ๋Š” ์œ„์น˜๊ฐ€ ์–ด๊ธ‹๋‚˜ ์Šคํ† ๋ฆฌ๊ฐ€ ์ „์ฒด ํ™”๋ฉด `Modal`๋กœ ๋„์šด๋‹ค. ์ œํ’ˆ๋„ ์•ฑ ํฌ๊ธฐ host์—์„œ ์“ด๋‹ค.
166
166
  - NativeContextMenu ํŒจ์น˜(Zeego 3.0.6 ๊ด€๋ จ)๋Š” HJM ์„ค์น˜๋กœ ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค. `@hjmds/react-native/docs/patches/`๋ฅผ ์ œํ’ˆ์— ๋ณต์‚ฌํ•ด ๋“ฑ๋กํ•œ๋‹ค.
167
167
  - ํ•„์š”ํ•œ optional peer(`react-native-zoom-toolkit` 5.1.1, `react-native-keyboard-controller` 1.22.5, `@gorhom/bottom-sheet` 5.2.14, `zeego` 3.0.6 ๋“ฑ)๊ฐ€ ์—†์œผ๋ฉด ๊ธฐ๊ธฐ Metro ๋ฒˆ๋“ค์ด ์‹คํŒจํ•œ๋‹ค.
168
- - ํ˜„์žฌ `ImageViewer`๋Š” ๋กœ๋”ฉ ๋ฌธ๊ตฌ๋งŒ live region์ด๊ณ  ์‹คํŒจ ๋ฌธ๊ตฌ(`errorLabel`)๋Š” ์•Œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ์‹คํŒจ๋ฅผ ๋‚ญ๋…ํ•ด์•ผ ํ•˜๋ฉด ์ œํ’ˆ์ด ๋”ฐ๋กœ ์•Œ๋ฆฐ๋‹ค(renderer ์ˆ˜์ • ํ›„๋ณด).
168
+ - `ImageViewer` ์‹คํŒจ๋Š” Android assertive live region๊ณผ iOS announceForAccessibility๋กœ ์•Œ๋ฆฐ๋‹ค. ์ œํ’ˆ์ด ๊ฐ™์€ ์˜ค๋ฅ˜๋ฅผ ๋‹ค์‹œ ๋‚ญ๋…ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ œํ’ˆ ์ด๋ฏธ์ง€ host์™€ ์ƒํƒœ ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ์€ [Image ์‚ฌ์šฉ ์ง€์นจ](../components/image.md)์˜ Native ImageViewer ์ ˆ์„ ๋”ฐ๋ฅธ๋‹ค.
169
169
  - 2026-10-06 ์˜ˆ์ œ ๊ฒ€์ˆ˜์—์„œ ๋จธ๋ฆฌยท๋ณธ๋ฌธยทํ•˜๋‹จ์˜ ์ขŒ์šฐ ์—ฌ๋ฐฑ์„ Container compact๋กœ ๋งž์ถ”๊ณ  ๋ณธ๋ฌธ ๊ฐ„๊ฒฉ์€ Stack md๋กœ ์˜ฎ๊ฒผ๋‹ค. ์•ˆ์ „ ์˜์—ญยทํ‚ค๋ณด๋“œ ์ขŒํ‘œ๋Š” ๋ฐ”๊นฅ host๊ฐ€ ์œ ์ง€ํ•œ๋‹ค. ๋ฉ”๋‰ด ๊ฒฐ๊ณผ๋Š” ์ƒํƒœ ๋ฌธ๊ตฌ์™€ iOS/Android ์•Œ๋ฆผ์„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•œ๋‹ค.
@@ -0,0 +1,108 @@
1
+ # ๋ฒ„ํŠผ์—์„œ ์ด์–ด์ง€๋Š” ํŽธ์ง‘
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: `showcase/web/src/patterns/origin-dialog-preview.tsx`, `showcase/native/src/origin-dialog-preview.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/์ž…๋ ฅ๊ณผ ์ž‘์„ฑ/๋ฒ„ํŠผ์—์„œ ์ด์–ด์ง€๋Š” ํŽธ์ง‘`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ํ˜„์žฌ ํ™”๋ฉด์˜ ํ•ญ๋ชฉ์„ ์งง๊ฒŒ ํŽธ์ง‘ํ•˜๊ณ  ๋Œ์•„์˜ฌ ๋•Œ ์ถœ๋ฐœ ์œ„์น˜๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค. ๋ณ„๋„ ์ƒํƒœ
16
+ ์—”์ง„ ์—†์ด Dialog์˜ ์„ ํƒ์  motionOrigin๊ณผ ์ œํ’ˆ ์†Œ์œ  ์ดˆ์•ˆ์„ ์‚ฌ์šฉํ•œ๋‹ค. ์›๋ณธ์˜ ๋‹ซ๊ธฐ ํ›„
17
+ ์ดˆ์ ยท์ดˆ์•ˆ ์†์‹ค์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ธฐ ์œ„ํ•œ ๊ตฌ์„ฑ์ด๋‹ค. ๊ธด ํŽธ์ง‘์€ ์ „์šฉ ํ™”๋ฉด์„ ์‚ฌ์šฉํ•œ๋‹ค.
18
+
19
+ ## ๊ตฌ์„ฑ ์š”์†Œ
20
+
21
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
22
+ | --- | --- | --- |
23
+ | Button | ์ธก์ •ํ•  ์—ด๊ธฐ ๋ฒ„ํŠผ๊ณผ ์ €์žฅ | [๋ฒ„ํŠผ](../components/button.md) |
24
+ | Popover | Web์—์„œ ํŽ˜์ด์ง€๋ฅผ ๊ณ„์† ๋ณผ ์ˆ˜ ์žˆ๋Š” ๋น„๋ชจ๋‹ฌ ํŽธ์ง‘ ๋ณ€ํ˜• | [ํŒ์˜ค๋ฒ„](../components/popover.md) |
25
+ | Dialog | ๊ธฐ์กด ๋ชจ๋‹ฌ ์ƒํƒœยท๋‹ซ๊ธฐยท์ดˆ์  ๋ณต๊ท€์™€ ์„ ํƒ์ ์ธ ๊ณต๊ฐ„ ์ „ํ™˜ | [๋Œ€ํ™”์ƒ์ž](../components/dialog.md) |
26
+ | TextField | ์ œํ’ˆ ์ƒํƒœ์— ๋ณด์กดํ•˜๋Š” ์ดˆ์•ˆ | [ํ•„๋“œ](../components/field.md) |
27
+ | Text | ํ™•์ •ํ•œ ๊ฒฐ๊ณผ ํ‘œ์‹œ | [ํ…์ŠคํŠธ](../components/text.md) |
28
+
29
+ ## ๋ฐฐ์น˜
30
+
31
+ ```text
32
+ [์•ˆ๋‚ด]
33
+ [๋ฉ”๋ชจ ํŽธ์ง‘] โ”€โ”€โ†’ [์—ฌํ–‰ ๋ฉ”๋ชจ ๋‹ซ๊ธฐ]
34
+ [์ €์žฅ ๊ฒฐ๊ณผ] [๋ฉ”๋ชจ ์ž…๋ ฅ]
35
+ [์ €์žฅ]
36
+ ```
37
+
38
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
39
+ | --- | --- | --- | --- |
40
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ์›๋ž˜ ํ™”๋ฉด | gap lg=20px |
41
+ | ์ง„ํ–‰ ์ค‘ | Dialog | ๊ธฐ๋ณธ overlay ์ค‘์•™ | ๊ธฐ๋ณธ Dialog recipe |
42
+ | ์ž…๋ ฅ | TextField | Dialog ๋ณธ๋ฌธ | ๊ณตํ†ต Field ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
43
+ | ํ™•์ • | Button | Web footer / Native primaryAction | ๊ณตํ†ต Dialog ํ–‰๋™ ์˜์—ญ |
44
+
45
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
46
+
47
+ 1. ์—ด๊ธฐ ์ง์ „์— ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์‹ค์ œ viewport/window ์ขŒํ‘œ๋กœ ์ธก์ •ํ•œ๋‹ค. ์ธก์ • ๋ถˆ๊ฐ€ ์‹œ ์ผ๋ฐ˜ Dialog๋ฅผ ์—ฐ๋‹ค.
48
+ 2. ์ œํ’ˆ์ด openยทdraftยทsaved๋ฅผ ๊ฐ๊ฐ ์†Œ์œ ํ•œ๋‹ค. ์ €์žฅ ์ „ ๋‹ซ๊ธฐ๋Š” draft๋ฅผ ์ง€์šฐ์ง€ ์•Š๋Š”๋‹ค.
49
+ 3. ์ €์žฅ ์‹œ draft๋ฅผ ๊ฒฐ๊ณผ๋กœ ํ™•์ •ํ•˜๊ณ  ๋‹ซ๋Š”๋‹ค. ์‹คํŒจ ์ฒดํ—˜์„ ์˜ˆ์•ฝํ•˜๋ฉด ์ฒซ ์ €์žฅ๋งŒ ์‹คํŒจํ•˜๊ณ 
50
+ ์˜ค๋ฅ˜ยท์ดˆ์•ˆ์„ ์œ ์ง€ํ•œ๋‹ค. ๋‹ค์‹œ ์ €์žฅ์€ ์„ฑ๊ณตํ•œ๋‹ค. ์‹ค์ œ ์„œ๋ฒ„๊ฐ€ ์žˆ์œผ๋ฉด busy์™€ ํ™•์ • ์‘๋‹ต์„ ์—ฐ๊ฒฐํ•œ๋‹ค.
51
+ 4. ๋‹ซ๊ธฐ ์ „ํ™˜์ด ๋๋‚œ ๋’ค ํŠธ๋ฆฌ๊ฑฐ๋กœ ์ดˆ์ ์„ ๋Œ๋ฆฐ๋‹ค. ๋น ๋ฅธ ์žฌ์—ด๊ธฐ๋Š” ์ด์ „ ์ข…๋ฃŒ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์‹œํ•œ๋‹ค.
52
+ 5. ๋ชจ์…˜ ๊ฐ์†Œยท์ž˜๋ชป๋œ ์ธก์ •์—์„œ๋Š” ๊ณต๊ฐ„ ์ „ํ™˜์„ ์ƒ๋žตํ•œ๋‹ค.
53
+
54
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
55
+ | --- | --- | --- |
56
+ | ๊ธฐ๋ณธ | ์—ด๊ธฐ ๋ฒ„ํŠผ๊ณผ ๋งˆ์ง€๋ง‰ ์ €์žฅ ๊ฒฐ๊ณผ | draft ์œ ์ง€ |
57
+ | ์ง„ํ–‰ ์ค‘ | ์ œ๋ชฉยท๋‹ซ๊ธฐยท์ž…๋ ฅยท์ €์žฅ | ์ œํ’ˆ ์ƒํƒœ๋กœ ๊ฐฑ์‹  |
58
+ | ๊ฒฐ๊ณผ | ๊ฒฐ๊ณผ ๊ฐฑ์‹  ํ›„ ๋‹ซ๊ธฐ | ํŠธ๋ฆฌ๊ฑฐ๋กœ ๋ณต๊ท€ |
59
+ | ์‹คํŒจ | ์˜ค๋ฅ˜ ์„ค๋ช…ยท์ž…๋ ฅยท๋‹ค์‹œ ์ €์žฅ | ์˜ค๋ฅ˜ ์•Œ๋ฆผ, ์ดˆ์•ˆ ์œ ์ง€ |
60
+ | ์ธก์ • ์—†์Œ | ์ผ๋ฐ˜ Dialog | ๊ฐ™์Œ |
61
+
62
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
63
+
64
+ ```tsx
65
+ // Web: origin์€ trigger.getBoundingClientRect()์˜ ์ธก์ •๊ฐ’
66
+ <Dialog open={open} onOpenChange={setOpen} title={title} closeLabel={closeLabel}
67
+ motionOrigin={origin} returnFocusRef={triggerRef} footer={saveButton}>
68
+ <TextField label={label} value={draft} onValueChange={setDraft} />
69
+ </Dialog>
70
+ ```
71
+
72
+ ```tsx
73
+ // Native: origin์€ trigger.measureInWindow ์ฝœ๋ฐฑ์˜ ์ธก์ •๊ฐ’
74
+ <Dialog open={open} onOpenChange={setOpen} title={title} closeLabel={closeLabel}
75
+ motionOrigin={origin} returnFocusRef={triggerRef} primaryAction={saveAction}>
76
+ <TextField label={label} value={draft} onValueChange={setDraft} />
77
+ </Dialog>
78
+ ```
79
+
80
+ ๋ฌธ๊ตฌยท์ดˆ์•ˆยท์ €์žฅ ๋กœ์งยทํŠธ๋ฆฌ๊ฑฐ ์ธก์ •์€ ์ œํ’ˆ ์†Œ์œ ๋‹ค. Showcase๋ฅผ ์•ฑ์—์„œ importํ•˜์ง€ ์•Š๋Š”๋‹ค.
81
+
82
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
83
+
84
+ | ํ•ญ๋ชฉ | Web | Native |
85
+ | --- | --- | --- |
86
+ | ์›์  | getBoundingClientRect | measureInWindow |
87
+ | ์ „ํ™˜ | WAAPI, ์ข…๋ฃŒ๊นŒ์ง€ ํ•œ subtree ์œ ์ง€ | ์‹ค์ œ modal ์ธก์ • + ๊ธฐ์กด Animated progress |
88
+ | ๋ชจ์…˜ ๊ฐ์†Œ | ๊ณต๊ฐ„ ์ „ํ™˜ ์—†์Œ | ๊ณต๊ฐ„ ์ „ํ™˜ ์—†์Œ, ๊ธฐ์กด opacity |
89
+ | ๋‹ซ๊ธฐ | Escapeยท๋ฐ”๊นฅยท๋‹ซ๊ธฐ | Backยท๋ฐ”๊นฅยท๋‹ซ๊ธฐ |
90
+
91
+ ํ‚ค๋ณด๋“œยทํšŒ์ „ยท์Šคํฌ๋กค์— ์˜ํ•œ ๋ชฉ์ ์ง€ ๋ณ€ํ™”, ์‹ค์ œ ๊ธฐ๊ธฐ์™€ ์ œํ’ˆ ํ…Œ๋งˆ ๋น„๊ต๋Š” ์Šน๊ฒฉ ์ „ ๊ฒ€์ฆํ•œ๋‹ค.
92
+
93
+
94
+ ### Web์˜ ํŽ˜์ด์ง€ ์•ˆ์—์„œ ํŽธ์ง‘ ๋ณ€ํ˜•
95
+
96
+ `Contextual`ยท`ContextualDark`ยท`ContextualLargeText`๋Š” ๊ธฐ์กด Popover์˜ ์„ ํƒ์ ์ธ `motionOrigin`์„
97
+ ์‚ฌ์šฉํ•œ๋‹ค. ๊ธฐ๋ณธ 360px ํ‘œ๋ฉด์ด ๋ฒ„ํŠผ ์•„๋ž˜ 8px ๊ฐ„๊ฒฉ์œผ๋กœ ์—ด๋ฆฌ๊ณ  viewport ์ถฉ๋Œ์— ๋”ฐ๋ผ ๋ฐฐ์น˜๊ฐ€ ๋ฐ”๋€๋‹ค.
98
+ ๋ณธ๋ฌธ์€ Stack gap md=16px ์•ˆ์— ์˜ค๋ฅ˜ โ†’ TextField โ†’ ์ €์žฅ ์ˆœ์„œ๋‹ค. Native๋Š” ๋ชจ๋‹ฌ ํŽธ์ง‘ ๋ณ€ํ˜•์„ ์“ด๋‹ค.
99
+
100
+ ```tsx
101
+ <Popover title={title} closeLabel={closeLabel} motionOrigin={origin}
102
+ trigger={<Button ref={triggerRef} onClick={() => setOrigin(triggerRef.current?.getBoundingClientRect())}>{openLabel}</Button>}>
103
+ {({ close }) => <Editor draft={draft} onDraftChange={setDraft} onSave={() => save(draft).then(close)} />}
104
+ </Popover>
105
+ ```
106
+
107
+ ๋น„๋ชจ๋‹ฌ์—์„œ๋Š” Escape/๋‹ซ๊ธฐ ์งํ›„ ๊ธฐ์กด ๊ทœ์น™๋Œ€๋กœ ์ดˆ์ ์„ ๋ณต๊ท€ํ•œ๋‹ค. ๋ฐ”๊นฅ ํด๋ฆญ์ด๋‚˜ Tab์œผ๋กœ ์ด๋™ํ•œ
108
+ ์ดˆ์ ์€ ๋˜๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค. ์ €์žฅ ์‹คํŒจ๋Š” ์ดˆ์•ˆยท์—ด๋ฆผ์„ ์œ ์ง€ํ•˜๊ณ , ๋‹ซ์•˜๋‹ค ๋‹ค์‹œ ์—ด์–ด๋„ ์ œํ’ˆ draft๋Š” ๋‚จ๋Š”๋‹ค.
@@ -0,0 +1,73 @@
1
+ # ์„ ํƒ ๋ฐฐ๊ฒฝ ์ด๋™
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: ๊ณต๊ฐœ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” `showcase/*/reference-adoption-previews.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/์ง์ ‘ ์กฐ์ž‘๊ณผ ๋ชจ์…˜/์„ ํƒ ๋ฐฐ๊ฒฝ ์ด๋™`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ์งง์€ ๋‹จ์ผ ์„ ํƒ์˜ ํ˜„์žฌ ํ•ญ๋ชฉ์„ ์ด์–ด์ง€๋Š” ๋ฐฐ๊ฒฝ์œผ๋กœ ๋ณด์—ฌ ์ค„ ๋•Œ ์“ด๋‹ค. ์„ ํƒ ๊ฐ’ยทํ‚ค๋ณด๋“œยท์ดˆ์ ์€ ๊ธฐ์กด SegmentedControl์ด ์†Œ์œ ํ•œ๋‹ค. ๋ณ„๋„ ์‹ ๊ทœ wrapper API๊ฐ€ ์•„๋‹ˆ๋ผ ์•„๋ž˜ ๊ณต๊ฐœ ์ปดํฌ๋„ŒํŠธ์˜ ์กฐํ•ฉ ๊ทœ๊ฒฉ์ด๋‹ค.
16
+
17
+ ## ๊ตฌ์„ฑ ์š”์†Œ
18
+
19
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
20
+ | --- | --- | --- |
21
+ | SegmentedControl ยท Text ยท TextField | ์ž…๋ ฅยทํ‘œํ˜„ยทํ–‰๋™์˜ ์—ญํ•  ๋ถ„๋ฆฌ | [์ž…๋ ฅ/๋™์ž‘](../components/segmented-control.md), [์ƒํƒœ/์ „ํ™˜](../components/content-transition.md) |
22
+
23
+ ## ๋ฐฐ์น˜
24
+
25
+ ```text
26
+ [์ œ๋ชฉ ๋˜๋Š” ํ˜„์žฌ ์ž…๋ ฅ]
27
+ [์ƒํƒœ/๋‚ด์šฉ ์˜์—ญ]
28
+ [๊ด€๋ จ ํ–‰๋™๊ณผ ๊ฒฐ๊ณผ]
29
+ ```
30
+
31
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
32
+ | --- | --- | --- | --- |
33
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ํ™”๋ฉด์˜ ๋ณธ๋ฌธ ํ๋ฆ„ | gap lg=20px, ์ „์ฒด ํญ |
34
+ | ์„ ํƒ | SegmentedControl 2๊ฐœ | ์ œ๋ชฉ ์•„๋ž˜, ์—ฐ๊ฒฐํ˜• ๋‹ค์Œ ํ•„ํ„ฐํ˜• | ๊ธฐ๋ณธ 44px ํ„ฐ์น˜ ์˜์—ญ, ์‹ค์ œ ๊ธ€์ž์— ๋”ฐ๋ฅธ ๋†’์ด |
35
+ | ๊ฒฐ๊ณผยท๋ฉ”๋ชจ | Text ยท TextField | ์„ ํƒ ์ปจํŠธ๋กค ์•„๋ž˜ | ์„ ํƒ ๋ณ€๊ฒฝ ์‹œ ํ•„๋“œ ์žฌ์ƒ์„ฑ ์—†์Œ |
36
+ | Native ํ™”๋ฉด host | ScreenLayout ยท KeyboardAvoiding | ๋ณธ๋ฌธ ์Šคํฌ๋กค๊ณผ ๋ฐ”๊นฅ ํ‚ค๋ณด๋“œ ์—ฌ๋ฐฑ | flex 1, ๋ ์—ฌ๋ฐฑ spacing.md=16 |
37
+
38
+ 2026-10-07 iOS 2๋ฐฐ ๊ธ€์ž์—์„œ Stack๋งŒ ์‚ฌ์šฉํ•œ ์˜ˆ์ œ์˜ ๋ฉ”๋ชจ๊ฐ€ ํ‚ค๋ณด๋“œ ๋’ค์— ๊ฐ€๋ ค์กŒ๋‹ค.
39
+ ๊ธ€์ž๋‚˜ ์„ ํƒ ํ•ญ๋ชฉ์„ ์ค„์ด์ง€ ์•Š๊ณ  ์œ„ ํ™”๋ฉด host๋ฅผ ์—ฐ๊ฒฐํ•ด ์ž…๋ ฅยท์„ ํƒ์„ ์Šคํฌ๋กค๋กœ ์˜ค๊ฐ€๊ฒŒ ํ•œ๋‹ค.
40
+ ์„ ํƒ ๋ฐฐ๊ฒฝ ์ด๋™์€ ์ž…๋ ฅ ์ดˆ์ ยท์Šคํฌ๋กค์„ ์†Œ์œ ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ œํ’ˆ ํ™”๋ฉด๋„ ์ด ๊ฒฝ๊ณ„๋ฅผ ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค.
41
+
42
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
43
+
44
+ 1. ํ˜„์žฌ ์„ ํƒ ์ธก์ •โ†’์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒโ†’๋ฐฐ๊ฒฝ๋งŒ ์ด๋™. ๋ฉ”๋ชจ ํ•„๋“œ๋Š” ์œ ์ง€ํ•œ๋‹ค.
45
+ 2. ์„œ๋ฒ„ ์‘๋‹ตยทํŒŒ์ผ ๊ถŒํ•œยท๋ฌธ๊ตฌยท๋ธŒ๋žœ๋“œ๋Š” ์ œํ’ˆ์ด ์ „๋‹ฌํ•œ๋‹ค. Showcase์˜ ์˜ˆ์ œ ์‘๋‹ต๊ณผ ๊ณ ์ • ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค.
46
+
47
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
48
+ | --- | --- | --- |
49
+ | ๊ธฐ๋ณธ | ์ž…๋ ฅ๊ณผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ–‰๋™ | ์กฐ์ž‘ ์ด๋ฆ„์€ ์ œํ’ˆ ์–ธ์–ด |
50
+ | ์ง„ํ–‰ ์ค‘ | ์‹ค์ œ ์ž‘์—… ์ƒํƒœ์— ๋งž๋Š” loading ๋˜๋Š” ์—ด๋ฆฐ ๋„๊ตฌ | ์ค‘๋ณต ์š”์ฒญ ์ฐจ๋‹จ, ์ž…๋ ฅ ์œ ์ง€ |
51
+ | ์‹คํŒจ | ๋ฌธ๊ตฌ์™€ ๊ฐ€๋Šฅํ•œ ์žฌ์‹œ๋„ | ์›๋ž˜ ์ž…๋ ฅ/ํŒŒ์ผ ์œ ์ง€, ๊ฒฐ๊ณผ ์•ˆ๋‚ด |
52
+
53
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
54
+
55
+ ```tsx
56
+ // Web
57
+ <SegmentedControl label={t("period")} items={items} value={value} onValueChange={setValue} selectionMotion="slide" />
58
+ ```
59
+
60
+ ```tsx
61
+ // Native
62
+ <SegmentedControl label={t("period")} items={items} value={value} onValueChange={setValue} selectionMotion="slide" />
63
+ ```
64
+
65
+ Web์€ `@hjmds/react`์˜ ํ•ด๋‹น granular entry, Native๋Š” `@hjmds/react-native` entry๋ฅผ ์“ด๋‹ค.
66
+ ์„ ํƒ ์ฝœ๋ฐฑ์€ ๋‘ ํ”Œ๋žซํผ ๋ชจ๋‘ `onValueChange`๋‹ค. ์œ„ ๊ณจ๊ฒฉ์˜ ๋ฒˆ์—ญยทํ•ญ๋ชฉยท๊ฐ’์€ ์ œํ’ˆ์ด ์ œ๊ณตํ•œ๋‹ค.
67
+
68
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
69
+
70
+ | ํ•ญ๋ชฉ | Web | Native |
71
+ | --- | --- | --- |
72
+ | ๋ฐฐ์น˜/ํ…Œ๋งˆ | Stack๊ณผ HjmProvider | Stack๊ณผ HjmNativeProvider |
73
+ | ํฐ ๊ธ€์žยท์ข์€ ํญ | ์ค„๋ฐ”๊ฟˆยท๋‹จ์ผ ๋‚ด์šฉ | ๊ฐ™์€ ์ˆœ์„œ, OS ํ™”๋ฉด ๊ฒ€์ฆ์€ ๋ณ„๋„ |
@@ -0,0 +1,86 @@
1
+ # ์งˆ๊ฐ ๋น„๊ต
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: [EffectSurface ๊ณ„์•ฝ](../../effect-surface.md), `showcase/*/texture-comparison-preview.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/์ •๋ณด ํ‘œ์‹œ/์งˆ๊ฐ ๋น„๊ต`
10
+
11
+ ์Šน๊ธ‰: 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ, [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md). Storybook ๋ถ„๋ฅ˜์ด๋ฉฐ ์ œํ’ˆ ์ ์šฉ ์ฆ๊ฑฐ๋Š” ๋ณ„๋„๋‹ค.
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ๊ธฐ์กด ๋ฐ˜๋ณต ์  grain๊ณผ ๋ถˆ๊ทœ์น™ํ•œ ์ •์  noise๋ฅผ ๊ฐ™์€ ๋ฐฐ๊ฒฝยท๊ฐ•๋„๋กœ ๋น„๊ตํ•  ๋•Œ ์“ด๋‹ค.
16
+ ๋…๋ฆฝ NoiseTexture API๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ  ๊ธฐ์กด EffectSurface์˜ ์„ ํƒ ๋ ˆ์ด์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
17
+ ์ผ๋ฐ˜ ์ž…๋ ฅยท๋ฐ์ดํ„ฐ ํŒจ๋„์€ ๋ถˆํˆฌ๋ช… Surface, ์ž‘์—… ์ง„ํ–‰ยท์„ฑ๊ณต์€ ProgressยทResult๋ฅผ ์“ด๋‹ค.
18
+
19
+ ## ๊ตฌ์„ฑ ์š”์†Œ
20
+
21
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
22
+ | --- | --- | --- |
23
+ | EffectSurface | ์งˆ๊ฐยทํ…Œ๋งˆยท์žฅ์‹ ์ˆ˜๋ช…์ฃผ๊ธฐ | [๋ฐฐ๊ฒฝ ํšจ๊ณผ](../components/effect-surface.md) |
24
+ | SegmentedControl | ๋‘ ์˜์—ญ์˜ ๊ฐ•๋„๋ฅผ ํ•จ๊ป˜ ์„ ํƒ | [๋‹จ์ผ ์„ ํƒ](../components/segmented-control.md) |
25
+ | TextFieldยทButtonยทText | ์ฝ๊ธฐยท์ž…๋ ฅยทํ–‰๋™ยท๊ฒฐ๊ณผ ํ™•์ธ | [์ž…๋ ฅ](../components/field.md) |
26
+
27
+ noise๋Š” ์ƒ์„ฑ๋œ64ร—64 PNG alpha mask์ด๋ฉฐ ์ž์ฒด ๋Ÿฐํƒ€์ž„ ํ”ฝ์…€ ์ƒ์„ฑยท์™ธ๋ถ€ ์š”์ฒญ์ด ์—†๋‹ค.
28
+ ๊ธฐ๋ณธ intensity0.22, ์‹คํ—˜์˜ ๋น„๊ต ๊ฐ•๋„0.6์€ ๊ฐ€๋…์„ฑ ์ŠคํŠธ๋ ˆ์Šค ์กฐ๊ฑด์ด๋ฉฐ ์ œํ’ˆ ๊ธฐ๋ณธ ๊ถŒ์žฅ์ด ์•„๋‹ˆ๋‹ค.
29
+ seed๋Š” ๊ณ ์ • ํƒ€์ผ์˜ ์œ„์ƒ๋งŒ ์ด๋™ํ•œ๋‹ค. ์ƒˆ ํƒ€์ผ ํ”ฝ์…€์„ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.
30
+
31
+ ## ๋ฐฐ์น˜
32
+
33
+ ```text
34
+ [๊ฐ•๋„ ์„ ํƒ: ๋‘ ์˜์—ญ์— ํ•จ๊ป˜ ์ ์šฉ]
35
+ [grain: ์ œ๋ชฉ โ†’ ์„ค๋ช… โ†’ ์ž…๋ ฅ โ†’ ๋ฒ„ํŠผ]
36
+ [noise: ์ œ๋ชฉ โ†’ ์„ค๋ช… โ†’ ์ž…๋ ฅ โ†’ ๋ฒ„ํŠผ]
37
+ [์„ ํƒ ๊ฒฐ๊ณผ์™€ ๋น„๊ต ํ•œ๊ณ„]
38
+ ```
39
+
40
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
41
+ | --- | --- | --- | --- |
42
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ํ™”๋ฉด ๋ณธ๋ฌธ ์„ธ๋กœ | gap lg20 |
43
+ | ๋น„๊ต ์˜์—ญ | EffectSurface | grain ๋‹ค์Œ noise | ๋‚ด๋ถ€ padding spacing.lg20, Stack gap md16 |
44
+ | ์งˆ๊ฐ | SVG mask | absolute ๋ฐฐ๊ฒฝ | viewBox ๋ฐ–64ร—64 CSS pixel/Native logical point ํƒ€์ผ ๋ฐ˜๋ณต |
45
+ | ๋ณธ๋ฌธ | TextFieldยทButton | ์žฅ์‹ ์•ž ์ผ๋ฐ˜ ๋ ˆ์ด์•„์›ƒ | ๊ธฐ์กด recipe |
46
+ | Native ํ™”๋ฉด | ScreenLayoutยทKeyboardAvoiding | ๋ณธ๋ฌธ ์Šคํฌ๋กค๊ณผ ๋ฐ”๊นฅ ํ‚ค๋ณด๋“œ ์—ฌ๋ฐฑ | ๊ธด ๋‚ด์šฉยทํฐ ๊ธ€์ž ์ ‘๊ทผ์šฉ |
47
+
48
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
49
+
50
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
51
+ | --- | --- | --- |
52
+ | ๊ธฐ๋ณธ | ๋‘ ์งˆ๊ฐ๊ณผ ๊ธฐ๋ณธ22% ๊ฐ•๋„ | ์ž…๋ ฅยท๋ฒ„ํŠผ ํ™œ์„ฑ |
53
+ | ์ง„ํ–‰ ์ค‘ | ๊ฐ•๋„ ์„ ํƒ์„ ๋‘ ์งˆ๊ฐ์— ํ•จ๊ป˜ ๋ฐ˜์˜ | ์ž…๋ ฅ ์œ ์ง€, ์ธ์œ„ ๋Œ€๊ธฐ ์—†์Œ |
54
+ | ์‹คํŒจ | ์žฅ์‹ host ์‹คํŒจ๋Š” ๊ธฐ์กด EffectSurface fallback | ์ œํ’ˆ ์ž…๋ ฅยทํ–‰๋™์€ ์œ ์ง€ |
55
+
56
+ 1. ๊ฐ•๋„๋ฅผ ๋ฐ”๊พธ๊ณ  ๋‘ ์˜์—ญ์˜ ๋ฌธ์žฅ์„ ์ฝ๊ณ  ํƒ€์ดํ•‘ํ•œ๋‹ค.
57
+ 2. ์˜์—ญ๋ณ„ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๊ฒฐ๊ณผ ์•ˆ๋‚ด๋ฅผ ๋น„๊ตํ•œ๋‹ค. ๊ฒฐ๊ณผ๋Š” ์˜ˆ์ œ ๋‚ด๋ถ€ ์„ ํƒ์ผ ๋ฟ ์ €์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค.
58
+
59
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
60
+
61
+ ```tsx
62
+ // Web
63
+ import { EffectSurface } from "@hjmds/react/effect-surface";
64
+ <EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
65
+ ```
66
+
67
+ ```tsx
68
+ // Native
69
+ import { EffectSurface } from "@hjmds/react-native/effect-surface";
70
+ <EffectSurface descriptor={{ layers: ["noise"], active: false }}><Content /></EffectSurface>
71
+ ```
72
+
73
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
74
+
75
+ | ํ•ญ๋ชฉ | Web | Native |
76
+ | --- | --- | --- |
77
+ | ์žฅ์‹ | SVG imageยทmask | react-native-svg ImageยทMask |
78
+ | peer | ์—†์Œ | ๊ธฐ์กด react-native-svg15.15.5 ํ•„์š” |
79
+ | ๋™์ž‘ | ๊ฐ€์‹œ์„ฑยทdocument visibility | visibleยทAppState |
80
+
81
+ Native FeTurbulence๋Š” ๋ฏธ๊ตฌํ˜„์ด๋ผ ๊ณต์œ  PNG mask๋ฅผ ํƒํ–ˆ๋‹ค. ์ƒˆ GPU peer๋‚˜ Native ๋ฌดํ‘œ์‹œ
82
+ fallback์„ ๋™์ผ ๊ตฌํ˜„์œผ๋กœ ์„ธ์ง€ ์•Š๋Š”๋‹ค. noise๋Š” theme.text ์ƒ‰์˜ alpha mask๋‹ค. ์ž„์˜ ๋ธŒ๋žœ๋“œ/๊ฐ•๋„์—์„œ
83
+ ๋Œ€๋น„๋ฅผ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ธฐ๋ณธ์€ ์ •์ ์ด๋ฉฐ active๋ฅผ ์ผœ๋ฉด ๊ธฐ์กด ๋ชจ์…˜ ๊ฐ์†Œยท์ˆจ๊น€ ์ˆ˜๋ช…์ฃผ๊ธฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค.
84
+ Web/RN rasterizationยท๋น„์œจ ์ฐจ์ด๋กœ ํ”ฝ์…€ ์ผ์น˜๋ฅผ ๋ณด์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค. ์›๋ณธ SVG Perlin noise์™€ ๋‹ฌ๋ฆฌ
85
+ ์ž์ฒด ์ƒ์„ฑํ•œ6 octave value noise์ด๋ฉฐ ์›๋ณธ ์ž์‚ฐ์„ ๋ณต์‚ฌํ•˜์ง€ ์•Š์•˜๋‹ค. ์‹ค์ œ ๊ธฐ๊ธฐ ํ‘œ์‹œยท๋น„์šฉยทVoiceOver
86
+ ๊ฒ€์ฆ์€ ๋”ฐ๋กœ ํ•œ๋‹ค.
@@ -0,0 +1,80 @@
1
+ # ์„ ํƒ๊ณผ ์˜ค๋ฅ˜ ๋ณต๊ตฌ
2
+
3
+ - ๋‹จ๊ณ„: ๊ตฌ์„ฑ
4
+ - ์ƒํƒœ: ๋ฐฐํฌ
5
+ - ์ง€์›: Web ยท Native
6
+ - ์ ์šฉ: 1.14.0
7
+ - ๊ฒ€ํ† ์ผ: 2026-10-07
8
+ - ๊ทผ๊ฑฐ: ๊ณต๊ฐœ API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” `showcase/*/reference-adoption-previews.tsx`
9
+ - ์Šคํ† ๋ฆฌ๋ถ: `๋ฐฐํฌ/๊ตฌ์„ฑ/ํ”ผ๋“œ๋ฐฑ๊ณผ ๋ณต๊ตฌ/์„ ํƒ๊ณผ ์˜ค๋ฅ˜ ๋ณต๊ตฌ`
10
+
11
+ 2026-10-07 ์‚ฌ์šฉ์ž ์Šน์ธ์œผ๋กœ ์Šน๊ธ‰. [๊ฒ€ํ†  ๊ฒฐ๊ณผ](../../../../../docs/qa/2026-10-07-experiment-promotion-release.md).
12
+
13
+ ## ์–ธ์ œ ์“ฐ๋‚˜
14
+
15
+ ํŒŒ์ผ ์„ ํƒ๊ณผ ์ „์†ก ์ƒํƒœ์˜ ์ทจ์†Œยท์žฌ์‹œ๋„๋ฅผ ์—ฐ๊ฒฐํ•  ๋•Œ ์“ด๋‹ค. ๋ณ„๋„ ์‹ ๊ทœ wrapper API๊ฐ€ ์•„๋‹ˆ๋ผ ์•„๋ž˜ ๊ณต๊ฐœ ์ปดํฌ๋„ŒํŠธ์˜ ์กฐํ•ฉ ๊ทœ๊ฒฉ์ด๋‹ค.
16
+
17
+ ## ๊ตฌ์„ฑ ์š”์†Œ
18
+
19
+ | ์ปดํฌ๋„ŒํŠธ | ์—ญํ•  | ์ง€์นจ |
20
+ | --- | --- | --- |
21
+ | FilePicker ยท UploadItem ยท Button | ์ž…๋ ฅยทํ‘œํ˜„ยทํ–‰๋™์˜ ์—ญํ•  ๋ถ„๋ฆฌ | [์ž…๋ ฅ/๋™์ž‘](../components/file-picker.md), [์ƒํƒœ/์ „ํ™˜](../components/upload-item.md) |
22
+
23
+ ## ๋ฐฐ์น˜
24
+
25
+ ```text
26
+ [์ œ๋ชฉ ๋˜๋Š” ํ˜„์žฌ ์ž…๋ ฅ]
27
+ [์ƒํƒœ/๋‚ด์šฉ ์˜์—ญ]
28
+ [๊ด€๋ จ ํ–‰๋™๊ณผ ๊ฒฐ๊ณผ]
29
+ ```
30
+
31
+ | ์˜์—ญ | ์ปดํฌ๋„ŒํŠธ | ์œ„์น˜ | ํฌ๊ธฐยท๊ฐ„๊ฒฉ |
32
+ | --- | --- | --- | --- |
33
+ | ๋ฐ”๊นฅ ํ‹€ | Stack | ํ™”๋ฉด์˜ ๋ณธ๋ฌธ ํ๋ฆ„ | gap md=16px, ์ „์ฒด ํญ |
34
+ | ๋‚ด์šฉ | ํ•ด๋‹น ๊ณต๊ฐœ API | ์ž…๋ ฅ๊ณผ ํ–‰๋™ ์‚ฌ์ด | ์ž์ฒด recipe ํฌ๊ธฐ, ์ค„๋ฐ”๊ฟˆ ํ—ˆ์šฉ |
35
+ | ํ–‰๋™ | Button/๊ธฐ์กด ์ปจํŠธ๋กค | ๊ด€๋ จ ํ•ญ๋ชฉ ๋ฐ”๋กœ ๋’ค | inline์ผ ๋•Œ wrap, md=16px |
36
+
37
+ ## ํ๋ฆ„๊ณผ ์ƒํƒœ
38
+
39
+ 1. ์„ ํƒโ†’์ค‘๋ณต ์ œ๊ฑฐโ†’์ „์†กโ†’์„ฑ๊ณต ๋˜๋Š” ์‹คํŒจโ†’์ทจ์†Œ/์žฌ์‹œ๋„
40
+ 2. ์„œ๋ฒ„ ์‘๋‹ตยทํŒŒ์ผ ๊ถŒํ•œยท๋ฌธ๊ตฌยท๋ธŒ๋žœ๋“œ๋Š” ์ œํ’ˆ์ด ์ „๋‹ฌํ•œ๋‹ค. Showcase์˜ ์˜ˆ์ œ ์‘๋‹ต๊ณผ ๊ณ ์ • ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š”๋‹ค.
41
+
42
+ | ์ƒํƒœ | ๋ชจ์Šต | ํฌ์ปค์Šคยท์•Œ๋ฆผ |
43
+ | --- | --- | --- |
44
+ | ๊ธฐ๋ณธ | ์ž…๋ ฅ๊ณผ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํ–‰๋™ | ์กฐ์ž‘ ์ด๋ฆ„์€ ์ œํ’ˆ ์–ธ์–ด |
45
+ | ์ง„ํ–‰ ์ค‘ | ์‹ค์ œ ์ž‘์—… ์ƒํƒœ์— ๋งž๋Š” loading ๋˜๋Š” ์—ด๋ฆฐ ๋„๊ตฌ | ์ค‘๋ณต ์š”์ฒญ ์ฐจ๋‹จ, ์ž…๋ ฅ ์œ ์ง€ |
46
+ | ์‹คํŒจ | ๋ฌธ๊ตฌ์™€ ๊ฐ€๋Šฅํ•œ ์žฌ์‹œ๋„ | ์›๋ž˜ ์ž…๋ ฅ/ํŒŒ์ผ ์œ ์ง€, ๊ฒฐ๊ณผ ์•ˆ๋‚ด |
47
+
48
+ ## ์ฝ”๋“œ ๊ณจ๊ฒฉ
49
+
50
+ ```tsx
51
+ // Web
52
+ <UploadItem descriptor={item} labels={labels} onCancel={cancel} onRetry={retry} />
53
+ ```
54
+
55
+ ```tsx
56
+ // Native
57
+ <UploadItem descriptor={item} labels={labels} onCancel={cancel} onRetry={retry} />
58
+ ```
59
+
60
+ Web์€ `@hjmds/react`์˜ ํ•ด๋‹น granular entry, Native๋Š” `@hjmds/react-native` entry๋ฅผ ์“ด๋‹ค.
61
+ Button์˜ ์‹คํ–‰ ์ฝœ๋ฐฑ์€ Web `onClick`, Native `onPress`๋กœ ์—ฐ๊ฒฐํ•œ๋‹ค. ์œ„ ๊ณจ๊ฒฉ์˜ ๋„๋ฉ”์ธ ํ•จ์ˆ˜ยท๋ณ€์ˆ˜๋Š” ์ œํ’ˆ์ด ์ œ๊ณตํ•œ๋‹ค.
62
+
63
+ ## ํ”Œ๋žซํผ ์ฐจ์ด
64
+
65
+ | ํ•ญ๋ชฉ | Web | Native |
66
+ | --- | --- | --- |
67
+ | ๋ฐฐ์น˜/ํ…Œ๋งˆ | Stack๊ณผ HjmProvider | Stack๊ณผ HjmNativeProvider |
68
+ | ํฐ ๊ธ€์žยท์ข์€ ํญ | ์ค„๋ฐ”๊ฟˆยท๋‹จ์ผ ๋‚ด์šฉ | ๊ฐ™์€ ์ˆœ์„œ, OS ํ™”๋ฉด ๊ฒ€์ฆ์€ ๋ณ„๋„ |
69
+
70
+ ### ์˜ˆ์ œ์˜ ์ œํ•œ๊ณผ ์ดˆ์  ๋ณต๊ตฌ
71
+
72
+ 2026-10-07 ์‹ค์ œ ๋ณต๊ตฌ ํ๋ฆ„์—์„œ ์ƒํƒœ ๋ฒ„ํŠผ์ด ์‚ฌ๋ผ์งˆ ๋•Œ Web ์ดˆ์ ์ด body๋กœ ๋น ์กŒ๋‹ค.
73
+ ์˜ˆ์ œ๋Š” ์ƒํƒœ ๋ณ€๊ฒฝ ์ „์— ํ•ด๋‹น ํŒŒ์ผ์˜ ์ด๋ฆ„ ์žˆ๋Š” ์˜์—ญ์œผ๋กœ ์ดˆ์ ์„ ์˜ฎ๊ธฐ๊ณ , ์ œ๊ฑฐ ํ›„์—๋Š” ์˜ˆ์ œ ์ถ”๊ฐ€
74
+ ๋ฒ„ํŠผ์œผ๋กœ ๋ณต๊ท€ํ•œ๋‹ค. ์ดํ›„ Tab์œผ๋กœ ์ทจ์†Œยท์žฌ์‹œ๋„ ๋“ฑ ํ˜„์žฌ ๊ฐ€๋Šฅํ•œ ํ–‰๋™์„ ์„ ํƒํ•œ๋‹ค.
75
+ ์ œํ’ˆ๋„ ๋น„๋™๊ธฐ ์ƒํƒœ ์ „ํ™˜์œผ๋กœ ์‚ฌ๋ผ์ง€๋Š” ํ–‰๋™์˜ ์ดˆ์  ๋ชฉ์ ์ง€๋ฅผ ์ •ํ•œ๋‹ค.
76
+
77
+ ์–‘์ชฝ ์˜ˆ์ œ๋Š” ์ด๋ฏธ์ง€3๊ฐœยทํŒŒ์ผ๋‹น5MB ์ œํ•œ์„ ์„ ์–ธํ•œ๋‹ค. Web์€ ์‹ค์ œ file chooser๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ,
78
+ Native onPick์€ ์˜ˆ์ œ ํ›„๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” fixture๋‹ค. Native ์‹œ์Šคํ…œ ์‚ฌ์ง„ ์„ ํƒ๊ธฐ ๊ฒ€์ฆ์œผ๋กœ ์„ธ์ง€ ์•Š๋Š”๋‹ค.
79
+ ์„œ๋ฒ„ ๋น„์ „์†ก ์•ˆ๋‚ด๋Š” ์„ฑ๊ณตยท๊ฑฐ๋ถ€ ์ƒํƒœ์™€ ๋ณ„๋„๋กœ ํ•ญ์ƒ ํ‘œ์‹œํ•œ๋‹ค. ์‹ค์ œ ์ œํ’ˆ์€ ์„œ๋ฒ„ ํ™•์ •ยท์ทจ์†Œ ์‹œ
80
+ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•˜๋Š” ์‘๋‹ตยท์ „์†ก ticket์˜ ์†Œ์œ ๊ถŒ์„ ์ œํ’ˆ ์—…๋กœ๋“œ ๊ณ„์ธต์— ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค.