@ncds/ui-admin 1.8.4 → 1.8.6

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 (187) hide show
  1. package/dist/cjs/assets/scripts/featuredIcon.js +87 -0
  2. package/dist/cjs/assets/scripts/notification/FloatingNotification.js +178 -0
  3. package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +133 -0
  4. package/dist/cjs/assets/scripts/notification/MessageNotification.js +159 -0
  5. package/dist/cjs/assets/scripts/notification/Notification.js +120 -0
  6. package/dist/cjs/assets/scripts/notification/const/classNames.js +50 -0
  7. package/dist/cjs/assets/scripts/notification/const/icons.js +31 -0
  8. package/dist/cjs/assets/scripts/notification/const/index.js +87 -0
  9. package/dist/cjs/assets/scripts/notification/const/sizes.js +46 -0
  10. package/dist/cjs/assets/scripts/notification/const/types.js +14 -0
  11. package/dist/cjs/assets/scripts/notification/index.js +116 -0
  12. package/dist/cjs/assets/scripts/notification/positionSync.js +180 -0
  13. package/dist/cjs/assets/scripts/notification/utils.js +122 -0
  14. package/dist/cjs/assets/scripts/shared/ButtonCloseX.js +45 -0
  15. package/dist/cjs/assets/scripts/utils/sanitize.js +39 -0
  16. package/dist/cjs/src/components/data-display/data-grid/DataGrid.js +5 -1
  17. package/dist/cjs/src/components/data-display/table/Table.js +118 -96
  18. package/dist/cjs/src/components/data-display/table/useTableScrollbars.js +187 -0
  19. package/dist/cjs/src/components/forms-and-input/combo-box/ComboBox.js +11 -10
  20. package/dist/cjs/src/components/forms-and-input/image-file-input/ImageFileInput.js +5 -2
  21. package/dist/cjs/src/components/forms-and-input/select-box/SelectBox.js +67 -29
  22. package/dist/cjs/src/components/forms-and-input/slider/Slider.js +2 -3
  23. package/dist/cjs/src/components/overlays/dropdown/Dropdown.js +47 -19
  24. package/dist/cjs/src/components/overlays/notification/CalloutNotification.js +25 -0
  25. package/dist/cjs/src/components/overlays/notification/FloatingNotification.js +86 -13
  26. package/dist/cjs/src/components/overlays/notification/Notification.js +7 -0
  27. package/dist/cjs/src/components/overlays/notification/host.js +12 -0
  28. package/dist/cjs/src/components/overlays/tooltip/Tooltip.js +57 -44
  29. package/dist/cjs/src/components/select-dropdown/SelectDropdown.js +2 -1
  30. package/dist/cjs/src/contexts/FloatingContext.js +11 -0
  31. package/dist/cjs/src/contexts/index.js +16 -0
  32. package/dist/cjs/src/hooks/index.js +11 -0
  33. package/dist/cjs/src/hooks/useFloatingPosition.js +78 -0
  34. package/dist/cjs/src/hooks/usePortalState.js +17 -0
  35. package/dist/cjs/src/types/component-meta.js +8 -1
  36. package/dist/cjs/src/utils/dropdown/maxSelection.js +35 -0
  37. package/dist/cjs/src/utils/dropdown/multiSelect.js +72 -15
  38. package/dist/esm/assets/scripts/featuredIcon.js +80 -0
  39. package/dist/esm/assets/scripts/notification/FloatingNotification.js +171 -0
  40. package/dist/esm/assets/scripts/notification/FullWidthNotification.js +126 -0
  41. package/dist/esm/assets/scripts/notification/MessageNotification.js +152 -0
  42. package/dist/esm/assets/scripts/notification/Notification.js +113 -0
  43. package/dist/esm/assets/scripts/notification/const/classNames.js +44 -0
  44. package/dist/esm/assets/scripts/notification/const/icons.js +25 -0
  45. package/dist/esm/assets/scripts/notification/const/index.js +4 -0
  46. package/dist/esm/assets/scripts/notification/const/sizes.js +40 -0
  47. package/dist/esm/assets/scripts/notification/const/types.js +8 -0
  48. package/dist/esm/assets/scripts/notification/index.js +10 -0
  49. package/dist/esm/assets/scripts/notification/positionSync.js +171 -0
  50. package/dist/esm/assets/scripts/notification/utils.js +109 -0
  51. package/dist/esm/assets/scripts/shared/ButtonCloseX.js +37 -0
  52. package/dist/esm/assets/scripts/utils/sanitize.js +31 -0
  53. package/dist/esm/src/components/data-display/data-grid/DataGrid.js +5 -1
  54. package/dist/esm/src/components/data-display/table/Table.js +118 -96
  55. package/dist/esm/src/components/data-display/table/useTableScrollbars.js +179 -0
  56. package/dist/esm/src/components/forms-and-input/combo-box/ComboBox.js +11 -10
  57. package/dist/esm/src/components/forms-and-input/image-file-input/ImageFileInput.js +5 -2
  58. package/dist/esm/src/components/forms-and-input/select-box/SelectBox.js +67 -29
  59. package/dist/esm/src/components/forms-and-input/slider/Slider.js +1 -2
  60. package/dist/esm/src/components/overlays/dropdown/Dropdown.js +47 -19
  61. package/dist/esm/src/components/overlays/notification/CalloutNotification.js +19 -0
  62. package/dist/esm/src/components/overlays/notification/FloatingNotification.js +86 -14
  63. package/dist/esm/src/components/overlays/notification/Notification.js +7 -0
  64. package/dist/esm/src/components/overlays/notification/host.js +9 -0
  65. package/dist/esm/src/components/overlays/tooltip/Tooltip.js +58 -45
  66. package/dist/esm/src/components/select-dropdown/SelectDropdown.js +2 -1
  67. package/dist/esm/src/contexts/FloatingContext.js +4 -0
  68. package/dist/esm/src/contexts/index.js +1 -0
  69. package/dist/esm/src/hooks/index.js +1 -0
  70. package/dist/esm/src/hooks/useFloatingPosition.js +71 -0
  71. package/dist/esm/src/hooks/usePortalState.js +10 -0
  72. package/dist/esm/src/types/component-meta.js +5 -1
  73. package/dist/esm/src/utils/dropdown/maxSelection.js +27 -0
  74. package/dist/esm/src/utils/dropdown/multiSelect.js +70 -14
  75. package/dist/temp/assets/scripts/featuredIcon.d.ts +22 -0
  76. package/dist/temp/assets/scripts/featuredIcon.js +79 -0
  77. package/dist/temp/assets/scripts/notification/FloatingNotification.d.ts +24 -0
  78. package/dist/temp/assets/scripts/notification/FloatingNotification.js +156 -0
  79. package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
  80. package/dist/temp/assets/scripts/notification/FullWidthNotification.js +111 -0
  81. package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +22 -0
  82. package/dist/temp/assets/scripts/notification/MessageNotification.js +140 -0
  83. package/dist/temp/assets/scripts/notification/Notification.d.ts +22 -0
  84. package/dist/temp/assets/scripts/notification/Notification.js +112 -0
  85. package/dist/temp/assets/scripts/notification/const/classNames.d.ts +43 -0
  86. package/dist/temp/assets/scripts/notification/const/classNames.js +44 -0
  87. package/dist/temp/assets/scripts/notification/const/icons.d.ts +25 -0
  88. package/dist/temp/assets/scripts/notification/const/icons.js +25 -0
  89. package/dist/temp/assets/scripts/notification/const/index.d.ts +5 -0
  90. package/dist/temp/assets/scripts/notification/const/index.js +4 -0
  91. package/dist/temp/assets/scripts/notification/const/sizes.d.ts +32 -0
  92. package/dist/temp/assets/scripts/notification/const/sizes.js +40 -0
  93. package/dist/temp/assets/scripts/notification/const/types.d.ts +19 -0
  94. package/dist/temp/assets/scripts/notification/const/types.js +8 -0
  95. package/dist/temp/assets/scripts/notification/index.d.ts +8 -0
  96. package/dist/temp/assets/scripts/notification/index.js +10 -0
  97. package/dist/temp/assets/scripts/notification/positionSync.d.ts +50 -0
  98. package/dist/temp/assets/scripts/notification/positionSync.js +170 -0
  99. package/dist/temp/assets/scripts/notification/utils.d.ts +8 -0
  100. package/dist/temp/assets/scripts/notification/utils.js +115 -0
  101. package/dist/temp/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
  102. package/dist/temp/assets/scripts/shared/ButtonCloseX.js +33 -0
  103. package/dist/temp/assets/scripts/utils/sanitize.d.ts +22 -0
  104. package/dist/temp/assets/scripts/utils/sanitize.js +31 -0
  105. package/dist/temp/src/components/data-display/data-grid/DataGrid.js +1 -1
  106. package/dist/temp/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
  107. package/dist/temp/src/components/data-display/table/Table.d.ts +4 -1
  108. package/dist/temp/src/components/data-display/table/Table.js +53 -68
  109. package/dist/temp/src/components/data-display/table/types.d.ts +18 -0
  110. package/dist/temp/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
  111. package/dist/temp/src/components/data-display/table/useTableScrollbars.js +136 -0
  112. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
  113. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.js +7 -11
  114. package/dist/temp/src/components/forms-and-input/image-file-input/ImageFileInput.js +1 -1
  115. package/dist/temp/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
  116. package/dist/temp/src/components/forms-and-input/select-box/SelectBox.js +30 -3
  117. package/dist/temp/src/components/forms-and-input/slider/Slider.d.ts +0 -1
  118. package/dist/temp/src/components/forms-and-input/slider/Slider.js +0 -1
  119. package/dist/temp/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
  120. package/dist/temp/src/components/overlays/dropdown/Dropdown.js +35 -11
  121. package/dist/temp/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
  122. package/dist/temp/src/components/overlays/notification/CalloutNotification.js +6 -0
  123. package/dist/temp/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
  124. package/dist/temp/src/components/overlays/notification/FloatingNotification.js +81 -13
  125. package/dist/temp/src/components/overlays/notification/Notification.d.ts +18 -3
  126. package/dist/temp/src/components/overlays/notification/Notification.js +4 -0
  127. package/dist/temp/src/components/overlays/notification/host.d.ts +9 -0
  128. package/dist/temp/src/components/overlays/notification/host.js +9 -0
  129. package/dist/temp/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
  130. package/dist/temp/src/components/overlays/tooltip/Tooltip.js +25 -22
  131. package/dist/temp/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
  132. package/dist/temp/src/components/select-dropdown/SelectDropdown.js +2 -2
  133. package/dist/temp/src/contexts/FloatingContext.d.ts +6 -0
  134. package/dist/temp/src/contexts/FloatingContext.js +4 -0
  135. package/dist/temp/src/contexts/index.d.ts +1 -0
  136. package/dist/temp/src/contexts/index.js +1 -0
  137. package/dist/temp/src/hooks/index.d.ts +1 -0
  138. package/dist/temp/src/hooks/index.js +1 -0
  139. package/dist/temp/src/hooks/useFloatingPosition.d.ts +19 -0
  140. package/dist/temp/src/hooks/useFloatingPosition.js +55 -0
  141. package/dist/temp/src/hooks/usePortalState.d.ts +6 -0
  142. package/dist/temp/src/hooks/usePortalState.js +7 -0
  143. package/dist/temp/src/types/component-meta.d.ts +6 -2
  144. package/dist/temp/src/types/component-meta.js +14 -1
  145. package/dist/temp/src/utils/dropdown/maxSelection.d.ts +24 -0
  146. package/dist/temp/src/utils/dropdown/maxSelection.js +28 -0
  147. package/dist/temp/src/utils/dropdown/multiSelect.d.ts +42 -2
  148. package/dist/temp/src/utils/dropdown/multiSelect.js +66 -13
  149. package/dist/types/assets/scripts/featuredIcon.d.ts +22 -0
  150. package/dist/types/assets/scripts/notification/FloatingNotification.d.ts +24 -0
  151. package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
  152. package/dist/types/assets/scripts/notification/MessageNotification.d.ts +22 -0
  153. package/dist/types/assets/scripts/notification/Notification.d.ts +22 -0
  154. package/dist/types/assets/scripts/notification/const/classNames.d.ts +43 -0
  155. package/dist/types/assets/scripts/notification/const/icons.d.ts +25 -0
  156. package/dist/types/assets/scripts/notification/const/index.d.ts +5 -0
  157. package/dist/types/assets/scripts/notification/const/sizes.d.ts +32 -0
  158. package/dist/types/assets/scripts/notification/const/types.d.ts +19 -0
  159. package/dist/types/assets/scripts/notification/index.d.ts +8 -0
  160. package/dist/types/assets/scripts/notification/positionSync.d.ts +50 -0
  161. package/dist/types/assets/scripts/notification/utils.d.ts +8 -0
  162. package/dist/types/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
  163. package/dist/types/assets/scripts/utils/sanitize.d.ts +22 -0
  164. package/dist/types/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
  165. package/dist/types/src/components/data-display/table/Table.d.ts +4 -1
  166. package/dist/types/src/components/data-display/table/types.d.ts +18 -0
  167. package/dist/types/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
  168. package/dist/types/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
  169. package/dist/types/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
  170. package/dist/types/src/components/forms-and-input/slider/Slider.d.ts +0 -1
  171. package/dist/types/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
  172. package/dist/types/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
  173. package/dist/types/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
  174. package/dist/types/src/components/overlays/notification/Notification.d.ts +18 -3
  175. package/dist/types/src/components/overlays/notification/host.d.ts +9 -0
  176. package/dist/types/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
  177. package/dist/types/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
  178. package/dist/types/src/contexts/FloatingContext.d.ts +6 -0
  179. package/dist/types/src/contexts/index.d.ts +1 -0
  180. package/dist/types/src/hooks/index.d.ts +1 -0
  181. package/dist/types/src/hooks/useFloatingPosition.d.ts +19 -0
  182. package/dist/types/src/hooks/usePortalState.d.ts +6 -0
  183. package/dist/types/src/types/component-meta.d.ts +6 -2
  184. package/dist/types/src/utils/dropdown/maxSelection.d.ts +24 -0
  185. package/dist/types/src/utils/dropdown/multiSelect.d.ts +42 -2
  186. package/dist/ui-admin/assets/styles/style.css +312 -64
  187. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ import type { NotificationColor } from './types';
2
+ export declare const SVG_ICONS: {
3
+ readonly 'pin-02': (size: string) => string;
4
+ readonly 'alert-triangle': (size: string) => string;
5
+ readonly 'alert-circle': (size: string) => string;
6
+ readonly 'check-circle': (size: string) => string;
7
+ readonly 'info-circle': (size: string) => string;
8
+ readonly 'message-chat-square': (size: string) => string;
9
+ readonly 'x-close': (size: string) => string;
10
+ };
11
+ export declare const FLOATING_ICON_MAP: Partial<Record<NotificationColor, (size: string) => string>>;
12
+ export declare const FULL_WIDTH_ICON_MAP: {
13
+ readonly neutral: (size: string) => string;
14
+ readonly error: (size: string) => string;
15
+ readonly warning: (size: string) => string;
16
+ readonly success: (size: string) => string;
17
+ readonly info: (size: string) => string;
18
+ };
19
+ export declare const ICON_MAP: {
20
+ readonly neutral: (size: string) => string;
21
+ readonly error: (size: string) => string;
22
+ readonly warning: (size: string) => string;
23
+ readonly success: (size: string) => string;
24
+ readonly info: (size: string) => string;
25
+ };
@@ -0,0 +1,25 @@
1
+ // SVG 아이콘들 (크기는 동적으로 설정)
2
+ export const SVG_ICONS = {
3
+ 'pin-02': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.377 15.616 2.72 21.273m8.974-14.631-1.56 1.56a2 2 0 0 1-.264.242 1 1 0 0 1-.207.111c-.082.032-.17.05-.347.085l-3.665.733c-.952.19-1.428.286-1.65.537a1 1 0 0 0-.243.8c.046.333.39.677 1.076 1.363l7.086 7.086c.686.687 1.03 1.03 1.362 1.076a1 1 0 0 0 .801-.242c.251-.223.346-.7.537-1.651l.733-3.665c.035-.176.053-.265.085-.347a1 1 0 0 1 .11-.207c.051-.072.115-.136.242-.263l1.561-1.561c.082-.082.122-.122.167-.158q.06-.047.126-.085c.05-.029.103-.051.208-.097l2.495-1.069c.727-.312 1.091-.467 1.256-.72a1 1 0 0 0 .144-.747c-.06-.295-.34-.575-.9-1.135l-5.142-5.143c-.56-.56-.84-.84-1.135-.9a1 1 0 0 0-.748.145c-.252.165-.407.529-.72 1.256l-1.068 2.495a2 2 0 0 1-.097.208 1 1 0 0 1-.085.126 2 2 0 0 1-.158.167"></path></svg>`,
4
+ 'alert-triangle': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v4m0 4h.01M10.615 3.892 2.39 18.098c-.456.788-.684 1.182-.65 1.506a1 1 0 0 0 .406.705c.263.191.718.191 1.629.191h16.45c.91 0 1.365 0 1.628-.191a1 1 0 0 0 .407-.705c.034-.324-.195-.718-.65-1.506L13.383 3.892c-.454-.785-.681-1.178-.978-1.31a1 1 0 0 0-.812 0c-.297.132-.524.525-.979 1.31"></path></svg>`,
5
+ 'alert-circle': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10"></path></svg>`,
6
+ 'check-circle': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7.5 12 3 3 6-6m5.5 3c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10"></path></svg>`,
7
+ 'info-circle': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none" color="#5720B7" class="ncua-full-width-notification__icon"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 16v-4m0-4h.01M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10"></path></svg>`,
8
+ 'message-chat-square': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none" color="#0C111D" class="ncua-full-width-notification__icon"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m10 15-3.075 3.114c-.43.434-.644.651-.828.666a.5.5 0 0 1-.421-.172c-.12-.14-.12-.446-.12-1.056v-1.56c0-.548-.449-.944-.99-1.024v0a3 3 0 0 1-2.534-2.533C2 12.219 2 11.96 2 11.445V6.8c0-1.68 0-2.52.327-3.162a3 3 0 0 1 1.311-1.311C4.28 2 5.12 2 6.8 2h7.4c1.68 0 2.52 0 3.162.327a3 3 0 0 1 1.311 1.311C19 4.28 19 5.12 19 6.8V11m0 11-2.176-1.513c-.306-.213-.46-.32-.626-.395a2 2 0 0 0-.462-.145c-.18-.033-.367-.033-.74-.033H13.2c-1.12 0-1.68 0-2.108-.218a2 2 0 0 1-.874-.874C10 18.394 10 17.834 10 16.714V14.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C11.52 11 12.08 11 13.2 11h5.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C22 12.52 22 13.08 22 14.2v2.714c0 .932 0 1.398-.152 1.766a2 2 0 0 1-1.083 1.082c-.367.152-.833.152-1.765.152z"></path></svg>`,
9
+ 'x-close': (size) => `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="none"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6 6 18M6 6l12 12"></path></svg>`,
10
+ };
11
+ export const FLOATING_ICON_MAP = {
12
+ neutral: SVG_ICONS['pin-02'],
13
+ error: SVG_ICONS['alert-triangle'],
14
+ warning: SVG_ICONS['alert-circle'],
15
+ success: SVG_ICONS['check-circle'],
16
+ // info는 floating에서는 지원하지 않음
17
+ };
18
+ export const FULL_WIDTH_ICON_MAP = {
19
+ neutral: SVG_ICONS['message-chat-square'],
20
+ error: SVG_ICONS['alert-triangle'],
21
+ warning: SVG_ICONS['alert-triangle'],
22
+ success: SVG_ICONS['check-circle'],
23
+ info: SVG_ICONS['info-circle'],
24
+ };
25
+ export const ICON_MAP = FULL_WIDTH_ICON_MAP;
@@ -0,0 +1,5 @@
1
+ export type { NotificationColor, NotificationHierarchy, NotificationAction, BaseNotificationOptions } from './types';
2
+ export { SVG_ICONS, FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP } from './icons';
3
+ export { CLASS_NAMES } from './classNames';
4
+ export { FEATURED_ICON_SIZES, ICON_PIXEL_SIZES, CLOSE_BUTTON_SIZES, CLOSE_BUTTON_SVG_SIZES, FULL_WIDTH_SIZES, MESSAGE_SIZES, getSizes, } from './sizes';
5
+ export { MESSAGE_CLOSE_ICON_COLORS } from './types';
@@ -0,0 +1,4 @@
1
+ export { SVG_ICONS, FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP } from './icons';
2
+ export { CLASS_NAMES } from './classNames';
3
+ export { FEATURED_ICON_SIZES, ICON_PIXEL_SIZES, CLOSE_BUTTON_SIZES, CLOSE_BUTTON_SVG_SIZES, FULL_WIDTH_SIZES, MESSAGE_SIZES, getSizes, } from './sizes';
4
+ export { MESSAGE_CLOSE_ICON_COLORS } from './types';
@@ -0,0 +1,32 @@
1
+ export declare const FEATURED_ICON_SIZES: {
2
+ readonly MOBILE: "md";
3
+ readonly DESKTOP: "sm";
4
+ };
5
+ export declare const ICON_PIXEL_SIZES: {
6
+ readonly MOBILE: "20";
7
+ readonly DESKTOP: "16";
8
+ readonly FULL_WIDTH: "16";
9
+ };
10
+ export declare const CLOSE_BUTTON_SIZES: {
11
+ readonly MOBILE: "sm";
12
+ readonly DESKTOP: "xs";
13
+ };
14
+ export declare const CLOSE_BUTTON_SVG_SIZES: {
15
+ readonly xs: 16;
16
+ readonly sm: 20;
17
+ };
18
+ export declare const FULL_WIDTH_SIZES: {
19
+ readonly ICON: "16";
20
+ readonly CLOSE_BUTTON: "20";
21
+ };
22
+ export declare const MESSAGE_SIZES: {
23
+ readonly FEATURED_ICON: "lg";
24
+ readonly ICON_PIXEL: "24";
25
+ readonly CLOSE_BUTTON: "20";
26
+ };
27
+ export declare const getSizes: {
28
+ readonly featuredIcon: (isMobile: boolean) => "sm" | "md";
29
+ readonly iconPixel: (isMobile: boolean) => "20" | "16";
30
+ readonly closeButton: (isMobile: boolean) => "xs" | "sm";
31
+ readonly closeButtonSvg: (size: keyof typeof CLOSE_BUTTON_SVG_SIZES) => 16 | 20;
32
+ };
@@ -0,0 +1,40 @@
1
+ // 알림 컴포넌트 사이즈 관련 상수들
2
+ // FeaturedIcon 사이즈
3
+ export const FEATURED_ICON_SIZES = {
4
+ MOBILE: 'md',
5
+ DESKTOP: 'sm',
6
+ };
7
+ // SVG 아이콘 픽셀 사이즈
8
+ export const ICON_PIXEL_SIZES = {
9
+ MOBILE: '20',
10
+ DESKTOP: '16',
11
+ FULL_WIDTH: '16', // Full-width는 항상 고정
12
+ };
13
+ // 닫기 버튼 사이즈
14
+ export const CLOSE_BUTTON_SIZES = {
15
+ MOBILE: 'sm',
16
+ DESKTOP: 'xs',
17
+ };
18
+ // 닫기 버튼 SVG 픽셀 사이즈
19
+ export const CLOSE_BUTTON_SVG_SIZES = {
20
+ xs: 16,
21
+ sm: 20,
22
+ };
23
+ // Full-width 알림 고정 사이즈
24
+ export const FULL_WIDTH_SIZES = {
25
+ ICON: '16',
26
+ CLOSE_BUTTON: '20',
27
+ };
28
+ // Message 알림 고정 사이즈
29
+ export const MESSAGE_SIZES = {
30
+ FEATURED_ICON: 'lg',
31
+ ICON_PIXEL: '24',
32
+ CLOSE_BUTTON: '20',
33
+ };
34
+ // 사이즈 유틸리티 함수들
35
+ export const getSizes = {
36
+ featuredIcon: (isMobile) => (isMobile ? FEATURED_ICON_SIZES.MOBILE : FEATURED_ICON_SIZES.DESKTOP),
37
+ iconPixel: (isMobile) => (isMobile ? ICON_PIXEL_SIZES.MOBILE : ICON_PIXEL_SIZES.DESKTOP),
38
+ closeButton: (isMobile) => (isMobile ? CLOSE_BUTTON_SIZES.MOBILE : CLOSE_BUTTON_SIZES.DESKTOP),
39
+ closeButtonSvg: (size) => CLOSE_BUTTON_SVG_SIZES[size],
40
+ };
@@ -0,0 +1,19 @@
1
+ export type NotificationColor = 'neutral' | 'error' | 'warning' | 'success' | 'info';
2
+ export type NotificationHierarchy = 'link' | 'link-gray';
3
+ export interface NotificationAction {
4
+ label: string;
5
+ onClick: () => void;
6
+ hierarchy?: NotificationHierarchy;
7
+ }
8
+ export interface BaseNotificationOptions {
9
+ title: string;
10
+ supportingText?: string;
11
+ color?: NotificationColor;
12
+ onClose?: () => void;
13
+ className?: string;
14
+ actions?: NotificationAction[];
15
+ autoClose?: number;
16
+ supportTextLink?: string;
17
+ onHidePermanently?: () => void;
18
+ }
19
+ export declare const MESSAGE_CLOSE_ICON_COLORS: Record<NotificationColor, string>;
@@ -0,0 +1,8 @@
1
+ // Message Notification 닫기 버튼 아이콘 색상 맵
2
+ export const MESSAGE_CLOSE_ICON_COLORS = {
3
+ neutral: '#6B7280',
4
+ error: '#EF4444',
5
+ warning: '#F97316',
6
+ success: '#16A34A',
7
+ info: '#6B7280', // info는 message 타입에서 지원하지 않지만 fallback용
8
+ };
@@ -0,0 +1,8 @@
1
+ export type { BaseNotificationOptions, NotificationAction, NotificationColor, NotificationHierarchy } from './const';
2
+ export { CLASS_NAMES, FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, SVG_ICONS } from './const';
3
+ export { FloatingNotification, type FloatingNotificationOptions } from './FloatingNotification';
4
+ export { FullWidthNotification, type FullWidthNotificationOptions } from './FullWidthNotification';
5
+ export { MessageNotification, type MessageNotificationOptions } from './MessageNotification';
6
+ export { NcuaNotification as Notification, type NotificationOptions } from './Notification';
7
+ export { mountFloatingNotificationHost, startPositionSync, stopPositionSync, syncNow, } from './positionSync';
8
+ export * from './utils';
@@ -0,0 +1,10 @@
1
+ export { CLASS_NAMES, FLOATING_ICON_MAP, FULL_WIDTH_ICON_MAP, ICON_MAP, SVG_ICONS } from './const';
2
+ export { FloatingNotification } from './FloatingNotification';
3
+ export { FullWidthNotification } from './FullWidthNotification';
4
+ export { MessageNotification } from './MessageNotification';
5
+ export { NcuaNotification as Notification } from './Notification';
6
+ // DES-SPEC-027 §5.1 — Floating notification 호스트 + 위치 동기화 (vanilla/React 공유 진입점).
7
+ // mountFloatingNotificationHost 가 자동으로 startPositionSync 를 호출하므로 일반 사용자는
8
+ // start/stop/syncNow 를 직접 부를 일이 없다 — HMR/SSR/테스트 정리용으로만 노출.
9
+ export { mountFloatingNotificationHost, startPositionSync, stopPositionSync, syncNow, } from './positionSync';
10
+ export * from './utils';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Floating Notification 호스트 싱글톤 + 위치 동기화
3
+ *
4
+ * 본 모듈은 다음 두 가지 책임을 가진다:
5
+ * 1. `.ncua-floating-notification-host` 싱글톤을 document.body 에 생성·재사용 (mountFloatingNotificationHost).
6
+ * 2. `.ncua-page-title` 의 rect.bottom 을 추적해 `--ncua-page-title-bottom` CSS 변수로 갱신.
7
+ *
8
+ * 두 책임을 한 파일에 두는 이유: 호스트가 처음 생성될 때 positionSync 가 함께 시작되며,
9
+ * React 측 훅과 vanilla 측 `NcuaNotification.show()` 가 동일 함수를 공유해 호스트 생성 로직이
10
+ * 한 곳에서만 유지되도록 한다.
11
+ *
12
+ * NCDS DES-SPEC-027 §5.1
13
+ * · Toast top 좌표 = PageTitle.bottom + 16px (viewport 기준).
14
+ * · PageTitle 이 sticky 로 Default/Fixed 변형 사이 높이가 변동(120/56px)하므로 단순 height
15
+ * 가 아닌 getBoundingClientRect().bottom 을 기준으로 동적 계산해야 한다.
16
+ *
17
+ * 동작 특성:
18
+ * - 다중 호출 idempotent. ensure/start 가 여러 번 호출되어도 호스트와 리스너는 한 벌만.
19
+ * - scroll/resize 는 rAF 로 throttle.
20
+ * - PageTitle 추가/제거를 MutationObserver 로 감지해 재바인딩 — body subtree 변경마다
21
+ * 호출되므로 콜백 자체도 rAF 로 한 프레임당 한 번만 querySelector 가 돌도록 보호.
22
+ * - SSR 가드: window/document 미정의 환경에서 ensure/start 는 no-op.
23
+ *
24
+ * 내부 상태는 모듈 최상단에 흩어진 `let` 대신 한 객체로 묶어 테스트/HMR 에서 일괄 초기화·검사하기
25
+ * 쉽도록 한다. 외부에는 함수만 노출되므로 캡슐화는 유지된다.
26
+ */
27
+ /**
28
+ * `.ncua-floating-notification-host` 싱글톤을 보장한다.
29
+ * **side-effect 함수** — 단순 조회가 아니라 다음을 모두 수행:
30
+ * · document.body 에 `<div class="ncua-floating-notification-host">` 를 (필요 시) append
31
+ * · 첫 호출 시 startPositionSync() 로 window scroll/resize/MutationObserver/ResizeObserver 부착
32
+ * · 한 번 만든 호스트는 페이지 lifetime 동안 유지 — 컴포넌트 언마운트 시에도 제거하지 않는다
33
+ * (다음 토스트의 mount 비용을 줄이기 위해 의도된 설계)
34
+ *
35
+ * React 측 hook 과 vanilla 측 `NcuaNotification.show()` 가 모두 이 함수를 사용해 호스트 생성
36
+ * 진입점이 한 곳만 존재한다.
37
+ *
38
+ * @returns 생성되었거나 이미 존재하는 호스트 엘리먼트. SSR 환경에서는 null.
39
+ */
40
+ export declare function mountFloatingNotificationHost(): HTMLElement | null;
41
+ /**
42
+ * PageTitle ↔ Floating Host 위치 동기화 시작 (idempotent).
43
+ * `mountFloatingNotificationHost()` 가 자동 호출하므로 일반 사용자는 직접 부를 필요 없음.
44
+ * SSR/테스트에서 수동 제어가 필요할 때만 export 됨.
45
+ */
46
+ export declare function startPositionSync(): void;
47
+ /** 동기화 중단 + 리소스 해제. 테스트 종료/HMR 정리 등에 사용. */
48
+ export declare function stopPositionSync(): void;
49
+ /** 외부에서 PageTitle.bottom 값을 강제로 다시 측정해 반영하고 싶을 때. */
50
+ export declare function syncNow(): void;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Floating Notification 호스트 싱글톤 + 위치 동기화
3
+ *
4
+ * 본 모듈은 다음 두 가지 책임을 가진다:
5
+ * 1. `.ncua-floating-notification-host` 싱글톤을 document.body 에 생성·재사용 (mountFloatingNotificationHost).
6
+ * 2. `.ncua-page-title` 의 rect.bottom 을 추적해 `--ncua-page-title-bottom` CSS 변수로 갱신.
7
+ *
8
+ * 두 책임을 한 파일에 두는 이유: 호스트가 처음 생성될 때 positionSync 가 함께 시작되며,
9
+ * React 측 훅과 vanilla 측 `NcuaNotification.show()` 가 동일 함수를 공유해 호스트 생성 로직이
10
+ * 한 곳에서만 유지되도록 한다.
11
+ *
12
+ * NCDS DES-SPEC-027 §5.1
13
+ * · Toast top 좌표 = PageTitle.bottom + 16px (viewport 기준).
14
+ * · PageTitle 이 sticky 로 Default/Fixed 변형 사이 높이가 변동(120/56px)하므로 단순 height
15
+ * 가 아닌 getBoundingClientRect().bottom 을 기준으로 동적 계산해야 한다.
16
+ *
17
+ * 동작 특성:
18
+ * - 다중 호출 idempotent. ensure/start 가 여러 번 호출되어도 호스트와 리스너는 한 벌만.
19
+ * - scroll/resize 는 rAF 로 throttle.
20
+ * - PageTitle 추가/제거를 MutationObserver 로 감지해 재바인딩 — body subtree 변경마다
21
+ * 호출되므로 콜백 자체도 rAF 로 한 프레임당 한 번만 querySelector 가 돌도록 보호.
22
+ * - SSR 가드: window/document 미정의 환경에서 ensure/start 는 no-op.
23
+ *
24
+ * 내부 상태는 모듈 최상단에 흩어진 `let` 대신 한 객체로 묶어 테스트/HMR 에서 일괄 초기화·검사하기
25
+ * 쉽도록 한다. 외부에는 함수만 노출되므로 캡슐화는 유지된다.
26
+ */
27
+ const CSS_VAR = '--ncua-page-title-bottom';
28
+ const PAGE_TITLE_SELECTOR = '.ncua-page-title';
29
+ const HOST_CLASS_NAME = 'ncua-floating-notification-host';
30
+ // add/remove EventListener 의 옵션 객체를 동일 참조로 사용해 capture/passive 미스매치를 차단.
31
+ const SCROLL_OPTIONS = { passive: true, capture: true };
32
+ const RESIZE_OPTIONS = { passive: true };
33
+ const state = {
34
+ started: false,
35
+ rafId: null,
36
+ rebindRafId: null,
37
+ pageTitle: null,
38
+ resizeObserver: null,
39
+ mutationObserver: null,
40
+ };
41
+ /** window/document 가 사용 가능한 브라우저 환경인지 — SSR/Node 가드. */
42
+ function isBrowserEnv() {
43
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
44
+ }
45
+ function readBottomPx() {
46
+ if (!state.pageTitle)
47
+ state.pageTitle = document.querySelector(PAGE_TITLE_SELECTOR);
48
+ if (!state.pageTitle)
49
+ return 0;
50
+ const rect = state.pageTitle.getBoundingClientRect();
51
+ // viewport 기준 음수는 0으로 클램프 (PageTitle이 스크롤되어 시야에서 벗어난 경우)
52
+ return Math.max(0, Math.round(rect.bottom));
53
+ }
54
+ function flush() {
55
+ document.documentElement.style.setProperty(CSS_VAR, `${readBottomPx()}px`);
56
+ state.rafId = null;
57
+ }
58
+ function schedule() {
59
+ if (state.rafId !== null)
60
+ return;
61
+ state.rafId = window.requestAnimationFrame(flush);
62
+ }
63
+ /** 현재 cached pageTitle 이 여전히 DOM 에 연결되어 있는지 — body subtree 변경 시 빠른 early-return. */
64
+ function pageTitleStillInDom() {
65
+ return state.pageTitle?.isConnected ?? false;
66
+ }
67
+ function rebindPageTitle() {
68
+ state.rebindRafId = null;
69
+ if (pageTitleStillInDom())
70
+ return;
71
+ const next = document.querySelector(PAGE_TITLE_SELECTOR);
72
+ if (next === state.pageTitle)
73
+ return;
74
+ state.pageTitle = next;
75
+ if (state.resizeObserver) {
76
+ state.resizeObserver.disconnect();
77
+ // box: 'content-box' 일관성 — startPositionSync 의 첫 observe 와 동일 옵션.
78
+ if (state.pageTitle)
79
+ state.resizeObserver.observe(state.pageTitle, { box: 'content-box' });
80
+ }
81
+ schedule();
82
+ }
83
+ /**
84
+ * MutationObserver 콜백 — body subtree 의 모든 변경마다 즉시 querySelector 를 돌리면 비싸므로
85
+ * 프레임당 한 번으로 throttle. 캐시된 pageTitle 이 아직 DOM 에 있으면 rebindPageTitle 안에서
86
+ * 추가 비용 없이 early-return 한다.
87
+ */
88
+ function scheduleRebind() {
89
+ if (state.rebindRafId !== null)
90
+ return;
91
+ state.rebindRafId = window.requestAnimationFrame(rebindPageTitle);
92
+ }
93
+ /**
94
+ * `.ncua-floating-notification-host` 싱글톤을 보장한다.
95
+ * **side-effect 함수** — 단순 조회가 아니라 다음을 모두 수행:
96
+ * · document.body 에 `<div class="ncua-floating-notification-host">` 를 (필요 시) append
97
+ * · 첫 호출 시 startPositionSync() 로 window scroll/resize/MutationObserver/ResizeObserver 부착
98
+ * · 한 번 만든 호스트는 페이지 lifetime 동안 유지 — 컴포넌트 언마운트 시에도 제거하지 않는다
99
+ * (다음 토스트의 mount 비용을 줄이기 위해 의도된 설계)
100
+ *
101
+ * React 측 hook 과 vanilla 측 `NcuaNotification.show()` 가 모두 이 함수를 사용해 호스트 생성
102
+ * 진입점이 한 곳만 존재한다.
103
+ *
104
+ * @returns 생성되었거나 이미 존재하는 호스트 엘리먼트. SSR 환경에서는 null.
105
+ */
106
+ export function mountFloatingNotificationHost() {
107
+ if (!isBrowserEnv())
108
+ return null;
109
+ let host = document.querySelector(`.${HOST_CLASS_NAME}`);
110
+ if (!host) {
111
+ host = document.createElement('div');
112
+ host.className = HOST_CLASS_NAME;
113
+ // 호스트는 layout container 일 뿐 — live region 책임은 각 토스트 카드의 role 에 위임.
114
+ document.body.appendChild(host);
115
+ startPositionSync();
116
+ }
117
+ return host;
118
+ }
119
+ /**
120
+ * PageTitle ↔ Floating Host 위치 동기화 시작 (idempotent).
121
+ * `mountFloatingNotificationHost()` 가 자동 호출하므로 일반 사용자는 직접 부를 필요 없음.
122
+ * SSR/테스트에서 수동 제어가 필요할 때만 export 됨.
123
+ */
124
+ export function startPositionSync() {
125
+ if (state.started || !isBrowserEnv())
126
+ return;
127
+ state.started = true;
128
+ state.pageTitle = document.querySelector(PAGE_TITLE_SELECTOR);
129
+ window.addEventListener('scroll', schedule, SCROLL_OPTIONS);
130
+ window.addEventListener('resize', schedule, RESIZE_OPTIONS);
131
+ if (typeof ResizeObserver !== 'undefined') {
132
+ state.resizeObserver = new ResizeObserver(schedule);
133
+ // box: 'content-box' 명시 — border/padding 변경으로 인한 콜백 재호출 회피 (DOM mutation 무한 루프 방지).
134
+ if (state.pageTitle)
135
+ state.resizeObserver.observe(state.pageTitle, { box: 'content-box' });
136
+ }
137
+ if (typeof MutationObserver !== 'undefined') {
138
+ state.mutationObserver = new MutationObserver(scheduleRebind);
139
+ state.mutationObserver.observe(document.body, { childList: true, subtree: true });
140
+ }
141
+ // 첫 동기화
142
+ flush();
143
+ }
144
+ /** 동기화 중단 + 리소스 해제. 테스트 종료/HMR 정리 등에 사용. */
145
+ export function stopPositionSync() {
146
+ if (!state.started)
147
+ return;
148
+ state.started = false;
149
+ window.removeEventListener('scroll', schedule, SCROLL_OPTIONS);
150
+ window.removeEventListener('resize', schedule, RESIZE_OPTIONS);
151
+ state.resizeObserver?.disconnect();
152
+ state.resizeObserver = null;
153
+ state.mutationObserver?.disconnect();
154
+ state.mutationObserver = null;
155
+ if (state.rafId !== null) {
156
+ window.cancelAnimationFrame(state.rafId);
157
+ state.rafId = null;
158
+ }
159
+ if (state.rebindRafId !== null) {
160
+ window.cancelAnimationFrame(state.rebindRafId);
161
+ state.rebindRafId = null;
162
+ }
163
+ state.pageTitle = null;
164
+ // 정리 시 CSS 변수도 초기 상태로 되돌림
165
+ document.documentElement.style.removeProperty(CSS_VAR);
166
+ }
167
+ /** 외부에서 PageTitle.bottom 값을 강제로 다시 측정해 반영하고 싶을 때. */
168
+ export function syncNow() {
169
+ flush();
170
+ }
@@ -0,0 +1,8 @@
1
+ import type { NotificationAction, NotificationColor } from './const';
2
+ export declare function createWrapperElement(baseClass: string, color: NotificationColor, className?: string): HTMLElement;
3
+ export declare function buildClassName(baseClass: string, color: NotificationColor, className?: string): string;
4
+ export declare function renderSupportingText(supportingText?: string, className?: string, supportTextLink?: string): string;
5
+ export declare function renderActions(actions: NotificationAction[], wrapperClass: string): string;
6
+ export declare function bindNotificationEvents(element: HTMLElement, actions: NotificationAction[], onClose?: () => void, onRemove?: () => void): void;
7
+ export declare function setupAutoClose(autoClose: number, onClose?: () => void, onRemove?: () => void): number | undefined;
8
+ export declare const isMobile: () => boolean;
@@ -0,0 +1,115 @@
1
+ import { BREAKPOINT } from '../../../src/constant/breakpoint';
2
+ import { CLASS_NAMES } from './const';
3
+ // 공통 유틸리티 함수들
4
+ export function createWrapperElement(baseClass, color, className) {
5
+ const wrapper = document.createElement('div');
6
+ wrapper.className = buildClassName(baseClass, color, className);
7
+ wrapper.setAttribute('role', 'alert');
8
+ return wrapper;
9
+ }
10
+ export function buildClassName(baseClass, color, className) {
11
+ const classes = [baseClass, `${baseClass}--${color}`];
12
+ if (className) {
13
+ classes.push(className);
14
+ }
15
+ return classes.join(' ');
16
+ }
17
+ /**
18
+ * http/https 프로토콜만 허용한다.
19
+ * javascript:, data:, file: 등 위험 프로토콜로 인한 XSS / 외부 페이로드 로딩을 차단.
20
+ */
21
+ function isSafeUrl(url) {
22
+ try {
23
+ const parsed = new URL(url, window.location.origin);
24
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * HTML 속성 값에 안전하게 삽입하기 위한 최소 이스케이프.
32
+ * 본문(body)이 아닌 속성 컨텍스트에서의 따옴표 탈출(`" onclick="..."`)을 방지.
33
+ */
34
+ function escapeHtmlAttr(str) {
35
+ return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
36
+ }
37
+ export function renderSupportingText(supportingText, className, supportTextLink) {
38
+ if (!supportingText)
39
+ return '';
40
+ // 본문(supportingText) 자체는 이 함수의 호출 결과가 wrapper.innerHTML에
41
+ // 들어가기 전에 DOMPurify로 sanitize되므로 별도 이스케이프하지 않는다.
42
+ // 이렇게 해야 <br>, <strong> 등 서식용 태그가 그대로 동작한다.
43
+ if (supportTextLink && isSafeUrl(supportTextLink)) {
44
+ const safeLink = escapeHtmlAttr(supportTextLink);
45
+ return `<a href="${safeLink}" class="ncua-full-width-notification__link" rel="noopener noreferrer" target="_blank"><span class="${className}">${supportingText}</span></a>`;
46
+ }
47
+ // unsafe URL이거나 link가 없으면 링크 없이 텍스트만 렌더링
48
+ return `<span class="${className}">${supportingText}</span>`;
49
+ }
50
+ export function renderActions(actions, wrapperClass) {
51
+ // 액션이 없으면 빈 문자열 반환
52
+ if (!actions || actions.length === 0) {
53
+ return '';
54
+ }
55
+ const buttonsHtml = actions
56
+ .map((action) => {
57
+ const buttonHtml = `
58
+ <button
59
+ class="ncua-btn ncua-btn--sm ncua-btn--${action.hierarchy || 'text'}"
60
+ data-action="${action.label}-${action.hierarchy}"
61
+ >
62
+ ${action.label}
63
+ </button>`;
64
+ return buttonHtml;
65
+ })
66
+ .join('');
67
+ return `<div class="${wrapperClass}">${buttonsHtml}</div>`;
68
+ }
69
+ // 공통 이벤트 처리
70
+ export function bindNotificationEvents(element, actions, onClose, onRemove) {
71
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 닫기/액션 버튼 분기를 한 핸들러에 묶는 기존 구조를 유지. 분리 리팩토링은 별도 작업 범위.
72
+ element.addEventListener('click', (event) => {
73
+ const target = event.target;
74
+ // 닫기 버튼 클릭 처리
75
+ if (target.matches(`.${CLASS_NAMES.FULL_WIDTH.CLOSE_BUTTON}, .${CLASS_NAMES.FLOATING.CLOSE_BUTTON}`) ||
76
+ target.closest(`.${CLASS_NAMES.FULL_WIDTH.CLOSE_BUTTON}, .${CLASS_NAMES.FLOATING.CLOSE_BUTTON}`) ||
77
+ target.closest(`.${CLASS_NAMES.MESSAGE.CLOSE_BUTTON}`)) {
78
+ onClose?.();
79
+ onRemove?.();
80
+ return;
81
+ }
82
+ // 액션 버튼 클릭 처리
83
+ const actionButton = target.closest('.ncua-btn[data-action]');
84
+ if (actionButton && actions) {
85
+ const actionData = actionButton.getAttribute('data-action');
86
+ if (actionData) {
87
+ let matchedAction = null;
88
+ for (const action of actions) {
89
+ const expectedDataAction = `${action.label}-${action.hierarchy || 'link'}`;
90
+ if (actionData === expectedDataAction) {
91
+ matchedAction = action;
92
+ break;
93
+ }
94
+ }
95
+ if (matchedAction?.onClick) {
96
+ matchedAction.onClick();
97
+ }
98
+ }
99
+ }
100
+ });
101
+ }
102
+ // 자동 닫기 설정
103
+ export function setupAutoClose(autoClose, onClose, onRemove) {
104
+ if (autoClose > 0) {
105
+ return window.setTimeout(() => {
106
+ onClose?.();
107
+ onRemove?.();
108
+ }, autoClose);
109
+ }
110
+ return undefined;
111
+ }
112
+ // Mobile detection utility
113
+ export const isMobile = () => {
114
+ return window.innerWidth <= Number.parseInt(BREAKPOINT.mobile, 10);
115
+ };
@@ -0,0 +1,5 @@
1
+ export type ButtonCloseXSize = 'xs' | 'sm' | 'md' | 'lg';
2
+ export type ButtonCloseXTheme = 'dark' | 'light';
3
+ export declare const SVG_SIZE: Record<ButtonCloseXSize, number>;
4
+ export declare const X_CLOSE_SVG: (size: string) => string;
5
+ export declare function ButtonCloseX(size: ButtonCloseXSize, theme?: ButtonCloseXTheme, additionalClasses?: string, ariaLabel?: string, onClick?: () => void): string;
@@ -0,0 +1,33 @@
1
+ // 공통 X버튼 로직 (React ButtonCloseX 컴포넌트와 동일한 구조)
2
+ // React ButtonCloseX와 동일한 SVG 사이즈 매핑
3
+ export const SVG_SIZE = {
4
+ xs: 16,
5
+ sm: 20,
6
+ md: 20,
7
+ lg: 24,
8
+ };
9
+ // X버튼 SVG 아이콘
10
+ export const X_CLOSE_SVG = (size) => `<svg width="${size}" height="${size}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
11
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
12
+ </svg>`;
13
+ // X버튼 렌더링 유틸리티 (React ButtonCloseX와 동일한 인터페이스)
14
+ export function ButtonCloseX(size, theme = 'light', additionalClasses = '', ariaLabel = '닫기', onClick) {
15
+ const svgSize = SVG_SIZE[size];
16
+ const buttonId = `close-btn-${Math.random().toString(36).substr(2, 9)}`;
17
+ const buttonHTML = `
18
+ <button type="button" id="${buttonId}" class="ncua-button-close-x ncua-button-close-x--${size} ncua-button-close-x--${theme} ${additionalClasses}" aria-label="${ariaLabel}">
19
+ ${X_CLOSE_SVG(svgSize.toString())}
20
+ </button>
21
+ `;
22
+ // onClick이 제공된 경우 이벤트 바인딩
23
+ if (onClick) {
24
+ // DOM에 추가된 후 이벤트 바인딩을 위해 setTimeout 사용
25
+ setTimeout(() => {
26
+ const button = document.getElementById(buttonId);
27
+ if (button) {
28
+ button.addEventListener('click', onClick);
29
+ }
30
+ }, 0);
31
+ }
32
+ return buttonHTML;
33
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * HTML 문자열을 DOMPurify 기본 설정으로 sanitize한다.
3
+ *
4
+ * 제거되는 항목 (기본 설정):
5
+ * - `<script>`, `<iframe>`, `<object>` 등 실행 위험 태그
6
+ * - `onclick`, `onerror` 등 inline 이벤트 핸들러 속성
7
+ * - `href="javascript:..."` 같은 javascript: URL
8
+ *
9
+ * 유지되는 항목:
10
+ * - `<svg>`, `<button>`, `<div>`, `<span>`, `<input>` 등 일반 HTML/SVG 태그
11
+ * - `class`, `style`, `role`, `aria-*`, `data-*` 등 표현용 속성
12
+ * - SVG 표현 속성 (`viewBox`, `stroke`, `fill` 등)
13
+ *
14
+ * 이벤트 바인딩은 addEventListener로 별도 처리할 것.
15
+ */
16
+ export declare function sanitizeHtml(dirty: string): string;
17
+ /**
18
+ * 엘리먼트에 콘텐츠를 안전하게 설정한다.
19
+ * - string: sanitize 후 innerHTML 교체
20
+ * - HTMLElement: 기존 자식 제거 후 appendChild
21
+ */
22
+ export declare function setSafeInnerHTML(element: HTMLElement, content: string | HTMLElement): void;
@@ -0,0 +1,31 @@
1
+ import DOMPurify from 'dompurify';
2
+ /**
3
+ * HTML 문자열을 DOMPurify 기본 설정으로 sanitize한다.
4
+ *
5
+ * 제거되는 항목 (기본 설정):
6
+ * - `<script>`, `<iframe>`, `<object>` 등 실행 위험 태그
7
+ * - `onclick`, `onerror` 등 inline 이벤트 핸들러 속성
8
+ * - `href="javascript:..."` 같은 javascript: URL
9
+ *
10
+ * 유지되는 항목:
11
+ * - `<svg>`, `<button>`, `<div>`, `<span>`, `<input>` 등 일반 HTML/SVG 태그
12
+ * - `class`, `style`, `role`, `aria-*`, `data-*` 등 표현용 속성
13
+ * - SVG 표현 속성 (`viewBox`, `stroke`, `fill` 등)
14
+ *
15
+ * 이벤트 바인딩은 addEventListener로 별도 처리할 것.
16
+ */
17
+ export function sanitizeHtml(dirty) {
18
+ return DOMPurify.sanitize(dirty);
19
+ }
20
+ /**
21
+ * 엘리먼트에 콘텐츠를 안전하게 설정한다.
22
+ * - string: sanitize 후 innerHTML 교체
23
+ * - HTMLElement: 기존 자식 제거 후 appendChild
24
+ */
25
+ export function setSafeInnerHTML(element, content) {
26
+ if (content instanceof HTMLElement) {
27
+ element.replaceChildren(content);
28
+ return;
29
+ }
30
+ element.innerHTML = sanitizeHtml(content);
31
+ }
@@ -19,7 +19,7 @@ const ActionBar = ({ children, className, position = 'top', align = 'space-betwe
19
19
  'ncua-data-grid__action-bar--space-between': align === 'space-between',
20
20
  }), ...rest, children: children }));
21
21
  ActionBar.displayName = 'DataGrid.ActionBar';
22
- const DataGridTable = forwardRef(({ children, className, type = 'horizontal', fixedHeader, maxHeight, hoverable, selectable }, ref) => (_jsx("div", { ref: ref, className: classNames('ncua-data-grid__table', className), children: _jsx(NcuaTable, { className: "ncua-table--in-data-grid", type: type, fixedHeader: fixedHeader, maxHeight: maxHeight, hoverable: hoverable, selectable: selectable, children: children }) })));
22
+ const DataGridTable = forwardRef(({ children, className, type = 'horizontal', fixedHeader, maxHeight, hoverable, selectable, horizontalScroll, minWidth, }, ref) => (_jsx("div", { ref: ref, className: classNames('ncua-data-grid__table', className), children: _jsx(NcuaTable, { className: "ncua-table--in-data-grid", type: type, fixedHeader: fixedHeader, maxHeight: maxHeight, hoverable: hoverable, selectable: selectable, horizontalScroll: horizontalScroll, minWidth: minWidth, children: children }) })));
23
23
  DataGridTable.displayName = 'DataGrid.Table';
24
24
  const Pagination = ({ children, className, ...rest }) => (_jsx("div", { className: classNames('ncua-data-grid__pagination', className), ...rest, children: children }));
25
25
  Pagination.displayName = 'DataGrid.Pagination';