@ncds/ui-admin 1.8.24 → 1.8.25-alpha.1

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 (86) hide show
  1. package/dist/cjs/src/components/feedback-and-status/badge/Badge.js +1 -1
  2. package/dist/cjs/src/components/forms-and-input/combo-box/ComboBox.js +109 -28
  3. package/dist/cjs/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +581 -0
  4. package/dist/cjs/src/components/navigation/bread-crumb/BreadCrumb.js +14 -6
  5. package/dist/cjs/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +207 -0
  6. package/dist/cjs/src/generated/scoped-css.js +1 -1
  7. package/dist/cjs/src/hooks/__tests__/useFloatingPosition.ssr.test.js +29 -0
  8. package/dist/cjs/src/hooks/__tests__/useFloatingPosition.test.js +322 -0
  9. package/dist/cjs/src/hooks/dropdown/useDropdown.js +2 -1
  10. package/dist/cjs/src/hooks/useFloatingPosition.js +14 -53
  11. package/dist/cjs/src/utils/dropdown/__tests__/dropdownUtils.test.js +316 -0
  12. package/dist/cjs/src/utils/dropdown/dropdownUtils.js +8 -12
  13. package/dist/cjs/src/utils/portal-position/__tests__/portalPosition.test.js +227 -0
  14. package/dist/cjs/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +397 -0
  15. package/dist/cjs/src/utils/portal-position/index.js +16 -0
  16. package/dist/cjs/src/utils/portal-position/portalPosition.js +162 -0
  17. package/dist/esm/src/components/feedback-and-status/badge/Badge.js +1 -1
  18. package/dist/esm/src/components/forms-and-input/combo-box/ComboBox.js +109 -28
  19. package/dist/esm/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +578 -0
  20. package/dist/esm/src/components/navigation/bread-crumb/BreadCrumb.js +14 -6
  21. package/dist/esm/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +204 -0
  22. package/dist/esm/src/generated/scoped-css.js +1 -1
  23. package/dist/esm/src/hooks/__tests__/useFloatingPosition.ssr.test.js +26 -0
  24. package/dist/esm/src/hooks/__tests__/useFloatingPosition.test.js +319 -0
  25. package/dist/esm/src/hooks/dropdown/useDropdown.js +2 -1
  26. package/dist/esm/src/hooks/useFloatingPosition.js +15 -54
  27. package/dist/esm/src/utils/dropdown/__tests__/dropdownUtils.test.js +314 -0
  28. package/dist/esm/src/utils/dropdown/dropdownUtils.js +8 -12
  29. package/dist/esm/src/utils/portal-position/__tests__/portalPosition.test.js +225 -0
  30. package/dist/esm/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +394 -0
  31. package/dist/esm/src/utils/portal-position/index.js +1 -0
  32. package/dist/esm/src/utils/portal-position/portalPosition.js +150 -0
  33. package/dist/temp/assets/scripts/notification/const/sizes.d.ts +1 -1
  34. package/dist/temp/src/components/action/button-group/ButtonGroup.d.ts +1 -1
  35. package/dist/temp/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
  36. package/dist/temp/src/components/feedback-and-status/badge/Badge.js +1 -1
  37. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.d.ts +6 -0
  38. package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.js +54 -4
  39. package/dist/temp/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.d.ts +1 -0
  40. package/dist/temp/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.js +418 -0
  41. package/dist/temp/src/components/navigation/bread-crumb/BreadCrumb.d.ts +3 -1
  42. package/dist/temp/src/components/navigation/bread-crumb/BreadCrumb.js +9 -5
  43. package/dist/temp/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.d.ts +1 -0
  44. package/dist/temp/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.js +161 -0
  45. package/dist/temp/src/generated/scoped-css.js +1 -1
  46. package/dist/temp/src/hooks/__tests__/useFloatingPosition.ssr.test.d.ts +1 -0
  47. package/dist/temp/src/hooks/__tests__/useFloatingPosition.ssr.test.js +26 -0
  48. package/dist/temp/src/hooks/__tests__/useFloatingPosition.test.d.ts +1 -0
  49. package/dist/temp/src/hooks/__tests__/useFloatingPosition.test.js +205 -0
  50. package/dist/temp/src/hooks/dropdown/useDropdown.d.ts +3 -1
  51. package/dist/temp/src/hooks/dropdown/useDropdown.js +2 -2
  52. package/dist/temp/src/hooks/dropdown/useDropdownPosition.d.ts +3 -3
  53. package/dist/temp/src/hooks/useFloatingPosition.d.ts +6 -4
  54. package/dist/temp/src/hooks/useFloatingPosition.js +14 -47
  55. package/dist/temp/src/types/dropdown/dropdown.d.ts +2 -1
  56. package/dist/temp/src/utils/dropdown/__tests__/dropdownUtils.test.d.ts +1 -0
  57. package/dist/temp/src/utils/dropdown/__tests__/dropdownUtils.test.js +174 -0
  58. package/dist/temp/src/utils/dropdown/dropdownUtils.js +8 -9
  59. package/dist/temp/src/utils/portal-position/__tests__/portalPosition.test.d.ts +1 -0
  60. package/dist/temp/src/utils/portal-position/__tests__/portalPosition.test.js +181 -0
  61. package/dist/temp/src/utils/portal-position/__tests__/portalPosition.tracking.test.d.ts +1 -0
  62. package/dist/temp/src/utils/portal-position/__tests__/portalPosition.tracking.test.js +301 -0
  63. package/dist/temp/src/utils/portal-position/index.d.ts +1 -0
  64. package/dist/temp/src/utils/portal-position/index.js +1 -0
  65. package/dist/temp/src/utils/portal-position/portalPosition.d.ts +67 -0
  66. package/dist/temp/src/utils/portal-position/portalPosition.js +88 -0
  67. package/dist/types/assets/scripts/notification/const/sizes.d.ts +1 -1
  68. package/dist/types/src/components/action/button-group/ButtonGroup.d.ts +1 -1
  69. package/dist/types/src/components/feedback-and-status/badge/Badge.d.ts +1 -1
  70. package/dist/types/src/components/forms-and-input/combo-box/ComboBox.d.ts +6 -0
  71. package/dist/types/src/components/forms-and-input/combo-box/__tests__/ComboBox.portal.test.d.ts +1 -0
  72. package/dist/types/src/components/navigation/bread-crumb/BreadCrumb.d.ts +3 -1
  73. package/dist/types/src/components/navigation/bread-crumb/__tests__/BreadCrumb.test.d.ts +1 -0
  74. package/dist/types/src/hooks/__tests__/useFloatingPosition.ssr.test.d.ts +1 -0
  75. package/dist/types/src/hooks/__tests__/useFloatingPosition.test.d.ts +1 -0
  76. package/dist/types/src/hooks/dropdown/useDropdown.d.ts +3 -1
  77. package/dist/types/src/hooks/dropdown/useDropdownPosition.d.ts +3 -3
  78. package/dist/types/src/hooks/useFloatingPosition.d.ts +6 -4
  79. package/dist/types/src/types/dropdown/dropdown.d.ts +2 -1
  80. package/dist/types/src/utils/dropdown/__tests__/dropdownUtils.test.d.ts +1 -0
  81. package/dist/types/src/utils/portal-position/__tests__/portalPosition.test.d.ts +1 -0
  82. package/dist/types/src/utils/portal-position/__tests__/portalPosition.tracking.test.d.ts +1 -0
  83. package/dist/types/src/utils/portal-position/index.d.ts +1 -0
  84. package/dist/types/src/utils/portal-position/portalPosition.d.ts +67 -0
  85. package/dist/ui-admin/assets/styles/style.css +26 -16
  86. package/package.json +2 -2
@@ -2,7 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { ChevronSelectorVertical, SearchLg } from '@ncds/ui-admin-icon';
3
3
  import classNames from 'classnames';
4
4
  import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
5
+ import { createPortal } from 'react-dom';
5
6
  import { useDropdown } from '../../../hooks/dropdown';
7
+ import { useFloatingPosition } from '../../../hooks/useFloatingPosition';
8
+ import { usePortalState } from '../../../hooks/usePortalState';
6
9
  import { useMultiSelect } from '../../../utils/dropdown/multiSelect';
7
10
  import { Button } from '../../action/button';
8
11
  import { Tag } from '../../feedback-and-status/tag';
@@ -20,7 +23,49 @@ const notifyFormChange = (register, multiple, newValue) => {
20
23
  });
21
24
  }
22
25
  };
23
- const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
26
+ /**
27
+ * Portal 모드에서만 패널에 `--portal` 클래스와 fixed 좌표를 얹는다.
28
+ * floatingStyle 이 null 인 초기 레이아웃 직전에는 클래스만 붙지만,
29
+ * useLayoutEffect 가 paint 전에 좌표를 채우므로 빈 fixed 가 노출되지 않는다.
30
+ */
31
+ const resolvePortalPanelBinding = (shouldPortal, floatingStyle) => {
32
+ if (!shouldPortal) {
33
+ return {};
34
+ }
35
+ return {
36
+ className: 'ncua-select-dropdown--portal',
37
+ style: floatingStyle ?? undefined
38
+ };
39
+ };
40
+ /**
41
+ * 패널 노드를 인라인·Portal 두 마운트 지점 중 한 곳에만 넘긴다. 양쪽에 두면 dropdownRef 가
42
+ * 나중 노드로 덮여 외부 클릭 판정과 키보드 스크롤이 유령 노드를 가리키게 된다.
43
+ */
44
+ const resolveDropdownMounts = _ref => {
45
+ let {
46
+ node,
47
+ shouldPortal,
48
+ portalContainer,
49
+ isOpen
50
+ } = _ref;
51
+ if (!shouldPortal) {
52
+ return {
53
+ inlineDropdown: node,
54
+ portaledDropdown: null
55
+ };
56
+ }
57
+ if (!portalContainer || !isOpen) {
58
+ return {
59
+ inlineDropdown: null,
60
+ portaledDropdown: null
61
+ };
62
+ }
63
+ return {
64
+ inlineDropdown: null,
65
+ portaledDropdown: /*#__PURE__*/createPortal(node, portalContainer)
66
+ };
67
+ };
68
+ const ComboBox = /*#__PURE__*/forwardRef((_ref2, ref) => {
24
69
  let {
25
70
  placeholder = '검색하세요',
26
71
  id,
@@ -44,11 +89,19 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
44
89
  maxSelection,
45
90
  onEdit,
46
91
  onComplete,
92
+ usePortal = false,
47
93
  ...props
48
- } = _ref;
94
+ } = _ref2;
49
95
  const internalRef = useRef(null);
96
+ // 인라인 패널의 offset parent 가 __content 다. Portal 좌표도 같은 박스를 기준으로 잡아야
97
+ // label·hintText 가 있을 때 두 모드의 패널 위치가 어긋나지 않는다.
98
+ const contentRef = useRef(null);
50
99
  const dropdownRef = useRef(null);
51
100
  const inputRef = useRef(null);
101
+ const {
102
+ shouldPortal,
103
+ portalContainer
104
+ } = usePortalState(usePortal);
52
105
  const [inputValue, setInputValue] = useState(searchValue);
53
106
  const [showAllItems, setShowAllItems] = useState(false);
54
107
  const [selectedTags, setSelectedTags] = useState([]);
@@ -101,6 +154,9 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
101
154
  } = useDropdown({
102
155
  triggerRef: internalRef,
103
156
  dropdownRef,
157
+ // Portal 모드에서는 hintText 등으로 root 가 content 보다 아래까지 차지할 수 있어
158
+ // internalRef 기준 direction 판정이 잘못될 수 있다. contentRef 로 교정한다.
159
+ positionRef: shouldPortal ? contentRef : undefined,
104
160
  maxHeight,
105
161
  itemCount: optionItems.length,
106
162
  optionItems: optionItems,
@@ -133,6 +189,14 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
133
189
  // 나머지는 useDropdown 훅에서 처리
134
190
  dropdownHandleKeyDown(e);
135
191
  };
192
+ const floatingStyle = useFloatingPosition({
193
+ enabled: shouldPortal,
194
+ isOpen,
195
+ triggerRef: contentRef,
196
+ floatingRef: dropdownRef,
197
+ direction: dropdownDirection,
198
+ matchTriggerWidth: true
199
+ });
136
200
  // biome-ignore lint/style/noNonNullAssertion: forwardRef 패턴에서 internalRef는 첫 렌더 후 항상 존재
137
201
  useImperativeHandle(ref, () => internalRef.current, []);
138
202
  const trailingElement = {
@@ -203,6 +267,47 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
203
267
  const showFooter = multiple || showFooterButtons;
204
268
  const hiddenInputValue = multiple ? JSON.stringify(value || []) : String(value || '');
205
269
  const hasTags = selectedTags.length > 0;
270
+ const {
271
+ className: portalPanelClassName,
272
+ style: portalPanelStyle
273
+ } = resolvePortalPanelBinding(shouldPortal, floatingStyle);
274
+ const selectDropdownNode = _jsxs(SelectDropdown, {
275
+ ref: dropdownRef,
276
+ isOpen: isOpen,
277
+ direction: dropdownDirection,
278
+ size: size,
279
+ options: displayedOptions,
280
+ value: value,
281
+ focusedIndex: focusedIndex,
282
+ maxHeight: maxHeight,
283
+ listboxId: comboboxId,
284
+ onOptionSelect: handleDropdownSelect,
285
+ onMouseMove: handleMouseMove,
286
+ isKeyboardNavigation: isKeyboardNavigation,
287
+ multiple: multiple,
288
+ showFooterButtons: showFooter,
289
+ selectAllButtonText: selectAllButtonText,
290
+ showSelectAllAction: !isMaxSelectionActive,
291
+ onSelectAll: handleSelectAll,
292
+ onEdit: handleEdit,
293
+ onComplete: handleComplete,
294
+ componentType: "combobox",
295
+ className: portalPanelClassName,
296
+ style: portalPanelStyle,
297
+ children: [showNoResults ? _jsx("li", {
298
+ className: "ncua-select-dropdown__no-results",
299
+ children: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."
300
+ }) : null, children]
301
+ });
302
+ const {
303
+ inlineDropdown,
304
+ portaledDropdown
305
+ } = resolveDropdownMounts({
306
+ node: selectDropdownNode,
307
+ shouldPortal,
308
+ portalContainer,
309
+ isOpen
310
+ });
206
311
  return _jsxs(_Fragment, {
207
312
  children: [_jsxs("div", {
208
313
  ref: internalRef,
@@ -213,6 +318,7 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
213
318
  }, className),
214
319
  ...props,
215
320
  children: [_jsxs("div", {
321
+ ref: contentRef,
216
322
  className: "ncua-combobox__content",
217
323
  children: [_jsx(InputBase, {
218
324
  ref: inputRef,
@@ -239,32 +345,7 @@ const ComboBox = /*#__PURE__*/forwardRef((_ref, ref) => {
239
345
  setInputValue('');
240
346
  onSearch?.('');
241
347
  }
242
- }), _jsxs(SelectDropdown, {
243
- ref: dropdownRef,
244
- isOpen: isOpen,
245
- direction: dropdownDirection,
246
- size: size,
247
- options: displayedOptions,
248
- value: value,
249
- focusedIndex: focusedIndex,
250
- maxHeight: maxHeight,
251
- listboxId: comboboxId,
252
- onOptionSelect: handleDropdownSelect,
253
- onMouseMove: handleMouseMove,
254
- isKeyboardNavigation: isKeyboardNavigation,
255
- multiple: multiple,
256
- showFooterButtons: showFooter,
257
- selectAllButtonText: selectAllButtonText,
258
- showSelectAllAction: !isMaxSelectionActive,
259
- onSelectAll: handleSelectAll,
260
- onEdit: handleEdit,
261
- onComplete: handleComplete,
262
- componentType: "combobox",
263
- children: [showNoResults ? _jsx("li", {
264
- className: "ncua-select-dropdown__no-results",
265
- children: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."
266
- }) : null, children]
267
- })]
348
+ }), inlineDropdown, portaledDropdown]
268
349
  }), register && _jsx("input", {
269
350
  type: "hidden",
270
351
  ...register,