@mezzanine-ui/react 1.0.0-beta.4 → 1.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AutoComplete/AutoComplete.d.ts +5 -0
- package/AutoComplete/AutoComplete.js +19 -2
- package/Breadcrumb/BreadcrumbDropdown.js +1 -1
- package/Breadcrumb/BreadcrumbItem.js +1 -1
- package/Breadcrumb/BreadcrumbOverflowMenuItem.js +1 -1
- package/Calendar/Calendar.js +2 -6
- package/Calendar/CalendarCell.d.ts +22 -0
- package/Calendar/CalendarCell.js +6 -2
- package/Calendar/CalendarControls.js +1 -1
- package/Calendar/CalendarDayOfWeek.js +3 -2
- package/Calendar/CalendarDays.js +5 -1
- package/Calendar/CalendarHalfYears.js +13 -7
- package/Calendar/CalendarMonths.js +13 -6
- package/Calendar/CalendarQuarters.js +13 -7
- package/Calendar/CalendarWeeks.js +87 -34
- package/Calendar/CalendarYears.js +13 -12
- package/Calendar/useCalendarControlModifiers.d.ts +1 -1
- package/Calendar/useCalendarControlModifiers.js +12 -12
- package/Calendar/useCalendarControls.d.ts +4 -4
- package/Calendar/useCalendarControls.js +33 -19
- package/Calendar/useRangeCalendarControls.d.ts +8 -8
- package/Calendar/useRangeCalendarControls.js +42 -31
- package/DateRangePicker/useDateRangeCalendarControls.js +8 -2
- package/DateTimePicker/DateTimePicker.js +8 -4
- package/DateTimeRangePicker/DateTimeRangePicker.d.ts +34 -0
- package/DateTimeRangePicker/DateTimeRangePicker.js +118 -0
- package/DateTimeRangePicker/index.d.ts +2 -0
- package/DateTimeRangePicker/index.js +1 -0
- package/Drawer/Drawer.d.ts +132 -1
- package/Drawer/Drawer.js +47 -3
- package/Dropdown/Dropdown.d.ts +34 -3
- package/Dropdown/Dropdown.js +17 -29
- package/Dropdown/DropdownItem.d.ts +32 -0
- package/Dropdown/DropdownItem.js +112 -10
- package/Dropdown/DropdownItemCard.d.ts +5 -0
- package/Dropdown/DropdownItemCard.js +2 -2
- package/Form/FormField.d.ts +14 -2
- package/Form/FormField.js +3 -3
- package/Form/useSelectValueControl.d.ts +3 -4
- package/Form/useSelectValueControl.js +51 -39
- package/Input/Input.js +3 -19
- package/Input/SelectButton/SelectButton.d.ts +25 -4
- package/Input/SelectButton/SelectButton.js +21 -9
- package/Modal/MediaPreviewModal.d.ts +11 -0
- package/Modal/MediaPreviewModal.js +24 -7
- package/Modal/Modal.d.ts +1 -1
- package/Modal/Modal.js +1 -1
- package/Modal/useModalContainer.js +6 -2
- package/MultipleDatePicker/MultipleDatePicker.d.ts +62 -0
- package/MultipleDatePicker/MultipleDatePicker.js +176 -0
- package/MultipleDatePicker/MultipleDatePickerTrigger.d.ts +56 -0
- package/MultipleDatePicker/MultipleDatePickerTrigger.js +92 -0
- package/MultipleDatePicker/index.d.ts +6 -0
- package/MultipleDatePicker/index.js +3 -0
- package/MultipleDatePicker/useMultipleDatePickerValue.d.ts +55 -0
- package/MultipleDatePicker/useMultipleDatePickerValue.js +68 -0
- package/NotificationCenter/NotificationCenterDrawer.d.ts +10 -52
- package/NotificationCenter/NotificationCenterDrawer.js +128 -0
- package/NotificationCenter/index.d.ts +2 -0
- package/NotificationCenter/index.js +1 -0
- package/OverflowTooltip/index.d.ts +2 -2
- package/Picker/RangePickerTrigger.js +1 -1
- package/Popper/Popper.js +2 -1
- package/Scrollbar/Scrollbar.js +1 -0
- package/Section/Section.d.ts +32 -0
- package/Section/Section.js +62 -0
- package/Section/index.d.ts +2 -0
- package/Select/Select.d.ts +42 -18
- package/Select/Select.js +165 -51
- package/Select/TreeSelect.d.ts +10 -5
- package/Select/TreeSelect.js +12 -5
- package/Select/index.d.ts +8 -9
- package/Select/index.js +3 -3
- package/TimePanel/TimePanelColumn.js +16 -14
- package/TimeRangePicker/TimeRangePicker.d.ts +29 -0
- package/TimeRangePicker/TimeRangePicker.js +96 -0
- package/TimeRangePicker/index.d.ts +3 -0
- package/TimeRangePicker/index.js +2 -0
- package/TimeRangePicker/useTimeRangePickerValue.d.ts +30 -0
- package/TimeRangePicker/useTimeRangePickerValue.js +92 -0
- package/Transition/Rotate.js +2 -5
- package/Tree/TreeNode.js +2 -2
- package/index.d.ts +9 -6
- package/index.js +7 -6
- package/package.json +4 -4
- package/AppBar/AppBar.d.ts +0 -14
- package/AppBar/AppBar.js +0 -33
- package/AppBar/AppBarBrand.d.ts +0 -4
- package/AppBar/AppBarBrand.js +0 -11
- package/AppBar/AppBarMain.d.ts +0 -4
- package/AppBar/AppBarMain.js +0 -11
- package/AppBar/AppBarSupport.d.ts +0 -4
- package/AppBar/AppBarSupport.js +0 -11
- package/AppBar/index.d.ts +0 -8
- package/AppBar/index.js +0 -4
- package/Popconfirm/Popconfirm.d.ts +0 -16
- package/Popconfirm/Popconfirm.js +0 -15
- package/Popconfirm/index.d.ts +0 -2
- package/Popconfirm/index.js +0 -1
- package/Popover/Popover.d.ts +0 -23
- package/Popover/Popover.js +0 -35
- package/Popover/index.d.ts +0 -2
- package/Popover/index.js +0 -1
package/Drawer/Drawer.js
CHANGED
|
@@ -1,22 +1,66 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, useState, useMemo } from 'react';
|
|
3
3
|
import { drawerClasses } from '@mezzanine-ui/core/drawer';
|
|
4
4
|
import { useDocumentEscapeKeyDown } from '../hooks/useDocumentEscapeKeyDown.js';
|
|
5
5
|
import useTopStack from '../_internal/SlideFadeOverlay/useTopStack.js';
|
|
6
6
|
import ClearActions from '../ClearActions/ClearActions.js';
|
|
7
7
|
import Button from '../Button/Button.js';
|
|
8
|
+
import Radio from '../Radio/Radio.js';
|
|
9
|
+
import RadioGroup from '../Radio/RadioGroup.js';
|
|
8
10
|
import { MOTION_EASING, MOTION_DURATION } from '@mezzanine-ui/system/motion';
|
|
9
11
|
import Backdrop from '../Backdrop/Backdrop.js';
|
|
10
12
|
import Slide from '../Transition/Slide.js';
|
|
11
13
|
import cx from 'clsx';
|
|
12
14
|
|
|
13
15
|
const Drawer = forwardRef((props, ref) => {
|
|
14
|
-
const { bottomGhostActionText, bottomOnGhostActionClick, bottomOnPrimaryActionClick, bottomOnSecondaryActionClick, bottomPrimaryActionText, bottomSecondaryActionText, children, className, container, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal, headerTitle, isBottomDisplay, isHeaderDisplay, onBackdropClick, onClose, open, size = 'medium', ...rest } = props;
|
|
16
|
+
const { bottomGhostActionDisabled, bottomGhostActionIcon, bottomGhostActionIconType, bottomGhostActionLoading, bottomGhostActionSize, bottomGhostActionText, bottomGhostActionVariant = 'base-ghost', bottomOnGhostActionClick, bottomOnPrimaryActionClick, bottomOnSecondaryActionClick, bottomPrimaryActionDisabled, bottomPrimaryActionIcon, bottomPrimaryActionIconType, bottomPrimaryActionLoading, bottomPrimaryActionSize, bottomPrimaryActionText, bottomPrimaryActionVariant = 'base-primary', bottomSecondaryActionDisabled, bottomSecondaryActionIcon, bottomSecondaryActionIconType, bottomSecondaryActionLoading, bottomSecondaryActionSize, bottomSecondaryActionText, bottomSecondaryActionVariant = 'base-secondary', children, className, container, controlBarAllRadioLabel, controlBarCustomButtonLabel = '全部已讀', controlBarDefaultValue, controlBarIsEmpty = false, controlBarOnCustomButtonClick, controlBarOnRadioChange, controlBarReadRadioLabel, controlBarShow = false, controlBarShowUnreadButton = false, controlBarUnreadRadioLabel, controlBarValue, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal, headerTitle, isBottomDisplay, isHeaderDisplay, onBackdropClick, onClose, open, renderControlBar: customRenderControlBar, size = 'medium', ...rest } = props;
|
|
15
17
|
const [exited, setExited] = useState(true);
|
|
16
18
|
/**
|
|
17
19
|
* Escape keydown close: escape will only close the top drawer
|
|
18
20
|
*/
|
|
19
21
|
const checkIsOnTheTop = useTopStack(open);
|
|
22
|
+
const renderControlBar = useMemo(() => {
|
|
23
|
+
// If custom renderControlBar is provided, use it
|
|
24
|
+
if (customRenderControlBar) {
|
|
25
|
+
return customRenderControlBar;
|
|
26
|
+
}
|
|
27
|
+
// Default control bar implementation
|
|
28
|
+
if (!controlBarShow) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return () => {
|
|
32
|
+
const radios = [];
|
|
33
|
+
if (controlBarAllRadioLabel) {
|
|
34
|
+
radios.push(jsx(Radio, { type: "segment", value: "all", children: controlBarAllRadioLabel }, "all"));
|
|
35
|
+
}
|
|
36
|
+
if (controlBarReadRadioLabel) {
|
|
37
|
+
radios.push(jsx(Radio, { type: "segment", value: "read", children: controlBarReadRadioLabel }, "read"));
|
|
38
|
+
}
|
|
39
|
+
if (controlBarUnreadRadioLabel && controlBarShowUnreadButton) {
|
|
40
|
+
radios.push(jsx(Radio, { type: "segment", value: "unread", children: controlBarUnreadRadioLabel }, "unread"));
|
|
41
|
+
}
|
|
42
|
+
const hasRadios = radios.length > 0;
|
|
43
|
+
const hasButton = controlBarOnCustomButtonClick !== undefined;
|
|
44
|
+
// Don't render if neither radios nor button are provided
|
|
45
|
+
if (!hasRadios && !hasButton) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
return (jsxs("div", { className: cx(drawerClasses.controlBar, !hasRadios && hasButton && drawerClasses.controlBarButtonOnly), children: [hasRadios && (jsx(RadioGroup, { defaultValue: controlBarDefaultValue !== null && controlBarDefaultValue !== void 0 ? controlBarDefaultValue : 'all', onChange: controlBarOnRadioChange, size: "minor", type: "segment", value: controlBarValue, children: radios })), hasButton && (jsx(Button, { disabled: controlBarIsEmpty, onClick: controlBarOnCustomButtonClick, size: "minor", type: "button", variant: "base-ghost", children: controlBarCustomButtonLabel }))] }));
|
|
49
|
+
};
|
|
50
|
+
}, [
|
|
51
|
+
controlBarAllRadioLabel,
|
|
52
|
+
controlBarCustomButtonLabel,
|
|
53
|
+
controlBarDefaultValue,
|
|
54
|
+
controlBarIsEmpty,
|
|
55
|
+
controlBarOnCustomButtonClick,
|
|
56
|
+
controlBarOnRadioChange,
|
|
57
|
+
controlBarReadRadioLabel,
|
|
58
|
+
controlBarShow,
|
|
59
|
+
controlBarShowUnreadButton,
|
|
60
|
+
controlBarUnreadRadioLabel,
|
|
61
|
+
controlBarValue,
|
|
62
|
+
customRenderControlBar,
|
|
63
|
+
]);
|
|
20
64
|
useDocumentEscapeKeyDown(() => {
|
|
21
65
|
if (!open || disableCloseOnEscapeKeyDown || !onClose) {
|
|
22
66
|
return;
|
|
@@ -37,7 +81,7 @@ const Drawer = forwardRef((props, ref) => {
|
|
|
37
81
|
}, easing: {
|
|
38
82
|
enter: MOTION_EASING.entrance,
|
|
39
83
|
exit: MOTION_EASING.exit,
|
|
40
|
-
}, in: open, onEntered: () => setExited(false), onExited: () => setExited(true), ref: ref, children: jsxs("div", { ...rest, className: cx(drawerClasses.host, drawerClasses.right, drawerClasses.size(size), className), children: [isHeaderDisplay && (jsxs("div", { className: drawerClasses.header, children: [headerTitle, jsx(ClearActions, { onClick: onClose })] })), jsx("div", { className: drawerClasses.content, children: children }), isBottomDisplay && (jsxs("div", { className: drawerClasses.bottom, children: [jsx("div", { children: bottomGhostActionText && bottomOnGhostActionClick && (jsx(Button, { onClick: bottomOnGhostActionClick, type: "button", variant:
|
|
84
|
+
}, in: open, onEntered: () => setExited(false), onExited: () => setExited(true), ref: ref, children: jsxs("div", { ...rest, className: cx(drawerClasses.host, drawerClasses.right, drawerClasses.size(size), className), children: [isHeaderDisplay && (jsxs("div", { className: drawerClasses.header, children: [headerTitle, jsx(ClearActions, { onClick: onClose })] })), renderControlBar === null || renderControlBar === void 0 ? void 0 : renderControlBar(), jsx("div", { className: drawerClasses.content, children: children }), isBottomDisplay && (jsxs("div", { className: drawerClasses.bottom, children: [jsx("div", { children: bottomGhostActionText && bottomOnGhostActionClick && (jsx(Button, { disabled: bottomGhostActionDisabled, icon: bottomGhostActionIcon, iconType: bottomGhostActionIconType, loading: bottomGhostActionLoading, onClick: bottomOnGhostActionClick, size: bottomGhostActionSize, type: "button", variant: bottomGhostActionVariant, children: bottomGhostActionText })) }), jsxs("div", { className: drawerClasses['bottom__actions'], children: [bottomSecondaryActionText && bottomOnSecondaryActionClick && (jsx(Button, { disabled: bottomSecondaryActionDisabled, icon: bottomSecondaryActionIcon, iconType: bottomSecondaryActionIconType, loading: bottomSecondaryActionLoading, onClick: bottomOnSecondaryActionClick, size: bottomSecondaryActionSize, type: "button", variant: bottomSecondaryActionVariant, children: bottomSecondaryActionText })), bottomPrimaryActionText && bottomOnPrimaryActionClick && (jsx(Button, { disabled: bottomPrimaryActionDisabled, icon: bottomPrimaryActionIcon, iconType: bottomPrimaryActionIconType, loading: bottomPrimaryActionLoading, onClick: bottomOnPrimaryActionClick, size: bottomPrimaryActionSize, type: "button", variant: bottomPrimaryActionVariant, children: bottomPrimaryActionText }))] })] }))] }) }) }));
|
|
41
85
|
});
|
|
42
86
|
|
|
43
87
|
export { Drawer as default };
|
package/Dropdown/Dropdown.d.ts
CHANGED
|
@@ -163,12 +163,12 @@ export interface DropdownProps extends DropdownItemSharedProps {
|
|
|
163
163
|
*/
|
|
164
164
|
emptyIcon?: IconDefinition;
|
|
165
165
|
/**
|
|
166
|
-
* Whether to
|
|
166
|
+
* Whether to enable portal.
|
|
167
167
|
* This prop is only relevant when `inputPosition` is set to 'outside'.
|
|
168
168
|
* Controls whether the dropdown content is rendered within the current hierarchy or portaled to the body.
|
|
169
|
-
* @default
|
|
169
|
+
* @default true
|
|
170
170
|
*/
|
|
171
|
-
|
|
171
|
+
globalPortal?: boolean;
|
|
172
172
|
/**
|
|
173
173
|
* Callback fired when the dropdown list reaches the bottom.
|
|
174
174
|
* Only fires when `maxHeight` is set and the list is scrollable.
|
|
@@ -179,5 +179,36 @@ export interface DropdownProps extends DropdownItemSharedProps {
|
|
|
179
179
|
* Only fires when `maxHeight` is set and the list is scrollable.
|
|
180
180
|
*/
|
|
181
181
|
onLeaveBottom?: () => void;
|
|
182
|
+
/**
|
|
183
|
+
* Callback fired when the dropdown list is scrolled.
|
|
184
|
+
* Receives the scroll event and computed scroll information.
|
|
185
|
+
*/
|
|
186
|
+
onScroll?: (computed: {
|
|
187
|
+
scrollTop: number;
|
|
188
|
+
maxScrollTop: number;
|
|
189
|
+
}, target: HTMLDivElement) => void;
|
|
190
|
+
/**
|
|
191
|
+
* Whether to defer the initialization of OverlayScrollbars.
|
|
192
|
+
* This can improve initial render performance.
|
|
193
|
+
* @default true
|
|
194
|
+
*/
|
|
195
|
+
scrollbarDefer?: boolean | object;
|
|
196
|
+
/**
|
|
197
|
+
* Whether to disable the custom scrollbar component.
|
|
198
|
+
* When false (default), Scrollbar component will be used when maxHeight is set.
|
|
199
|
+
* When true, falls back to native div scrolling (backward compatible).
|
|
200
|
+
* @default false
|
|
201
|
+
*/
|
|
202
|
+
scrollbarDisabled?: boolean;
|
|
203
|
+
/**
|
|
204
|
+
* The maximum width of the scrollable container.
|
|
205
|
+
* Can be a CSS value string (e.g., '500px', '100%') or a number (treated as pixels).
|
|
206
|
+
*/
|
|
207
|
+
scrollbarMaxWidth?: number | string;
|
|
208
|
+
/**
|
|
209
|
+
* Additional options to pass to OverlayScrollbars.
|
|
210
|
+
* @see https://kingsora.github.io/OverlayScrollbars/#!documentation/options
|
|
211
|
+
*/
|
|
212
|
+
scrollbarOptions?: import('overlayscrollbars').PartialOptions;
|
|
182
213
|
}
|
|
183
214
|
export default function Dropdown(props: DropdownProps): import("react/jsx-runtime").JSX.Element;
|
package/Dropdown/Dropdown.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
3
3
|
import { useId, useMemo, useState, useRef, useCallback, useEffect, cloneElement, createElement } from 'react';
|
|
4
4
|
import cx from 'clsx';
|
|
5
5
|
import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
|
|
6
|
-
import { size, offset
|
|
6
|
+
import { size, offset } from '@floating-ui/react-dom';
|
|
7
7
|
import { MOTION_EASING, MOTION_DURATION } from '@mezzanine-ui/system/motion';
|
|
8
8
|
import { TransitionGroup } from 'react-transition-group';
|
|
9
9
|
import Button from '../Button/Button.js';
|
|
@@ -14,7 +14,7 @@ import DropdownItem from './DropdownItem.js';
|
|
|
14
14
|
import Popper from '../Popper/Popper.js';
|
|
15
15
|
|
|
16
16
|
function Dropdown(props) {
|
|
17
|
-
const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, followText: followTextProp,
|
|
17
|
+
const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, followText: followTextProp, globalPortal = true, onReachBottom, onLeaveBottom, onScroll, mode, value, scrollbarDefer, scrollbarDisabled, scrollbarMaxWidth, scrollbarOptions, } = props;
|
|
18
18
|
const isInline = inputPosition === 'inside';
|
|
19
19
|
const inputId = useId();
|
|
20
20
|
const defaultListboxId = `${inputId}-listbox`;
|
|
@@ -101,9 +101,7 @@ function Dropdown(props) {
|
|
|
101
101
|
if (!customWidth) {
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
|
-
const widthValue = typeof customWidth === 'number'
|
|
105
|
-
? `${customWidth}px`
|
|
106
|
-
: customWidth;
|
|
104
|
+
const widthValue = typeof customWidth === 'number' ? `${customWidth}px` : customWidth;
|
|
107
105
|
return {
|
|
108
106
|
name: 'customWidth',
|
|
109
107
|
fn: ({ elements }) => {
|
|
@@ -138,7 +136,9 @@ function Dropdown(props) {
|
|
|
138
136
|
fn: ({ elements }) => {
|
|
139
137
|
const zIndexNum = typeof zIndexValue === 'number'
|
|
140
138
|
? zIndexValue
|
|
141
|
-
:
|
|
139
|
+
: typeof zIndexValue === 'string'
|
|
140
|
+
? parseInt(zIndexValue, 10) || zIndexValue
|
|
141
|
+
: 1;
|
|
142
142
|
Object.assign(elements.floating.style, {
|
|
143
143
|
zIndex: zIndexNum,
|
|
144
144
|
});
|
|
@@ -177,28 +177,6 @@ function Dropdown(props) {
|
|
|
177
177
|
const anchorRef = useRef(null);
|
|
178
178
|
const popperRef = useRef(null);
|
|
179
179
|
const popperControllerRef = useRef(null);
|
|
180
|
-
// Auto-update popper position when anchor element size changes
|
|
181
|
-
useEffect(() => {
|
|
182
|
-
if (!isOpen || isInline || !anchorRef.current || !popperControllerRef.current) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
const update = popperControllerRef.current.update;
|
|
186
|
-
if (!update) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
// Get floating element from controller refs
|
|
190
|
-
// Check refs exists before accessing nested properties
|
|
191
|
-
const refs = popperControllerRef.current.refs;
|
|
192
|
-
if (!refs) {
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
const floatingElement = refs.floating.current;
|
|
196
|
-
if (!floatingElement) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const cleanup = autoUpdate(anchorRef.current, floatingElement, update);
|
|
200
|
-
return cleanup;
|
|
201
|
-
}, [isOpen, isInline]);
|
|
202
180
|
// Extract combobox props logic to avoid duplication
|
|
203
181
|
const getComboboxProps = useMemo(() => {
|
|
204
182
|
const childWithRef = children;
|
|
@@ -231,6 +209,7 @@ function Dropdown(props) {
|
|
|
231
209
|
onSelect,
|
|
232
210
|
onReachBottom,
|
|
233
211
|
onLeaveBottom,
|
|
212
|
+
onScroll,
|
|
234
213
|
options,
|
|
235
214
|
type,
|
|
236
215
|
status,
|
|
@@ -239,6 +218,10 @@ function Dropdown(props) {
|
|
|
239
218
|
emptyIcon,
|
|
240
219
|
mode,
|
|
241
220
|
value,
|
|
221
|
+
scrollbarDefer,
|
|
222
|
+
scrollbarDisabled,
|
|
223
|
+
scrollbarMaxWidth,
|
|
224
|
+
scrollbarOptions,
|
|
242
225
|
}), [
|
|
243
226
|
actionConfig,
|
|
244
227
|
mergedActiveIndex,
|
|
@@ -252,6 +235,7 @@ function Dropdown(props) {
|
|
|
252
235
|
onSelect,
|
|
253
236
|
onReachBottom,
|
|
254
237
|
onLeaveBottom,
|
|
238
|
+
onScroll,
|
|
255
239
|
options,
|
|
256
240
|
type,
|
|
257
241
|
status,
|
|
@@ -260,6 +244,10 @@ function Dropdown(props) {
|
|
|
260
244
|
emptyIcon,
|
|
261
245
|
mode,
|
|
262
246
|
value,
|
|
247
|
+
scrollbarDefer,
|
|
248
|
+
scrollbarDisabled,
|
|
249
|
+
scrollbarMaxWidth,
|
|
250
|
+
scrollbarOptions,
|
|
263
251
|
]);
|
|
264
252
|
const triggerElement = useMemo(() => {
|
|
265
253
|
const childWithRef = children;
|
|
@@ -353,7 +341,7 @@ function Dropdown(props) {
|
|
|
353
341
|
}, [isInline, isOpen, setOpen]);
|
|
354
342
|
return (jsxs("div", { id: id, ref: containerRef, className: cx(dropdownClasses.root, dropdownClasses.inputPosition(inputPosition)), children: [isInline && (jsxs(TransitionGroup, { component: null, children: [!isOpen && inlineTriggerElement && (createElement(Translate, { ...translateProps, from: translateFrom, key: "inline-trigger", in: true },
|
|
355
343
|
jsx("div", { children: inlineTriggerElement }))), isOpen && (createElement(Translate, { ...translateProps, from: translateFrom, key: "inline-list", in: true },
|
|
356
|
-
jsx("div", { children: jsx(DropdownItem, { ...baseDropdownItemProps, headerContent: inlineTriggerElement }) })))] })), !isInline && (jsx(Popper, { ref: popperRef, anchor: anchorRef, className: dropdownClasses.popperWithPortal, controllerRef: popperControllerRef, open: isOpen, disablePortal:
|
|
344
|
+
jsx("div", { children: jsx(DropdownItem, { ...baseDropdownItemProps, headerContent: inlineTriggerElement }) })))] })), !isInline && (jsx(Popper, { ref: popperRef, anchor: anchorRef, className: dropdownClasses.popperWithPortal, controllerRef: popperControllerRef, open: isOpen, disablePortal: !globalPortal, options: {
|
|
357
345
|
placement: popoverPlacement,
|
|
358
346
|
middleware: [
|
|
359
347
|
offsetMiddleware,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { DropdownItemSharedProps, DropdownOptionsByType, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
|
|
3
3
|
import { type IconDefinition } from '@mezzanine-ui/icons';
|
|
4
|
+
import type { PartialOptions } from 'overlayscrollbars';
|
|
4
5
|
import { type DropdownActionProps } from './DropdownAction';
|
|
5
6
|
export interface DropdownItemProps<T extends DropdownType | undefined = DropdownType> extends Omit<DropdownItemSharedProps, 'type'> {
|
|
6
7
|
/**
|
|
@@ -83,5 +84,36 @@ export interface DropdownItemProps<T extends DropdownType | undefined = Dropdown
|
|
|
83
84
|
* Only fires when `maxHeight` is set and the list is scrollable.
|
|
84
85
|
*/
|
|
85
86
|
onLeaveBottom?: () => void;
|
|
87
|
+
/**
|
|
88
|
+
* Callback fired when the dropdown list is scrolled.
|
|
89
|
+
* Receives the scroll event and computed scroll information.
|
|
90
|
+
*/
|
|
91
|
+
onScroll?: (computed: {
|
|
92
|
+
scrollTop: number;
|
|
93
|
+
maxScrollTop: number;
|
|
94
|
+
}, target: HTMLDivElement) => void;
|
|
95
|
+
/**
|
|
96
|
+
* Whether to defer the initialization of OverlayScrollbars.
|
|
97
|
+
* This can improve initial render performance.
|
|
98
|
+
* @default true
|
|
99
|
+
*/
|
|
100
|
+
scrollbarDefer?: boolean | object;
|
|
101
|
+
/**
|
|
102
|
+
* Whether to disable the custom scrollbar component.
|
|
103
|
+
* When false (default), Scrollbar component will be used when maxHeight is set.
|
|
104
|
+
* When true, falls back to native div scrolling (backward compatible).
|
|
105
|
+
* @default false
|
|
106
|
+
*/
|
|
107
|
+
scrollbarDisabled?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* The maximum width of the scrollable container.
|
|
110
|
+
* Can be a CSS value string (e.g., '500px', '100%') or a number (treated as pixels).
|
|
111
|
+
*/
|
|
112
|
+
scrollbarMaxWidth?: number | string;
|
|
113
|
+
/**
|
|
114
|
+
* Additional options to pass to OverlayScrollbars.
|
|
115
|
+
* @see https://kingsora.github.io/OverlayScrollbars/#!documentation/options
|
|
116
|
+
*/
|
|
117
|
+
scrollbarOptions?: PartialOptions;
|
|
86
118
|
}
|
|
87
119
|
export default function DropdownItem<T extends DropdownType | undefined = DropdownType>(props: DropdownItemProps<T>): import("react/jsx-runtime").JSX.Element;
|
package/Dropdown/DropdownItem.js
CHANGED
|
@@ -10,7 +10,19 @@ import DropdownAction from './DropdownAction.js';
|
|
|
10
10
|
import DropdownItemCard from './DropdownItemCard.js';
|
|
11
11
|
import DropdownStatus from './DropdownStatus.js';
|
|
12
12
|
import { shortcutTextHandler } from './shortcutTextHandler.js';
|
|
13
|
+
import Scrollbar from '../Scrollbar/Scrollbar.js';
|
|
13
14
|
|
|
15
|
+
// Helper function to recursively get all descendant IDs from a tree option (excluding the option itself)
|
|
16
|
+
function getAllDescendantIds(option) {
|
|
17
|
+
const ids = [];
|
|
18
|
+
if (option.children && option.children.length > 0) {
|
|
19
|
+
option.children.forEach((child) => {
|
|
20
|
+
ids.push(String(child.id));
|
|
21
|
+
ids.push(...getAllDescendantIds(child));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return ids;
|
|
25
|
+
}
|
|
14
26
|
/**
|
|
15
27
|
* Limits DropdownOption array to a maximum depth, truncating extra children levels and showing error message if exceeded.
|
|
16
28
|
* @param input - The original DropdownOption array
|
|
@@ -57,13 +69,16 @@ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
|
|
|
57
69
|
return truncate(input);
|
|
58
70
|
}
|
|
59
71
|
function DropdownItem(props) {
|
|
60
|
-
const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, onReachBottom, onLeaveBottom, } = props;
|
|
72
|
+
const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, onReachBottom, onLeaveBottom, onScroll, scrollbarDefer = true, scrollbarDisabled = false, scrollbarMaxWidth, scrollbarOptions, } = props;
|
|
61
73
|
const optionsContent = truncateArrayDepth(options, 3);
|
|
62
74
|
const listRef = useRef(null);
|
|
63
75
|
const listWrapperRef = useRef(null);
|
|
76
|
+
const viewportRef = useRef(null);
|
|
77
|
+
const wasAtBottomRef = useRef(false);
|
|
64
78
|
const [expandedNodes, setExpandedNodes] = useState(new Set());
|
|
65
79
|
const hasActions = Boolean(actionConfig === null || actionConfig === void 0 ? void 0 : actionConfig.showActions);
|
|
66
80
|
const hasHeader = Boolean(headerContent);
|
|
81
|
+
const shouldUseScrollbar = maxHeight && !scrollbarDisabled;
|
|
67
82
|
// Use custom hook to measure element heights
|
|
68
83
|
const [actionRef, actionHeight] = useElementHeight(hasActions && !!maxHeight);
|
|
69
84
|
const [headerRef, headerHeight] = useElementHeight(hasHeader && !!maxHeight);
|
|
@@ -200,17 +215,39 @@ function DropdownItem(props) {
|
|
|
200
215
|
});
|
|
201
216
|
return { elements, nextIndex: currentIndex };
|
|
202
217
|
};
|
|
218
|
+
const calculateNodeSelectionState = useCallback((option, selectedIds) => {
|
|
219
|
+
if (!option.children || option.children.length === 0) {
|
|
220
|
+
const isSelected = selectedIds.includes(String(option.id));
|
|
221
|
+
return { checked: isSelected, indeterminate: false };
|
|
222
|
+
}
|
|
223
|
+
// Get all descendant IDs (excluding the parent node itself)
|
|
224
|
+
const allDescendantIds = getAllDescendantIds(option);
|
|
225
|
+
const selectedDescendants = allDescendantIds.filter((id) => selectedIds.includes(id));
|
|
226
|
+
const totalDescendants = allDescendantIds.length;
|
|
227
|
+
if (totalDescendants === 0) {
|
|
228
|
+
// No descendants, check if parent itself is selected
|
|
229
|
+
const isSelected = selectedIds.includes(String(option.id));
|
|
230
|
+
return { checked: isSelected, indeterminate: false };
|
|
231
|
+
}
|
|
232
|
+
if (selectedDescendants.length === 0) {
|
|
233
|
+
return { checked: false, indeterminate: false };
|
|
234
|
+
}
|
|
235
|
+
if (selectedDescendants.length === totalDescendants) {
|
|
236
|
+
// All descendants are selected
|
|
237
|
+
return { checked: true, indeterminate: false };
|
|
238
|
+
}
|
|
239
|
+
// Some but not all descendants are selected
|
|
240
|
+
return { checked: false, indeterminate: true };
|
|
241
|
+
}, []);
|
|
203
242
|
const renderTreeOptions = (optionList, depth, startIndex) => {
|
|
204
243
|
let currentIndex = startIndex;
|
|
244
|
+
const selectedIds = Array.isArray(value) ? value.map((id) => String(id)) : value ? [String(value)] : [];
|
|
205
245
|
const elements = (optionList !== null && optionList !== void 0 ? optionList : []).flatMap((option) => {
|
|
206
246
|
var _a, _b, _c;
|
|
207
247
|
currentIndex += 1;
|
|
208
248
|
const optionIndex = currentIndex;
|
|
209
249
|
const level = Math.min(depth, 2);
|
|
210
250
|
const isActive = optionIndex === activeIndex;
|
|
211
|
-
const isSelected = Array.isArray(value)
|
|
212
|
-
? value.includes(option.id)
|
|
213
|
-
: value === option.id;
|
|
214
251
|
const hasChildren = Boolean(option.children && option.children.length > 0);
|
|
215
252
|
const isExpanded = hasChildren && expandedNodes.has(option.id);
|
|
216
253
|
let prependIcon = undefined;
|
|
@@ -221,13 +258,31 @@ function DropdownItem(props) {
|
|
|
221
258
|
const shortcutText = option.shortcutText
|
|
222
259
|
? option.shortcutText
|
|
223
260
|
: shortcutTextHandler((_a = option.shortcutKeys) !== null && _a !== void 0 ? _a : []);
|
|
224
|
-
const
|
|
261
|
+
const selectionState = hasChildren && mode === 'multiple'
|
|
262
|
+
? calculateNodeSelectionState(option, selectedIds)
|
|
263
|
+
: {
|
|
264
|
+
checked: selectedIds.includes(String(option.id)),
|
|
265
|
+
indeterminate: false,
|
|
266
|
+
};
|
|
267
|
+
const card = (jsx(DropdownItemCard, { active: isActive, checked: selectionState.checked, indeterminate: selectionState.indeterminate, disabled: disabled, id: `${listboxId}-option-${optionIndex}`, label: option.name, level: level, mode: mode, name: option.name, onClick: () => {
|
|
225
268
|
if (disabled)
|
|
226
269
|
return;
|
|
227
|
-
if (hasChildren && type === 'tree') {
|
|
270
|
+
if (hasChildren && type === 'tree' && mode === 'multiple' && option.showCheckbox) {
|
|
271
|
+
toggleExpand(option.id);
|
|
272
|
+
}
|
|
273
|
+
else if (hasChildren && type === 'tree') {
|
|
228
274
|
toggleExpand(option.id);
|
|
229
275
|
}
|
|
230
276
|
else {
|
|
277
|
+
// In `tree` + `multiple` mode, `DropdownItemCard` already triggers selection via
|
|
278
|
+
// `onCheckedChange` when row is clicked (it toggles checked first, then calls `onClick`),
|
|
279
|
+
// so calling `onSelect` here would cause it to fire twice for leaf nodes.
|
|
280
|
+
if (!(type === 'tree' && mode === 'multiple')) {
|
|
281
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}, onCheckedChange: () => {
|
|
285
|
+
if (!disabled) {
|
|
231
286
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(option);
|
|
232
287
|
}
|
|
233
288
|
}, followText: followText, checkSite: checkSite, onMouseEnter: () => onHover === null || onHover === void 0 ? void 0 : onHover(optionIndex), prependIcon: prependIcon, showUnderline: (_b = option.showUnderline) !== null && _b !== void 0 ? _b : false, validate: (_c = option.validate) !== null && _c !== void 0 ? _c : 'default', appendContent: shortcutText }, option.id));
|
|
@@ -292,6 +347,10 @@ function DropdownItem(props) {
|
|
|
292
347
|
maxHeight: `${availableHeight}px`,
|
|
293
348
|
};
|
|
294
349
|
}, [maxHeight, actionHeight, headerHeight]);
|
|
350
|
+
const getIsAtBottom = useCallback((viewport) => {
|
|
351
|
+
const { scrollTop, scrollHeight, clientHeight } = viewport;
|
|
352
|
+
return scrollTop + clientHeight >= scrollHeight - 1;
|
|
353
|
+
}, []);
|
|
295
354
|
useEffect(() => {
|
|
296
355
|
const listElement = listRef.current;
|
|
297
356
|
if (!listElement || disabled) {
|
|
@@ -321,10 +380,17 @@ function DropdownItem(props) {
|
|
|
321
380
|
listElement.removeEventListener('keydown', handleKeyDown);
|
|
322
381
|
};
|
|
323
382
|
}, [disabled, matchShortcut, onSelect, type, toggleExpand, visibleShortcutOptions]);
|
|
324
|
-
|
|
383
|
+
const handleViewportReady = useCallback((viewport) => {
|
|
384
|
+
viewportRef.current = viewport;
|
|
385
|
+
listWrapperRef.current = viewport;
|
|
386
|
+
wasAtBottomRef.current = getIsAtBottom(viewport);
|
|
387
|
+
}, [getIsAtBottom]);
|
|
325
388
|
useEffect(() => {
|
|
389
|
+
if (shouldUseScrollbar) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
326
392
|
const listWrapperElement = listWrapperRef.current;
|
|
327
|
-
if (!listWrapperElement || !maxHeight || (!onReachBottom && !onLeaveBottom)) {
|
|
393
|
+
if (!listWrapperElement || !maxHeight || (!onReachBottom && !onLeaveBottom && !onScroll)) {
|
|
328
394
|
return;
|
|
329
395
|
}
|
|
330
396
|
// Initialize wasAtBottom state by checking current position
|
|
@@ -335,6 +401,14 @@ function DropdownItem(props) {
|
|
|
335
401
|
let wasAtBottom = checkInitialState();
|
|
336
402
|
const handleScroll = () => {
|
|
337
403
|
const { scrollTop, scrollHeight, clientHeight } = listWrapperElement;
|
|
404
|
+
const maxScrollTop = scrollHeight - clientHeight;
|
|
405
|
+
// Call onScroll callback if provided
|
|
406
|
+
if (onScroll) {
|
|
407
|
+
onScroll({
|
|
408
|
+
scrollTop,
|
|
409
|
+
maxScrollTop,
|
|
410
|
+
}, listWrapperElement);
|
|
411
|
+
}
|
|
338
412
|
// Check if scrolled to bottom (with 1px threshold for rounding errors)
|
|
339
413
|
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 1;
|
|
340
414
|
// Trigger onReachBottom when entering bottom state
|
|
@@ -351,9 +425,37 @@ function DropdownItem(props) {
|
|
|
351
425
|
return () => {
|
|
352
426
|
listWrapperElement.removeEventListener('scroll', handleScroll);
|
|
353
427
|
};
|
|
354
|
-
}, [maxHeight, onReachBottom, onLeaveBottom]);
|
|
428
|
+
}, [maxHeight, onReachBottom, onLeaveBottom, onScroll, shouldUseScrollbar]);
|
|
429
|
+
const scrollbarEvents = useMemo(() => {
|
|
430
|
+
if (!shouldUseScrollbar || (!onReachBottom && !onLeaveBottom && !onScroll)) {
|
|
431
|
+
return undefined;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
scroll: (_instance, _event) => {
|
|
435
|
+
const viewport = viewportRef.current;
|
|
436
|
+
if (!viewport)
|
|
437
|
+
return;
|
|
438
|
+
const { scrollTop, scrollHeight, clientHeight } = viewport;
|
|
439
|
+
const maxScrollTop = scrollHeight - clientHeight;
|
|
440
|
+
if (onScroll) {
|
|
441
|
+
onScroll({
|
|
442
|
+
scrollTop,
|
|
443
|
+
maxScrollTop,
|
|
444
|
+
}, viewport);
|
|
445
|
+
}
|
|
446
|
+
const isAtBottom = getIsAtBottom(viewport);
|
|
447
|
+
if (isAtBottom && !wasAtBottomRef.current) {
|
|
448
|
+
onReachBottom === null || onReachBottom === void 0 ? void 0 : onReachBottom();
|
|
449
|
+
}
|
|
450
|
+
if (!isAtBottom && wasAtBottomRef.current) {
|
|
451
|
+
onLeaveBottom === null || onLeaveBottom === void 0 ? void 0 : onLeaveBottom();
|
|
452
|
+
}
|
|
453
|
+
wasAtBottomRef.current = isAtBottom;
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}, [getIsAtBottom, shouldUseScrollbar, onReachBottom, onLeaveBottom, onScroll]);
|
|
355
457
|
return (jsxs("ul", { "aria-label": listboxLabel || (optionsContent.length === 0 ? 'Dropdown options' : undefined), className: dropdownClasses.list, id: listboxId, ref: listRef, role: "listbox", style: listStyle, tabIndex: -1, children: [hasHeader && (jsx("li", { className: dropdownClasses.listHeader, role: "presentation", ref: headerRef, children: jsx("div", { className: dropdownClasses.listHeaderInner, children: headerContent }) })), maxHeight
|
|
356
|
-
? (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) }))
|
|
458
|
+
? (shouldUseScrollbar ? (jsx(Scrollbar, { className: dropdownClasses.listWrapper, defer: scrollbarDefer, disabled: false, events: scrollbarEvents, maxHeight: listWrapperStyle === null || listWrapperStyle === void 0 ? void 0 : listWrapperStyle.maxHeight, maxWidth: scrollbarMaxWidth, onViewportReady: handleViewportReady, options: scrollbarOptions, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) })) : (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions) })))
|
|
357
459
|
: shouldShowStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (renderedOptions), hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
|
|
358
460
|
}
|
|
359
461
|
|
|
@@ -25,6 +25,11 @@ export interface DropdownItemCardProps {
|
|
|
25
25
|
* When provided, the state is controlled externally.
|
|
26
26
|
*/
|
|
27
27
|
checked?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the checkbox is in indeterminate state.
|
|
30
|
+
* Used in tree mode when some but not all children are selected.
|
|
31
|
+
*/
|
|
32
|
+
indeterminate?: boolean;
|
|
28
33
|
/**
|
|
29
34
|
* Additional className for the list item.
|
|
30
35
|
*/
|
|
@@ -10,7 +10,7 @@ import Icon from '../Icon/Icon.js';
|
|
|
10
10
|
import Checkbox from '../Checkbox/Checkbox.js';
|
|
11
11
|
|
|
12
12
|
function DropdownItemCard(props) {
|
|
13
|
-
const { active = false, appendIcon, appendContent, followText, id, label, level: levelProp, mode, name: _name, prependIcon, subTitle, validate, disabled, checked, defaultChecked, checkSite, onCheckedChange, onClick, className, onMouseEnter, showUnderline, } = props;
|
|
13
|
+
const { active = false, appendIcon, appendContent, followText, id, label, level: levelProp, mode, name: _name, prependIcon, subTitle, validate, disabled, checked, defaultChecked, indeterminate = false, checkSite, onCheckedChange, onClick, className, onMouseEnter, showUnderline, } = props;
|
|
14
14
|
const cardLabel = label || '';
|
|
15
15
|
const cardName = _name || cardLabel;
|
|
16
16
|
const level = levelProp || 0;
|
|
@@ -111,7 +111,7 @@ function DropdownItemCard(props) {
|
|
|
111
111
|
[dropdownClasses.cardActive]: active || isChecked,
|
|
112
112
|
[dropdownClasses.cardDisabled]: disabled,
|
|
113
113
|
[dropdownClasses.cardDanger]: validate === 'danger',
|
|
114
|
-
}, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
|
|
114
|
+
}, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, indeterminate: indeterminate, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
|
|
115
115
|
renderHighlightedText(labelParts, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 &&
|
|
116
116
|
renderHighlightedText(subTitleParts, dropdownClasses.cardDescription)] }), showAppendContent && (jsxs("div", { className: dropdownClasses.cardAppendContent, children: [appendContent && (jsx(Typography, { color: "text-neutral-light", children: appendContent })), appendIcon && jsx(Icon, { icon: appendIcon, color: iconColor }), checkSite === 'suffix' && isChecked && (jsx(Icon, { icon: CheckedIcon, color: appendIconColor, size: 16 }))] }))] }) }), showUnderline && jsx("div", { className: dropdownClasses.cardUnderline })] }));
|
|
117
117
|
}
|
package/Form/FormField.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FormFieldCounterColor, FormFieldSize } from '@mezzanine-ui/core/form';
|
|
1
|
+
import { ControlFieldSlotLayout, FormFieldCounterColor, FormFieldSize, LabelLayout } from '@mezzanine-ui/core/form';
|
|
2
2
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
3
3
|
import { IconDefinition } from '@mezzanine-ui/icons';
|
|
4
4
|
import { SeverityWithInfo } from '@mezzanine-ui/system/severity';
|
|
@@ -13,6 +13,12 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
13
13
|
* @default FormFieldCounterColor.INFO
|
|
14
14
|
*/
|
|
15
15
|
counterColor?: FormFieldCounterColor;
|
|
16
|
+
/**
|
|
17
|
+
* The layout variant for the control field slot.
|
|
18
|
+
* Controls the visual styling and appearance of the input control area.
|
|
19
|
+
* @default ControlFieldSlotLayout.MAIN
|
|
20
|
+
*/
|
|
21
|
+
controlFieldSlotLayout?: ControlFieldSlotLayout;
|
|
16
22
|
/**
|
|
17
23
|
* To control the field passed from children whether should be disabled.
|
|
18
24
|
* The form message won't appear if disabled.
|
|
@@ -34,7 +40,7 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
34
40
|
/**
|
|
35
41
|
* The label text for the form field.
|
|
36
42
|
*/
|
|
37
|
-
label
|
|
43
|
+
label?: string;
|
|
38
44
|
/**
|
|
39
45
|
* The icon to display next to the label.
|
|
40
46
|
* When provided, displays an icon that shows a tooltip on hover.
|
|
@@ -50,6 +56,12 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
50
56
|
* Typically used to show "(optional)" or similar text.
|
|
51
57
|
*/
|
|
52
58
|
labelOptionalMarker?: string;
|
|
59
|
+
/**
|
|
60
|
+
* The layout variant for the label area.
|
|
61
|
+
* Controls the visual styling and appearance of the label.
|
|
62
|
+
* @default LabelLayout.HORIZONTAL_MAIN
|
|
63
|
+
*/
|
|
64
|
+
labelLayout?: LabelLayout;
|
|
53
65
|
/**
|
|
54
66
|
* The name attribute for the form field.
|
|
55
67
|
* Used to identify the field in form submissions and as htmlFor in the label.
|
package/Form/FormField.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef } from 'react';
|
|
3
|
-
import { formFieldClasses, FormFieldCounterColor } from '@mezzanine-ui/core/form';
|
|
3
|
+
import { ControlFieldSlotLayout, LabelLayout, formFieldClasses, FormFieldCounterColor } from '@mezzanine-ui/core/form';
|
|
4
4
|
import { FormControlContext } from './FormControlContext.js';
|
|
5
5
|
import FormHintText from './FormHintText.js';
|
|
6
6
|
import FormLabel from './FormLabel.js';
|
|
@@ -10,7 +10,7 @@ import cx from 'clsx';
|
|
|
10
10
|
* The React component for `mezzanine` form field.
|
|
11
11
|
*/
|
|
12
12
|
const FormField = forwardRef(function FormField(props, ref) {
|
|
13
|
-
const { children, className, counter, counterColor, disabled = false, fullWidth = false, hintText, hintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker, name, required = false, size, severity = 'info', ...rest } = props;
|
|
13
|
+
const { children, className, counter, counterColor, controlFieldSlotLayout = ControlFieldSlotLayout.MAIN, disabled = false, fullWidth = false, hintText, hintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker, labelLayout = LabelLayout.HORIZONTAL_MAIN, name, required = false, size, severity = 'info', ...rest } = props;
|
|
14
14
|
const formControl = {
|
|
15
15
|
disabled,
|
|
16
16
|
fullWidth,
|
|
@@ -20,7 +20,7 @@ const FormField = forwardRef(function FormField(props, ref) {
|
|
|
20
20
|
return (jsx("div", { ...rest, ref: ref, className: cx(formFieldClasses.host, formFieldClasses.size(size), {
|
|
21
21
|
[formFieldClasses.disabled]: disabled,
|
|
22
22
|
[formFieldClasses.fullWidth]: fullWidth,
|
|
23
|
-
}, className), children: jsxs(FormControlContext.Provider, { value: formControl, children: [jsx(FormLabel, { className: formFieldClasses.labelArea, htmlFor: name, informationIcon: labelInformationIcon, informationText: labelInformationText, labelText: label, optionalMarker: labelOptionalMarker }), jsxs("div", { className: cx(formFieldClasses.dataEntry), children: [children, hintText || hintTextIcon || counter ? (jsxs("div", { className: cx(formFieldClasses.hintTextAndCounterArea, {
|
|
23
|
+
}, className), children: jsxs(FormControlContext.Provider, { value: formControl, children: [label && (jsx(FormLabel, { className: cx(formFieldClasses.labelArea, `${formFieldClasses.labelArea}--${labelLayout}`), htmlFor: name, informationIcon: labelInformationIcon, informationText: labelInformationText, labelText: label, optionalMarker: labelOptionalMarker })), jsxs("div", { className: cx(formFieldClasses.dataEntry), children: [jsx("div", { className: cx(`${formFieldClasses.controlFieldSlot}--${controlFieldSlotLayout}`), children: children }), hintText || hintTextIcon || counter ? (jsxs("div", { className: cx(formFieldClasses.hintTextAndCounterArea, {
|
|
24
24
|
[formFieldClasses.hintTextAndCounterArea + '--align-right']: !(hintText || hintTextIcon) && counter,
|
|
25
25
|
}), children: [(hintText || hintTextIcon) && (jsx(FormHintText, { hintText: hintText, hintTextIcon: hintTextIcon, severity: severity })), counter && (jsx("span", { className: cx(formFieldClasses.counter, formFieldClasses.counterColor(counterColor || FormFieldCounterColor.INFO)), children: counter }))] })) : null] })] }) }));
|
|
26
26
|
});
|
|
@@ -2,19 +2,18 @@ import { MouseEvent } from 'react';
|
|
|
2
2
|
import { SelectValue } from '../Select/typings';
|
|
3
3
|
export interface UseSelectBaseValueControl {
|
|
4
4
|
onClear?(e: MouseEvent<Element>): void;
|
|
5
|
-
onChange?(newOptions: SelectValue[] | SelectValue): any;
|
|
6
5
|
onClose?(): void;
|
|
7
6
|
}
|
|
8
7
|
export type UseSelectMultipleValueControl = UseSelectBaseValueControl & {
|
|
9
8
|
defaultValue?: SelectValue[];
|
|
10
9
|
mode: 'multiple';
|
|
11
|
-
onChange?(newOptions: SelectValue[]):
|
|
10
|
+
onChange?(newOptions: SelectValue[]): void;
|
|
12
11
|
value?: SelectValue[];
|
|
13
12
|
};
|
|
14
13
|
export type UseSelectSingleValueControl = UseSelectBaseValueControl & {
|
|
15
14
|
defaultValue?: SelectValue;
|
|
16
15
|
mode: 'single';
|
|
17
|
-
onChange?(newOption: SelectValue):
|
|
16
|
+
onChange?(newOption: SelectValue | null): void;
|
|
18
17
|
value?: SelectValue | null;
|
|
19
18
|
};
|
|
20
19
|
export type UseSelectValueControl = UseSelectMultipleValueControl | UseSelectSingleValueControl;
|
|
@@ -22,7 +21,7 @@ export interface SelectBaseValueControl {
|
|
|
22
21
|
onClear(e: MouseEvent<Element>): void;
|
|
23
22
|
}
|
|
24
23
|
export type SelectMultipleValueControl = SelectBaseValueControl & {
|
|
25
|
-
onChange: (v: SelectValue | null) => SelectValue[];
|
|
24
|
+
onChange: (v: SelectValue | SelectValue[] | null) => SelectValue[];
|
|
26
25
|
value: SelectValue[];
|
|
27
26
|
};
|
|
28
27
|
export type SelectSingleValueControl = SelectBaseValueControl & {
|