@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.
- package/dist/cjs/assets/scripts/featuredIcon.js +87 -0
- package/dist/cjs/assets/scripts/notification/FloatingNotification.js +178 -0
- package/dist/cjs/assets/scripts/notification/FullWidthNotification.js +133 -0
- package/dist/cjs/assets/scripts/notification/MessageNotification.js +159 -0
- package/dist/cjs/assets/scripts/notification/Notification.js +120 -0
- package/dist/cjs/assets/scripts/notification/const/classNames.js +50 -0
- package/dist/cjs/assets/scripts/notification/const/icons.js +31 -0
- package/dist/cjs/assets/scripts/notification/const/index.js +87 -0
- package/dist/cjs/assets/scripts/notification/const/sizes.js +46 -0
- package/dist/cjs/assets/scripts/notification/const/types.js +14 -0
- package/dist/cjs/assets/scripts/notification/index.js +116 -0
- package/dist/cjs/assets/scripts/notification/positionSync.js +180 -0
- package/dist/cjs/assets/scripts/notification/utils.js +122 -0
- package/dist/cjs/assets/scripts/shared/ButtonCloseX.js +45 -0
- package/dist/cjs/assets/scripts/utils/sanitize.js +39 -0
- package/dist/cjs/src/components/data-display/data-grid/DataGrid.js +5 -1
- package/dist/cjs/src/components/data-display/table/Table.js +118 -96
- package/dist/cjs/src/components/data-display/table/useTableScrollbars.js +187 -0
- package/dist/cjs/src/components/forms-and-input/combo-box/ComboBox.js +11 -10
- package/dist/cjs/src/components/forms-and-input/image-file-input/ImageFileInput.js +5 -2
- package/dist/cjs/src/components/forms-and-input/select-box/SelectBox.js +67 -29
- package/dist/cjs/src/components/index.js +33 -0
- package/dist/cjs/src/components/layout/block-container/BlockContainer.js +38 -0
- package/dist/cjs/src/components/layout/block-container/index.js +16 -0
- package/dist/cjs/src/components/layout/block-header/BlockHeader.js +107 -0
- package/dist/cjs/src/components/layout/block-header/SubTitle.js +56 -0
- package/dist/cjs/src/components/layout/block-header/index.js +27 -0
- package/dist/cjs/src/components/layout/page-title/PageTitle.js +95 -0
- package/dist/cjs/src/components/layout/page-title/index.js +16 -0
- package/dist/cjs/src/components/overlays/dropdown/Dropdown.js +47 -19
- package/dist/cjs/src/components/overlays/notification/CalloutNotification.js +25 -0
- package/dist/cjs/src/components/overlays/notification/FloatingNotification.js +86 -13
- package/dist/cjs/src/components/overlays/notification/Notification.js +7 -0
- package/dist/cjs/src/components/overlays/notification/host.js +12 -0
- package/dist/cjs/src/components/overlays/tooltip/Tooltip.js +57 -44
- package/dist/cjs/src/components/select-dropdown/SelectDropdown.js +2 -1
- package/dist/cjs/src/contexts/FloatingContext.js +11 -0
- package/dist/cjs/src/contexts/index.js +16 -0
- package/dist/cjs/src/hooks/index.js +11 -0
- package/dist/cjs/src/hooks/useFloatingPosition.js +78 -0
- package/dist/cjs/src/hooks/usePortalState.js +17 -0
- package/dist/cjs/src/utils/dropdown/maxSelection.js +35 -0
- package/dist/cjs/src/utils/dropdown/multiSelect.js +72 -15
- package/dist/esm/assets/scripts/featuredIcon.js +80 -0
- package/dist/esm/assets/scripts/notification/FloatingNotification.js +171 -0
- package/dist/esm/assets/scripts/notification/FullWidthNotification.js +126 -0
- package/dist/esm/assets/scripts/notification/MessageNotification.js +152 -0
- package/dist/esm/assets/scripts/notification/Notification.js +113 -0
- package/dist/esm/assets/scripts/notification/const/classNames.js +44 -0
- package/dist/esm/assets/scripts/notification/const/icons.js +25 -0
- package/dist/esm/assets/scripts/notification/const/index.js +4 -0
- package/dist/esm/assets/scripts/notification/const/sizes.js +40 -0
- package/dist/esm/assets/scripts/notification/const/types.js +8 -0
- package/dist/esm/assets/scripts/notification/index.js +10 -0
- package/dist/esm/assets/scripts/notification/positionSync.js +171 -0
- package/dist/esm/assets/scripts/notification/utils.js +109 -0
- package/dist/esm/assets/scripts/shared/ButtonCloseX.js +37 -0
- package/dist/esm/assets/scripts/utils/sanitize.js +31 -0
- package/dist/esm/src/components/data-display/data-grid/DataGrid.js +5 -1
- package/dist/esm/src/components/data-display/table/Table.js +118 -96
- package/dist/esm/src/components/data-display/table/useTableScrollbars.js +179 -0
- package/dist/esm/src/components/forms-and-input/combo-box/ComboBox.js +11 -10
- package/dist/esm/src/components/forms-and-input/image-file-input/ImageFileInput.js +5 -2
- package/dist/esm/src/components/forms-and-input/select-box/SelectBox.js +67 -29
- package/dist/esm/src/components/index.js +3 -0
- package/dist/esm/src/components/layout/block-container/BlockContainer.js +31 -0
- package/dist/esm/src/components/layout/block-container/index.js +1 -0
- package/dist/esm/src/components/layout/block-header/BlockHeader.js +100 -0
- package/dist/esm/src/components/layout/block-header/SubTitle.js +49 -0
- package/dist/esm/src/components/layout/block-header/index.js +2 -0
- package/dist/esm/src/components/layout/page-title/PageTitle.js +88 -0
- package/dist/esm/src/components/layout/page-title/index.js +1 -0
- package/dist/esm/src/components/overlays/dropdown/Dropdown.js +47 -19
- package/dist/esm/src/components/overlays/notification/CalloutNotification.js +19 -0
- package/dist/esm/src/components/overlays/notification/FloatingNotification.js +86 -14
- package/dist/esm/src/components/overlays/notification/Notification.js +7 -0
- package/dist/esm/src/components/overlays/notification/host.js +9 -0
- package/dist/esm/src/components/overlays/tooltip/Tooltip.js +58 -45
- package/dist/esm/src/components/select-dropdown/SelectDropdown.js +2 -1
- package/dist/esm/src/contexts/FloatingContext.js +4 -0
- package/dist/esm/src/contexts/index.js +1 -0
- package/dist/esm/src/hooks/index.js +1 -0
- package/dist/esm/src/hooks/useFloatingPosition.js +71 -0
- package/dist/esm/src/hooks/usePortalState.js +10 -0
- package/dist/esm/src/utils/dropdown/maxSelection.js +27 -0
- package/dist/esm/src/utils/dropdown/multiSelect.js +70 -14
- package/dist/temp/assets/scripts/featuredIcon.d.ts +22 -0
- package/dist/temp/assets/scripts/featuredIcon.js +79 -0
- package/dist/temp/assets/scripts/notification/FloatingNotification.d.ts +24 -0
- package/dist/temp/assets/scripts/notification/FloatingNotification.js +156 -0
- package/dist/temp/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
- package/dist/temp/assets/scripts/notification/FullWidthNotification.js +111 -0
- package/dist/temp/assets/scripts/notification/MessageNotification.d.ts +22 -0
- package/dist/temp/assets/scripts/notification/MessageNotification.js +140 -0
- package/dist/temp/assets/scripts/notification/Notification.d.ts +22 -0
- package/dist/temp/assets/scripts/notification/Notification.js +112 -0
- package/dist/temp/assets/scripts/notification/const/classNames.d.ts +43 -0
- package/dist/temp/assets/scripts/notification/const/classNames.js +44 -0
- package/dist/temp/assets/scripts/notification/const/icons.d.ts +25 -0
- package/dist/temp/assets/scripts/notification/const/icons.js +25 -0
- package/dist/temp/assets/scripts/notification/const/index.d.ts +5 -0
- package/dist/temp/assets/scripts/notification/const/index.js +4 -0
- package/dist/temp/assets/scripts/notification/const/sizes.d.ts +32 -0
- package/dist/temp/assets/scripts/notification/const/sizes.js +40 -0
- package/dist/temp/assets/scripts/notification/const/types.d.ts +19 -0
- package/dist/temp/assets/scripts/notification/const/types.js +8 -0
- package/dist/temp/assets/scripts/notification/index.d.ts +8 -0
- package/dist/temp/assets/scripts/notification/index.js +10 -0
- package/dist/temp/assets/scripts/notification/positionSync.d.ts +50 -0
- package/dist/temp/assets/scripts/notification/positionSync.js +170 -0
- package/dist/temp/assets/scripts/notification/utils.d.ts +8 -0
- package/dist/temp/assets/scripts/notification/utils.js +115 -0
- package/dist/temp/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
- package/dist/temp/assets/scripts/shared/ButtonCloseX.js +33 -0
- package/dist/temp/assets/scripts/utils/sanitize.d.ts +22 -0
- package/dist/temp/assets/scripts/utils/sanitize.js +31 -0
- package/dist/temp/src/components/data-display/data-grid/DataGrid.js +1 -1
- package/dist/temp/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
- package/dist/temp/src/components/data-display/table/Table.d.ts +4 -1
- package/dist/temp/src/components/data-display/table/Table.js +53 -68
- package/dist/temp/src/components/data-display/table/types.d.ts +18 -0
- package/dist/temp/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
- package/dist/temp/src/components/data-display/table/useTableScrollbars.js +136 -0
- package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
- package/dist/temp/src/components/forms-and-input/combo-box/ComboBox.js +7 -11
- package/dist/temp/src/components/forms-and-input/image-file-input/ImageFileInput.js +1 -1
- package/dist/temp/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
- package/dist/temp/src/components/forms-and-input/select-box/SelectBox.js +30 -3
- package/dist/temp/src/components/index.d.ts +3 -0
- package/dist/temp/src/components/index.js +3 -0
- package/dist/temp/src/components/layout/block-container/BlockContainer.d.ts +19 -0
- package/dist/temp/src/components/layout/block-container/BlockContainer.js +11 -0
- package/dist/temp/src/components/layout/block-container/index.d.ts +1 -0
- package/dist/temp/src/components/layout/block-container/index.js +1 -0
- package/dist/temp/src/components/layout/block-header/BlockHeader.d.ts +23 -0
- package/dist/temp/src/components/layout/block-header/BlockHeader.js +21 -0
- package/dist/temp/src/components/layout/block-header/SubTitle.d.ts +19 -0
- package/dist/temp/src/components/layout/block-header/SubTitle.js +8 -0
- package/dist/temp/src/components/layout/block-header/index.d.ts +2 -0
- package/dist/temp/src/components/layout/block-header/index.js +2 -0
- package/dist/temp/src/components/layout/page-title/PageTitle.d.ts +22 -0
- package/dist/temp/src/components/layout/page-title/PageTitle.js +19 -0
- package/dist/temp/src/components/layout/page-title/index.d.ts +1 -0
- package/dist/temp/src/components/layout/page-title/index.js +1 -0
- package/dist/temp/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
- package/dist/temp/src/components/overlays/dropdown/Dropdown.js +35 -11
- package/dist/temp/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
- package/dist/temp/src/components/overlays/notification/CalloutNotification.js +6 -0
- package/dist/temp/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
- package/dist/temp/src/components/overlays/notification/FloatingNotification.js +81 -13
- package/dist/temp/src/components/overlays/notification/Notification.d.ts +18 -3
- package/dist/temp/src/components/overlays/notification/Notification.js +4 -0
- package/dist/temp/src/components/overlays/notification/host.d.ts +9 -0
- package/dist/temp/src/components/overlays/notification/host.js +9 -0
- package/dist/temp/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
- package/dist/temp/src/components/overlays/tooltip/Tooltip.js +25 -22
- package/dist/temp/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
- package/dist/temp/src/components/select-dropdown/SelectDropdown.js +2 -2
- package/dist/temp/src/contexts/FloatingContext.d.ts +6 -0
- package/dist/temp/src/contexts/FloatingContext.js +4 -0
- package/dist/temp/src/contexts/index.d.ts +1 -0
- package/dist/temp/src/contexts/index.js +1 -0
- package/dist/temp/src/hooks/index.d.ts +1 -0
- package/dist/temp/src/hooks/index.js +1 -0
- package/dist/temp/src/hooks/useFloatingPosition.d.ts +19 -0
- package/dist/temp/src/hooks/useFloatingPosition.js +55 -0
- package/dist/temp/src/hooks/usePortalState.d.ts +6 -0
- package/dist/temp/src/hooks/usePortalState.js +7 -0
- package/dist/temp/src/utils/dropdown/maxSelection.d.ts +24 -0
- package/dist/temp/src/utils/dropdown/maxSelection.js +28 -0
- package/dist/temp/src/utils/dropdown/multiSelect.d.ts +42 -2
- package/dist/temp/src/utils/dropdown/multiSelect.js +66 -13
- package/dist/types/assets/scripts/featuredIcon.d.ts +22 -0
- package/dist/types/assets/scripts/notification/FloatingNotification.d.ts +24 -0
- package/dist/types/assets/scripts/notification/FullWidthNotification.d.ts +21 -0
- package/dist/types/assets/scripts/notification/MessageNotification.d.ts +22 -0
- package/dist/types/assets/scripts/notification/Notification.d.ts +22 -0
- package/dist/types/assets/scripts/notification/const/classNames.d.ts +43 -0
- package/dist/types/assets/scripts/notification/const/icons.d.ts +25 -0
- package/dist/types/assets/scripts/notification/const/index.d.ts +5 -0
- package/dist/types/assets/scripts/notification/const/sizes.d.ts +32 -0
- package/dist/types/assets/scripts/notification/const/types.d.ts +19 -0
- package/dist/types/assets/scripts/notification/index.d.ts +8 -0
- package/dist/types/assets/scripts/notification/positionSync.d.ts +50 -0
- package/dist/types/assets/scripts/notification/utils.d.ts +8 -0
- package/dist/types/assets/scripts/shared/ButtonCloseX.d.ts +5 -0
- package/dist/types/assets/scripts/utils/sanitize.d.ts +22 -0
- package/dist/types/src/components/data-display/data-grid/DataGrid.types.d.ts +7 -0
- package/dist/types/src/components/data-display/table/Table.d.ts +4 -1
- package/dist/types/src/components/data-display/table/types.d.ts +18 -0
- package/dist/types/src/components/data-display/table/useTableScrollbars.d.ts +25 -0
- package/dist/types/src/components/forms-and-input/combo-box/ComboBox.d.ts +8 -0
- package/dist/types/src/components/forms-and-input/select-box/SelectBox.d.ts +13 -0
- package/dist/types/src/components/index.d.ts +3 -0
- package/dist/types/src/components/layout/block-container/BlockContainer.d.ts +19 -0
- package/dist/types/src/components/layout/block-container/index.d.ts +1 -0
- package/dist/types/src/components/layout/block-header/BlockHeader.d.ts +23 -0
- package/dist/types/src/components/layout/block-header/SubTitle.d.ts +19 -0
- package/dist/types/src/components/layout/block-header/index.d.ts +2 -0
- package/dist/types/src/components/layout/page-title/PageTitle.d.ts +22 -0
- package/dist/types/src/components/layout/page-title/index.d.ts +1 -0
- package/dist/types/src/components/overlays/dropdown/Dropdown.d.ts +5 -0
- package/dist/types/src/components/overlays/notification/CalloutNotification.d.ts +9 -0
- package/dist/types/src/components/overlays/notification/FloatingNotification.d.ts +15 -0
- package/dist/types/src/components/overlays/notification/Notification.d.ts +18 -3
- package/dist/types/src/components/overlays/notification/host.d.ts +9 -0
- package/dist/types/src/components/overlays/tooltip/Tooltip.d.ts +5 -1
- package/dist/types/src/components/select-dropdown/SelectDropdown.d.ts +6 -0
- package/dist/types/src/contexts/FloatingContext.d.ts +6 -0
- package/dist/types/src/contexts/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/useFloatingPosition.d.ts +19 -0
- package/dist/types/src/hooks/usePortalState.d.ts +6 -0
- package/dist/types/src/utils/dropdown/maxSelection.d.ts +24 -0
- package/dist/types/src/utils/dropdown/multiSelect.d.ts +42 -2
- package/dist/ui-admin/assets/styles/style.css +596 -64
- package/package.json +1 -1
|
@@ -21,6 +21,14 @@ interface ComboBoxProps extends Omit<ComponentPropsWithRef<'div'>, 'size' | 'onC
|
|
|
21
21
|
required?: boolean;
|
|
22
22
|
multiple?: boolean;
|
|
23
23
|
showFooterButtons?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* 최대 선택 가능 개수 (multiple=true에서만 유효).
|
|
26
|
+
* - `0` 이상의 정수: 제한 활성 → footer의 "전체선택" Link 미노출 (DES-SPEC-009 §3.10.1).
|
|
27
|
+
* - `0`은 모든 새 선택을 차단한다.
|
|
28
|
+
* - 양수는 도달 후 추가 선택을 무시한다 (이미 선택된 항목 해제는 정상).
|
|
29
|
+
* - 음수 / 비정수 / `null` / `undefined`: 제한 없음.
|
|
30
|
+
*/
|
|
31
|
+
maxSelection?: number | null;
|
|
24
32
|
onEdit?: () => void;
|
|
25
33
|
}
|
|
26
34
|
declare const ComboBox: import("react").ForwardRefExoticComponent<Omit<ComboBoxProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -10,13 +10,6 @@ import { SelectDropdown } from '../../select-dropdown';
|
|
|
10
10
|
import { HintText } from '../../shared/hintText/HintText';
|
|
11
11
|
import { InputBase } from '../input-base/InputBase';
|
|
12
12
|
const defaultMaxHeight = 275;
|
|
13
|
-
const toggleMultiSelectValue = (currentValue, optionId) => {
|
|
14
|
-
const currentValues = Array.isArray(currentValue) ? currentValue : [];
|
|
15
|
-
if (currentValues.includes(optionId)) {
|
|
16
|
-
return currentValues.filter((v) => v !== optionId);
|
|
17
|
-
}
|
|
18
|
-
return [...currentValues, optionId];
|
|
19
|
-
};
|
|
20
13
|
const notifyFormChange = (register, multiple, newValue) => {
|
|
21
14
|
if (register?.onChange) {
|
|
22
15
|
register.onChange({
|
|
@@ -27,7 +20,7 @@ const notifyFormChange = (register, multiple, newValue) => {
|
|
|
27
20
|
});
|
|
28
21
|
}
|
|
29
22
|
};
|
|
30
|
-
const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, hintText, children, size = 'xs', destructive = false, value, optionItems = [], onChange, onSearch, disabled = false, register, maxHeight = defaultMaxHeight, searchValue = '', label, required = false, multiple = false, showFooterButtons = false, onEdit, ...props }, ref) => {
|
|
23
|
+
const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, hintText, children, size = 'xs', destructive = false, value, optionItems = [], onChange, onSearch, disabled = false, register, maxHeight = defaultMaxHeight, searchValue = '', label, required = false, multiple = false, showFooterButtons = false, maxSelection, onEdit, ...props }, ref) => {
|
|
31
24
|
const internalRef = useRef(null);
|
|
32
25
|
const dropdownRef = useRef(null);
|
|
33
26
|
const inputRef = useRef(null);
|
|
@@ -38,7 +31,9 @@ const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, h
|
|
|
38
31
|
if (disabled)
|
|
39
32
|
return;
|
|
40
33
|
if (multiple) {
|
|
41
|
-
const newValue =
|
|
34
|
+
const newValue = tryToggle(option.id, Array.isArray(value) ? value : []);
|
|
35
|
+
if (newValue === null)
|
|
36
|
+
return;
|
|
42
37
|
onChange?.(newValue);
|
|
43
38
|
notifyFormChange(register, multiple, newValue);
|
|
44
39
|
return;
|
|
@@ -103,6 +98,7 @@ const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, h
|
|
|
103
98
|
// 나머지는 useDropdown 훅에서 처리
|
|
104
99
|
dropdownHandleKeyDown(e);
|
|
105
100
|
};
|
|
101
|
+
// biome-ignore lint/style/noNonNullAssertion: forwardRef 패턴에서 internalRef는 첫 렌더 후 항상 존재
|
|
106
102
|
useImperativeHandle(ref, () => internalRef.current, []);
|
|
107
103
|
const trailingElement = {
|
|
108
104
|
type: 'custom',
|
|
@@ -116,7 +112,7 @@ const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, h
|
|
|
116
112
|
color: 'gray300',
|
|
117
113
|
};
|
|
118
114
|
const currentSelectedValues = multiple && Array.isArray(value) ? value : [];
|
|
119
|
-
const { buttonText: selectAllButtonText, toggleSelectAll, getSelectedTagsData, removeTag, } = useMultiSelect(currentSelectedValues, optionItems);
|
|
115
|
+
const { buttonText: selectAllButtonText, toggleSelectAll, getSelectedTagsData, removeTag, isMaxSelectionActive, tryToggle, } = useMultiSelect(currentSelectedValues, optionItems, { maxSelection });
|
|
120
116
|
const handleSelectAll = () => {
|
|
121
117
|
if (!multiple || !onChange)
|
|
122
118
|
return;
|
|
@@ -160,7 +156,7 @@ const ComboBox = forwardRef(({ placeholder = '검색하세요', id, className, h
|
|
|
160
156
|
}, className), ...props, children: [_jsxs("div", { className: "ncua-combobox__content", children: [_jsx(InputBase, { ref: inputRef, size: size, label: label, required: required, placeholder: placeholder, value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onClick: handleInputClick, disabled: disabled, destructive: destructive, leadingElement: leadingElement, trailingElement: trailingElement, role: "combobox", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": comboboxId, "aria-disabled": disabled, autoComplete: "off", clearText: !!inputValue, onClearText: () => {
|
|
161
157
|
setInputValue('');
|
|
162
158
|
onSearch?.('');
|
|
163
|
-
} }), _jsxs(SelectDropdown, { ref: dropdownRef, isOpen: isOpen, direction: dropdownDirection, size: size, options: displayedOptions, value: value, focusedIndex: focusedIndex, maxHeight: maxHeight, listboxId: comboboxId, onOptionSelect: handleDropdownSelect, onMouseMove: handleMouseMove, isKeyboardNavigation: isKeyboardNavigation, multiple: multiple, showFooterButtons: showFooter, selectAllButtonText: selectAllButtonText, onSelectAll: handleSelectAll, onEdit: handleEdit, onComplete: handleComplete, componentType: "combobox", children: [showNoResults ? _jsx("li", { className: "ncua-select-dropdown__no-results", children: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null, children] })] }), register && _jsx("input", { type: "hidden", ...register, value: hiddenInputValue }), hintText && _jsx(HintText, { destructive: destructive, children: hintText })] }), hasTags && (_jsx("div", { className: "ncua-combobox__tags", children: selectedTags.map((tag) => (_jsx(Tag, { text: tag.label, size: "sm", close: true, onButtonClick: () => handleRemoveTag(tag.id) }, tag.id))) }))] }));
|
|
159
|
+
} }), _jsxs(SelectDropdown, { ref: dropdownRef, isOpen: isOpen, direction: dropdownDirection, size: size, options: displayedOptions, value: value, focusedIndex: focusedIndex, maxHeight: maxHeight, listboxId: comboboxId, onOptionSelect: handleDropdownSelect, onMouseMove: handleMouseMove, isKeyboardNavigation: isKeyboardNavigation, multiple: multiple, showFooterButtons: showFooter, selectAllButtonText: selectAllButtonText, showSelectAllAction: !isMaxSelectionActive, onSelectAll: handleSelectAll, onEdit: handleEdit, onComplete: handleComplete, componentType: "combobox", children: [showNoResults ? _jsx("li", { className: "ncua-select-dropdown__no-results", children: "\uC77C\uCE58\uD558\uB294 \uACB0\uACFC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null, children] })] }), register && _jsx("input", { type: "hidden", ...register, value: hiddenInputValue }), hintText && _jsx(HintText, { destructive: destructive, children: hintText })] }), hasTags && (_jsx("div", { className: "ncua-combobox__tags", children: selectedTags.map((tag) => (_jsx(Tag, { text: tag.label, size: "sm", close: true, onButtonClick: () => handleRemoveTag(tag.id) }, tag.id))) }))] }));
|
|
164
160
|
});
|
|
165
161
|
ComboBox.displayName = 'ComboBox';
|
|
166
162
|
export { defaultMaxHeight, ComboBox };
|
|
@@ -85,7 +85,7 @@ export const ImageFileInput = forwardRef(({ size = 'sm', accept, multiple = fals
|
|
|
85
85
|
};
|
|
86
86
|
const renderImagePreview = (files = []) => {
|
|
87
87
|
const showEmptySlot = maxFileCount ? files.length < maxFileCount : files.length === 0;
|
|
88
|
-
return (_jsxs("div", { className: "ncua-image-file-input__previews", children: [files.map((file, index) => (_jsx(ImagePreview, { file: file, onRemove: () => handleRemoveFile(index) }, `${file.name}-${index}`))), showEmptySlot && (_jsxs("div", { className: "ncua-image-file-input__empty-slot-wrapper", onMouseEnter: () => !disabled && setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), onClick: handleBrowseClick, children: [_jsx(Button, { onlyIcon: true, size: size, className: classNames('ncua-image-file-input__preview-container'), onClick: handleBrowseClick, disabled: disabled, label: imagePreviewTooltipLabel }),
|
|
88
|
+
return (_jsxs("div", { className: "ncua-image-file-input__previews", children: [files.map((file, index) => (_jsx(ImagePreview, { file: file, onRemove: () => handleRemoveFile(index) }, `${file.name}-${index}`))), showEmptySlot && (_jsxs("div", { className: "ncua-image-file-input__empty-slot-wrapper", onMouseEnter: () => !disabled && setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), onClick: handleBrowseClick, children: [_jsx(Button, { onlyIcon: true, size: size, className: classNames('ncua-image-file-input__preview-container'), onClick: handleBrowseClick, disabled: disabled, label: imagePreviewTooltipLabel }), _jsx(Tooltip, { content: imagePreviewTooltipLabel, position: "bottom", tooltipType: "black", forceVisible: isButtonHovered && !disabled, disablePortal: true })] }))] }));
|
|
89
89
|
};
|
|
90
90
|
const renderHintList = () => {
|
|
91
91
|
if (!hintItems || hintItems.length === 0)
|
|
@@ -19,8 +19,21 @@ type SelectBoxProps = Omit<ComponentPropsWithRef<'div'>, 'size' | 'onChange'> &
|
|
|
19
19
|
register?: UseFormRegisterReturn;
|
|
20
20
|
maxHeight?: number;
|
|
21
21
|
multiple?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 최대 선택 가능 개수 (multiple=true에서만 유효).
|
|
24
|
+
* - `0` 이상의 정수: 제한 활성 → footer "전체선택" Link 미노출.
|
|
25
|
+
* - `0`은 모든 새 선택을 차단한다.
|
|
26
|
+
* - 양수는 도달 후 추가 선택을 무시한다 (이미 선택된 항목 해제는 정상).
|
|
27
|
+
* - 음수 / 비정수 / `null` / `undefined`: 제한 없음.
|
|
28
|
+
*/
|
|
29
|
+
maxSelection?: number | null;
|
|
22
30
|
onEdit?: () => void;
|
|
23
31
|
align?: 'left' | 'right';
|
|
32
|
+
/**
|
|
33
|
+
* 옵션 패널을 React Portal로 body에 렌더한다.
|
|
34
|
+
* 미지정 시 FloatingContext.preferPortal 값을 따른다 (DataGrid.Table horizontalScroll 내부에서는 자동 true).
|
|
35
|
+
*/
|
|
36
|
+
usePortal?: boolean;
|
|
24
37
|
};
|
|
25
38
|
declare const SelectBox: import("react").ForwardRefExoticComponent<Omit<SelectBoxProps, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
|
|
26
39
|
export type { SelectBoxProps };
|
|
@@ -2,8 +2,11 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { ChevronDown } from '@ncds/ui-admin-icon';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { forwardRef, useCallback, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
import { COLOR } from '../../../../constant/color';
|
|
6
7
|
import { useDropdown, useScrollLock } from '../../../hooks/dropdown';
|
|
8
|
+
import { useFloatingPosition } from '../../../hooks/useFloatingPosition';
|
|
9
|
+
import { usePortalState } from '../../../hooks/usePortalState';
|
|
7
10
|
import { useMultiSelect } from '../../../utils/dropdown/multiSelect';
|
|
8
11
|
import { Tag } from '../../feedback-and-status/tag';
|
|
9
12
|
import { SelectDropdown } from '../../select-dropdown';
|
|
@@ -37,9 +40,12 @@ function DisplayValue({ displayValue }) {
|
|
|
37
40
|
}
|
|
38
41
|
return (_jsxs("div", { className: "ncua-selectbox__value-container", children: [displayValue.icon && (_jsx("span", { className: "ncua-selectbox__value-icon", children: _jsx(displayValue.icon, { width: 16, height: 16 }) })), _jsx("span", { className: "ncua-selectbox__value-text", children: displayValue.label })] }));
|
|
39
42
|
}
|
|
40
|
-
const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceholder = false, hintText, size = 'xs', type = 'default', autoWidth = true, destructive = false, value, optionItems = [], disabled = false, maxHeight = DEFAULT_MAX_HEIGHT, multiple = false, align = 'left', id, className, children, register, onChange, onEdit, ...props }, ref
|
|
43
|
+
const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceholder = false, hintText, size = 'xs', type = 'default', autoWidth = true, destructive = false, value, optionItems = [], disabled = false, maxHeight = DEFAULT_MAX_HEIGHT, multiple = false, maxSelection, align = 'left', usePortal, id, className, children, register, onChange, onEdit, ...props }, ref
|
|
44
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 옵션/멀티/태그/포탈 등 필수 분기 통합
|
|
45
|
+
) => {
|
|
41
46
|
const internalRef = useRef(null);
|
|
42
47
|
const dropdownRef = useRef(null);
|
|
48
|
+
const { shouldPortal, portalContainer } = usePortalState(usePortal);
|
|
43
49
|
const [selectedTags, setSelectedTags] = useState([]);
|
|
44
50
|
const selectedOption = useMemo(() => {
|
|
45
51
|
if (multiple)
|
|
@@ -55,6 +61,14 @@ const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceho
|
|
|
55
61
|
const handleOptionSelect = (option) => {
|
|
56
62
|
if (disabled)
|
|
57
63
|
return;
|
|
64
|
+
if (multiple) {
|
|
65
|
+
const newValue = tryToggle(option.id, Array.isArray(value) ? value : []);
|
|
66
|
+
if (newValue === null)
|
|
67
|
+
return;
|
|
68
|
+
onChange?.(newValue);
|
|
69
|
+
notifyRegister(register, newValue, multiple);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
58
72
|
const newValue = computeNewValue(option, value, multiple);
|
|
59
73
|
onChange?.(newValue);
|
|
60
74
|
notifyRegister(register, newValue, multiple);
|
|
@@ -82,7 +96,7 @@ const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceho
|
|
|
82
96
|
}, [handleMouseMove, setFocusedIndex]);
|
|
83
97
|
// Multiple select 관련 로직
|
|
84
98
|
const currentSelectedValues = multiple && Array.isArray(value) ? value : [];
|
|
85
|
-
const { buttonText: selectAllButtonText, toggleSelectAll, getSelectedTagsData, removeTag, } = useMultiSelect(currentSelectedValues, optionItems);
|
|
99
|
+
const { buttonText: selectAllButtonText, toggleSelectAll, getSelectedTagsData, removeTag, isMaxSelectionActive, tryToggle, } = useMultiSelect(currentSelectedValues, optionItems, { maxSelection });
|
|
86
100
|
const handleSelectAll = () => {
|
|
87
101
|
if (!multiple || !onChange)
|
|
88
102
|
return;
|
|
@@ -109,13 +123,26 @@ const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceho
|
|
|
109
123
|
}
|
|
110
124
|
};
|
|
111
125
|
useScrollLock(isOpen, dropdownRef);
|
|
126
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: optionItems 변경 시 너비 재계산 필요
|
|
112
127
|
useLayoutEffect(() => {
|
|
113
128
|
if (autoWidth && isOpen && dropdownRef.current && internalRef.current) {
|
|
114
129
|
const dropdownWidth = dropdownRef.current.offsetWidth;
|
|
115
130
|
internalRef.current.style.width = `${dropdownWidth}px`;
|
|
116
131
|
}
|
|
117
132
|
}, [autoWidth, isOpen, optionItems]);
|
|
133
|
+
const floatingStyle = useFloatingPosition({
|
|
134
|
+
enabled: shouldPortal,
|
|
135
|
+
isOpen,
|
|
136
|
+
triggerRef: internalRef,
|
|
137
|
+
floatingRef: dropdownRef,
|
|
138
|
+
direction: dropdownDirection,
|
|
139
|
+
align,
|
|
140
|
+
matchTriggerWidth: true,
|
|
141
|
+
});
|
|
142
|
+
// biome-ignore lint/style/noNonNullAssertion: forwardRef 패턴에서 internalRef는 항상 존재
|
|
118
143
|
useImperativeHandle(ref, () => internalRef.current, []);
|
|
144
|
+
const selectDropdownNode = (_jsx(SelectDropdown, { ref: dropdownRef, isOpen: isOpen, direction: dropdownDirection, size: size, options: optionItems, value: value, focusedIndex: focusedIndex, maxHeight: maxHeight, listboxId: `selectbox-options-${id || 'default'}`, multiple: multiple, showFooterButtons: multiple, selectAllButtonText: selectAllButtonText, showSelectAllAction: !isMaxSelectionActive, componentType: "selectbox", isKeyboardNavigation: isKeyboardNavigation, activeDescendantId: activeDescendantId, align: align, className: shouldPortal ? 'ncua-select-dropdown--portal' : undefined, style: shouldPortal && floatingStyle ? floatingStyle : undefined, onOptionSelect: handleDropdownSelect, onMouseMove: handleMouseMove, onOptionHover: handleOptionHover, onSelectAll: handleSelectAll, onEdit: handleEdit, onComplete: handleComplete, children: children }));
|
|
145
|
+
const portaledDropdown = shouldPortal && portalContainer && isOpen ? createPortal(selectDropdownNode, portalContainer) : null;
|
|
119
146
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { ref: internalRef, className: classNames('ncua-selectbox', `ncua-selectbox--${size}`, {
|
|
120
147
|
'ncua-selectbox--open': isOpen,
|
|
121
148
|
'ncua-selectbox--disabled': disabled,
|
|
@@ -124,7 +151,7 @@ const SelectBox = forwardRef(({ placeholder = '선택하세요', disabledPlaceho
|
|
|
124
151
|
destructive: destructive,
|
|
125
152
|
}, className), ...props, children: [_jsxs("div", { className: classNames('ncua-selectbox__content'), onClick: toggleDropdown, onKeyDown: handleKeyDown, tabIndex: disabled ? -1 : 0, role: "combobox", "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": `selectbox-options-${id || 'default'}`, "aria-disabled": disabled, "aria-label": selectedOption ? selectedOption.label : placeholder, "aria-activedescendant": activeDescendantId, children: [_jsxs("div", { className: "ncua-selectbox__content-inner", children: [_jsx("div", { className: "ncua-selectbox__value", children: _jsx(DisplayValue, { displayValue: displayValue }) }), _jsx(ChevronDown, { width: size === 'xs' ? ICON_SIZE_XS : ICON_SIZE_SM, height: size === 'xs' ? ICON_SIZE_XS : ICON_SIZE_SM, color: disabled ? COLOR.gray300 : COLOR.gray500, className: classNames('ncua-selectbox__arrow', {
|
|
126
153
|
'ncua-selectbox__arrow--up': isOpen,
|
|
127
|
-
}) })] }),
|
|
154
|
+
}) })] }), !shouldPortal && selectDropdownNode] }), hintText && (_jsx(HintText, { destructive: destructive, className: "ncua-hint-text", children: hintText })), register && (_jsx("input", { type: "hidden", ...register, value: multiple ? JSON.stringify(value || []) : String(value || '') }))] }), selectedTags.length > 0 && (_jsx("div", { className: "ncua-selectbox__tags", children: selectedTags.map((tag) => (_jsx(Tag, { text: tag.label, size: "sm", close: true, onButtonClick: () => handleRemoveTag(tag.id) }, tag.id))) })), portaledDropdown] }));
|
|
128
155
|
});
|
|
129
156
|
SelectBox.displayName = 'SelectBox';
|
|
130
157
|
export { DEFAULT_MAX_HEIGHT, SelectBox };
|
|
@@ -31,7 +31,10 @@ export * from './forms-and-input/textarea';
|
|
|
31
31
|
export * from './forms-and-input/toggle';
|
|
32
32
|
export * from './image-and-icons/dot';
|
|
33
33
|
export * from './image-and-icons/featured-icon';
|
|
34
|
+
export * from './layout/block-container';
|
|
35
|
+
export * from './layout/block-header';
|
|
34
36
|
export * from './layout/divider';
|
|
37
|
+
export * from './layout/page-title';
|
|
35
38
|
export * from './navigation/bread-crumb';
|
|
36
39
|
export * from './navigation/horizontal-tab';
|
|
37
40
|
export * from './navigation/pagination';
|
|
@@ -36,7 +36,10 @@ export * from './forms-and-input/toggle';
|
|
|
36
36
|
export * from './image-and-icons/dot';
|
|
37
37
|
export * from './image-and-icons/featured-icon';
|
|
38
38
|
// Layout
|
|
39
|
+
export * from './layout/block-container';
|
|
40
|
+
export * from './layout/block-header';
|
|
39
41
|
export * from './layout/divider';
|
|
42
|
+
export * from './layout/page-title';
|
|
40
43
|
// Navigation
|
|
41
44
|
export * from './navigation/bread-crumb';
|
|
42
45
|
export * from './navigation/horizontal-tab';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
interface BlockContainerProps extends ComponentPropsWithoutRef<'section'> {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
className?: string;
|
|
5
|
+
}
|
|
6
|
+
interface BlockContainerBodyProps extends ComponentPropsWithoutRef<'div'> {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const BlockContainer: {
|
|
11
|
+
({ children, className, ...rest }: BlockContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
displayName: string;
|
|
13
|
+
} & {
|
|
14
|
+
Body: {
|
|
15
|
+
({ children, className, ...rest }: BlockContainerBodyProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
displayName: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type { BlockContainerProps, BlockContainerBodyProps };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
const BlockContainerBase = ({ children, className, ...rest }) => {
|
|
4
|
+
return (_jsx("section", { className: classNames('ncua-block-container', className), ...rest, children: children }));
|
|
5
|
+
};
|
|
6
|
+
BlockContainerBase.displayName = 'BlockContainer';
|
|
7
|
+
const Body = ({ children, className, ...rest }) => {
|
|
8
|
+
return (_jsx("div", { className: classNames('ncua-block-container__body', className), ...rest, children: children }));
|
|
9
|
+
};
|
|
10
|
+
Body.displayName = 'BlockContainer.Body';
|
|
11
|
+
export const BlockContainer = Object.assign(BlockContainerBase, { Body });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BlockContainer';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './BlockContainer';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
interface BlockHeaderProps extends Omit<ComponentPropsWithoutRef<'header'>, 'title' | 'children'> {
|
|
3
|
+
title: ReactNode;
|
|
4
|
+
tooltip?: string;
|
|
5
|
+
action?: ReactNode;
|
|
6
|
+
showDivider?: boolean;
|
|
7
|
+
badge?: ReactNode;
|
|
8
|
+
description?: string;
|
|
9
|
+
collapsible?: {
|
|
10
|
+
expanded: boolean;
|
|
11
|
+
onToggle: () => void;
|
|
12
|
+
};
|
|
13
|
+
showRequiredNotice?: boolean;
|
|
14
|
+
controlStrip?: ReactNode;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
declare const BlockHeader: {
|
|
19
|
+
({ title, tooltip, action, showDivider, badge, description, collapsible, showRequiredNotice, controlStrip, children, className, ...rest }: BlockHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
displayName: string;
|
|
21
|
+
};
|
|
22
|
+
export { BlockHeader };
|
|
23
|
+
export type { BlockHeaderProps };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown, ChevronUp } from '@ncds/ui-admin-icon';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { Tooltip } from '../../overlays/tooltip/Tooltip';
|
|
5
|
+
const ICON_SIZE = 24;
|
|
6
|
+
const CollapsibleButton = ({ expanded, onToggle }) => (_jsx("button", { type: "button", className: "ncua-block-header__collapsible-btn", onClick: onToggle, "aria-expanded": expanded, children: expanded ? (_jsx(ChevronUp, { width: ICON_SIZE, height: ICON_SIZE, color: "var(--gray-300)" })) : (_jsx(ChevronDown, { width: ICON_SIZE, height: ICON_SIZE, color: "var(--gray-300)" })) }));
|
|
7
|
+
const BlockHeader = ({ title, tooltip, action, showDivider = true, badge, description, collapsible, showRequiredNotice = false, controlStrip, children, className, ...rest }) => {
|
|
8
|
+
const hasColumnLayout = !!description;
|
|
9
|
+
const hasTabChildren = !!children;
|
|
10
|
+
const hasControlStrip = !!controlStrip;
|
|
11
|
+
const isCollapsed = !!collapsible && !collapsible.expanded;
|
|
12
|
+
return (_jsxs("header", { className: classNames('ncua-block-header', {
|
|
13
|
+
'ncua-block-header--column': hasColumnLayout,
|
|
14
|
+
'ncua-block-header--no-divider': !showDivider || hasTabChildren || isCollapsed,
|
|
15
|
+
'ncua-block-header--has-tab': hasTabChildren && !isCollapsed,
|
|
16
|
+
'ncua-block-header--has-control-strip': hasControlStrip,
|
|
17
|
+
'ncua-block-header--is-required': showRequiredNotice,
|
|
18
|
+
}, className), ...rest, children: [_jsxs("div", { className: "ncua-block-header__row", children: [_jsxs("div", { className: "ncua-block-header__title-area", children: [_jsx("span", { className: "ncua-block-header__title", children: title }), tooltip && _jsx(Tooltip, { content: tooltip, size: "sm", position: "top", hideArrow: true, iconType: "fill" }), badge && _jsx("span", { className: "ncua-block-header__badge", children: badge })] }), _jsxs("div", { className: "ncua-block-header__action-area", children: [showRequiredNotice && (_jsxs("span", { className: "ncua-block-header__required-notice", children: [_jsx("span", { className: "ncua-block-header__required-notice--red", children: "* \uB294 \uD544\uC218 \uC785\uB825" }), _jsx("span", { className: "ncua-block-header__required-notice--gray", children: " \uD56D\uBAA9\uC785\uB2C8\uB2E4." })] })), action, collapsible && _jsx(CollapsibleButton, { ...collapsible })] })] }), hasColumnLayout && _jsx("p", { className: "ncua-block-header__description", children: description }), hasControlStrip && _jsx("div", { className: "ncua-block-header__control-strip", children: controlStrip }), hasTabChildren && !isCollapsed && _jsx("div", { className: "ncua-block-header__tabs", children: children })] }));
|
|
19
|
+
};
|
|
20
|
+
BlockHeader.displayName = 'BlockHeader';
|
|
21
|
+
export { BlockHeader };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
import type { Size } from '../../../../constant/size';
|
|
3
|
+
type SubTitleSize = 'middle' | Extract<Size, 'sm' | 'xs'>;
|
|
4
|
+
interface SubTitleProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
size?: SubTitleSize;
|
|
7
|
+
tooltip?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
error?: string;
|
|
10
|
+
action?: ReactNode;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const SubTitle: {
|
|
15
|
+
({ title, size, tooltip, description, error, action, required, className, ...rest }: SubTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
displayName: string;
|
|
17
|
+
};
|
|
18
|
+
export { SubTitle };
|
|
19
|
+
export type { SubTitleProps, SubTitleSize };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Tooltip } from '../../overlays/tooltip/Tooltip';
|
|
4
|
+
const SubTitle = ({ title, size = 'sm', tooltip, description, error, action, required = false, className, ...rest }) => {
|
|
5
|
+
return (_jsxs("div", { className: classNames('ncua-sub-title', `ncua-sub-title--${size}`, className), ...rest, children: [_jsxs("div", { className: "ncua-sub-title__title-row", children: [_jsxs("div", { className: "ncua-sub-title__title-area", children: [required && _jsx("span", { className: "ncua-sub-title__required-marker", children: "*" }), _jsx("span", { className: "ncua-sub-title__title", children: title }), tooltip && _jsx(Tooltip, { content: tooltip, size: "sm", position: "right", hideArrow: true })] }), action && _jsx("div", { className: "ncua-sub-title__action", children: action })] }), description && _jsx("p", { className: "ncua-sub-title__description", children: description }), error && _jsx("p", { className: "ncua-sub-title__error", children: error })] }));
|
|
6
|
+
};
|
|
7
|
+
SubTitle.displayName = 'BlockHeader.SubTitle';
|
|
8
|
+
export { SubTitle };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
|
+
type PageTitleVariant = 'default' | 'detail' | 'fixed' | 'fixed-detail';
|
|
3
|
+
interface PageTitleBreadcrumbItem {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
}
|
|
7
|
+
interface PageTitleProps extends ComponentPropsWithoutRef<'header'> {
|
|
8
|
+
title: string;
|
|
9
|
+
variant?: PageTitleVariant;
|
|
10
|
+
breadcrumbItems?: PageTitleBreadcrumbItem[];
|
|
11
|
+
onBack?: () => void;
|
|
12
|
+
guideButton?: ReactNode;
|
|
13
|
+
primaryAction?: ReactNode;
|
|
14
|
+
secondaryAction?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
declare const PageTitle: {
|
|
18
|
+
({ title, variant, breadcrumbItems, onBack, guideButton, primaryAction, secondaryAction, className, ...rest }: PageTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
displayName: string;
|
|
20
|
+
};
|
|
21
|
+
export { PageTitle };
|
|
22
|
+
export type { PageTitleProps, PageTitleVariant, PageTitleBreadcrumbItem };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronLeft, ChevronRight } from '@ncds/ui-admin-icon';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { Fragment } from 'react';
|
|
5
|
+
import { Button } from '../../action/button/Button';
|
|
6
|
+
const renderBreadcrumb = (items) => (_jsx("nav", { className: "ncua-page-title__breadcrumb", "aria-label": "breadcrumb", children: items.map((item, i) => (
|
|
7
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: breadcrumb items may have duplicate labels
|
|
8
|
+
_jsx(Fragment, { children: i < items.length - 1 ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "ncua-page-title__breadcrumb-item", children: item.href ? _jsx("a", { href: item.href, children: item.label }) : item.label }), _jsx(ChevronRight, { className: "ncua-page-title__breadcrumb-separator" })] })) : (_jsx("span", { className: "ncua-page-title__breadcrumb-current", children: item.label })) }, i))) }));
|
|
9
|
+
const renderBackButton = (onBack) => (_jsx(Button, { label: "", hierarchy: "secondary-gray", size: "xs", onlyIcon: true, leadingIcon: { type: 'icon', icon: ChevronLeft }, onClick: onBack, "aria-label": "\uC774\uC804 \uD398\uC774\uC9C0\uB85C \uB3CC\uC544\uAC00\uAE30", className: "ncua-page-title__back-btn" }));
|
|
10
|
+
const PageTitle = ({ title, variant = 'default', breadcrumbItems, onBack, guideButton, primaryAction, secondaryAction, className, ...rest }) => {
|
|
11
|
+
const isFixed = variant === 'fixed' || variant === 'fixed-detail';
|
|
12
|
+
const isDetail = variant === 'detail' || variant === 'fixed-detail';
|
|
13
|
+
const hasBreadcrumb = !isFixed && breadcrumbItems && breadcrumbItems.length > 0;
|
|
14
|
+
return (_jsx("header", { className: classNames('ncua-page-title', { 'ncua-page-title--fixed': isFixed }, className), ...rest, children: _jsx("div", { className: "ncua-page-title__page-header", children: _jsxs("div", { className: classNames('ncua-page-title__header', {
|
|
15
|
+
'ncua-page-title__header--has-breadcrumb': hasBreadcrumb,
|
|
16
|
+
}), children: [_jsxs("div", { className: "ncua-page-title__container", children: [hasBreadcrumb && renderBreadcrumb(breadcrumbItems), _jsxs("div", { className: "ncua-page-title__title-row", children: [isDetail && onBack && renderBackButton(onBack), _jsx("h1", { className: "ncua-page-title__title", children: title }), guideButton && _jsx("div", { className: "ncua-page-title__guide-btn", children: guideButton })] })] }), (secondaryAction || primaryAction) && (_jsxs("div", { className: "ncua-page-title__actions", children: [secondaryAction, primaryAction] }))] }) }) }));
|
|
17
|
+
};
|
|
18
|
+
PageTitle.displayName = 'PageTitle';
|
|
19
|
+
export { PageTitle };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PageTitle';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PageTitle';
|
|
@@ -49,6 +49,11 @@ export type DropdownBaseProps = {
|
|
|
49
49
|
className?: string;
|
|
50
50
|
opened?: boolean;
|
|
51
51
|
closeOnClickOutside?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* 메뉴를 React Portal로 body에 렌더한다.
|
|
54
|
+
* 미지정 시 FloatingContext.preferPortal 값을 따른다 (DataGrid.Table horizontalScroll 내부에서는 자동 true).
|
|
55
|
+
*/
|
|
56
|
+
usePortal?: boolean;
|
|
52
57
|
};
|
|
53
58
|
export type ActionDropdownProps = DropdownBaseProps & {
|
|
54
59
|
variant?: 'action';
|
|
@@ -7,6 +7,9 @@ import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-sc
|
|
|
7
7
|
import { attachClosestEdge, extractClosestEdge, } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
8
8
|
import { DotsGrid02, DotsVertical, Eye, EyeOff } from '@ncds/ui-admin-icon';
|
|
9
9
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
10
|
+
import { createPortal } from 'react-dom';
|
|
11
|
+
import { useFloatingPosition } from '../../../hooks/useFloatingPosition';
|
|
12
|
+
import { usePortalState } from '../../../hooks/usePortalState';
|
|
10
13
|
import { Button } from '../../action/button';
|
|
11
14
|
import { applyDraftToItems, arrayReorderByEdge, hasDraftChanged, initDraftState } from './utils';
|
|
12
15
|
const DROPDOWN_ID_RADIX = 36;
|
|
@@ -82,14 +85,25 @@ const SortableConfigItem = ({ item, isVisible, showVisibilityToggle, sortable, d
|
|
|
82
85
|
}
|
|
83
86
|
}, children: _jsx(DotsGrid02, {}) })), _jsx("div", { className: "ncua-dropdown__item-content", children: _jsxs("div", { className: "ncua-dropdown__item-text-group", children: [ItemIcon && _jsx(ItemIcon, { className: "ncua-dropdown__item-icon" }), _jsx("span", { className: "ncua-dropdown__item-text", children: item.text })] }) }), showVisibilityToggle && (_jsx("button", { type: "button", className: "ncua-dropdown__item-visibility", "aria-label": `${item.text} 노출`, "aria-pressed": isVisible, onClick: () => onToggleVisibility(item.id), children: isVisible ? _jsx(Eye, {}) : _jsx(EyeOff, {}) }))] }));
|
|
84
87
|
};
|
|
88
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: 액션/설정 두 variant 통합 + drag-and-drop 필수 분기
|
|
85
89
|
export const Dropdown = (props) => {
|
|
86
|
-
const { trigger, align = 'left', header, groups, className, opened = false, closeOnClickOutside = true } = props;
|
|
90
|
+
const { trigger, align = 'left', header, groups, className, opened = false, closeOnClickOutside = true, usePortal, } = props;
|
|
87
91
|
const variant = props.variant ?? 'action';
|
|
88
92
|
const closeOnClickItem = variant === 'action' ? (props.closeOnClickItem ?? true) : false;
|
|
89
93
|
const [isOpen, setIsOpen] = useState(opened);
|
|
90
94
|
const dropdownRef = useRef(null);
|
|
91
95
|
const triggerRef = useRef(null);
|
|
96
|
+
const menuRef = useRef(null);
|
|
92
97
|
const menuItemsRef = useRef(null);
|
|
98
|
+
const { shouldPortal, portalContainer } = usePortalState(usePortal);
|
|
99
|
+
const floatingStyle = useFloatingPosition({
|
|
100
|
+
enabled: shouldPortal,
|
|
101
|
+
isOpen,
|
|
102
|
+
triggerRef,
|
|
103
|
+
floatingRef: menuRef,
|
|
104
|
+
direction: 'down',
|
|
105
|
+
align,
|
|
106
|
+
});
|
|
93
107
|
const dropdownIdRef = useRef(`ncua-dropdown-${Math.random().toString(DROPDOWN_ID_RADIX).slice(DROPDOWN_ID_SLICE_START, DROPDOWN_ID_SLICE_END)}`);
|
|
94
108
|
const dropdownId = dropdownIdRef.current;
|
|
95
109
|
useEffect(() => {
|
|
@@ -144,7 +158,10 @@ export const Dropdown = (props) => {
|
|
|
144
158
|
triggerRef.current?.focus();
|
|
145
159
|
};
|
|
146
160
|
const handleClickOutside = (event) => {
|
|
147
|
-
|
|
161
|
+
const target = event.target;
|
|
162
|
+
const insideContainer = dropdownRef.current?.contains(target) ?? false;
|
|
163
|
+
const insidePortaledMenu = menuRef.current?.contains(target) ?? false;
|
|
164
|
+
if (!insideContainer && !insidePortaledMenu) {
|
|
148
165
|
setIsOpen(false);
|
|
149
166
|
}
|
|
150
167
|
};
|
|
@@ -156,6 +173,7 @@ export const Dropdown = (props) => {
|
|
|
156
173
|
}
|
|
157
174
|
}
|
|
158
175
|
};
|
|
176
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: handleClickOutside는 안정적 참조
|
|
159
177
|
useEffect(() => {
|
|
160
178
|
if (closeOnClickOutside) {
|
|
161
179
|
document.addEventListener('mousedown', handleClickOutside);
|
|
@@ -165,6 +183,7 @@ export const Dropdown = (props) => {
|
|
|
165
183
|
}
|
|
166
184
|
}, [closeOnClickOutside]);
|
|
167
185
|
// ESC 키로 닫고 trigger로 포커스 복귀
|
|
186
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: closeAndRestoreFocus는 안정적 참조
|
|
168
187
|
useEffect(() => {
|
|
169
188
|
if (!isOpen)
|
|
170
189
|
return;
|
|
@@ -261,13 +280,18 @@ export const Dropdown = (props) => {
|
|
|
261
280
|
const dropdownClasses = ['ncua-dropdown', className, align === 'right' ? 'ncua-dropdown--right' : '']
|
|
262
281
|
.filter(Boolean)
|
|
263
282
|
.join(' ');
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
283
|
+
const menuClasses = ['ncua-dropdown__menu', shouldPortal ? 'ncua-dropdown__menu--portal' : '']
|
|
284
|
+
.filter(Boolean)
|
|
285
|
+
.join(' ');
|
|
286
|
+
const menuNode = isOpen ? (_jsxs("div", { ref: menuRef, className: menuClasses, role: variant === 'config' ? 'dialog' : 'menu', "aria-label": variant === 'config' ? '설정' : undefined, style: shouldPortal && floatingStyle ? floatingStyle : undefined, children: [renderHeader(), _jsx("div", { ref: menuItemsRef, className: "ncua-dropdown__menu-items", children: groups.map((group) => {
|
|
287
|
+
// config variant uses draft.order to drive the rendered order
|
|
288
|
+
const orderedItems = variant === 'config' && draft
|
|
289
|
+
? draft.order
|
|
290
|
+
.map((id) => group.items.find((i) => i.id === id))
|
|
291
|
+
.filter((i) => i !== undefined)
|
|
292
|
+
: group.items;
|
|
293
|
+
return (_jsx("div", { className: "ncua-dropdown__group", children: orderedItems.map((item) => variant === 'config' ? renderConfigItem(item, group.sortable === true) : renderActionItem(item)) }, group.items[0]?.id));
|
|
294
|
+
}) }), renderFooter()] })) : null;
|
|
295
|
+
const portaledMenu = shouldPortal && portalContainer && menuNode ? createPortal(menuNode, portalContainer) : null;
|
|
296
|
+
return (_jsxs("div", { className: dropdownClasses, ref: dropdownRef, children: [renderTrigger(), !shouldPortal && menuNode, portaledMenu] }));
|
|
273
297
|
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ComponentPropsWithoutRef, type ReactNode } from 'react';
|
|
2
|
+
import type { NotificationColor } from './Notification';
|
|
3
|
+
interface CalloutNotificationProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title'> {
|
|
4
|
+
color?: NotificationColor;
|
|
5
|
+
title?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
declare const CalloutNotification: import("react").ForwardRefExoticComponent<CalloutNotificationProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
8
|
+
export type { CalloutNotificationProps };
|
|
9
|
+
export { CalloutNotification };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
const CalloutNotification = forwardRef(({ color = 'neutral', className, title, ...rest }, ref) => (_jsx("div", { ref: ref, className: classNames('ncua-callout-notification', `ncua-callout-notification--${color}`, className), ...rest, children: title })));
|
|
5
|
+
CalloutNotification.displayName = 'CalloutNotification';
|
|
6
|
+
export { CalloutNotification };
|
|
@@ -31,6 +31,21 @@ interface FloatingNotificationProps extends Omit<ComponentPropsWithoutRef<'div'>
|
|
|
31
31
|
* @default 0
|
|
32
32
|
*/
|
|
33
33
|
autoClose?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Portal 마운트 여부.
|
|
36
|
+
*
|
|
37
|
+
* - `false` (기본값): 부모 JSX 트리 안에 카드로 그대로 렌더된다.
|
|
38
|
+
* 특정 컨테이너 안에서 표시하거나, 인라인 미리보기 (docs/스토리북) 용도.
|
|
39
|
+
*
|
|
40
|
+
* - `true`: document.body의 `.ncua-floating-notification-host` 싱글톤에
|
|
41
|
+
* `createPortal`로 마운트되어 우측 상단에 노출. 다중 발생 시 최신 토스트가
|
|
42
|
+
* 상단에 노출되고 이전 토스트들이 아래로 12px씩 겹쳐 쌓인다 (LIFO,
|
|
43
|
+
* `--ncua-floating-notification-stack-overlap` 변수로 조정 가능).
|
|
44
|
+
* Vanilla(CDN) 측 `new ncua.Notification({type:'floating'}).show()` 와 동일한 동작.
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
portal?: boolean;
|
|
34
49
|
}
|
|
35
50
|
declare const FloatingNotification: import("react").ForwardRefExoticComponent<FloatingNotificationProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
36
51
|
export type { FloatingNotificationProps };
|