@mezzanine-ui/react 1.0.0-beta.6 → 1.0.0-beta.7
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/Accordion/Accordion.d.ts +23 -1
- package/Accordion/Accordion.js +59 -11
- package/Accordion/AccordionActions.d.ts +13 -0
- package/Accordion/AccordionActions.js +24 -0
- package/Accordion/AccordionContent.d.ts +9 -0
- package/Accordion/{AccordionDetails.js → AccordionContent.js} +4 -6
- package/Accordion/AccordionControlContext.d.ts +2 -2
- package/Accordion/AccordionGroup.d.ts +10 -0
- package/Accordion/AccordionGroup.js +26 -0
- package/Accordion/AccordionTitle.d.ts +14 -0
- package/Accordion/AccordionTitle.js +56 -0
- package/Accordion/index.d.ts +8 -4
- package/Accordion/index.js +4 -2
- package/AutoComplete/AutoComplete.d.ts +20 -6
- package/AutoComplete/AutoComplete.js +118 -30
- package/Backdrop/Backdrop.js +15 -19
- package/Calendar/CalendarDays.js +1 -1
- package/Card/BaseCard.d.ts +11 -0
- package/Card/BaseCard.js +48 -0
- package/Card/BaseCardSkeleton.d.ts +14 -0
- package/Card/BaseCardSkeleton.js +18 -0
- package/Card/CardGroup.d.ts +47 -0
- package/Card/CardGroup.js +147 -0
- package/Card/FourThumbnailCard.d.ts +14 -0
- package/Card/FourThumbnailCard.js +73 -0
- package/Card/FourThumbnailCardSkeleton.d.ts +14 -0
- package/Card/FourThumbnailCardSkeleton.js +20 -0
- package/Card/QuickActionCard.d.ts +12 -0
- package/Card/QuickActionCard.js +23 -0
- package/Card/QuickActionCardSkeleton.d.ts +14 -0
- package/Card/QuickActionCardSkeleton.js +18 -0
- package/Card/SingleThumbnailCard.d.ts +13 -0
- package/Card/SingleThumbnailCard.js +44 -0
- package/Card/SingleThumbnailCardSkeleton.d.ts +19 -0
- package/Card/SingleThumbnailCardSkeleton.js +18 -0
- package/Card/Thumbnail.d.ts +12 -0
- package/Card/Thumbnail.js +18 -0
- package/Card/ThumbnailCardInfo.d.ts +34 -0
- package/Card/ThumbnailCardInfo.js +43 -0
- package/Card/index.d.ts +43 -4
- package/Card/index.js +19 -2
- package/Card/typings.d.ts +442 -0
- package/Checkbox/Checkbox.d.ts +8 -0
- package/Checkbox/Checkbox.js +3 -2
- package/Checkbox/CheckboxGroup.js +1 -1
- package/ContentHeader/ContentHeader.d.ts +22 -70
- package/ContentHeader/ContentHeader.js +1 -1
- package/ContentHeader/ContentHeaderResponsive.d.ts +9 -0
- package/ContentHeader/ContentHeaderResponsive.js +7 -0
- package/ContentHeader/utils.d.ts +3 -3
- package/ContentHeader/utils.js +66 -20
- package/Cropper/Cropper.d.ts +66 -0
- package/Cropper/Cropper.js +115 -0
- package/Cropper/CropperElement.d.ts +10 -0
- package/Cropper/CropperElement.js +892 -0
- package/Cropper/index.d.ts +18 -0
- package/Cropper/index.js +8 -0
- package/Cropper/tools.d.ts +90 -0
- package/Cropper/tools.js +143 -0
- package/Cropper/typings.d.ts +69 -0
- package/Cropper/utils/cropper-calculations.d.ts +39 -0
- package/Cropper/utils/cropper-calculations.js +95 -0
- package/DateTimePicker/DateTimePicker.d.ts +1 -1
- package/DateTimePicker/DateTimePicker.js +14 -1
- package/Dropdown/Dropdown.d.ts +7 -1
- package/Dropdown/Dropdown.js +31 -14
- package/Dropdown/DropdownItem.d.ts +7 -1
- package/Dropdown/DropdownItem.js +36 -6
- package/Dropdown/DropdownItemCard.js +2 -1
- package/FloatingButton/FloatingButton.d.ts +21 -0
- package/FloatingButton/FloatingButton.js +18 -0
- package/FloatingButton/index.d.ts +2 -0
- package/FloatingButton/index.js +1 -0
- package/Form/FormField.d.ts +21 -10
- package/Form/FormField.js +12 -4
- package/Input/Input.js +9 -2
- package/Message/Message.js +1 -1
- package/MultipleDatePicker/MultipleDatePicker.js +2 -2
- package/Navigation/NavigationHeader.js +1 -1
- package/Picker/FormattedInput.d.ts +1 -1
- package/Picker/FormattedInput.js +2 -1
- package/Picker/PickerTriggerWithSeparator.d.ts +10 -0
- package/Picker/PickerTriggerWithSeparator.js +2 -2
- package/Picker/useDateInputFormatter.d.ts +6 -0
- package/Picker/useDateInputFormatter.js +4 -1
- package/Select/Select.d.ts +2 -8
- package/Select/Select.js +12 -33
- package/Select/SelectTrigger.js +21 -7
- package/Select/index.d.ts +0 -4
- package/Select/index.js +0 -2
- package/Select/typings.d.ts +0 -4
- package/Select/useSelectTriggerTags.d.ts +1 -1
- package/Select/useSelectTriggerTags.js +9 -6
- package/Separator/Separator.d.ts +14 -0
- package/Separator/Separator.js +17 -0
- package/Separator/index.d.ts +2 -0
- package/Separator/index.js +1 -0
- package/Table/utils/useTableRowSelection.js +6 -0
- package/Tag/TagGroup.d.ts +4 -2
- package/Tag/TagGroup.js +7 -4
- package/TextField/TextField.d.ts +1 -1
- package/TextField/TextField.js +63 -9
- package/TimePanel/TimePanelColumn.js +19 -12
- package/index.d.ts +27 -28
- package/index.js +23 -25
- package/package.json +4 -4
- package/Accordion/AccordionDetails.d.ts +0 -9
- package/Accordion/AccordionSummary.d.ts +0 -18
- package/Accordion/AccordionSummary.js +0 -51
- package/Alert/Alert.d.ts +0 -20
- package/Alert/Alert.js +0 -18
- package/Alert/index.d.ts +0 -3
- package/Alert/index.js +0 -1
- package/Card/Card.d.ts +0 -51
- package/Card/Card.js +0 -20
- package/Card/CardActions.d.ts +0 -34
- package/Card/CardActions.js +0 -15
- package/ConfirmActions/ConfirmActions.d.ts +0 -46
- package/ConfirmActions/ConfirmActions.js +0 -15
- package/ConfirmActions/index.d.ts +0 -2
- package/ConfirmActions/index.js +0 -1
- package/Select/Option.d.ts +0 -18
- package/Select/Option.js +0 -45
- package/Select/TreeSelect.d.ts +0 -72
- package/Select/TreeSelect.js +0 -205
- package/Tree/Tree.d.ts +0 -70
- package/Tree/Tree.js +0 -139
- package/Tree/TreeNode.d.ts +0 -40
- package/Tree/TreeNode.js +0 -50
- package/Tree/TreeNodeList.d.ts +0 -24
- package/Tree/TreeNodeList.js +0 -28
- package/Tree/getTreeNodeEntities.d.ts +0 -11
- package/Tree/getTreeNodeEntities.js +0 -92
- package/Tree/index.d.ts +0 -13
- package/Tree/index.js +0 -7
- package/Tree/toggleValue.d.ts +0 -4
- package/Tree/toggleValue.js +0 -19
- package/Tree/traverseTree.d.ts +0 -2
- package/Tree/traverseTree.js +0 -11
- package/Tree/typings.d.ts +0 -16
- package/Tree/useTreeExpandedValue.d.ts +0 -14
- package/Tree/useTreeExpandedValue.js +0 -33
package/Dropdown/DropdownItem.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import keycode from 'keycode';
|
|
4
4
|
import { useRef, useState, useCallback, useMemo, useEffect } from 'react';
|
|
5
5
|
import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
|
|
@@ -69,7 +69,7 @@ function truncateArrayDepth(input, maxDepth = 3, warn = true) {
|
|
|
69
69
|
return truncate(input);
|
|
70
70
|
}
|
|
71
71
|
function DropdownItem(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;
|
|
72
|
+
const { activeIndex, disabled = false, listboxId, listboxLabel, mode = 'single', options, value, type, maxHeight, actionConfig, onHover, onSelect, followText, headerContent, status, loadingText, emptyText, emptyIcon, loadingPosition = 'full', onReachBottom, onLeaveBottom, onScroll, scrollbarDefer = true, scrollbarDisabled = false, scrollbarMaxWidth, scrollbarOptions, } = props;
|
|
73
73
|
const optionsContent = truncateArrayDepth(options, 3);
|
|
74
74
|
const listRef = useRef(null);
|
|
75
75
|
const listWrapperRef = useRef(null);
|
|
@@ -327,8 +327,10 @@ function DropdownItem(props) {
|
|
|
327
327
|
return renderDefaultOptions(optionList, startIndex);
|
|
328
328
|
};
|
|
329
329
|
const { elements: renderedOptions } = renderOptions(optionsContent, 0, -1);
|
|
330
|
-
// Show status when options are empty and status is provided
|
|
331
|
-
const
|
|
330
|
+
// Show full status when options are empty and status is provided, but not when loadingPosition is 'bottom'
|
|
331
|
+
const shouldShowFullStatus = optionsContent.length === 0 && status && loadingPosition !== 'bottom';
|
|
332
|
+
// Show bottom loading when status is loading and loadingPosition is bottom
|
|
333
|
+
const shouldShowBottomLoading = status === 'loading' && loadingPosition === 'bottom';
|
|
332
334
|
const listStyle = useMemo(() => {
|
|
333
335
|
if (!maxHeight) {
|
|
334
336
|
return undefined;
|
|
@@ -385,6 +387,34 @@ function DropdownItem(props) {
|
|
|
385
387
|
listWrapperRef.current = viewport;
|
|
386
388
|
wasAtBottomRef.current = getIsAtBottom(viewport);
|
|
387
389
|
}, [getIsAtBottom]);
|
|
390
|
+
// Track previous loading state to only scroll when loading appears (not when it disappears)
|
|
391
|
+
const prevShouldShowBottomLoadingRef = useRef(false);
|
|
392
|
+
// Auto-scroll to bottom when bottom loading appears and user was at bottom
|
|
393
|
+
useEffect(() => {
|
|
394
|
+
// Only scroll when loading appears (transitions from false to true)
|
|
395
|
+
const loadingJustAppeared = shouldShowBottomLoading && !prevShouldShowBottomLoadingRef.current;
|
|
396
|
+
prevShouldShowBottomLoadingRef.current = shouldShowBottomLoading;
|
|
397
|
+
if (!loadingJustAppeared)
|
|
398
|
+
return;
|
|
399
|
+
const scrollToBottom = (element) => {
|
|
400
|
+
// Use requestAnimationFrame to ensure DOM is updated after loading element is rendered
|
|
401
|
+
requestAnimationFrame(() => {
|
|
402
|
+
element.scrollTop = element.scrollHeight;
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
if (shouldUseScrollbar) {
|
|
406
|
+
const viewport = viewportRef.current;
|
|
407
|
+
if (viewport && wasAtBottomRef.current) {
|
|
408
|
+
scrollToBottom(viewport);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
const listWrapperElement = listWrapperRef.current;
|
|
413
|
+
if (listWrapperElement && maxHeight && wasAtBottomRef.current) {
|
|
414
|
+
scrollToBottom(listWrapperElement);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}, [shouldShowBottomLoading, shouldUseScrollbar, maxHeight]);
|
|
388
418
|
useEffect(() => {
|
|
389
419
|
if (shouldUseScrollbar) {
|
|
390
420
|
return;
|
|
@@ -455,8 +485,8 @@ function DropdownItem(props) {
|
|
|
455
485
|
};
|
|
456
486
|
}, [getIsAtBottom, shouldUseScrollbar, onReachBottom, onLeaveBottom, onScroll]);
|
|
457
487
|
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
|
|
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:
|
|
459
|
-
:
|
|
488
|
+
? (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: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) })) : (jsx("div", { ref: listWrapperRef, className: dropdownClasses.listWrapper, style: listWrapperStyle, children: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })) })))
|
|
489
|
+
: shouldShowFullStatus ? (jsx(DropdownStatus, { status: status, loadingText: loadingText, emptyText: emptyText, emptyIcon: emptyIcon })) : (jsxs(Fragment, { children: [renderedOptions, shouldShowBottomLoading && (jsx("li", { className: dropdownClasses.loadingMore, "aria-live": "polite", role: "status", children: jsx(DropdownStatus, { status: "loading", loadingText: loadingText }) }))] })), hasActions && (jsx("div", { ref: actionRef, children: jsx(DropdownAction, { ...actionConfig }) }))] }));
|
|
460
490
|
}
|
|
461
491
|
|
|
462
492
|
export { DropdownItem as default };
|
|
@@ -8,6 +8,7 @@ import Typography from '../Typography/Typography.js';
|
|
|
8
8
|
import { highlightText } from './highlightText.js';
|
|
9
9
|
import Icon from '../Icon/Icon.js';
|
|
10
10
|
import Checkbox from '../Checkbox/Checkbox.js';
|
|
11
|
+
import Separator from '../Separator/Separator.js';
|
|
11
12
|
|
|
12
13
|
function DropdownItemCard(props) {
|
|
13
14
|
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;
|
|
@@ -113,7 +114,7 @@ function DropdownItemCard(props) {
|
|
|
113
114
|
[dropdownClasses.cardDanger]: validate === 'danger',
|
|
114
115
|
}, 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
116
|
renderHighlightedText(labelParts, dropdownClasses.cardTitle, labelId), subTitleParts.length > 0 &&
|
|
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("
|
|
117
|
+
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("li", { role: "presentation", "aria-hidden": "true", children: jsx(Separator, { orientation: "horizontal", className: dropdownClasses.cardUnderline }) }))] }));
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
export { DropdownItemCard as default };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ButtonProps } from '../Button';
|
|
2
|
+
export interface FloatingButtonProps extends Omit<ButtonProps, 'variant' | 'size' | 'className' | 'tooltipPosition'> {
|
|
3
|
+
/**
|
|
4
|
+
* Auto hide floating button when `open` is true.
|
|
5
|
+
* @default false
|
|
6
|
+
*/
|
|
7
|
+
autoHideWhenOpen?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* The class name of the root element.
|
|
10
|
+
*/
|
|
11
|
+
className?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Whether the floating button is in open state.
|
|
14
|
+
*/
|
|
15
|
+
open?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The react component for `mezzanine` floating button.
|
|
19
|
+
*/
|
|
20
|
+
declare const FloatingButton: import("react").ForwardRefExoticComponent<FloatingButtonProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
21
|
+
export default FloatingButton;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import Button from '../Button/Button.js';
|
|
5
|
+
import { floatingButtonClasses } from '@mezzanine-ui/core/floating-button';
|
|
6
|
+
import cx from 'clsx';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The react component for `mezzanine` floating button.
|
|
10
|
+
*/
|
|
11
|
+
const FloatingButton = forwardRef(function FloatingButton(props, ref) {
|
|
12
|
+
const { autoHideWhenOpen = false, children, className, open = false, ...rest } = props;
|
|
13
|
+
return (jsx("div", { ref: ref, className: cx(floatingButtonClasses.host, className), children: jsx(Button, { ...rest, className: cx(floatingButtonClasses.button, {
|
|
14
|
+
[floatingButtonClasses.buttonHidden]: autoHideWhenOpen && open,
|
|
15
|
+
}), variant: "base-primary", size: "main", tooltipPosition: "left", children: children }) }));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export { FloatingButton as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FloatingButton.js';
|
package/Form/FormField.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ControlFieldSlotLayout, FormFieldCounterColor,
|
|
1
|
+
import { ControlFieldSlotLayout, FormFieldCounterColor, FormFieldDensity, FormFieldLabelSpacing, FormFieldLayout } 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';
|
|
@@ -57,11 +57,27 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
57
57
|
*/
|
|
58
58
|
labelOptionalMarker?: string;
|
|
59
59
|
/**
|
|
60
|
-
* The
|
|
61
|
-
* Controls the
|
|
62
|
-
*
|
|
60
|
+
* The spacing variant for the label area.
|
|
61
|
+
* Controls the padding and min-height of the label.
|
|
62
|
+
* Only applicable when layout is 'horizontal' or 'stretch'.
|
|
63
|
+
* Ignored when layout is 'vertical'.
|
|
64
|
+
* @default FormFieldLabelSpacing.MAIN
|
|
63
65
|
*/
|
|
64
|
-
|
|
66
|
+
labelSpacing?: FormFieldLabelSpacing;
|
|
67
|
+
/**
|
|
68
|
+
* The layout variant of the form field.
|
|
69
|
+
* Controls the arrangement direction of label and input.
|
|
70
|
+
* When set to 'vertical', density and labelSpacing are ignored.
|
|
71
|
+
* @default FormFieldLayout.HORIZONTAL
|
|
72
|
+
*/
|
|
73
|
+
layout?: FormFieldLayout;
|
|
74
|
+
/**
|
|
75
|
+
* The density variant of the form field.
|
|
76
|
+
* Controls the width of label and max-width of data entry.
|
|
77
|
+
* Only applicable when layout is 'horizontal' or 'stretch'.
|
|
78
|
+
* Ignored when layout is 'vertical'.
|
|
79
|
+
*/
|
|
80
|
+
density?: FormFieldDensity;
|
|
65
81
|
/**
|
|
66
82
|
* The name attribute for the form field.
|
|
67
83
|
* Used to identify the field in form submissions and as htmlFor in the label.
|
|
@@ -71,11 +87,6 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
71
87
|
* To control the field passed from children whether should be required.
|
|
72
88
|
*/
|
|
73
89
|
required?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* The size variant of the form field.
|
|
76
|
-
* Controls the layout and spacing of label, input, and other elements.
|
|
77
|
-
*/
|
|
78
|
-
size: FormFieldSize;
|
|
79
90
|
/**
|
|
80
91
|
* The severity level of the form field.
|
|
81
92
|
* Used to indicate the importance or urgency of the field.
|
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 { ControlFieldSlotLayout,
|
|
3
|
+
import { ControlFieldSlotLayout, FormFieldLabelSpacing, FormFieldLayout, 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,17 +10,25 @@ 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, controlFieldSlotLayout = ControlFieldSlotLayout.MAIN, disabled = false, fullWidth = false, hintText, hintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker,
|
|
13
|
+
const { children, className, counter, counterColor, controlFieldSlotLayout = ControlFieldSlotLayout.MAIN, density, disabled = false, fullWidth = false, hintText, hintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker, labelSpacing = FormFieldLabelSpacing.MAIN, layout = FormFieldLayout.HORIZONTAL, name, required = false, severity = 'info', ...rest } = props;
|
|
14
14
|
const formControl = {
|
|
15
15
|
disabled,
|
|
16
16
|
fullWidth,
|
|
17
17
|
required,
|
|
18
18
|
severity,
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
const shouldApplyDensity = density && layout !== FormFieldLayout.VERTICAL;
|
|
21
|
+
const densityClass = shouldApplyDensity
|
|
22
|
+
? formFieldClasses.density(density)
|
|
23
|
+
: undefined;
|
|
24
|
+
const shouldApplyLabelSpacing = layout !== FormFieldLayout.VERTICAL;
|
|
25
|
+
const labelSpacingClass = shouldApplyLabelSpacing
|
|
26
|
+
? formFieldClasses.labelSpacing(labelSpacing)
|
|
27
|
+
: undefined;
|
|
28
|
+
return (jsx("div", { ...rest, ref: ref, className: cx(formFieldClasses.host, formFieldClasses.layout(layout), densityClass, {
|
|
21
29
|
[formFieldClasses.disabled]: disabled,
|
|
22
30
|
[formFieldClasses.fullWidth]: fullWidth,
|
|
23
|
-
}, className), children: jsxs(FormControlContext.Provider, { value: formControl, children: [label && (jsx(FormLabel, { className: cx(formFieldClasses.labelArea,
|
|
31
|
+
}, className), children: jsxs(FormControlContext.Provider, { value: formControl, children: [label && (jsx(FormLabel, { className: cx(formFieldClasses.labelArea, labelSpacingClass), 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
32
|
[formFieldClasses.hintTextAndCounterArea + '--align-right']: !(hintText || hintTextIcon) && counter,
|
|
25
33
|
}), 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
34
|
});
|
package/Input/Input.js
CHANGED
|
@@ -124,6 +124,7 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
124
124
|
break;
|
|
125
125
|
}
|
|
126
126
|
case 'currency': {
|
|
127
|
+
// 需注意 input type 不應是 number,因為要允許輸入格式化後的字串(例如 1,000)
|
|
127
128
|
const currencyProps = props;
|
|
128
129
|
const { step = 1, max, min, onSpinUp, onSpinDown } = currencyProps;
|
|
129
130
|
// 預設置右對齊
|
|
@@ -166,11 +167,17 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
166
167
|
const { actionButton } = actionProps;
|
|
167
168
|
if (actionButton.position === 'prefix') {
|
|
168
169
|
const { ...restActionButtonProps } = actionButton;
|
|
169
|
-
|
|
170
|
+
const actionDisabled = typeof restActionButtonProps.disabled === 'boolean'
|
|
171
|
+
? restActionButtonProps.disabled
|
|
172
|
+
: disabled || readonly;
|
|
173
|
+
prefixExternalButton = (jsx(ActionButton, { ...restActionButtonProps, disabled: actionDisabled, size: size }));
|
|
170
174
|
}
|
|
171
175
|
if (actionButton.position === 'suffix') {
|
|
172
176
|
const { ...restActionButtonProps } = actionButton;
|
|
173
|
-
|
|
177
|
+
const actionDisabled = typeof restActionButtonProps.disabled === 'boolean'
|
|
178
|
+
? restActionButtonProps.disabled
|
|
179
|
+
: disabled || readonly;
|
|
180
|
+
suffixExternalButton = (jsx(ActionButton, { ...restActionButtonProps, disabled: actionDisabled, size: size }));
|
|
174
181
|
}
|
|
175
182
|
break;
|
|
176
183
|
}
|
package/Message/Message.js
CHANGED
|
@@ -5,8 +5,8 @@ import { messageClasses, messageIcons } from '@mezzanine-ui/core/message';
|
|
|
5
5
|
import { messageTimerController } from './MessageTimerController.js';
|
|
6
6
|
import Translate from '../Transition/Translate.js';
|
|
7
7
|
import Icon from '../Icon/Icon.js';
|
|
8
|
-
import { createNotifier } from '../Notifier/createNotifier.js';
|
|
9
8
|
import cx from 'clsx';
|
|
9
|
+
import { createNotifier } from '../Notifier/createNotifier.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* The react component for `mezzanine` message.
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
4
3
|
import { getDefaultModeFormat, calendarClasses } from '@mezzanine-ui/core/calendar';
|
|
5
4
|
import { multipleDatePickerClasses } from '@mezzanine-ui/core/multiple-date-picker';
|
|
6
5
|
import { CalendarIcon } from '@mezzanine-ui/icons';
|
|
6
|
+
import { forwardRef, useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
|
7
7
|
import CalendarFooterActions from '../Calendar/CalendarFooterActions.js';
|
|
8
|
+
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
8
9
|
import MultipleDatePickerTrigger from './MultipleDatePickerTrigger.js';
|
|
9
10
|
import { useMultipleDatePickerValue } from './useMultipleDatePickerValue.js';
|
|
10
|
-
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
11
11
|
import { useCalendarContext } from '../Calendar/CalendarContext.js';
|
|
12
12
|
import { useCalendarControls } from '../Calendar/useCalendarControls.js';
|
|
13
13
|
import { usePickerDocumentEventClose } from '../Picker/usePickerDocumentEventClose.js';
|
|
@@ -10,7 +10,7 @@ const NavigationHeader = forwardRef((props, ref) => {
|
|
|
10
10
|
const { children, className, title, onBrandClick, ...rest } = props;
|
|
11
11
|
const { collapsed, handleCollapseChange } = use(NavigationActivatedContext);
|
|
12
12
|
const BrandComponent = onBrandClick ? 'button' : 'span';
|
|
13
|
-
return (jsxs("header", { ...rest, ref: ref, className: cx(navigationHeaderClasses.host, collapsed && navigationHeaderClasses.collapsed, className), children: [jsx(NavigationIconButton, { onClick: () => handleCollapseChange(!collapsed), icon: SiderIcon }), jsxs(BrandComponent, { type: "button", className: navigationHeaderClasses.content, onClick: onBrandClick, children: [children, jsx("span", { className: navigationHeaderClasses.title, children: title })] })] }));
|
|
13
|
+
return (jsxs("header", { ...rest, ref: ref, className: cx(navigationHeaderClasses.host, collapsed && navigationHeaderClasses.collapsed, className), children: [jsx(NavigationIconButton, { onClick: () => handleCollapseChange(!collapsed), icon: SiderIcon }), jsxs(BrandComponent, { type: "button", className: navigationHeaderClasses.content, onClick: onBrandClick, children: [children, jsx("span", { className: navigationHeaderClasses.title, children: collapsed ? title === null || title === void 0 ? void 0 : title[0] : title })] })] }));
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
export { NavigationHeader as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { InputHTMLAttributes } from 'react';
|
|
2
2
|
import { type UseDateInputFormatterProps } from './useDateInputFormatter';
|
|
3
|
-
export interface FormattedInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'>, Pick<UseDateInputFormatterProps, 'errorMessages' | 'validate' | 'format' | 'onChange'> {
|
|
3
|
+
export interface FormattedInputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value'>, Pick<UseDateInputFormatterProps, 'errorMessages' | 'validate' | 'format' | 'onChange' | 'onPasteIsoValue'> {
|
|
4
4
|
/**
|
|
5
5
|
* Placeholder to show when not focused and value is empty
|
|
6
6
|
*/
|
package/Picker/FormattedInput.js
CHANGED
|
@@ -14,7 +14,7 @@ const FormattedInput = forwardRef(function FormattedInput(props, ref) {
|
|
|
14
14
|
enabled: true,
|
|
15
15
|
invalidInput: 'Input value is not valid.',
|
|
16
16
|
invalidPaste: 'Pasted content is not valid.',
|
|
17
|
-
}, format, placeholder, validate, value: externalValue, onChange, onFocus, onBlur, ...inputProps } = props;
|
|
17
|
+
}, format, placeholder, validate, value: externalValue, onChange, onFocus, onBlur, onPasteIsoValue, ...inputProps } = props;
|
|
18
18
|
const internalInputRef = useRef(null);
|
|
19
19
|
const composedRef = useComposeRefs([ref, internalInputRef]);
|
|
20
20
|
const { value, focused, handleKeyDown, handleFocus, handleBlur, handlePaste, } = useDateInputFormatter({
|
|
@@ -26,6 +26,7 @@ const FormattedInput = forwardRef(function FormattedInput(props, ref) {
|
|
|
26
26
|
onFocus,
|
|
27
27
|
onBlur,
|
|
28
28
|
validate,
|
|
29
|
+
onPasteIsoValue,
|
|
29
30
|
});
|
|
30
31
|
const segments = useRef(parseFormatSegments(format)).current;
|
|
31
32
|
const renderMixedColorDisplay = () => {
|
|
@@ -63,6 +63,16 @@ export interface PickerTriggerWithSeparatorProps extends Omit<TextFieldProps, 'a
|
|
|
63
63
|
* Can be used to trigger auto-focus to right input.
|
|
64
64
|
*/
|
|
65
65
|
onLeftComplete?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* Callback when a valid ISO date is pasted into the left input.
|
|
68
|
+
* Allows parent to sync other fields (e.g., update time when date+time is pasted).
|
|
69
|
+
*/
|
|
70
|
+
onPasteIsoValueLeft?: (isoValue: string) => void;
|
|
71
|
+
/**
|
|
72
|
+
* Callback when a valid ISO date is pasted into the right input.
|
|
73
|
+
* Allows parent to sync other fields (e.g., update date when date+time is pasted).
|
|
74
|
+
*/
|
|
75
|
+
onPasteIsoValueRight?: (isoValue: string) => void;
|
|
66
76
|
/**
|
|
67
77
|
* Callback when right input is completed (all mask positions filled with valid value).
|
|
68
78
|
*/
|
|
@@ -12,7 +12,7 @@ import cx from 'clsx';
|
|
|
12
12
|
* typically used for date-time pickers where left is date and right is time.
|
|
13
13
|
*/
|
|
14
14
|
const PickerTriggerWithSeparator = forwardRef(function PickerTriggerWithSeparator(props, ref) {
|
|
15
|
-
const { className, clearable = true, disabled, errorMessagesLeft, errorMessagesRight, formatLeft, formatRight, inputLeftProps, inputLeftRef: inputLeftRefProp, inputRightProps, inputRightRef: inputRightRefProp, onBlurLeft, onBlurRight, onChangeLeft, onChangeRight, onFocusLeft, onFocusRight, onLeftComplete, onRightComplete, placeholderLeft, placeholderRight, readOnly, required, suffix, validateLeft, validateRight, valueLeft, valueRight, ...restTextFieldProps } = props;
|
|
15
|
+
const { className, clearable = true, disabled, errorMessagesLeft, errorMessagesRight, formatLeft, formatRight, inputLeftProps, inputLeftRef: inputLeftRefProp, inputRightProps, inputRightRef: inputRightRefProp, onBlurLeft, onBlurRight, onChangeLeft, onChangeRight, onFocusLeft, onFocusRight, onLeftComplete, onPasteIsoValueLeft, onPasteIsoValueRight, onRightComplete, placeholderLeft, placeholderRight, readOnly, required, suffix, validateLeft, validateRight, valueLeft, valueRight, ...restTextFieldProps } = props;
|
|
16
16
|
const internalLeftRef = useRef(null);
|
|
17
17
|
const internalRightRef = useRef(null);
|
|
18
18
|
const leftRef = useComposeRefs([
|
|
@@ -74,7 +74,7 @@ const PickerTriggerWithSeparator = forwardRef(function PickerTriggerWithSeparato
|
|
|
74
74
|
const handleRightBlur = useCallback((e) => {
|
|
75
75
|
onBlurRight === null || onBlurRight === void 0 ? void 0 : onBlurRight(e);
|
|
76
76
|
}, [onBlurRight]);
|
|
77
|
-
return (jsx(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, className), clearable: !readOnly && clearable, suffix: suffix, children: jsxs("div", { className: pickerClasses.separatorInputs, children: [jsx("div", { className: pickerClasses.separatorInput, children: jsx(FormattedInput, { ...inputLeftProps, ref: leftRef, "aria-disabled": disabled, "aria-label": "Date input", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesLeft, format: formatLeft, onBlur: handleLeftBlur, onChange: handleLeftChange, onFocus: handleLeftFocus, placeholder: placeholderLeft, readOnly: readOnly, required: required, validate: validateLeft, value: valueLeft }) }), jsx("div", { className: pickerClasses.separator }), jsx("div", { className: pickerClasses.separatorInput, children: jsx(FormattedInput, { ...inputRightProps, ref: rightRef, "aria-disabled": disabled, "aria-label": "Time input", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesRight, format: formatRight, onBlur: handleRightBlur, onChange: handleRightChange, onFocus: handleRightFocus, placeholder: placeholderRight, readOnly: readOnly, required: required, validate: validateRight, value: valueRight }) })] }) }));
|
|
77
|
+
return (jsx(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, className), clearable: !readOnly && clearable, suffix: suffix, children: jsxs("div", { className: pickerClasses.separatorInputs, children: [jsx("div", { className: pickerClasses.separatorInput, children: jsx(FormattedInput, { ...inputLeftProps, ref: leftRef, "aria-disabled": disabled, "aria-label": "Date input", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesLeft, format: formatLeft, onBlur: handleLeftBlur, onChange: handleLeftChange, onFocus: handleLeftFocus, onPasteIsoValue: onPasteIsoValueLeft, placeholder: placeholderLeft, readOnly: readOnly, required: required, validate: validateLeft, value: valueLeft }) }), jsx("div", { className: pickerClasses.separator }), jsx("div", { className: pickerClasses.separatorInput, children: jsx(FormattedInput, { ...inputRightProps, ref: rightRef, "aria-disabled": disabled, "aria-label": "Time input", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesRight, format: formatRight, onBlur: handleRightBlur, onChange: handleRightChange, onFocus: handleRightFocus, onPasteIsoValue: onPasteIsoValueRight, placeholder: placeholderRight, readOnly: readOnly, required: required, validate: validateRight, value: valueRight }) })] }) }));
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
export { PickerTriggerWithSeparator, PickerTriggerWithSeparator as default };
|
|
@@ -38,6 +38,12 @@ export interface UseDateInputFormatterProps {
|
|
|
38
38
|
* Called after format validation passes.
|
|
39
39
|
*/
|
|
40
40
|
validate?: (isoDate: string) => boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Callback when a valid ISO date is pasted.
|
|
43
|
+
* This allows parent components to handle cross-field updates
|
|
44
|
+
* (e.g., updating time field when date+time is pasted into date field).
|
|
45
|
+
*/
|
|
46
|
+
onPasteIsoValue?: (isoValue: string) => void;
|
|
41
47
|
}
|
|
42
48
|
/**
|
|
43
49
|
* Hook for formatting date/time input with mask format
|
|
@@ -12,7 +12,7 @@ function useDateInputFormatter(props) {
|
|
|
12
12
|
enabled: true,
|
|
13
13
|
invalidInput: 'Input value is not valid.',
|
|
14
14
|
invalidPaste: 'Pasted content is not valid.',
|
|
15
|
-
}, format, value: externalValue = '', onChange, inputRef, onFocus: onFocusProp, onBlur: onBlurProp, validate, } = props;
|
|
15
|
+
}, format, value: externalValue = '', onChange, inputRef, onFocus: onFocusProp, onBlur: onBlurProp, validate, onPasteIsoValue, } = props;
|
|
16
16
|
const { parseFormattedValue, isValid, locale, formatToString } = useCalendarContext();
|
|
17
17
|
const maskFormat = useRef(new MaskFormat(format)).current;
|
|
18
18
|
const [internalValue, setInternalValue] = useState(externalValue || getTemplateWithoutBrackets(format));
|
|
@@ -325,6 +325,8 @@ function useDateInputFormatter(props) {
|
|
|
325
325
|
// If pasted data is a valid ISO date, format it accordingly
|
|
326
326
|
const parsedDate = formatToString(locale, pasteData, format);
|
|
327
327
|
if (parsedDate) {
|
|
328
|
+
// Notify parent about the full ISO value for cross-field sync
|
|
329
|
+
onPasteIsoValue === null || onPasteIsoValue === void 0 ? void 0 : onPasteIsoValue(pasteData);
|
|
328
330
|
triggerChange(parsedDate);
|
|
329
331
|
return;
|
|
330
332
|
}
|
|
@@ -363,6 +365,7 @@ function useDateInputFormatter(props) {
|
|
|
363
365
|
errorMessages,
|
|
364
366
|
internalValue,
|
|
365
367
|
maskFormat,
|
|
368
|
+
onPasteIsoValue,
|
|
366
369
|
triggerChange,
|
|
367
370
|
isValid,
|
|
368
371
|
format,
|
package/Select/Select.d.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
import { DropdownOption, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
|
|
2
2
|
import { SelectInputSize } from '@mezzanine-ui/core/select';
|
|
3
|
-
import React, { ReactElement } from 'react';
|
|
4
3
|
import { FormElementFocusHandlers } from '../Form';
|
|
5
4
|
import { SelectTriggerInputProps, SelectTriggerProps, SelectValue } from './typings';
|
|
6
5
|
export interface SelectBaseProps extends Omit<SelectTriggerProps, 'active' | 'inputProps' | 'mode' | 'onBlur' | 'onChange' | 'onClick' | 'onFocus' | 'onKeyDown' | 'onScroll' | 'type' | 'renderValue' | 'value'>, FormElementFocusHandlers {
|
|
7
|
-
/**
|
|
8
|
-
* The children of select (Option components).
|
|
9
|
-
* If `options` is provided, this will be ignored.
|
|
10
|
-
*/
|
|
11
|
-
children?: ReactElement | ReactElement[];
|
|
12
6
|
/**
|
|
13
7
|
* Direct options array for dropdown (supports tree structure).
|
|
14
|
-
* If provided, `
|
|
8
|
+
* If provided, `type` will be automatically set.
|
|
15
9
|
*/
|
|
16
10
|
options?: DropdownOption[];
|
|
17
11
|
/**
|
|
@@ -113,5 +107,5 @@ export type SelectSingleProps = SelectBaseProps & {
|
|
|
113
107
|
value?: SelectValue | null;
|
|
114
108
|
};
|
|
115
109
|
export type SelectProps = SelectMultipleProps | SelectSingleProps;
|
|
116
|
-
declare const Select:
|
|
110
|
+
declare const Select: import("react").ForwardRefExoticComponent<SelectProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
117
111
|
export default Select;
|
package/Select/Select.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
3
|
import { selectClasses } from '@mezzanine-ui/core/select';
|
|
4
4
|
import isArray from 'lodash/isArray';
|
|
5
|
-
import
|
|
5
|
+
import { forwardRef, useContext, useState, useCallback, useRef, useMemo } from 'react';
|
|
6
6
|
import { useSelectValueControl } from '../Form/useSelectValueControl.js';
|
|
7
7
|
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
8
|
-
import Option from './Option.js';
|
|
9
8
|
import { SelectControlContext } from './SelectControlContext.js';
|
|
10
9
|
import SelectTrigger from './SelectTrigger.js';
|
|
11
10
|
import { FormControlContext } from '../Form/FormControlContext.js';
|
|
@@ -14,7 +13,7 @@ import cx from 'clsx';
|
|
|
14
13
|
|
|
15
14
|
const Select = forwardRef(function Select(props, ref) {
|
|
16
15
|
const { disabled: disabledFromFormControl, fullWidth: fullWidthFromFormControl, required: requiredFromFormControl, severity, } = useContext(FormControlContext) || {};
|
|
17
|
-
const {
|
|
16
|
+
const { className, clearable = false, defaultValue, disabled = disabledFromFormControl || false, error = severity === 'error' || false, fullWidth = fullWidthFromFormControl || false, inputProps, inputRef, menuMaxHeight, mode = 'single', onBlur, onChange: onChangeProp, onClear: onClearProp, onFocus, onScroll, options: optionsProp, placeholder = '', prefix, readOnly = false, renderValue, required = requiredFromFormControl || false, size, suffixActionIcon, type = 'default', value: valueProp, dropdownZIndex, globalPortal = true, } = props;
|
|
18
17
|
const [open, toggleOpen] = useState(false);
|
|
19
18
|
const onOpen = useCallback(() => {
|
|
20
19
|
// Prevent opening when readOnly is true
|
|
@@ -78,39 +77,19 @@ const Select = forwardRef(function Select(props, ref) {
|
|
|
78
77
|
}
|
|
79
78
|
return null;
|
|
80
79
|
}, []);
|
|
81
|
-
//
|
|
82
|
-
// Or use provided options directly
|
|
80
|
+
// Use provided options directly
|
|
83
81
|
const options = useMemo(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
82
|
+
if (!optionsProp)
|
|
83
|
+
return [];
|
|
84
|
+
// In tree mode (multiple mode with tree structure), ensure all options have checkbox
|
|
85
|
+
if (mode === 'multiple') {
|
|
86
|
+
const hasTreeStructure = optionsProp.some((opt) => opt.children && opt.children.length > 0);
|
|
87
|
+
if (hasTreeStructure) {
|
|
88
|
+
return addCheckboxToTreeOptions(optionsProp);
|
|
92
89
|
}
|
|
93
|
-
return optionsProp;
|
|
94
90
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return [];
|
|
98
|
-
return Children.toArray(children)
|
|
99
|
-
.filter((child) => {
|
|
100
|
-
var _a;
|
|
101
|
-
return (React.isValidElement(child) &&
|
|
102
|
-
(child.type === Option || ((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === 'Option'));
|
|
103
|
-
})
|
|
104
|
-
.map((child) => {
|
|
105
|
-
const props = child.props;
|
|
106
|
-
return {
|
|
107
|
-
id: props.value,
|
|
108
|
-
name: props.children,
|
|
109
|
-
showCheckbox: mode === 'multiple',
|
|
110
|
-
checkSite: mode === 'multiple' ? 'prefix' : 'suffix',
|
|
111
|
-
};
|
|
112
|
-
});
|
|
113
|
-
}, [children, mode, optionsProp, addCheckboxToTreeOptions]);
|
|
91
|
+
return optionsProp;
|
|
92
|
+
}, [mode, optionsProp, addCheckboxToTreeOptions]);
|
|
114
93
|
// Determine dropdown type based on options structure and mode
|
|
115
94
|
// Tree mode is only available in multiple mode
|
|
116
95
|
const dropdownType = useMemo(() => {
|
package/Select/SelectTrigger.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef } from 'react';
|
|
4
3
|
import { selectClasses } from '@mezzanine-ui/core/select';
|
|
5
4
|
import { ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
5
|
+
import { forwardRef } from 'react';
|
|
6
6
|
import SelectTriggerTags from './SelectTriggerTags.js';
|
|
7
7
|
import Icon from '../Icon/Icon.js';
|
|
8
8
|
import TextField from '../TextField/TextField.js';
|
|
@@ -12,24 +12,36 @@ const isMultipleSelection = (props) => props.mode === 'multiple';
|
|
|
12
12
|
function SelectTriggerComponent(props) {
|
|
13
13
|
var _a, _b, _c;
|
|
14
14
|
const { active, className, disabled, forceHideSuffixActionIcon, inputProps, innerRef, inputRef, isForceClearable = false, mode = 'single', onTagClose, overflowStrategy = 'counter', placeholder, readOnly, required, searchText, size = 'main', showTextInputAfterTags = false, suffixAction, suffixActionIcon: suffixActionIconProp, type = 'default', onClick, ...restTextFieldProps } = props;
|
|
15
|
+
const renderValueProp = 'renderValue' in props ? props.renderValue : undefined;
|
|
16
|
+
// Exclude renderValue to avoid leaking unknown props to DOM.
|
|
17
|
+
const sanitizedTextFieldProps = (() => {
|
|
18
|
+
if ('renderValue' in restTextFieldProps) {
|
|
19
|
+
const { renderValue: _removed, ...rest } = restTextFieldProps;
|
|
20
|
+
return rest;
|
|
21
|
+
}
|
|
22
|
+
return restTextFieldProps;
|
|
23
|
+
})();
|
|
15
24
|
/** Render value to string for single selection trigger input */
|
|
16
25
|
const renderValue = () => {
|
|
17
26
|
var _a, _b;
|
|
18
27
|
if (isMultipleSelection(props))
|
|
19
28
|
return;
|
|
20
|
-
if (typeof
|
|
21
|
-
return
|
|
29
|
+
if (typeof renderValueProp === 'function') {
|
|
30
|
+
return renderValueProp(props.value);
|
|
22
31
|
}
|
|
23
32
|
return (_b = (_a = props.value) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : '';
|
|
24
33
|
};
|
|
25
34
|
/** Compute suffix action icon */
|
|
26
35
|
const suffixActionIcon = suffixActionIconProp || (jsx(Icon, { icon: ChevronDownIcon, onClick: (e) => {
|
|
36
|
+
var _a;
|
|
27
37
|
e.stopPropagation();
|
|
28
38
|
if (suffixAction) {
|
|
29
39
|
suffixAction();
|
|
30
40
|
}
|
|
31
41
|
else {
|
|
32
|
-
|
|
42
|
+
// Delegate to trigger click behavior without fabricating a synthetic event.
|
|
43
|
+
(_a = e.currentTarget
|
|
44
|
+
.closest(`.${selectClasses.trigger}`)) === null || _a === void 0 ? void 0 : _a.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
33
45
|
}
|
|
34
46
|
}, className: cx(selectClasses.triggerSuffixActionIcon, {
|
|
35
47
|
[selectClasses.triggerSuffixActionIconActive]: active,
|
|
@@ -43,7 +55,7 @@ function SelectTriggerComponent(props) {
|
|
|
43
55
|
}
|
|
44
56
|
return {};
|
|
45
57
|
})();
|
|
46
|
-
return (jsxs(TextField, { ref: innerRef, ...interactiveProps, ...
|
|
58
|
+
return (jsxs(TextField, { ref: innerRef, ...interactiveProps, ...sanitizedTextFieldProps, onClick: onClick, active: active, className: cx(selectClasses.trigger, selectClasses.triggerMode(mode), selectClasses.triggerSelected(Array.isArray(props.value) ? (_a = props.value) === null || _a === void 0 ? void 0 : _a.length : props.value), {
|
|
47
59
|
[selectClasses.triggerReadOnly]: readOnly,
|
|
48
60
|
[selectClasses.triggerDisabled]: disabled,
|
|
49
61
|
}, className), error: type === 'error', size: size, suffix: forceHideSuffixActionIcon ? undefined : suffixActionIcon, clearable: isForceClearable ||
|
|
@@ -53,9 +65,11 @@ function SelectTriggerComponent(props) {
|
|
|
53
65
|
}
|
|
54
66
|
const SelectTrigger = forwardRef((props, ref) => {
|
|
55
67
|
if (props.mode === 'multiple') {
|
|
56
|
-
|
|
68
|
+
const { mode: _mode, value, ...multipleModeProps } = props;
|
|
69
|
+
return (jsx(SelectTriggerComponent, { ...multipleModeProps, innerRef: ref, mode: "multiple", value: Array.isArray(value) ? value : undefined }));
|
|
57
70
|
}
|
|
58
|
-
|
|
71
|
+
const { mode: _mode, overflowStrategy: _overflowStrategy, value, ...singleModeProps } = props;
|
|
72
|
+
return (jsx(SelectTriggerComponent, { ...singleModeProps, innerRef: ref, mode: "single", value: Array.isArray(value) ? undefined : value }));
|
|
59
73
|
});
|
|
60
74
|
|
|
61
75
|
export { SelectTrigger as default };
|
package/Select/index.d.ts
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
export { default as OptionGroup } from '../Menu/MenuItemGroup';
|
|
2
2
|
export type { MenuItemGroupProps as OptionGroupProps } from '../Menu/MenuItemGroup';
|
|
3
|
-
export { default as Option } from './Option';
|
|
4
|
-
export type { OptionProps } from './Option';
|
|
5
3
|
export { default } from './Select';
|
|
6
4
|
export type { SelectProps } from './Select';
|
|
7
5
|
export { SelectControlContext } from './SelectControlContext';
|
|
8
6
|
export { default as SelectTrigger } from './SelectTrigger';
|
|
9
7
|
export { default as SelectTriggerTags } from './SelectTriggerTags';
|
|
10
8
|
export type { SelectTriggerTagsProps } from './SelectTriggerTags';
|
|
11
|
-
export { default as TreeSelect } from './TreeSelect';
|
|
12
|
-
export type { TreeSelectProps } from './TreeSelect';
|
|
13
9
|
export * from './typings';
|
package/Select/index.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export { default as OptionGroup } from '../Menu/MenuItemGroup.js';
|
|
2
|
-
export { default as Option } from './Option.js';
|
|
3
2
|
export { default } from './Select.js';
|
|
4
3
|
export { SelectControlContext } from './SelectControlContext.js';
|
|
5
4
|
export { default as SelectTrigger } from './SelectTrigger.js';
|
|
6
5
|
export { default as SelectTriggerTags } from './SelectTriggerTags.js';
|
|
7
|
-
export { default as TreeSelect } from './TreeSelect.js';
|