@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,171 @@
1
+ import { MEDIA_QUERY } from '../../../src/constant/breakpoint';
2
+ import { FeaturedIcon } from '../featuredIcon';
3
+ import { ButtonCloseX } from '../shared/ButtonCloseX';
4
+ import { sanitizeHtml } from '../utils/sanitize';
5
+ import { CLASS_NAMES, FLOATING_ICON_MAP, getSizes } from './const';
6
+ import { bindNotificationEvents, createWrapperElement, isMobile, renderActions, renderSupportingText, setupAutoClose } from './utils';
7
+ export class FloatingNotification {
8
+ constructor(options) {
9
+ this.options = {
10
+ color: 'neutral',
11
+ className: '',
12
+ actions: [],
13
+ autoClose: 0,
14
+ supportingText: undefined,
15
+ ...options
16
+ };
17
+ this.element = this.createElement();
18
+ this.bindEvents();
19
+ this.setupAutoClose();
20
+ this.setupMobileListener();
21
+ }
22
+ createElement() {
23
+ const {
24
+ title,
25
+ supportingText,
26
+ color,
27
+ className,
28
+ actions,
29
+ onClose
30
+ } = this.options;
31
+ // 플로팅 알림에서 info 색상 사용 시 neutral로 대체
32
+ let actualColor = color;
33
+ if (color === 'info') {
34
+ actualColor = 'neutral';
35
+ }
36
+ const wrapper = createWrapperElement(CLASS_NAMES.FLOATING.BASE, actualColor, className);
37
+ const iconFunction = FLOATING_ICON_MAP[actualColor];
38
+ const mobile = isMobile();
39
+ // FeaturedIcon 생성
40
+ let featuredIconElement = null;
41
+ if (iconFunction) {
42
+ const size = getSizes.featuredIcon(mobile);
43
+ const iconSize = getSizes.iconPixel(mobile);
44
+ const iconSvg = iconFunction(iconSize);
45
+ this.featuredIcon = FeaturedIcon.create({
46
+ svgString: iconSvg,
47
+ theme: 'dark-circle',
48
+ color: actualColor,
49
+ size: size
50
+ });
51
+ featuredIconElement = this.featuredIcon.getElement();
52
+ }
53
+ wrapper.innerHTML = sanitizeHtml(this.buildTemplate({
54
+ title,
55
+ supportingText,
56
+ actions,
57
+ onClose,
58
+ isMobile: mobile
59
+ }));
60
+ // FeaturedIcon을 container에 추가
61
+ if (featuredIconElement) {
62
+ const container = wrapper.querySelector(`.${CLASS_NAMES.FLOATING.CONTAINER}`);
63
+ if (container) {
64
+ container.insertBefore(featuredIconElement, container.firstChild);
65
+ }
66
+ }
67
+ return wrapper;
68
+ }
69
+ buildTemplate(params) {
70
+ const {
71
+ title,
72
+ supportingText,
73
+ actions,
74
+ onClose,
75
+ isMobile
76
+ } = params;
77
+ return `
78
+ <div class="${CLASS_NAMES.FLOATING.CONTENT}">
79
+ <div class="${CLASS_NAMES.FLOATING.CONTAINER}">
80
+ <div class="${CLASS_NAMES.FLOATING.TEXT_CONTAINER}">
81
+ <div class="${CLASS_NAMES.FLOATING.TITLE_WRAPPER}">
82
+ <span class="${CLASS_NAMES.FLOATING.TITLE}">${title}</span>
83
+ </div>
84
+ ${renderSupportingText(supportingText, CLASS_NAMES.FLOATING.SUPPORTING_TEXT)}
85
+ ${actions && actions.length > 0 ? renderActions(actions, CLASS_NAMES.FLOATING.ACTIONS) : ''}
86
+ </div>
87
+ </div>
88
+ </div>
89
+ ${this.renderCloseButton(onClose, isMobile)}
90
+ `;
91
+ }
92
+ renderCloseButton(onClose) {
93
+ let isMobile = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
94
+ if (!onClose) return '';
95
+ const size = getSizes.closeButton(isMobile);
96
+ return ButtonCloseX(size, 'light', CLASS_NAMES.FLOATING.CLOSE_BUTTON, '알림 닫기', onClose);
97
+ }
98
+ setupMobileListener() {
99
+ const mediaQuery = window.matchMedia(MEDIA_QUERY.mobile);
100
+ const handleChange = e => {
101
+ this.updateMobileStyles(e.matches);
102
+ if (e.matches && this.options.onClose) {
103
+ this.options.onClose();
104
+ this.remove();
105
+ }
106
+ };
107
+ handleChange({
108
+ matches: mediaQuery.matches
109
+ });
110
+ mediaQuery.addEventListener('change', handleChange);
111
+ this.mobileCleanup = () => {
112
+ mediaQuery.removeEventListener('change', handleChange);
113
+ };
114
+ }
115
+ updateMobileStyles(mobile) {
116
+ if (this.featuredIcon) {
117
+ const newSize = getSizes.featuredIcon(mobile);
118
+ this.featuredIcon.updateSize(newSize);
119
+ }
120
+ const closeButton = this.element.querySelector('.ncua-button-close-x');
121
+ if (closeButton) {
122
+ const newSize = getSizes.closeButton(mobile);
123
+ const newSvgSize = getSizes.closeButtonSvg(newSize);
124
+ closeButton.className = closeButton.className.replace(/ncua-button-close-x--(xs|sm)/, `ncua-button-close-x--${newSize}`);
125
+ const svg = closeButton.querySelector('svg');
126
+ if (svg) {
127
+ svg.setAttribute('width', newSvgSize.toString());
128
+ svg.setAttribute('height', newSvgSize.toString());
129
+ }
130
+ }
131
+ }
132
+ bindEvents() {
133
+ bindNotificationEvents(this.element, this.options.actions, this.options.onClose, () => this.remove());
134
+ }
135
+ setupAutoClose() {
136
+ this.autoCloseTimer = setupAutoClose(this.options.autoClose, this.options.onClose, () => this.remove());
137
+ }
138
+ // Public methods
139
+ getElement() {
140
+ return this.element;
141
+ }
142
+ appendTo(parent) {
143
+ parent.appendChild(this.element);
144
+ }
145
+ remove() {
146
+ if (this.autoCloseTimer) {
147
+ clearTimeout(this.autoCloseTimer);
148
+ this.autoCloseTimer = undefined;
149
+ }
150
+ if (this.element?.parentNode) {
151
+ this.element.parentNode.removeChild(this.element);
152
+ }
153
+ }
154
+ destroy() {
155
+ // FeaturedIcon 정리
156
+ if (this.featuredIcon) {
157
+ this.featuredIcon.destroy();
158
+ this.featuredIcon = undefined;
159
+ }
160
+ // 모바일 리스너 정리
161
+ if (this.mobileCleanup) {
162
+ this.mobileCleanup();
163
+ this.mobileCleanup = undefined;
164
+ }
165
+ this.remove();
166
+ }
167
+ // Static factory methods
168
+ static create(options) {
169
+ return new FloatingNotification(options);
170
+ }
171
+ }
@@ -0,0 +1,126 @@
1
+ import { sanitizeHtml } from '../utils/sanitize';
2
+ import { CLASS_NAMES, FULL_WIDTH_ICON_MAP, FULL_WIDTH_SIZES, SVG_ICONS } from './const';
3
+ import { bindNotificationEvents, createWrapperElement, renderActions, renderSupportingText, setupAutoClose } from './utils';
4
+ export class FullWidthNotification {
5
+ constructor(options) {
6
+ this.options = {
7
+ color: 'neutral',
8
+ className: '',
9
+ actions: [],
10
+ autoClose: 0,
11
+ supportingText: undefined,
12
+ ...options
13
+ };
14
+ this.element = this.createElement();
15
+ this.bindEvents();
16
+ this.setupAutoClose();
17
+ }
18
+ createElement() {
19
+ const {
20
+ title,
21
+ supportingText,
22
+ color,
23
+ className,
24
+ actions,
25
+ onClose,
26
+ supportTextLink,
27
+ onHidePermanently
28
+ } = this.options;
29
+ const wrapper = createWrapperElement(CLASS_NAMES.FULL_WIDTH.BASE, color, className);
30
+ const iconHtml = FULL_WIDTH_ICON_MAP[color](FULL_WIDTH_SIZES.ICON);
31
+ wrapper.innerHTML = sanitizeHtml(this.buildTemplate({
32
+ iconHtml,
33
+ title,
34
+ supportingText,
35
+ actions,
36
+ onClose,
37
+ supportTextLink,
38
+ onHidePermanently
39
+ }));
40
+ return wrapper;
41
+ }
42
+ buildTemplate(params) {
43
+ const {
44
+ iconHtml,
45
+ title,
46
+ supportingText,
47
+ actions,
48
+ onClose,
49
+ supportTextLink,
50
+ onHidePermanently
51
+ } = params;
52
+ return `
53
+ <div class="${CLASS_NAMES.FULL_WIDTH.CONTAINER}">
54
+ <div class="${CLASS_NAMES.FULL_WIDTH.CONTENT}">
55
+ <div class="${CLASS_NAMES.FULL_WIDTH.CONTENT_WRAPPER}">
56
+ <div class="${CLASS_NAMES.FULL_WIDTH.ICON}">${iconHtml}</div>
57
+ <div class="${CLASS_NAMES.FULL_WIDTH.TEXT_CONTAINER}">
58
+ <span class="${CLASS_NAMES.FULL_WIDTH.TITLE}">${title}</span>
59
+ ${renderSupportingText(supportingText, CLASS_NAMES.FULL_WIDTH.SUPPORTING_TEXT, supportTextLink)}
60
+ </div>
61
+ </div>
62
+ <div class="${CLASS_NAMES.FULL_WIDTH.ACTIONS_CONTAINER}">
63
+ ${actions && actions.length > 0 ? renderActions(actions, CLASS_NAMES.FULL_WIDTH.ACTIONS) : ''}
64
+ ${this.renderHidePermanentlyButton(onHidePermanently)}
65
+ ${this.renderCloseButton(onClose)}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ `;
70
+ }
71
+ renderHidePermanentlyButton(onHidePermanently) {
72
+ if (!onHidePermanently) return '';
73
+ return `
74
+ <button type="button" class="ncua-notification__action-button ncua-notification__action-button--text ncua-full-width-notification__link" data-hide-permanently="true">
75
+ 다시보지 않기
76
+ </button>
77
+ `;
78
+ }
79
+ renderCloseButton(onClose) {
80
+ if (!onClose) return '';
81
+ return `
82
+ <button type="button" class="${CLASS_NAMES.FULL_WIDTH.CLOSE_BUTTON}" aria-label="알림 닫기">
83
+ ${SVG_ICONS['x-close'](FULL_WIDTH_SIZES.CLOSE_BUTTON)}
84
+ </button>
85
+ `;
86
+ }
87
+ bindEvents() {
88
+ bindNotificationEvents(this.element, this.options.actions, this.options.onClose, () => this.remove());
89
+ // 다시보지 않기 버튼 이벤트 바인딩
90
+ if (this.options.onHidePermanently) {
91
+ const hidePermanentlyButton = this.element.querySelector('[data-hide-permanently="true"]');
92
+ if (hidePermanentlyButton) {
93
+ hidePermanentlyButton.addEventListener('click', () => {
94
+ this.options.onHidePermanently?.();
95
+ this.remove();
96
+ });
97
+ }
98
+ }
99
+ }
100
+ setupAutoClose() {
101
+ this.autoCloseTimer = setupAutoClose(this.options.autoClose, this.options.onClose, () => this.remove());
102
+ }
103
+ // Public methods
104
+ getElement() {
105
+ return this.element;
106
+ }
107
+ appendTo(parent) {
108
+ parent.appendChild(this.element);
109
+ }
110
+ remove() {
111
+ if (this.autoCloseTimer) {
112
+ clearTimeout(this.autoCloseTimer);
113
+ this.autoCloseTimer = undefined;
114
+ }
115
+ if (this.element?.parentNode) {
116
+ this.element.parentNode.removeChild(this.element);
117
+ }
118
+ }
119
+ destroy() {
120
+ this.remove();
121
+ }
122
+ // Static factory methods
123
+ static create(options) {
124
+ return new FullWidthNotification(options);
125
+ }
126
+ }
@@ -0,0 +1,152 @@
1
+ import { FeaturedIcon } from '../featuredIcon';
2
+ import { sanitizeHtml } from '../utils/sanitize';
3
+ import { CLASS_NAMES, FLOATING_ICON_MAP, MESSAGE_SIZES, SVG_ICONS } from './const';
4
+ import { bindNotificationEvents, createWrapperElement, renderActions, renderSupportingText, setupAutoClose } from './utils';
5
+ export class MessageNotification {
6
+ constructor(options) {
7
+ this.options = {
8
+ color: 'neutral',
9
+ className: '',
10
+ actions: [],
11
+ autoClose: 0,
12
+ supportingText: undefined,
13
+ ...options
14
+ };
15
+ this.element = this.createElement();
16
+ this.bindEvents();
17
+ this.setupAutoClose();
18
+ }
19
+ createElement() {
20
+ const {
21
+ title,
22
+ supportingText,
23
+ color,
24
+ className,
25
+ actions,
26
+ onClose,
27
+ onHidePermanently
28
+ } = this.options;
29
+ // message 타입은 neutral, error, warning, success 4가지 색상만 지원
30
+ let actualColor = color;
31
+ if (color === 'info') {
32
+ actualColor = 'neutral';
33
+ }
34
+ const wrapper = createWrapperElement(CLASS_NAMES.MESSAGE.BASE, actualColor, className);
35
+ const iconFunction = FLOATING_ICON_MAP[actualColor];
36
+ // FeaturedIcon 생성
37
+ let featuredIconElement = null;
38
+ if (iconFunction) {
39
+ const iconSvg = iconFunction(MESSAGE_SIZES.ICON_PIXEL);
40
+ this.featuredIcon = FeaturedIcon.create({
41
+ svgString: iconSvg,
42
+ theme: 'light-circle',
43
+ color: actualColor,
44
+ size: MESSAGE_SIZES.FEATURED_ICON
45
+ });
46
+ featuredIconElement = this.featuredIcon.getElement();
47
+ }
48
+ wrapper.innerHTML = sanitizeHtml(this.buildTemplate({
49
+ title,
50
+ supportingText,
51
+ actions,
52
+ onClose,
53
+ onHidePermanently
54
+ }));
55
+ // FeaturedIcon을 content-wrapper에 추가
56
+ if (featuredIconElement) {
57
+ const contentWrapper = wrapper.querySelector(`.${CLASS_NAMES.MESSAGE.CONTENT_WRAPPER}`);
58
+ if (contentWrapper) {
59
+ contentWrapper.insertBefore(featuredIconElement, contentWrapper.firstChild);
60
+ }
61
+ }
62
+ return wrapper;
63
+ }
64
+ buildTemplate(params) {
65
+ const {
66
+ title,
67
+ supportingText,
68
+ actions,
69
+ onClose,
70
+ onHidePermanently
71
+ } = params;
72
+ return `
73
+ <div class="${CLASS_NAMES.MESSAGE.CONTAINER}">
74
+ <div class="${CLASS_NAMES.MESSAGE.CONTENT}">
75
+ <div class="${CLASS_NAMES.MESSAGE.CONTENT_WRAPPER}">
76
+ <div class="${CLASS_NAMES.MESSAGE.TEXT_CONTAINER}">
77
+ <span class="${CLASS_NAMES.MESSAGE.TITLE}">${title}</span>
78
+ ${renderSupportingText(supportingText, CLASS_NAMES.MESSAGE.SUPPORTING_TEXT)}
79
+ </div>
80
+ </div>
81
+ <div class="${CLASS_NAMES.MESSAGE.ACTIONS_CONTAINER}">
82
+ ${actions && actions.length > 0 ? renderActions(actions, CLASS_NAMES.MESSAGE.ACTIONS) : ''}
83
+ </div>
84
+ <div class="${CLASS_NAMES.MESSAGE.ACTIONS_CONTAINER}">
85
+ ${this.renderHidePermanentlyButton(onHidePermanently)}
86
+ ${this.renderCloseButton(onClose)}
87
+ </div>
88
+ </div>
89
+ </div>
90
+ `;
91
+ }
92
+ renderHidePermanentlyButton(onHidePermanently) {
93
+ if (!onHidePermanently) return '';
94
+ return `
95
+ <button type="button" class="${CLASS_NAMES.COMMON.ACTION_BUTTON} ${CLASS_NAMES.COMMON.ACTION_BUTTON}--text ${CLASS_NAMES.MESSAGE.HIDE_LINK}" data-hide-permanently="true">
96
+ 다시 보지 않기
97
+ </button>
98
+ `;
99
+ }
100
+ renderCloseButton(onClose) {
101
+ if (!onClose) return '';
102
+ return `
103
+ <button type="button" class="${CLASS_NAMES.MESSAGE.CLOSE_BUTTON}" aria-label="알림 닫기">
104
+ ${SVG_ICONS['x-close'](MESSAGE_SIZES.CLOSE_BUTTON).replace('stroke="currentColor"', `stroke="#2F2F30"`)}
105
+ </button>
106
+ `;
107
+ }
108
+ bindEvents() {
109
+ bindNotificationEvents(this.element, this.options.actions, this.options.onClose, () => this.remove());
110
+ // 다시보지 않기 버튼 이벤트 바인딩
111
+ if (this.options.onHidePermanently) {
112
+ const hidePermanentlyButton = this.element.querySelector('[data-hide-permanently="true"]');
113
+ if (hidePermanentlyButton) {
114
+ hidePermanentlyButton.addEventListener('click', () => {
115
+ this.options.onHidePermanently?.();
116
+ this.remove();
117
+ });
118
+ }
119
+ }
120
+ }
121
+ setupAutoClose() {
122
+ this.autoCloseTimer = setupAutoClose(this.options.autoClose, this.options.onClose, () => this.remove());
123
+ }
124
+ // Public methods
125
+ getElement() {
126
+ return this.element;
127
+ }
128
+ appendTo(parent) {
129
+ parent.appendChild(this.element);
130
+ }
131
+ remove() {
132
+ if (this.autoCloseTimer) {
133
+ clearTimeout(this.autoCloseTimer);
134
+ this.autoCloseTimer = undefined;
135
+ }
136
+ if (this.element?.parentNode) {
137
+ this.element.parentNode.removeChild(this.element);
138
+ }
139
+ }
140
+ destroy() {
141
+ // FeaturedIcon 정리
142
+ if (this.featuredIcon) {
143
+ this.featuredIcon.destroy();
144
+ this.featuredIcon = undefined;
145
+ }
146
+ this.remove();
147
+ }
148
+ // Static factory methods
149
+ static create(options) {
150
+ return new MessageNotification(options);
151
+ }
152
+ }
@@ -0,0 +1,113 @@
1
+ import { FloatingNotification } from './FloatingNotification';
2
+ import { FullWidthNotification } from './FullWidthNotification';
3
+ import { MessageNotification } from './MessageNotification';
4
+ import { mountFloatingNotificationHost } from './positionSync';
5
+ // 통합 Notification 클래스 (글로벌 Web Notifications API와의 식별자 충돌을 피하기 위해 NcuaNotification 으로 명명)
6
+ export class NcuaNotification {
7
+ constructor(options) {
8
+ const {
9
+ type = 'floating',
10
+ ...baseOptions
11
+ } = options;
12
+ this.resolvedType = type;
13
+ if (type === 'message') {
14
+ this.instance = new MessageNotification(baseOptions);
15
+ } else if (type === 'full-width') {
16
+ this.instance = new FullWidthNotification(baseOptions);
17
+ } else {
18
+ this.instance = new FloatingNotification(baseOptions);
19
+ }
20
+ }
21
+ // 모든 메서드를 instance에 위임
22
+ getElement() {
23
+ return this.instance.getElement();
24
+ }
25
+ appendTo(parent) {
26
+ return this.instance.appendTo(parent);
27
+ }
28
+ remove() {
29
+ return this.instance.remove();
30
+ }
31
+ destroy() {
32
+ return this.instance.destroy();
33
+ }
34
+ show(parent) {
35
+ // 사용자가 부모를 명시한 경우 기존 동작 유지 (legacy 호환).
36
+ if (parent) {
37
+ this.instance.appendTo(parent);
38
+ return;
39
+ }
40
+ // §5.1 — floating 은 자동으로 우측 상단 호스트에 append.
41
+ // 시각적으로는 최신 토스트가 상단에 노출되고, 이전 토스트들은 그 아래로 12px 씩 겹쳐 쌓인다
42
+ // (LIFO, §5.4 "최신 상단" 준수. godomall5 등 기존 admin wrapper 와 동일 정책.
43
+ // 겹침 너비는 --ncua-floating-notification-stack-overlap CSS 변수로 오버라이드 가능).
44
+ //
45
+ // SSR 가드는 mountFloatingNotificationHost 내부의 isBrowserEnv() 로 위임 — null 이면
46
+ // 토스트가 표시될 환경이 아니라는 의미이므로 silently return 한다 (document.body 에 append
47
+ // 시도하는 fallthrough 는 SSR 에서 ReferenceError 를 일으키므로 금지).
48
+ if (this.resolvedType === 'floating') {
49
+ const host = mountFloatingNotificationHost();
50
+ if (host) {
51
+ this.instance.appendTo(host);
52
+ }
53
+ return;
54
+ }
55
+ // full-width / message는 기존대로 body에 append.
56
+ this.instance.appendTo(document.body);
57
+ }
58
+ // Static factory methods
59
+ static create(options) {
60
+ return new NcuaNotification(options);
61
+ }
62
+ // Convenience method for creating notifications with specific color
63
+ static createWithColor(color, title, supportingText, options) {
64
+ const autoCloseMap = {
65
+ success: 3000,
66
+ error: 5000,
67
+ warning: 3000,
68
+ info: 3000,
69
+ neutral: 0
70
+ };
71
+ return new NcuaNotification({
72
+ title,
73
+ supportingText,
74
+ color,
75
+ type: 'floating',
76
+ autoClose: autoCloseMap[color],
77
+ ...options
78
+ });
79
+ }
80
+ // 클래스를 생성하지 않고도 기본 floating 알림 생성을 하기 위한 함수들
81
+ static success(title, supportingText, options) {
82
+ const notification = NcuaNotification.createWithColor('success', title, supportingText, options);
83
+ notification.show();
84
+ return notification;
85
+ }
86
+ static error(title, supportingText, options) {
87
+ const notification = NcuaNotification.createWithColor('error', title, supportingText, options);
88
+ notification.show();
89
+ return notification;
90
+ }
91
+ static warning(title, supportingText, options) {
92
+ const notification = NcuaNotification.createWithColor('warning', title, supportingText, options);
93
+ notification.show();
94
+ return notification;
95
+ }
96
+ static info(title, supportingText, options) {
97
+ const notification = NcuaNotification.createWithColor('info', title, supportingText, options);
98
+ notification.show();
99
+ return notification;
100
+ }
101
+ static neutral(title, supportingText, options) {
102
+ const notification = NcuaNotification.createWithColor('neutral', title, supportingText, options);
103
+ notification.show();
104
+ return notification;
105
+ }
106
+ // showFullWidth method for backward compatibility
107
+ static showFullWidth(options) {
108
+ return new NcuaNotification({
109
+ ...options,
110
+ type: 'full-width'
111
+ });
112
+ }
113
+ }
@@ -0,0 +1,44 @@
1
+ // CSS class names
2
+ export const CLASS_NAMES = {
3
+ FULL_WIDTH: {
4
+ BASE: 'ncua-full-width-notification',
5
+ CONTAINER: 'ncua-full-width-notification__container',
6
+ CONTENT: 'ncua-full-width-notification__content',
7
+ CONTENT_WRAPPER: 'ncua-full-width-notification__content-wrapper',
8
+ ICON: 'ncua-full-width-notification__icon',
9
+ TEXT_CONTAINER: 'ncua-full-width-notification__text-container',
10
+ TITLE: 'ncua-full-width-notification__title',
11
+ SUPPORTING_TEXT: 'ncua-full-width-notification__supporting-text',
12
+ ACTIONS_CONTAINER: 'ncua-full-width-notification__actions-container',
13
+ ACTIONS: 'ncua-full-width-notification__actions',
14
+ CLOSE_BUTTON: 'ncua-full-width-notification__close-button'
15
+ },
16
+ FLOATING: {
17
+ BASE: 'ncua-floating-notification',
18
+ CONTAINER: 'ncua-floating-notification__container',
19
+ CONTENT: 'ncua-floating-notification__content',
20
+ TEXT_CONTAINER: 'ncua-floating-notification__text-container',
21
+ TITLE_WRAPPER: 'ncua-floating-notification__title-wrapper',
22
+ TITLE: 'ncua-floating-notification__title',
23
+ SUPPORTING_TEXT: 'ncua-floating-notification__supporting-text',
24
+ ACTIONS: 'ncua-floating-notification__actions',
25
+ CLOSE_BUTTON: 'ncua-floating-notification__close-button'
26
+ },
27
+ MESSAGE: {
28
+ BASE: 'ncua-message-notification',
29
+ CONTAINER: 'ncua-message-notification__container',
30
+ CONTENT: 'ncua-message-notification__content',
31
+ CONTENT_WRAPPER: 'ncua-message-notification__content-wrapper',
32
+ ICON: 'ncua-message-notification__icon',
33
+ TEXT_CONTAINER: 'ncua-message-notification__text-container',
34
+ TITLE: 'ncua-message-notification__title',
35
+ SUPPORTING_TEXT: 'ncua-message-notification__supporting-text',
36
+ ACTIONS_CONTAINER: 'ncua-message-notification__actions-container',
37
+ ACTIONS: 'ncua-message-notification__actions',
38
+ HIDE_LINK: 'ncua-message-notification__hide-link',
39
+ CLOSE_BUTTON: 'ncua-message-notification__close-button'
40
+ },
41
+ COMMON: {
42
+ ACTION_BUTTON: 'ncua-notification__action-button'
43
+ }
44
+ };
@@ -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,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,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
+ };