@ncds/ui-admin 1.8.3 → 1.8.5

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 (217) 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/index.js +33 -0
  23. package/dist/cjs/src/components/layout/block-container/BlockContainer.js +38 -0
  24. package/dist/cjs/src/components/layout/block-container/index.js +16 -0
  25. package/dist/cjs/src/components/layout/block-header/BlockHeader.js +107 -0
  26. package/dist/cjs/src/components/layout/block-header/SubTitle.js +56 -0
  27. package/dist/cjs/src/components/layout/block-header/index.js +27 -0
  28. package/dist/cjs/src/components/layout/page-title/PageTitle.js +95 -0
  29. package/dist/cjs/src/components/layout/page-title/index.js +16 -0
  30. package/dist/cjs/src/components/overlays/dropdown/Dropdown.js +47 -19
  31. package/dist/cjs/src/components/overlays/notification/CalloutNotification.js +25 -0
  32. package/dist/cjs/src/components/overlays/notification/FloatingNotification.js +86 -13
  33. package/dist/cjs/src/components/overlays/notification/Notification.js +7 -0
  34. package/dist/cjs/src/components/overlays/notification/host.js +12 -0
  35. package/dist/cjs/src/components/overlays/tooltip/Tooltip.js +57 -44
  36. package/dist/cjs/src/components/select-dropdown/SelectDropdown.js +2 -1
  37. package/dist/cjs/src/contexts/FloatingContext.js +11 -0
  38. package/dist/cjs/src/contexts/index.js +16 -0
  39. package/dist/cjs/src/hooks/index.js +11 -0
  40. package/dist/cjs/src/hooks/useFloatingPosition.js +78 -0
  41. package/dist/cjs/src/hooks/usePortalState.js +17 -0
  42. package/dist/cjs/src/utils/dropdown/maxSelection.js +35 -0
  43. package/dist/cjs/src/utils/dropdown/multiSelect.js +72 -15
  44. package/dist/esm/assets/scripts/featuredIcon.js +80 -0
  45. package/dist/esm/assets/scripts/notification/FloatingNotification.js +171 -0
  46. package/dist/esm/assets/scripts/notification/FullWidthNotification.js +126 -0
  47. package/dist/esm/assets/scripts/notification/MessageNotification.js +152 -0
  48. package/dist/esm/assets/scripts/notification/Notification.js +113 -0
  49. package/dist/esm/assets/scripts/notification/const/classNames.js +44 -0
  50. package/dist/esm/assets/scripts/notification/const/icons.js +25 -0
  51. package/dist/esm/assets/scripts/notification/const/index.js +4 -0
  52. package/dist/esm/assets/scripts/notification/const/sizes.js +40 -0
  53. package/dist/esm/assets/scripts/notification/const/types.js +8 -0
  54. package/dist/esm/assets/scripts/notification/index.js +10 -0
  55. package/dist/esm/assets/scripts/notification/positionSync.js +171 -0
  56. package/dist/esm/assets/scripts/notification/utils.js +109 -0
  57. package/dist/esm/assets/scripts/shared/ButtonCloseX.js +37 -0
  58. package/dist/esm/assets/scripts/utils/sanitize.js +31 -0
  59. package/dist/esm/src/components/data-display/data-grid/DataGrid.js +5 -1
  60. package/dist/esm/src/components/data-display/table/Table.js +118 -96
  61. package/dist/esm/src/components/data-display/table/useTableScrollbars.js +179 -0
  62. package/dist/esm/src/components/forms-and-input/combo-box/ComboBox.js +11 -10
  63. package/dist/esm/src/components/forms-and-input/image-file-input/ImageFileInput.js +5 -2
  64. package/dist/esm/src/components/forms-and-input/select-box/SelectBox.js +67 -29
  65. package/dist/esm/src/components/index.js +3 -0
  66. package/dist/esm/src/components/layout/block-container/BlockContainer.js +31 -0
  67. package/dist/esm/src/components/layout/block-container/index.js +1 -0
  68. package/dist/esm/src/components/layout/block-header/BlockHeader.js +100 -0
  69. package/dist/esm/src/components/layout/block-header/SubTitle.js +49 -0
  70. package/dist/esm/src/components/layout/block-header/index.js +2 -0
  71. package/dist/esm/src/components/layout/page-title/PageTitle.js +88 -0
  72. package/dist/esm/src/components/layout/page-title/index.js +1 -0
  73. package/dist/esm/src/components/overlays/dropdown/Dropdown.js +47 -19
  74. package/dist/esm/src/components/overlays/notification/CalloutNotification.js +19 -0
  75. package/dist/esm/src/components/overlays/notification/FloatingNotification.js +86 -14
  76. package/dist/esm/src/components/overlays/notification/Notification.js +7 -0
  77. package/dist/esm/src/components/overlays/notification/host.js +9 -0
  78. package/dist/esm/src/components/overlays/tooltip/Tooltip.js +58 -45
  79. package/dist/esm/src/components/select-dropdown/SelectDropdown.js +2 -1
  80. package/dist/esm/src/contexts/FloatingContext.js +4 -0
  81. package/dist/esm/src/contexts/index.js +1 -0
  82. package/dist/esm/src/hooks/index.js +1 -0
  83. package/dist/esm/src/hooks/useFloatingPosition.js +71 -0
  84. package/dist/esm/src/hooks/usePortalState.js +10 -0
  85. package/dist/esm/src/utils/dropdown/maxSelection.js +27 -0
  86. package/dist/esm/src/utils/dropdown/multiSelect.js +70 -14
  87. package/dist/temp/assets/scripts/featuredIcon.d.ts +22 -0
  88. package/dist/temp/assets/scripts/featuredIcon.js +79 -0
  89. package/dist/temp/assets/scripts/notification/FloatingNotification.d.ts +24 -0
  90. package/dist/temp/assets/scripts/notification/FloatingNotification.js +156 -0
  91. package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
  92. package/dist/temp/assets/scripts/notification/FullWidthNotification.js +111 -0
  93. package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +22 -0
  94. package/dist/temp/assets/scripts/notification/MessageNotification.js +140 -0
  95. package/dist/temp/assets/scripts/notification/Notification.d.ts +22 -0
  96. package/dist/temp/assets/scripts/notification/Notification.js +112 -0
  97. package/dist/temp/assets/scripts/notification/const/classNames.d.ts +43 -0
  98. package/dist/temp/assets/scripts/notification/const/classNames.js +44 -0
  99. package/dist/temp/assets/scripts/notification/const/icons.d.ts +25 -0
  100. package/dist/temp/assets/scripts/notification/const/icons.js +25 -0
  101. package/dist/temp/assets/scripts/notification/const/index.d.ts +5 -0
  102. package/dist/temp/assets/scripts/notification/const/index.js +4 -0
  103. package/dist/temp/assets/scripts/notification/const/sizes.d.ts +32 -0
  104. package/dist/temp/assets/scripts/notification/const/sizes.js +40 -0
  105. package/dist/temp/assets/scripts/notification/const/types.d.ts +19 -0
  106. package/dist/temp/assets/scripts/notification/const/types.js +8 -0
  107. package/dist/temp/assets/scripts/notification/index.d.ts +8 -0
  108. package/dist/temp/assets/scripts/notification/index.js +10 -0
  109. package/dist/temp/assets/scripts/notification/positionSync.d.ts +50 -0
  110. package/dist/temp/assets/scripts/notification/positionSync.js +170 -0
  111. package/dist/temp/assets/scripts/notification/utils.d.ts +8 -0
  112. package/dist/temp/assets/scripts/notification/utils.js +115 -0
  113. package/dist/temp/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
  114. package/dist/temp/assets/scripts/shared/ButtonCloseX.js +33 -0
  115. package/dist/temp/assets/scripts/utils/sanitize.d.ts +22 -0
  116. package/dist/temp/assets/scripts/utils/sanitize.js +31 -0
  117. package/dist/temp/src/components/data-display/data-grid/DataGrid.js +1 -1
  118. package/dist/temp/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
  119. package/dist/temp/src/components/data-display/table/Table.d.ts +4 -1
  120. package/dist/temp/src/components/data-display/table/Table.js +53 -68
  121. package/dist/temp/src/components/data-display/table/types.d.ts +18 -0
  122. package/dist/temp/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
  123. package/dist/temp/src/components/data-display/table/useTableScrollbars.js +136 -0
  124. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
  125. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.js +7 -11
  126. package/dist/temp/src/components/forms-and-input/image-file-input/ImageFileInput.js +1 -1
  127. package/dist/temp/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
  128. package/dist/temp/src/components/forms-and-input/select-box/SelectBox.js +30 -3
  129. package/dist/temp/src/components/index.d.ts +3 -0
  130. package/dist/temp/src/components/index.js +3 -0
  131. package/dist/temp/src/components/layout/block-container/BlockContainer.d.ts +19 -0
  132. package/dist/temp/src/components/layout/block-container/BlockContainer.js +11 -0
  133. package/dist/temp/src/components/layout/block-container/index.d.ts +1 -0
  134. package/dist/temp/src/components/layout/block-container/index.js +1 -0
  135. package/dist/temp/src/components/layout/block-header/BlockHeader.d.ts +23 -0
  136. package/dist/temp/src/components/layout/block-header/BlockHeader.js +21 -0
  137. package/dist/temp/src/components/layout/block-header/SubTitle.d.ts +19 -0
  138. package/dist/temp/src/components/layout/block-header/SubTitle.js +8 -0
  139. package/dist/temp/src/components/layout/block-header/index.d.ts +2 -0
  140. package/dist/temp/src/components/layout/block-header/index.js +2 -0
  141. package/dist/temp/src/components/layout/page-title/PageTitle.d.ts +22 -0
  142. package/dist/temp/src/components/layout/page-title/PageTitle.js +19 -0
  143. package/dist/temp/src/components/layout/page-title/index.d.ts +1 -0
  144. package/dist/temp/src/components/layout/page-title/index.js +1 -0
  145. package/dist/temp/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
  146. package/dist/temp/src/components/overlays/dropdown/Dropdown.js +35 -11
  147. package/dist/temp/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
  148. package/dist/temp/src/components/overlays/notification/CalloutNotification.js +6 -0
  149. package/dist/temp/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
  150. package/dist/temp/src/components/overlays/notification/FloatingNotification.js +81 -13
  151. package/dist/temp/src/components/overlays/notification/Notification.d.ts +18 -3
  152. package/dist/temp/src/components/overlays/notification/Notification.js +4 -0
  153. package/dist/temp/src/components/overlays/notification/host.d.ts +9 -0
  154. package/dist/temp/src/components/overlays/notification/host.js +9 -0
  155. package/dist/temp/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
  156. package/dist/temp/src/components/overlays/tooltip/Tooltip.js +25 -22
  157. package/dist/temp/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
  158. package/dist/temp/src/components/select-dropdown/SelectDropdown.js +2 -2
  159. package/dist/temp/src/contexts/FloatingContext.d.ts +6 -0
  160. package/dist/temp/src/contexts/FloatingContext.js +4 -0
  161. package/dist/temp/src/contexts/index.d.ts +1 -0
  162. package/dist/temp/src/contexts/index.js +1 -0
  163. package/dist/temp/src/hooks/index.d.ts +1 -0
  164. package/dist/temp/src/hooks/index.js +1 -0
  165. package/dist/temp/src/hooks/useFloatingPosition.d.ts +19 -0
  166. package/dist/temp/src/hooks/useFloatingPosition.js +55 -0
  167. package/dist/temp/src/hooks/usePortalState.d.ts +6 -0
  168. package/dist/temp/src/hooks/usePortalState.js +7 -0
  169. package/dist/temp/src/utils/dropdown/maxSelection.d.ts +24 -0
  170. package/dist/temp/src/utils/dropdown/maxSelection.js +28 -0
  171. package/dist/temp/src/utils/dropdown/multiSelect.d.ts +42 -2
  172. package/dist/temp/src/utils/dropdown/multiSelect.js +66 -13
  173. package/dist/types/assets/scripts/featuredIcon.d.ts +22 -0
  174. package/dist/types/assets/scripts/notification/FloatingNotification.d.ts +24 -0
  175. package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
  176. package/dist/types/assets/scripts/notification/MessageNotification.d.ts +22 -0
  177. package/dist/types/assets/scripts/notification/Notification.d.ts +22 -0
  178. package/dist/types/assets/scripts/notification/const/classNames.d.ts +43 -0
  179. package/dist/types/assets/scripts/notification/const/icons.d.ts +25 -0
  180. package/dist/types/assets/scripts/notification/const/index.d.ts +5 -0
  181. package/dist/types/assets/scripts/notification/const/sizes.d.ts +32 -0
  182. package/dist/types/assets/scripts/notification/const/types.d.ts +19 -0
  183. package/dist/types/assets/scripts/notification/index.d.ts +8 -0
  184. package/dist/types/assets/scripts/notification/positionSync.d.ts +50 -0
  185. package/dist/types/assets/scripts/notification/utils.d.ts +8 -0
  186. package/dist/types/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
  187. package/dist/types/assets/scripts/utils/sanitize.d.ts +22 -0
  188. package/dist/types/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
  189. package/dist/types/src/components/data-display/table/Table.d.ts +4 -1
  190. package/dist/types/src/components/data-display/table/types.d.ts +18 -0
  191. package/dist/types/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
  192. package/dist/types/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
  193. package/dist/types/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
  194. package/dist/types/src/components/index.d.ts +3 -0
  195. package/dist/types/src/components/layout/block-container/BlockContainer.d.ts +19 -0
  196. package/dist/types/src/components/layout/block-container/index.d.ts +1 -0
  197. package/dist/types/src/components/layout/block-header/BlockHeader.d.ts +23 -0
  198. package/dist/types/src/components/layout/block-header/SubTitle.d.ts +19 -0
  199. package/dist/types/src/components/layout/block-header/index.d.ts +2 -0
  200. package/dist/types/src/components/layout/page-title/PageTitle.d.ts +22 -0
  201. package/dist/types/src/components/layout/page-title/index.d.ts +1 -0
  202. package/dist/types/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
  203. package/dist/types/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
  204. package/dist/types/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
  205. package/dist/types/src/components/overlays/notification/Notification.d.ts +18 -3
  206. package/dist/types/src/components/overlays/notification/host.d.ts +9 -0
  207. package/dist/types/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
  208. package/dist/types/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
  209. package/dist/types/src/contexts/FloatingContext.d.ts +6 -0
  210. package/dist/types/src/contexts/index.d.ts +1 -0
  211. package/dist/types/src/hooks/index.d.ts +1 -0
  212. package/dist/types/src/hooks/useFloatingPosition.d.ts +19 -0
  213. package/dist/types/src/hooks/usePortalState.d.ts +6 -0
  214. package/dist/types/src/utils/dropdown/maxSelection.d.ts +24 -0
  215. package/dist/types/src/utils/dropdown/multiSelect.d.ts +42 -2
  216. package/dist/ui-admin/assets/styles/style.css +596 -64
  217. package/package.json +1 -1
@@ -13,6 +13,9 @@ var _element = require("@atlaskit/pragmatic-drag-and-drop-auto-scroll/element");
13
13
  var _closestEdge = require("@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge");
14
14
  var _uiAdminIcon = require("@ncds/ui-admin-icon");
15
15
  var _react = require("react");
16
+ var _reactDom = require("react-dom");
17
+ var _useFloatingPosition = require("../../../hooks/useFloatingPosition");
18
+ var _usePortalState = require("../../../hooks/usePortalState");
16
19
  var _button = require("../../action/button");
17
20
  var _utils = require("./utils");
18
21
  const DROPDOWN_ID_RADIX = 36;
@@ -149,6 +152,7 @@ const SortableConfigItem = _ref => {
149
152
  })]
150
153
  });
151
154
  };
155
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 액션/설정 두 variant 통합 + drag-and-drop 필수 분기
152
156
  const Dropdown = props => {
153
157
  const {
154
158
  trigger,
@@ -157,14 +161,28 @@ const Dropdown = props => {
157
161
  groups,
158
162
  className,
159
163
  opened = false,
160
- closeOnClickOutside = true
164
+ closeOnClickOutside = true,
165
+ usePortal
161
166
  } = props;
162
167
  const variant = props.variant ?? 'action';
163
168
  const closeOnClickItem = variant === 'action' ? props.closeOnClickItem ?? true : false;
164
169
  const [isOpen, setIsOpen] = (0, _react.useState)(opened);
165
170
  const dropdownRef = (0, _react.useRef)(null);
166
171
  const triggerRef = (0, _react.useRef)(null);
172
+ const menuRef = (0, _react.useRef)(null);
167
173
  const menuItemsRef = (0, _react.useRef)(null);
174
+ const {
175
+ shouldPortal,
176
+ portalContainer
177
+ } = (0, _usePortalState.usePortalState)(usePortal);
178
+ const floatingStyle = (0, _useFloatingPosition.useFloatingPosition)({
179
+ enabled: shouldPortal,
180
+ isOpen,
181
+ triggerRef,
182
+ floatingRef: menuRef,
183
+ direction: 'down',
184
+ align
185
+ });
168
186
  const dropdownIdRef = (0, _react.useRef)(`ncua-dropdown-${Math.random().toString(DROPDOWN_ID_RADIX).slice(DROPDOWN_ID_SLICE_START, DROPDOWN_ID_SLICE_END)}`);
169
187
  const dropdownId = dropdownIdRef.current;
170
188
  (0, _react.useEffect)(() => {
@@ -227,7 +245,10 @@ const Dropdown = props => {
227
245
  triggerRef.current?.focus();
228
246
  };
229
247
  const handleClickOutside = event => {
230
- if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
248
+ const target = event.target;
249
+ const insideContainer = dropdownRef.current?.contains(target) ?? false;
250
+ const insidePortaledMenu = menuRef.current?.contains(target) ?? false;
251
+ if (!insideContainer && !insidePortaledMenu) {
231
252
  setIsOpen(false);
232
253
  }
233
254
  };
@@ -239,6 +260,7 @@ const Dropdown = props => {
239
260
  }
240
261
  }
241
262
  };
263
+ // biome-ignore lint/correctness/useExhaustiveDependencies: handleClickOutside는 안정적 참조
242
264
  (0, _react.useEffect)(() => {
243
265
  if (closeOnClickOutside) {
244
266
  document.addEventListener('mousedown', handleClickOutside);
@@ -248,6 +270,7 @@ const Dropdown = props => {
248
270
  }
249
271
  }, [closeOnClickOutside]);
250
272
  // ESC 키로 닫고 trigger로 포커스 복귀
273
+ // biome-ignore lint/correctness/useExhaustiveDependencies: closeAndRestoreFocus는 안정적 참조
251
274
  (0, _react.useEffect)(() => {
252
275
  if (!isOpen) return;
253
276
  const handleEsc = event => {
@@ -428,26 +451,31 @@ const Dropdown = props => {
428
451
  });
429
452
  };
430
453
  const dropdownClasses = ['ncua-dropdown', className, align === 'right' ? 'ncua-dropdown--right' : ''].filter(Boolean).join(' ');
454
+ const menuClasses = ['ncua-dropdown__menu', shouldPortal ? 'ncua-dropdown__menu--portal' : ''].filter(Boolean).join(' ');
455
+ const menuNode = isOpen ? (0, _jsxRuntime.jsxs)("div", {
456
+ ref: menuRef,
457
+ className: menuClasses,
458
+ role: variant === 'config' ? 'dialog' : 'menu',
459
+ "aria-label": variant === 'config' ? '설정' : undefined,
460
+ style: shouldPortal && floatingStyle ? floatingStyle : undefined,
461
+ children: [renderHeader(), (0, _jsxRuntime.jsx)("div", {
462
+ ref: menuItemsRef,
463
+ className: "ncua-dropdown__menu-items",
464
+ children: groups.map(group => {
465
+ // config variant uses draft.order to drive the rendered order
466
+ const orderedItems = variant === 'config' && draft ? draft.order.map(id => group.items.find(i => i.id === id)).filter(i => i !== undefined) : group.items;
467
+ return (0, _jsxRuntime.jsx)("div", {
468
+ className: "ncua-dropdown__group",
469
+ children: orderedItems.map(item => variant === 'config' ? renderConfigItem(item, group.sortable === true) : renderActionItem(item))
470
+ }, group.items[0]?.id);
471
+ })
472
+ }), renderFooter()]
473
+ }) : null;
474
+ const portaledMenu = shouldPortal && portalContainer && menuNode ? /*#__PURE__*/(0, _reactDom.createPortal)(menuNode, portalContainer) : null;
431
475
  return (0, _jsxRuntime.jsxs)("div", {
432
476
  className: dropdownClasses,
433
477
  ref: dropdownRef,
434
- children: [renderTrigger(), isOpen && (0, _jsxRuntime.jsxs)("div", {
435
- className: "ncua-dropdown__menu",
436
- role: variant === 'config' ? 'dialog' : 'menu',
437
- "aria-label": variant === 'config' ? '설정' : undefined,
438
- children: [renderHeader(), (0, _jsxRuntime.jsx)("div", {
439
- ref: menuItemsRef,
440
- className: "ncua-dropdown__menu-items",
441
- children: groups.map(group => {
442
- // config variant uses draft.order to drive the rendered order
443
- const orderedItems = variant === 'config' && draft ? draft.order.map(id => group.items.find(i => i.id === id)).filter(i => i !== undefined) : group.items;
444
- return (0, _jsxRuntime.jsx)("div", {
445
- className: "ncua-dropdown__group",
446
- children: orderedItems.map(item => variant === 'config' ? renderConfigItem(item, group.sortable === true) : renderActionItem(item))
447
- }, group.items[0]?.id);
448
- })
449
- }), renderFooter()]
450
- })]
478
+ children: [renderTrigger(), !shouldPortal && menuNode, portaledMenu]
451
479
  });
452
480
  };
453
481
  exports.Dropdown = Dropdown;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.CalloutNotification = void 0;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _classnames = _interopRequireDefault(require("classnames"));
9
+ var _react = require("react");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ const CalloutNotification = exports.CalloutNotification = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
12
+ let {
13
+ color = 'neutral',
14
+ className,
15
+ title,
16
+ ...rest
17
+ } = _ref;
18
+ return (0, _jsxRuntime.jsx)("div", {
19
+ ref: ref,
20
+ className: (0, _classnames.default)('ncua-callout-notification', `ncua-callout-notification--${color}`, className),
21
+ ...rest,
22
+ children: title
23
+ });
24
+ });
25
+ CalloutNotification.displayName = 'CalloutNotification';
@@ -8,19 +8,65 @@ var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _uiAdminIcon = require("@ncds/ui-admin-icon");
9
9
  var _classnames = _interopRequireDefault(require("classnames"));
10
10
  var _react = require("react");
11
+ var _reactDom = require("react-dom");
11
12
  var _breakpoint = require("../../../constant/breakpoint");
12
13
  var _useMediaQuery = require("../../../hooks/useMediaQuery");
13
14
  var _button = require("../../action/button");
14
15
  var _ButtonCloseX = require("../../action/button/ButtonCloseX");
15
16
  var _FeaturedIcon = require("../../image-and-icons/featured-icon/FeaturedIcon");
17
+ var _host = require("./host");
16
18
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
19
+ // 호스트 싱글톤은 vanilla/React 양쪽이 동일한 함수를 공유한다 — host.ts 가 가까운 진입점 역할을
20
+ // 하므로 React 컴포넌트는 deep relative path 로 vanilla internals 를 직접 import 하지 않는다.
21
+
22
+ /**
23
+ * 색상별 a11y role.
24
+ * - error/warning → role="alert" (implicit aria-live="assertive": 즉시 발화)
25
+ * - 그 외 → role="status" (implicit aria-live="polite": 현재 발화 끝난 후 발화)
26
+ * role 이 implicit live-region 을 가져오므로 호스트 컨테이너에는 aria-live 를 두지 않는다.
27
+ */
28
+ const ASSERTIVE_COLORS = {
29
+ error: true,
30
+ warning: true
31
+ };
17
32
  const iconMap = {
18
33
  neutral: _uiAdminIcon.Pin02,
19
34
  error: _uiAdminIcon.AlertTriangle,
20
35
  warning: _uiAdminIcon.AlertCircle,
21
36
  success: _uiAdminIcon.CheckCircle
22
- // info는 floating에서는 지원하지 않음
37
+ // info full-width 전용이라 floating 아이콘 매핑 없음.
38
+ };
39
+ /**
40
+ * NotificationColor → FeaturedIconColor 매핑.
41
+ * `info` 는 FeaturedIconColor 에 존재하지 않으므로 `neutral` 로 안전 fallback.
42
+ * `satisfies` 가 매핑 누락을 컴파일 타임에 강제하므로 향후 색상이 추가되면 즉시 타입 에러로 잡힌다.
43
+ */
44
+ const iconColorMap = {
45
+ neutral: 'neutral',
46
+ error: 'error',
47
+ warning: 'warning',
48
+ success: 'success',
49
+ info: 'neutral'
23
50
  };
51
+ /**
52
+ * `.ncua-floating-notification-host` 싱글톤을 보장하고 반환하는 훅.
53
+ *
54
+ * 이름의 `useMount...` 는 단순 조회가 아닌 **DOM 부수효과** 를 동반함을 신호한다 —
55
+ * 내부적으로 `mountFloatingNotificationHost()` 를 호출해 document.body 에 호스트를 append 하고
56
+ * (없으면) 글로벌 scroll/resize/MutationObserver 까지 부착한다. 호스트는 페이지 lifetime
57
+ * 싱글톤이므로 언마운트 시 제거하지 않는다.
58
+ *
59
+ * `mountFloatingNotificationHost` 는 **동기 함수** 이므로 effect 안의 호출 → setHost 가 같은 tick
60
+ * 에 끝나 cancellation race 가 없다. cancel flag 는 의미 없어 두지 않는다.
61
+ */
62
+ function useMountFloatingNotificationHost(enabled) {
63
+ const [host, setHost] = (0, _react.useState)(null);
64
+ (0, _react.useEffect)(() => {
65
+ if (!enabled) return;
66
+ setHost((0, _host.mountFloatingNotificationHost)());
67
+ }, [enabled]);
68
+ return host;
69
+ }
24
70
  const FloatingNotification = exports.FloatingNotification = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
25
71
  let {
26
72
  title,
@@ -30,49 +76,65 @@ const FloatingNotification = exports.FloatingNotification = /*#__PURE__*/(0, _re
30
76
  className,
31
77
  actions,
32
78
  autoClose = 0,
79
+ portal = false,
33
80
  ...rest
34
81
  } = _ref;
35
82
  const [shouldRemove, setShouldRemove] = (0, _react.useState)(false);
36
- const iconColor = color;
37
83
  const featuredIconProps = {
38
84
  icon: iconMap[color] || _uiAdminIcon.Pin02,
39
85
  size: 'sm',
40
- color: iconColor,
86
+ color: iconColorMap[color],
41
87
  theme: 'dark-circle'
42
88
  };
43
89
  const isMobile = (0, _useMediaQuery.useMediaQuery)(_breakpoint.MEDIA_QUERY.mobile, {
44
90
  onMatched: onClose
45
91
  });
92
+ // onClose 는 매 렌더 새 함수 ref 일 수 있으므로 ref 로 latest 만 보관.
93
+ // → autoClose 타이머 effect 의 deps 에서 onClose 를 빼서 타이머 재시작 회피.
94
+ // render 본문에서 직접 할당 — useEffect 로 미루면 같은 commit 안에서 fire 되는 타이머가 stale onClose 를 볼 위험.
95
+ const onCloseRef = (0, _react.useRef)(onClose);
96
+ onCloseRef.current = onClose;
46
97
  // autoClose 타이머 관리
47
98
  const timerRef = (0, _react.useRef)(null);
99
+ // onClose 는 onCloseRef 로 latest 만 추적하므로 deps 에서 제외 (의도된 패턴).
48
100
  (0, _react.useEffect)(() => {
49
- // autoClose가 0보다 크면 무조건 타이머 설정
101
+ // autoClose 가 0 보다 크면 타이머 설정.
50
102
  if (autoClose > 0) {
51
103
  timerRef.current = setTimeout(() => {
52
- if (onClose) {
53
- onClose();
54
- }
55
- // DOM에서 바로 제거
104
+ onCloseRef.current?.();
105
+ // DOM 에서 바로 제거
56
106
  setShouldRemove(true);
57
107
  }, autoClose);
58
108
  }
59
- // cleanup 함수: 컴포넌트 언마운트 시 타이머 정리
109
+ // cleanup: 컴포넌트 언마운트 또는 autoClose 변경 시 타이머 정리
60
110
  return () => {
61
111
  if (timerRef.current) {
62
112
  clearTimeout(timerRef.current);
63
113
  timerRef.current = null;
64
114
  }
65
115
  };
66
- }, [autoClose, onClose]);
116
+ }, [autoClose]);
117
+ // portal=true 일 때만 호스트가 필요하다.
118
+ const host = useMountFloatingNotificationHost(portal);
119
+ // a11y — 카드 root 의 role 과 제목/본문 ID. 색상에 따라 alert(error/warning) / status(나머지) 로 분기.
120
+ const reactId = (0, _react.useId)();
121
+ const titleId = `${reactId}-title`;
122
+ const descId = `${reactId}-desc`;
123
+ const role = ASSERTIVE_COLORS[color] ? 'alert' : 'status';
67
124
  // DOM에서 완전히 제거
68
125
  if (shouldRemove) {
69
126
  return null;
70
127
  }
71
- return (0, _jsxRuntime.jsxs)("div", {
128
+ const card =
129
+ // a11y/예측가능성 — caller 가 className/id 등 일부는 덮어쓸 수 있게 {...rest} 를 먼저 펼치고,
130
+ // role/aria-labelledby/aria-describedby 같은 컴포넌트 본질 prop 은 그 뒤에 두어 우선 적용.
131
+ (0, _jsxRuntime.jsxs)("div", {
132
+ ...rest,
72
133
  ref: ref,
73
134
  className: (0, _classnames.default)('ncua-floating-notification', `ncua-floating-notification--${color}`, className),
74
- role: "alert",
75
- ...rest,
135
+ role: role,
136
+ "aria-labelledby": titleId,
137
+ "aria-describedby": supportingText ? descId : undefined,
76
138
  children: [(0, _jsxRuntime.jsx)("div", {
77
139
  className: "ncua-floating-notification__content",
78
140
  children: (0, _jsxRuntime.jsxs)("div", {
@@ -85,10 +147,12 @@ const FloatingNotification = exports.FloatingNotification = /*#__PURE__*/(0, _re
85
147
  children: [(0, _jsxRuntime.jsx)("div", {
86
148
  className: "ncua-floating-notification__title-wrapper",
87
149
  children: (0, _jsxRuntime.jsx)("span", {
150
+ id: titleId,
88
151
  className: "ncua-floating-notification__title",
89
152
  children: title
90
153
  })
91
154
  }), supportingText && (0, _jsxRuntime.jsx)("span", {
155
+ id: descId,
92
156
  className: "ncua-floating-notification__supporting-text",
93
157
  children: supportingText
94
158
  }), actions && (0, _jsxRuntime.jsx)("div", {
@@ -109,4 +173,13 @@ const FloatingNotification = exports.FloatingNotification = /*#__PURE__*/(0, _re
109
173
  onClick: onClose
110
174
  })]
111
175
  });
176
+ // 기본은 인라인 렌더 — 부모 JSX 트리에 카드를 그대로 둔다.
177
+ if (!portal) {
178
+ return card;
179
+ }
180
+ // portal=true 인데 SSR이거나 첫 렌더(호스트 미생성)에서는 null. useEffect 후 host 설정되면 재렌더되어 portal 마운트.
181
+ if (!host) {
182
+ return null;
183
+ }
184
+ return /*#__PURE__*/(0, _reactDom.createPortal)(card, host);
112
185
  });
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Notification = void 0;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _react = require("react");
9
+ var _CalloutNotification = require("./CalloutNotification");
9
10
  var _FloatingNotification = require("./FloatingNotification");
10
11
  var _FullWidthNotification = require("./FullWidthNotification");
11
12
  var _MessageNotification = require("./MessageNotification");
@@ -36,6 +37,12 @@ const Notification = exports.Notification = /*#__PURE__*/(0, _react.forwardRef)(
36
37
  ref: ref
37
38
  });
38
39
  }
40
+ if (type === 'callout') {
41
+ return (0, _jsxRuntime.jsx)(_CalloutNotification.CalloutNotification, {
42
+ color: color,
43
+ ...rest
44
+ });
45
+ }
39
46
  return null;
40
47
  });
41
48
  Notification.displayName = 'Notification';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "mountFloatingNotificationHost", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _notification.mountFloatingNotificationHost;
10
+ }
11
+ });
12
+ var _notification = require("../../../../assets/scripts/notification");
@@ -77,6 +77,42 @@ const computePanelCoords = (prefer, anchor, panel) => {
77
77
  calculatedPosition
78
78
  };
79
79
  };
80
+ const buildPanelStyle = (disablePortal, coords, visible, zIndex) => {
81
+ const opacity = visible ? 1 : 0;
82
+ if (disablePortal) return {
83
+ opacity
84
+ };
85
+ return {
86
+ top: `${coords.top}px`,
87
+ left: `${coords.left}px`,
88
+ opacity,
89
+ ...(zIndex && {
90
+ zIndex
91
+ })
92
+ };
93
+ };
94
+ const renderIcon = (iconStyle, iconType, iconSize, iconColor) => {
95
+ if (iconStyle === 'help-circle') {
96
+ return iconType === 'stroke' ? (0, _jsxRuntime.jsx)(_uiAdminIcon.HelpCircle, {
97
+ width: iconSize,
98
+ height: iconSize,
99
+ color: iconColor
100
+ }) : (0, _jsxRuntime.jsx)(_uiAdminIcon.HelpCircleFill, {
101
+ width: iconSize,
102
+ height: iconSize,
103
+ color: iconColor
104
+ });
105
+ }
106
+ return iconType === 'stroke' ? (0, _jsxRuntime.jsx)(_uiAdminIcon.AlertCircle, {
107
+ width: iconSize,
108
+ height: iconSize,
109
+ color: iconColor
110
+ }) : (0, _jsxRuntime.jsx)(_uiAdminIcon.AlertCircleFill, {
111
+ width: iconSize,
112
+ height: iconSize,
113
+ color: iconColor
114
+ });
115
+ };
80
116
  const Tooltip = _ref => {
81
117
  let {
82
118
  tooltipType = 'white',
@@ -90,7 +126,9 @@ const Tooltip = _ref => {
90
126
  iconColor = 'var(--gray-300)',
91
127
  iconStyle = 'help-circle',
92
128
  className,
93
- zIndex
129
+ zIndex,
130
+ forceVisible,
131
+ disablePortal = false
94
132
  } = _ref;
95
133
  const iconSize = size === 'sm' ? ICON_SIZE_SM : ICON_SIZE_DEFAULT;
96
134
  const anchorRef = (0, _react.useRef)(null);
@@ -104,6 +142,7 @@ const Tooltip = _ref => {
104
142
  const [calculatedPosition, setCalculatedPosition] = (0, _react.useState)(position === 'auto' ? 'bottom' : position);
105
143
  const [isVisible, setIsVisible] = (0, _react.useState)(false);
106
144
  const [isManuallyClose, setIsManuallyClose] = (0, _react.useState)(false);
145
+ const effectiveVisible = forceVisible ?? isVisible;
107
146
  (0, _react.useEffect)(() => {
108
147
  setMounted(true);
109
148
  return () => {
@@ -114,7 +153,7 @@ const Tooltip = _ref => {
114
153
  };
115
154
  }, []);
116
155
  const updatePosition = (0, _react.useCallback)(() => {
117
- if (!anchorRef.current || !panelRef.current) return;
156
+ if (disablePortal || !anchorRef.current || !panelRef.current) return;
118
157
  const anchor = anchorRef.current.getBoundingClientRect();
119
158
  const panel = panelRef.current.getBoundingClientRect();
120
159
  const next = computePanelCoords(position, anchor, panel);
@@ -123,9 +162,9 @@ const Tooltip = _ref => {
123
162
  left: next.left
124
163
  });
125
164
  setCalculatedPosition(next.calculatedPosition);
126
- }, [position]);
165
+ }, [position, disablePortal]);
127
166
  (0, _react.useEffect)(() => {
128
- if (!isVisible) return;
167
+ if (!effectiveVisible || disablePortal) return;
129
168
  updatePosition();
130
169
  window.addEventListener('resize', updatePosition, {
131
170
  passive: true
@@ -140,13 +179,10 @@ const Tooltip = _ref => {
140
179
  capture: true
141
180
  });
142
181
  };
143
- }, [isVisible, updatePosition]);
182
+ }, [effectiveVisible, updatePosition, disablePortal]);
144
183
  const handleMouseEnter = (0, _react.useCallback)(() => {
145
184
  if (isManuallyClose) return;
146
- // opacity 전환 전에 좌표 확정 (ref 가드는 updatePosition 내부)
147
185
  updatePosition();
148
- // 웹폰트 로드·max-content 재계산 등 비동기 layout 안정화 후 한 번 더 보정
149
- // 빠른 hover in/out 시 이전 프레임 요청은 취소해 중복/unmount 후 실행 방지
150
186
  if (rafIdRef.current !== null) cancelAnimationFrame(rafIdRef.current);
151
187
  rafIdRef.current = requestAnimationFrame(() => {
152
188
  rafIdRef.current = null;
@@ -170,18 +206,13 @@ const Tooltip = _ref => {
170
206
  'ncua-tooltip--stroke': iconType === 'stroke',
171
207
  'ncua-tooltip--auto': position === 'auto'
172
208
  }, className), [size, type, hideArrow, iconType, position, className]);
173
- const panelClassName = (0, _react.useMemo)(() => (0, _classnames.default)('ncua-tooltip-panel', 'ncua-tooltip__bg', `ncua-tooltip__bg--${tooltipType}`, `ncua-tooltip__bg--${finalPosition}`, {
174
- 'ncua-tooltip__bg--visible': isVisible,
175
- 'ncua-tooltip__bg--force-hidden': isManuallyClose
176
- }), [tooltipType, finalPosition, isVisible, isManuallyClose]);
177
- const panelStyle = {
178
- top: `${coords.top}px`,
179
- left: `${coords.left}px`,
180
- opacity: isVisible ? 1 : 0,
181
- ...(zIndex && {
182
- zIndex
183
- })
184
- };
209
+ const panelClassName = (0, _react.useMemo)(() => (0, _classnames.default)({
210
+ 'ncua-tooltip-panel': !disablePortal
211
+ }, 'ncua-tooltip__bg', `ncua-tooltip__bg--${tooltipType}`, `ncua-tooltip__bg--${finalPosition}`, {
212
+ 'ncua-tooltip__bg--visible': effectiveVisible,
213
+ 'ncua-tooltip__bg--force-hidden': isManuallyClose && !forceVisible
214
+ }), [tooltipType, finalPosition, effectiveVisible, isManuallyClose, forceVisible, disablePortal]);
215
+ const panelStyle = buildPanelStyle(disablePortal, coords, effectiveVisible, zIndex);
185
216
  const portalTarget = mounted ? resolvePortalTarget() : null;
186
217
  const panel = (0, _jsxRuntime.jsxs)("span", {
187
218
  ref: panelRef,
@@ -201,30 +232,12 @@ const Tooltip = _ref => {
201
232
  "aria-label": "\uD234\uD301 \uB2EB\uAE30"
202
233
  })]
203
234
  });
204
- return (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
205
- children: [(0, _jsxRuntime.jsxs)("span", {
206
- ref: anchorRef,
207
- className: tooltipClassName,
208
- onMouseEnter: handleMouseEnter,
209
- onMouseLeave: handleMouseLeave,
210
- children: [iconStyle === 'help-circle' && (iconType === 'stroke' ? (0, _jsxRuntime.jsx)(_uiAdminIcon.HelpCircle, {
211
- width: iconSize,
212
- height: iconSize,
213
- color: iconColor
214
- }) : (0, _jsxRuntime.jsx)(_uiAdminIcon.HelpCircleFill, {
215
- width: iconSize,
216
- height: iconSize,
217
- color: iconColor
218
- })), iconStyle === 'alert-circle' && (iconType === 'stroke' ? (0, _jsxRuntime.jsx)(_uiAdminIcon.AlertCircle, {
219
- width: iconSize,
220
- height: iconSize,
221
- color: iconColor
222
- }) : (0, _jsxRuntime.jsx)(_uiAdminIcon.AlertCircleFill, {
223
- width: iconSize,
224
- height: iconSize,
225
- color: iconColor
226
- }))]
227
- }), portalTarget && /*#__PURE__*/(0, _reactDom.createPortal)(panel, portalTarget)]
235
+ return (0, _jsxRuntime.jsxs)("span", {
236
+ ref: anchorRef,
237
+ className: tooltipClassName,
238
+ onMouseEnter: handleMouseEnter,
239
+ onMouseLeave: handleMouseLeave,
240
+ children: [renderIcon(iconStyle, iconType, iconSize, iconColor), disablePortal ? panel : portalTarget && /*#__PURE__*/(0, _reactDom.createPortal)(panel, portalTarget)]
228
241
  });
229
242
  };
230
243
  exports.Tooltip = Tooltip;
@@ -27,6 +27,7 @@ const SelectDropdown = exports.SelectDropdown = /*#__PURE__*/(0, _react.forwardR
27
27
  multiple = false,
28
28
  showFooterButtons = false,
29
29
  selectAllButtonText = '전체 선택',
30
+ showSelectAllAction = true,
30
31
  onSelectAll,
31
32
  onEdit,
32
33
  onComplete,
@@ -92,7 +93,7 @@ const SelectDropdown = exports.SelectDropdown = /*#__PURE__*/(0, _react.forwardR
92
93
  className: "ncua-select-dropdown__footer-buttons",
93
94
  children: [(0, _jsxRuntime.jsx)("div", {
94
95
  className: "ncua-select-dropdown__footer-left",
95
- children: multiple && (0, _jsxRuntime.jsx)(_button.Button, {
96
+ children: multiple && showSelectAllAction && (0, _jsxRuntime.jsx)(_button.Button, {
96
97
  label: selectAllButtonText,
97
98
  hierarchy: "text",
98
99
  size: "xs",
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useFloatingContext = exports.FloatingProvider = void 0;
7
+ var _react = require("react");
8
+ const FloatingContext = /*#__PURE__*/(0, _react.createContext)(null);
9
+ const FloatingProvider = exports.FloatingProvider = FloatingContext.Provider;
10
+ const useFloatingContext = () => (0, _react.useContext)(FloatingContext);
11
+ exports.useFloatingContext = useFloatingContext;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ var _FloatingContext = require("./FloatingContext");
7
+ Object.keys(_FloatingContext).forEach(function (key) {
8
+ if (key === "default" || key === "__esModule") return;
9
+ if (key in exports && exports[key] === _FloatingContext[key]) return;
10
+ Object.defineProperty(exports, key, {
11
+ enumerable: true,
12
+ get: function () {
13
+ return _FloatingContext[key];
14
+ }
15
+ });
16
+ });
@@ -25,6 +25,17 @@ Object.keys(_useCallbackRef).forEach(function (key) {
25
25
  }
26
26
  });
27
27
  });
28
+ var _useFloatingPosition = require("./useFloatingPosition");
29
+ Object.keys(_useFloatingPosition).forEach(function (key) {
30
+ if (key === "default" || key === "__esModule") return;
31
+ if (key in exports && exports[key] === _useFloatingPosition[key]) return;
32
+ Object.defineProperty(exports, key, {
33
+ enumerable: true,
34
+ get: function () {
35
+ return _useFloatingPosition[key];
36
+ }
37
+ });
38
+ });
28
39
  var _useMediaQuery = require("./useMediaQuery");
29
40
  Object.keys(_useMediaQuery).forEach(function (key) {
30
41
  if (key === "default" || key === "__esModule") return;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.useFloatingPosition = void 0;
7
+ var _react = require("react");
8
+ const FLOATING_Z_INDEX = 1500;
9
+ const useFloatingPosition = _ref => {
10
+ let {
11
+ enabled,
12
+ isOpen,
13
+ triggerRef,
14
+ floatingRef,
15
+ direction,
16
+ offset = 4,
17
+ align = 'left',
18
+ matchTriggerWidth = false
19
+ } = _ref;
20
+ const [style, setStyle] = (0, _react.useState)(null);
21
+ (0, _react.useLayoutEffect)(() => {
22
+ if (!enabled || !isOpen) {
23
+ setStyle(null);
24
+ return;
25
+ }
26
+ const trigger = triggerRef.current;
27
+ if (!trigger) return;
28
+ const calculatePosition = (trigger, floatingHeight, floatingWidth) => {
29
+ const top = direction === 'up' ? trigger.top - floatingHeight - offset : trigger.bottom + offset;
30
+ const left = align === 'right' ? trigger.right - floatingWidth : trigger.left;
31
+ return {
32
+ top,
33
+ left
34
+ };
35
+ };
36
+ const update = () => {
37
+ const t = triggerRef.current;
38
+ const f = floatingRef.current;
39
+ if (!t) return;
40
+ const r = t.getBoundingClientRect();
41
+ const floatingHeight = f?.offsetHeight ?? 0;
42
+ const floatingWidth = matchTriggerWidth ? r.width : f?.offsetWidth ?? r.width;
43
+ const {
44
+ top,
45
+ left
46
+ } = calculatePosition(r, floatingHeight, floatingWidth);
47
+ // matchTriggerWidth=true: width는 max-content(콘텐츠 자연 너비), min-width만 trigger 너비로 보장.
48
+ // 옵션이 짧으면 trigger 너비, 길면 자연 확장 — 기존 absolute 모드와 동일한 UX.
49
+ setStyle({
50
+ position: 'fixed',
51
+ top,
52
+ left,
53
+ zIndex: FLOATING_Z_INDEX,
54
+ ...(matchTriggerWidth ? {
55
+ minWidth: r.width
56
+ } : {})
57
+ });
58
+ };
59
+ update();
60
+ window.addEventListener('scroll', update, true);
61
+ window.addEventListener('resize', update);
62
+ const triggerObserver = new ResizeObserver(update);
63
+ triggerObserver.observe(trigger);
64
+ let floatingObserver;
65
+ if (floatingRef.current) {
66
+ floatingObserver = new ResizeObserver(update);
67
+ floatingObserver.observe(floatingRef.current);
68
+ }
69
+ return () => {
70
+ window.removeEventListener('scroll', update, true);
71
+ window.removeEventListener('resize', update);
72
+ triggerObserver.disconnect();
73
+ floatingObserver?.disconnect();
74
+ };
75
+ }, [enabled, isOpen, direction, offset, align, matchTriggerWidth, triggerRef, floatingRef]);
76
+ return style;
77
+ };
78
+ exports.useFloatingPosition = useFloatingPosition;