@mezzanine-ui/react 1.0.0-rc.0 → 1.0.0-rc.2
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/Badge/Badge.d.ts +4 -0
- package/Badge/Badge.js +2 -2
- package/Badge/typings.d.ts +13 -1
- package/Calendar/CalendarDays.js +4 -3
- package/Calendar/CalendarWeeks.js +4 -3
- package/Cascader/Cascader.d.ts +3 -0
- package/Cascader/Cascader.js +241 -0
- package/Cascader/CascaderPanel.d.ts +29 -0
- package/Cascader/CascaderPanel.js +29 -0
- package/Cascader/index.d.ts +5 -0
- package/Cascader/index.js +2 -0
- package/Cascader/typings.d.ts +92 -0
- package/Description/Description.d.ts +6 -1
- package/Description/Description.js +11 -4
- package/Description/DescriptionContent.d.ts +9 -3
- package/Description/DescriptionContent.js +4 -1
- package/Description/DescriptionContext.d.ts +6 -0
- package/Description/DescriptionContext.js +9 -0
- package/Description/index.d.ts +2 -0
- package/Description/index.js +1 -0
- package/Form/FormField.d.ts +6 -0
- package/Form/FormField.js +2 -2
- package/Form/FormHintText.d.ts +12 -0
- package/Form/FormHintText.js +3 -2
- package/Layout/Layout.d.ts +21 -5
- package/Layout/Layout.js +23 -19
- package/Layout/LayoutContext.d.ts +6 -6
- package/Layout/LayoutContext.js +2 -2
- package/Layout/LayoutHost.d.ts +0 -4
- package/Layout/LayoutHost.js +16 -13
- package/Layout/LayoutLeftPanel.d.ts +19 -0
- package/Layout/LayoutLeftPanel.js +86 -0
- package/Layout/LayoutMain.d.ts +10 -1
- package/Layout/LayoutMain.js +12 -3
- package/Layout/LayoutRightPanel.d.ts +19 -0
- package/Layout/{LayoutSidePanel.js → LayoutRightPanel.js} +32 -36
- package/Layout/index.d.ts +2 -2
- package/Layout/index.js +2 -1
- package/Modal/MediaPreviewModal.d.ts +4 -0
- package/Modal/MediaPreviewModal.js +2 -2
- package/Modal/Modal.d.ts +4 -0
- package/Modal/Modal.js +2 -2
- package/Modal/useModalContainer.js +0 -1
- package/Navigation/Navigation.d.ts +4 -0
- package/Navigation/Navigation.js +39 -3
- package/Navigation/NavigationFooter.js +19 -2
- package/Navigation/NavigationOption.d.ts +4 -0
- package/Navigation/NavigationOption.js +40 -19
- package/Navigation/NavigationOverflowMenuOption.js +11 -7
- package/Navigation/NavigationUserMenu.d.ts +1 -0
- package/Navigation/NavigationUserMenu.js +24 -5
- package/Navigation/context.d.ts +2 -0
- package/Navigation/context.js +4 -1
- package/Picker/RangePickerTrigger.js +1 -1
- package/Section/Section.js +6 -6
- package/Transition/Collapse.d.ts +2 -1
- package/Transition/Collapse.js +2 -1
- package/Upload/Upload.js +63 -9
- package/Upload/UploadPictureCard.d.ts +25 -15
- package/Upload/UploadPictureCard.js +14 -6
- package/index.d.ts +4 -2
- package/index.js +4 -1
- package/package.json +4 -4
- package/Layout/LayoutSidePanel.d.ts +0 -14
package/Modal/Modal.js
CHANGED
|
@@ -12,14 +12,14 @@ import cx from 'clsx';
|
|
|
12
12
|
* The react component for `mezzanine` modal.
|
|
13
13
|
*/
|
|
14
14
|
const Modal = forwardRef(function Modal(props, ref) {
|
|
15
|
-
const { actionsButtonLayout, annotation, auxiliaryContentButtonProps, auxiliaryContentButtonText, auxiliaryContentChecked, auxiliaryContentLabel, auxiliaryContentOnChange, auxiliaryContentOnClick, auxiliaryContentType, cancelButtonProps, cancelText, children, className, confirmButtonProps, confirmText, container, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, extendedSplitLeftSideContent, extendedSplitRightSideContent, fullScreen = false, loading = false, modalStatusType = 'info', modalType = 'standard', onBackdropClick, onCancel, onClose, onConfirm, open, passwordButtonProps, passwordButtonText, passwordChecked, passwordCheckedLabel, passwordCheckedOnChange, passwordOnClick, showCancelButton, showDismissButton = true, showModalFooter = false, showModalHeader, showStatusTypeIcon, size = 'regular', statusTypeIconLayout, supportingText, supportingTextAlign, title, titleAlign, ...rest } = props;
|
|
15
|
+
const { actionsButtonLayout, annotation, auxiliaryContentButtonProps, auxiliaryContentButtonText, auxiliaryContentChecked, auxiliaryContentLabel, auxiliaryContentOnChange, auxiliaryContentOnClick, auxiliaryContentType, backdropClassName, cancelButtonProps, cancelText, children, className, confirmButtonProps, confirmText, container, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, extendedSplitLeftSideContent, extendedSplitRightSideContent, fullScreen = false, loading = false, modalStatusType = 'info', modalType = 'standard', onBackdropClick, onCancel, onClose, onConfirm, open, passwordButtonProps, passwordButtonText, passwordChecked, passwordCheckedLabel, passwordCheckedOnChange, passwordOnClick, showCancelButton, showDismissButton = true, showModalFooter = false, showModalHeader, showStatusTypeIcon, size = 'regular', statusTypeIconLayout, supportingText, supportingTextAlign, title, titleAlign, ...rest } = props;
|
|
16
16
|
const modalControl = useMemo(() => ({
|
|
17
17
|
loading,
|
|
18
18
|
modalStatusType: modalStatusType,
|
|
19
19
|
}), [loading, modalStatusType]);
|
|
20
20
|
const { Container: ModalContainer } = useModalContainer();
|
|
21
21
|
const renderModalFooter = () => (jsx(ModalFooter, { actionsButtonLayout: actionsButtonLayout, annotation: annotation, auxiliaryContentButtonProps: auxiliaryContentButtonProps, auxiliaryContentButtonText: auxiliaryContentButtonText, auxiliaryContentChecked: auxiliaryContentChecked, auxiliaryContentLabel: auxiliaryContentLabel, auxiliaryContentOnChange: auxiliaryContentOnChange, auxiliaryContentOnClick: auxiliaryContentOnClick, auxiliaryContentType: auxiliaryContentType, cancelButtonProps: cancelButtonProps, cancelText: cancelText, confirmButtonProps: confirmButtonProps, confirmText: confirmText, loading: loading, onCancel: onCancel, onConfirm: onConfirm, passwordButtonProps: passwordButtonProps, passwordButtonText: passwordButtonText, passwordChecked: passwordChecked, passwordCheckedLabel: passwordCheckedLabel, passwordCheckedOnChange: passwordCheckedOnChange, passwordOnClick: passwordOnClick, showCancelButton: showCancelButton }));
|
|
22
|
-
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className:
|
|
22
|
+
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className: backdropClassName, container: container, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: jsxs("div", { ...rest, className: cx(modalClasses.host, modalClasses.modalStatusType(modalStatusType), modalClasses.size(size), {
|
|
23
23
|
[modalClasses.fullScreen]: fullScreen,
|
|
24
24
|
[modalClasses.withCloseIcon]: showDismissButton,
|
|
25
25
|
}, className), role: "dialog", children: [showModalHeader && (jsx(ModalHeader, { showStatusTypeIcon: showStatusTypeIcon, statusTypeIconLayout: statusTypeIconLayout, supportingText: supportingText, supportingTextAlign: supportingTextAlign, title: title, titleAlign: titleAlign })), modalType === 'extendedSplit' && (jsxs("div", { className: modalClasses.modalBodyContainerExtendedSplit, children: [jsx("div", { className: modalClasses.modalBodyContainerExtendedSplitRight, children: extendedSplitRightSideContent }), jsxs("div", { className: modalClasses.modalBodyContainerExtendedSplitLeft, children: [jsx("div", { className: modalClasses.modalBodyContainerExtendedSplitLeftSideContent, children: extendedSplitLeftSideContent }), showModalFooter && renderModalFooter()] })] })), (modalType === 'standard' ||
|
|
@@ -32,6 +32,10 @@ export interface NavigationProps extends Omit<NativeElementPropsWithoutKeyAndRef
|
|
|
32
32
|
* Called when a navigation option is clicked.
|
|
33
33
|
*/
|
|
34
34
|
onOptionClick?: (activePath?: string[]) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Custom component for rendering navigation options which have an href prop.
|
|
37
|
+
*/
|
|
38
|
+
optionsAnchorComponent?: React.ElementType;
|
|
35
39
|
}
|
|
36
40
|
declare const Navigation: import("react").ForwardRefExoticComponent<NavigationProps & import("react").RefAttributes<HTMLElement>>;
|
|
37
41
|
export default Navigation;
|
package/Navigation/Navigation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useState, useCallback, useMemo, Children, isValidElement } from 'react';
|
|
3
|
+
import { forwardRef, useState, useCallback, useMemo, Children, isValidElement, useRef, useEffect } from 'react';
|
|
4
4
|
import { navigationClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import NavigationOption from './NavigationOption.js';
|
|
6
6
|
import NavigationHeader from './NavigationHeader.js';
|
|
@@ -15,17 +15,19 @@ import Input from '../Input/Input.js';
|
|
|
15
15
|
import cx from 'clsx';
|
|
16
16
|
|
|
17
17
|
const Navigation = forwardRef((props, ref) => {
|
|
18
|
-
const { activatedPath, children = [], className, collapsed: collapsedProp, filter, onCollapseChange, onOptionClick, ...rest } = props;
|
|
18
|
+
const { activatedPath, children = [], className, collapsed: collapsedProp, filter, onCollapseChange, onOptionClick, optionsAnchorComponent, ...rest } = props;
|
|
19
19
|
const [collapsedState, setCollapsedState] = useState(collapsedProp || false);
|
|
20
20
|
const collapsed = collapsedProp !== null && collapsedProp !== void 0 ? collapsedProp : collapsedState;
|
|
21
21
|
const handleCollapseChange = useCallback((newCollapsed) => {
|
|
22
22
|
setCollapsedState(newCollapsed);
|
|
23
23
|
onCollapseChange === null || onCollapseChange === void 0 ? void 0 : onCollapseChange(newCollapsed);
|
|
24
24
|
}, [onCollapseChange]);
|
|
25
|
-
const [innerActivatedPath, setInnerActivatedPath] = useState([]);
|
|
25
|
+
const [innerActivatedPath, setInnerActivatedPath] = useState(activatedPath || []);
|
|
26
|
+
const [activatedPathKey, setActivatedPathKey] = useState(activatedPath ? activatedPath.join('::') : '');
|
|
26
27
|
const combineSetActivatedPath = useCallback((newActivatedPath) => {
|
|
27
28
|
onOptionClick === null || onOptionClick === void 0 ? void 0 : onOptionClick(newActivatedPath);
|
|
28
29
|
setInnerActivatedPath(newActivatedPath);
|
|
30
|
+
setActivatedPathKey(newActivatedPath.join('::'));
|
|
29
31
|
}, [onOptionClick]);
|
|
30
32
|
const currentPathname = useCurrentPathname();
|
|
31
33
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
@@ -65,6 +67,38 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
65
67
|
});
|
|
66
68
|
return { headerComponent, footerComponent, items, level1Items };
|
|
67
69
|
}, [flattenedChildren]);
|
|
70
|
+
const hrefActivated = useRef(false);
|
|
71
|
+
// Scan level1Items and its descendants (up to level3) to find out whether href matches to determine whether to preset expansion and activatedPath
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (hrefActivated.current || !currentPathname) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const checkActivatedPathKey = (items, path) => {
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!isValidElement(item) || item.type !== NavigationOption) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const newKey = item.props.id || item.props.title || item.props.href;
|
|
82
|
+
if (!newKey) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const newPath = [...path, newKey];
|
|
86
|
+
if (item.props.href && item.props.href === currentPathname) {
|
|
87
|
+
combineSetActivatedPath(newPath);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (item.props.children) {
|
|
91
|
+
const flattenedChildren = flattenChildren(item.props.children);
|
|
92
|
+
if (checkActivatedPathKey(flattenedChildren, newPath)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
checkActivatedPathKey(level1Items, []);
|
|
100
|
+
hrefActivated.current = true;
|
|
101
|
+
}, [combineSetActivatedPath, currentPathname, level1Items]);
|
|
68
102
|
const { contentRef, visibleCount } = useVisibleItems(items, collapsed);
|
|
69
103
|
const { collapsedItems, collapsedMenuItems } = useMemo(() => {
|
|
70
104
|
return {
|
|
@@ -77,11 +111,13 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
77
111
|
const [filterText, setFilterText] = useState('');
|
|
78
112
|
return (jsx("nav", { ...rest, ref: ref, className: cx(navigationClasses.host, collapsed ? navigationClasses.collapsed : navigationClasses.expand, className), children: jsxs(NavigationActivatedContext.Provider, { value: {
|
|
79
113
|
activatedPath: activatedPath || innerActivatedPath,
|
|
114
|
+
activatedPathKey,
|
|
80
115
|
collapsed,
|
|
81
116
|
currentPathname,
|
|
82
117
|
filterText,
|
|
83
118
|
handleCollapseChange,
|
|
84
119
|
setActivatedPath: combineSetActivatedPath,
|
|
120
|
+
optionsAnchorComponent,
|
|
85
121
|
}, children: [headerComponent, jsx(NavigationOptionLevelContext.Provider, { value: navigationOptionLevelContextDefaultValues, children: jsxs("div", { ref: contentRef, className: navigationClasses.content, children: [filter && (jsx(Input, { size: "sub", variant: "search", className: cx(navigationClasses.searchInput), value: filterText, onChange: (e) => setFilterText(e.target.value) })), jsxs("ul", { children: [collapsed ? collapsedItems : items, collapsed &&
|
|
86
122
|
visibleCount !== null &&
|
|
87
123
|
visibleCount < level1Items.length && (jsx(NavigationOverflowMenu, { items: collapsedMenuItems }))] }, collapsed ? 'collapsed' : 'expand')] }) }), footerComponent] }) }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, use, Children, isValidElement } from 'react';
|
|
2
|
+
import { forwardRef, use, useRef, useState, useEffect, Children, isValidElement } from 'react';
|
|
3
3
|
import { navigationFooterClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import NavigationUserMenu from './NavigationUserMenu.js';
|
|
5
5
|
import { NavigationActivatedContext } from './context.js';
|
|
@@ -22,7 +22,24 @@ const NavigationFooter = forwardRef((props, ref) => {
|
|
|
22
22
|
const { children, className, ...rest } = props;
|
|
23
23
|
const { collapsed } = use(NavigationActivatedContext);
|
|
24
24
|
const { userMenu, otherChildren } = resolveChildren(children);
|
|
25
|
-
|
|
25
|
+
const iconsRef = useRef(null);
|
|
26
|
+
const [iconsWidth, setIconsWidth] = useState(0);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!iconsRef.current)
|
|
29
|
+
return;
|
|
30
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
31
|
+
if (iconsRef.current) {
|
|
32
|
+
setIconsWidth(iconsRef.current.offsetWidth);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
resizeObserver.observe(iconsRef.current);
|
|
36
|
+
return () => {
|
|
37
|
+
resizeObserver.disconnect();
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
return (jsxs("footer", { ...rest, ref: ref, className: cx(navigationFooterClasses.host, collapsed && navigationFooterClasses.collapsed, className), style: {
|
|
41
|
+
['--icons-width']: `${iconsWidth}px`,
|
|
42
|
+
}, children: [userMenu, jsx("span", { ref: iconsRef, className: navigationFooterClasses.icons, children: otherChildren })] }));
|
|
26
43
|
});
|
|
27
44
|
|
|
28
45
|
export { NavigationFooter as default };
|
|
@@ -13,6 +13,10 @@ export interface NavigationOptionProps extends Omit<NativeElementPropsWithoutKey
|
|
|
13
13
|
* Strict children with `NavigationOption`.
|
|
14
14
|
*/
|
|
15
15
|
children?: NavigationOptionChildren;
|
|
16
|
+
/**
|
|
17
|
+
* Custom component to render, it should support `href` and `onClick` props if provided.
|
|
18
|
+
*/
|
|
19
|
+
anchorComponent?: React.ElementType;
|
|
16
20
|
/**
|
|
17
21
|
* Icon of the item.
|
|
18
22
|
*/
|
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef,
|
|
3
|
+
import { forwardRef, use, useState, useId, useMemo, Children, isValidElement, useEffect, useRef } from 'react';
|
|
4
4
|
import { navigationOptionClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import { ChevronUpIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
6
|
-
import {
|
|
6
|
+
import { NavigationActivatedContext, NavigationOptionLevelContext } from './context.js';
|
|
7
7
|
import { flattenChildren } from '../utils/flatten-children.js';
|
|
8
8
|
import Badge from '../Badge/Badge.js';
|
|
9
9
|
import Tooltip from '../Tooltip/Tooltip.js';
|
|
10
10
|
import Icon from '../Icon/Icon.js';
|
|
11
|
+
import Fade from '../Transition/Fade.js';
|
|
11
12
|
import Collapse from '../Transition/Collapse.js';
|
|
12
13
|
import cx from 'clsx';
|
|
13
14
|
|
|
14
15
|
const NavigationOption = forwardRef((props, ref) => {
|
|
15
|
-
|
|
16
|
-
const
|
|
16
|
+
var _a;
|
|
17
|
+
const { active, children, className, anchorComponent, defaultOpen, href, icon, id, onTriggerClick, title, ...rest } = props;
|
|
18
|
+
const { activatedPathKey, activatedPath, collapsed, filterText, handleCollapseChange, setActivatedPath, optionsAnchorComponent, } = use(NavigationActivatedContext);
|
|
19
|
+
const [open, setOpen] = useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
|
|
17
20
|
const GroupToggleIcon = open ? ChevronUpIcon : ChevronDownIcon;
|
|
18
21
|
const { level, path: parentPath } = use(NavigationOptionLevelContext);
|
|
19
22
|
const currentLevel = level + 1; // start as 1
|
|
20
23
|
const uuid = useId();
|
|
21
24
|
const currentKey = id || title || href || uuid;
|
|
22
25
|
const currentPath = useMemo(() => [...parentPath, currentKey], [parentPath, currentKey]);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
setOpen(true);
|
|
28
|
-
}
|
|
29
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
-
}, []);
|
|
31
|
-
const Component = href ? 'a' : 'div';
|
|
26
|
+
const currentPathKey = currentPath.join('::');
|
|
27
|
+
const Component = href && !children
|
|
28
|
+
? ((_a = anchorComponent !== null && anchorComponent !== void 0 ? anchorComponent : optionsAnchorComponent) !== null && _a !== void 0 ? _a : 'a')
|
|
29
|
+
: 'div';
|
|
32
30
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
33
31
|
const { badge, items } = useMemo(() => {
|
|
34
32
|
let badgeComponent = null;
|
|
@@ -51,6 +49,13 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
51
49
|
});
|
|
52
50
|
return { badge: badgeComponent, items };
|
|
53
51
|
}, [flattenedChildren]);
|
|
52
|
+
// Default open if current path is activated
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (activatedPathKey === currentPathKey ||
|
|
55
|
+
activatedPathKey.startsWith(`${currentPathKey}::`)) {
|
|
56
|
+
setOpen(true);
|
|
57
|
+
}
|
|
58
|
+
}, [activatedPathKey, currentLevel, currentPathKey]);
|
|
54
59
|
const [filter, setFilter] = useState(true);
|
|
55
60
|
useEffect(() => {
|
|
56
61
|
var _a;
|
|
@@ -60,10 +65,28 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
60
65
|
}
|
|
61
66
|
setFilter((_a = (title.includes(filterText) || (href === null || href === void 0 ? void 0 : href.includes(filterText)))) !== null && _a !== void 0 ? _a : false);
|
|
62
67
|
}, [currentPath, filterText, href, title]);
|
|
68
|
+
const titleRef = useRef(null);
|
|
69
|
+
const [titleOverflow, setTitleOverflow] = useState(false);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!titleRef.current)
|
|
72
|
+
return;
|
|
73
|
+
const checkOverflow = () => {
|
|
74
|
+
if (!titleRef.current)
|
|
75
|
+
return;
|
|
76
|
+
const { scrollWidth, clientWidth } = titleRef.current;
|
|
77
|
+
setTitleOverflow(scrollWidth > clientWidth);
|
|
78
|
+
};
|
|
79
|
+
checkOverflow();
|
|
80
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
81
|
+
resizeObserver.observe(titleRef.current);
|
|
82
|
+
return () => {
|
|
83
|
+
resizeObserver.disconnect();
|
|
84
|
+
};
|
|
85
|
+
}, [title]);
|
|
63
86
|
return (jsxs("li", { ...rest, ref: ref, className: cx(navigationOptionClasses.host, open && navigationOptionClasses.open, !children && navigationOptionClasses.basic, (active !== null && active !== void 0 ? active : (activatedPath === null || activatedPath === void 0 ? void 0 : activatedPath[currentLevel - 1]) === currentKey) &&
|
|
64
|
-
navigationOptionClasses.active, collapsed && navigationOptionClasses.collapsed, !collapsed && !filter && navigationOptionClasses.hidden, className), "data-id": currentKey, children: [jsx(Tooltip, { options: {
|
|
65
|
-
placement: 'right',
|
|
66
|
-
}, title: collapsed ? title : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipChildRef }) => (jsxs(Component, { className: cx(navigationOptionClasses.content, navigationOptionClasses.level(currentLevel)),
|
|
87
|
+
navigationOptionClasses.active, collapsed && navigationOptionClasses.collapsed, !collapsed && !filter && navigationOptionClasses.hidden, className), "data-id": currentKey, children: [jsx(Tooltip, { disablePortal: false, options: {
|
|
88
|
+
placement: collapsed ? 'right' : 'top',
|
|
89
|
+
}, title: collapsed || titleOverflow ? title : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipChildRef }) => (jsxs(Component, { className: cx(navigationOptionClasses.content, navigationOptionClasses.level(currentLevel)), href: Component === 'div' ? undefined : href, onClick: () => {
|
|
67
90
|
setOpen(!open);
|
|
68
91
|
onTriggerClick === null || onTriggerClick === void 0 ? void 0 : onTriggerClick(currentPath, currentKey, href);
|
|
69
92
|
if (collapsed) {
|
|
@@ -78,9 +101,7 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
78
101
|
if (!children)
|
|
79
102
|
setActivatedPath(currentPath);
|
|
80
103
|
}
|
|
81
|
-
}, role: "menuitem", tabIndex: 0, children: [icon && jsx(Icon, { className: navigationOptionClasses.icon, icon: icon }), jsx("span", { className: navigationOptionClasses.title, children: title }), badge, children && (jsx(Icon, { className: navigationOptionClasses.toggleIcon, icon: GroupToggleIcon }))] })) }), children && !collapsed && (jsx(Collapse, { className: navigationOptionClasses.childrenWrapper,
|
|
82
|
-
width: '100%',
|
|
83
|
-
}, in: !!open, children: jsx(NavigationOptionLevelContext.Provider, { value: {
|
|
104
|
+
}, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: tooltipChildRef, role: "menuitem", tabIndex: 0, children: [icon && jsx(Icon, { className: navigationOptionClasses.icon, icon: icon }), jsx(Fade, { ref: titleRef, in: collapsed === false || !icon, children: jsx("span", { className: navigationOptionClasses.title, children: title }) }), badge, children && (jsx(Icon, { className: navigationOptionClasses.toggleIcon, icon: GroupToggleIcon }))] })) }), children && !collapsed && (jsx(Collapse, { lazyMount: true, className: cx(navigationOptionClasses.childrenWrapper), in: open, children: jsx(NavigationOptionLevelContext.Provider, { value: {
|
|
84
105
|
level: currentLevel,
|
|
85
106
|
path: currentPath,
|
|
86
107
|
}, children: jsx("ul", { className: navigationOptionClasses.group, children: items }) }) }))] }));
|
|
@@ -11,22 +11,26 @@ import Icon from '../Icon/Icon.js';
|
|
|
11
11
|
import cx from 'clsx';
|
|
12
12
|
|
|
13
13
|
const NavigationOverflowMenuOption = forwardRef((props, ref) => {
|
|
14
|
-
|
|
14
|
+
var _a;
|
|
15
|
+
const { active, children, className, anchorComponent, defaultOpen = false, href, icon, id, onTriggerClick, title, ...rest } = props;
|
|
15
16
|
const [open, setOpen] = useState(defaultOpen);
|
|
16
17
|
const { level, path: parentPath } = use(NavigationOptionLevelContext);
|
|
17
18
|
const currentLevel = level + 1; // start as 1
|
|
18
19
|
const uuid = useId();
|
|
19
20
|
const currentKey = id || title || href || uuid;
|
|
20
21
|
const currentPath = useMemo(() => [...parentPath, currentKey], [parentPath, currentKey]);
|
|
21
|
-
const
|
|
22
|
+
const currentPathKey = currentPath.join('::');
|
|
23
|
+
const { activatedPath, activatedPathKey, setActivatedPath, optionsAnchorComponent, } = use(NavigationActivatedContext);
|
|
24
|
+
// Default open if current path is activated
|
|
22
25
|
useEffect(() => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
26
|
+
if (activatedPathKey === currentPathKey ||
|
|
27
|
+
activatedPathKey.startsWith(`${currentPathKey}::`)) {
|
|
25
28
|
setOpen(true);
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
}, [activatedPathKey, currentLevel, currentPathKey]);
|
|
31
|
+
const Component = href
|
|
32
|
+
? ((_a = anchorComponent !== null && anchorComponent !== void 0 ? anchorComponent : optionsAnchorComponent) !== null && _a !== void 0 ? _a : 'a')
|
|
33
|
+
: 'div';
|
|
30
34
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
31
35
|
const { badge, items } = useMemo(() => {
|
|
32
36
|
let badgeComponent = null;
|
|
@@ -3,6 +3,7 @@ import { DropdownProps } from '../Dropdown';
|
|
|
3
3
|
export interface NavigationUserMenuProps extends Omit<DropdownProps, 'children' | 'type'> {
|
|
4
4
|
children?: ReactNode;
|
|
5
5
|
className?: string;
|
|
6
|
+
collapsedPlacement?: DropdownProps['placement'];
|
|
6
7
|
imgSrc?: string;
|
|
7
8
|
onClick?: () => void;
|
|
8
9
|
}
|
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, useState, use, useRef, useEffect } from 'react';
|
|
3
3
|
import { navigationUserMenuClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import { UserIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
5
|
+
import { NavigationActivatedContext } from './context.js';
|
|
5
6
|
import Dropdown from '../Dropdown/Dropdown.js';
|
|
7
|
+
import Tooltip from '../Tooltip/Tooltip.js';
|
|
6
8
|
import Icon from '../Icon/Icon.js';
|
|
7
9
|
import cx from 'clsx';
|
|
8
10
|
|
|
9
11
|
const NavigationUserMenu = forwardRef((props, ref) => {
|
|
10
|
-
// shared props
|
|
11
12
|
const { children, className, imgSrc, onClick, ...rest } = props;
|
|
12
|
-
const { open: openProp, onClose, placement = 'top-end', onVisibilityChange, ...dropdownRest } = rest;
|
|
13
|
+
const { open: openProp, onClose, placement = 'top-end', collapsedPlacement = 'right-end', onVisibilityChange, ...dropdownRest } = rest;
|
|
13
14
|
const [imgError, setImgError] = useState(false);
|
|
14
15
|
const [_open, setOpen] = useState(false);
|
|
16
|
+
const { collapsed } = use(NavigationActivatedContext);
|
|
15
17
|
const open = openProp !== null && openProp !== void 0 ? openProp : _open;
|
|
16
|
-
|
|
18
|
+
const userNameRef = useRef(null);
|
|
19
|
+
const [userNameOverflow, setUserNameOverflow] = useState(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!userNameRef.current)
|
|
22
|
+
return;
|
|
23
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
24
|
+
if (userNameRef.current) {
|
|
25
|
+
setUserNameOverflow(userNameRef.current.scrollWidth > userNameRef.current.offsetWidth);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
resizeObserver.observe(userNameRef.current);
|
|
29
|
+
return () => {
|
|
30
|
+
resizeObserver.disconnect();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
return (jsx(Dropdown, { ...dropdownRest, open: open, placement: collapsed ? collapsedPlacement : placement, onVisibilityChange: () => {
|
|
17
34
|
setOpen(!open);
|
|
18
35
|
onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(!open);
|
|
19
36
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
|
20
37
|
}, onClose: () => {
|
|
21
38
|
setOpen(false);
|
|
22
39
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
23
|
-
}, children:
|
|
40
|
+
}, children: jsx("button", { className: cx(navigationUserMenuClasses.host, open && navigationUserMenuClasses.open, className), ref: ref, type: "button", children: jsx(Tooltip, { disablePortal: false, options: {
|
|
41
|
+
placement: collapsed ? 'right' : 'top',
|
|
42
|
+
}, title: (collapsed || userNameOverflow) && !open ? children : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipRef }) => (jsxs("span", { className: navigationUserMenuClasses.content, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: tooltipRef, children: [jsx("span", { className: navigationUserMenuClasses.avatar, children: imgError || !imgSrc ? (jsx(Icon, { icon: UserIcon })) : (jsx("img", { alt: "User avatar", className: navigationUserMenuClasses.avatar, src: imgSrc, onError: () => setImgError(true) })) }), children && (jsx("span", { className: navigationUserMenuClasses.userName, children: jsx("span", { ref: userNameRef, children: children }) })), jsx(Icon, { className: navigationUserMenuClasses.icon, icon: ChevronDownIcon })] })) }) }) }));
|
|
24
43
|
});
|
|
25
44
|
|
|
26
45
|
export { NavigationUserMenu as default };
|
package/Navigation/context.d.ts
CHANGED
|
@@ -8,9 +8,11 @@ export declare const NavigationOptionLevelContext: import("react").Context<{
|
|
|
8
8
|
}>;
|
|
9
9
|
export declare const NavigationActivatedContext: import("react").Context<{
|
|
10
10
|
activatedPath: string[];
|
|
11
|
+
activatedPathKey: string;
|
|
11
12
|
collapsed: boolean;
|
|
12
13
|
currentPathname: string | null;
|
|
13
14
|
filterText: string;
|
|
14
15
|
handleCollapseChange: (newCollapsed: boolean) => void;
|
|
15
16
|
setActivatedPath: (path: string[]) => void;
|
|
17
|
+
optionsAnchorComponent?: React.ElementType;
|
|
16
18
|
}>;
|
package/Navigation/context.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
|
-
const navigationOptionLevelContextDefaultValues = {
|
|
3
|
+
const navigationOptionLevelContextDefaultValues = {
|
|
4
|
+
level: 0,
|
|
5
|
+
path: [],
|
|
6
|
+
};
|
|
4
7
|
const NavigationOptionLevelContext = createContext(navigationOptionLevelContextDefaultValues);
|
|
5
8
|
const NavigationActivatedContext = createContext(null);
|
|
6
9
|
|
|
@@ -69,7 +69,7 @@ const RangePickerTrigger = forwardRef(function RangePickerTrigger(props, ref) {
|
|
|
69
69
|
const handleToBlur = useCallback((e) => {
|
|
70
70
|
onToBlur === null || onToBlur === void 0 ? void 0 : onToBlur(e);
|
|
71
71
|
}, [onToBlur]);
|
|
72
|
-
return (jsxs(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, className), clearable: !readOnly && clearable, suffix: suffix !== null && suffix !== void 0 ? suffix : defaultSuffix, children: [jsx(FormattedInput, { ...inputFromProps, ref: fromRef, "aria-disabled": disabled, "aria-label": "Start date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesFrom, format: format, onBlur: handleFromBlur, onChange: handleFromChange, onFocus: handleFromFocus, placeholder: inputFromPlaceholder, readOnly: readOnly, required: required, validate: validateFrom, value: inputFromValue }), jsx(Icon, { icon: LongTailArrowRightIcon, className: pickerClasses.arrowIcon, "aria-hidden": "true" }), jsx(FormattedInput, { ...inputToProps, ref: toRef, "aria-disabled": disabled, "aria-label": "End date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesTo, format: format, onBlur: handleToBlur, onChange: handleToChange, onFocus: handleToFocus, placeholder: inputToPlaceholder, readOnly: readOnly, required: required, validate: validateTo, value: inputToValue })] }));
|
|
72
|
+
return (jsxs(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, pickerClasses.hostRange, className), clearable: !readOnly && clearable, suffix: suffix !== null && suffix !== void 0 ? suffix : defaultSuffix, children: [jsx(FormattedInput, { ...inputFromProps, ref: fromRef, "aria-disabled": disabled, "aria-label": "Start date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesFrom, format: format, onBlur: handleFromBlur, onChange: handleFromChange, onFocus: handleFromFocus, placeholder: inputFromPlaceholder, readOnly: readOnly, required: required, validate: validateFrom, value: inputFromValue }), jsx(Icon, { icon: LongTailArrowRightIcon, className: pickerClasses.arrowIcon, "aria-hidden": "true" }), jsx(FormattedInput, { ...inputToProps, ref: toRef, "aria-disabled": disabled, "aria-label": "End date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesTo, format: format, onBlur: handleToBlur, onChange: handleToChange, onFocus: handleToFocus, placeholder: inputToPlaceholder, readOnly: readOnly, required: required, validate: validateTo, value: inputToValue })] }));
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
export { RangePickerTrigger as default };
|
package/Section/Section.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef, cloneElement, isValidElement } from 'react';
|
|
3
3
|
import { sectionClasses } from '@mezzanine-ui/core/section';
|
|
4
4
|
import ContentHeader from '../ContentHeader/ContentHeader.js';
|
|
@@ -11,9 +11,9 @@ function getDisplayName(element) {
|
|
|
11
11
|
if (typeof type === 'string') {
|
|
12
12
|
return type;
|
|
13
13
|
}
|
|
14
|
-
return type.displayName
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return (type.displayName ||
|
|
15
|
+
type.name ||
|
|
16
|
+
'Unknown');
|
|
17
17
|
}
|
|
18
18
|
function isContentHeaderElement(element) {
|
|
19
19
|
return isValidElement(element) && element.type === ContentHeader;
|
|
@@ -28,7 +28,7 @@ function isTabElement(element) {
|
|
|
28
28
|
* The react component for `mezzanine` section.
|
|
29
29
|
*/
|
|
30
30
|
const Section = forwardRef(function Section(props, ref) {
|
|
31
|
-
const { children, className, contentHeader, filterArea, tab
|
|
31
|
+
const { children, className, contentHeader, filterArea, tab } = props;
|
|
32
32
|
let renderedContentHeader = null;
|
|
33
33
|
let renderedFilterArea = null;
|
|
34
34
|
let renderedTab = null;
|
|
@@ -56,7 +56,7 @@ const Section = forwardRef(function Section(props, ref) {
|
|
|
56
56
|
console.warn(`[Section] Invalid tab type: <${getDisplayName(tab)}>. Only <Tab /> component from @mezzanine-ui/react is allowed.`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
return (jsxs("div", { ref: ref, className: cx(sectionClasses.host, className), children: [renderedContentHeader, renderedFilterArea, renderedTab, children] }));
|
|
59
|
+
return (jsxs("div", { ref: ref, className: cx(sectionClasses.host, className), children: [renderedContentHeader, renderedFilterArea, renderedTab, jsx("div", { className: sectionClasses.hostContent, children: children })] }));
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
export { Section as default };
|
package/Transition/Collapse.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ export interface CollapseProps extends TransitionImplementationProps, Omit<Nativ
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* The react component for `mezzanine` transition collapse.
|
|
13
|
-
* @deprecated 設計師未定義,暫時標記為 deprecated
|
|
13
|
+
* @deprecated 設計師未定義,暫時標記為 deprecated.
|
|
14
|
+
* 目前 NavigationOption 與 Accordion 正在使用此元件.
|
|
14
15
|
*/
|
|
15
16
|
declare const Collapse: import("react").ForwardRefExoticComponent<CollapseProps & import("react").RefAttributes<HTMLElement>>;
|
|
16
17
|
export default Collapse;
|
package/Transition/Collapse.js
CHANGED
|
@@ -27,7 +27,8 @@ const defaultEasing = {
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* The react component for `mezzanine` transition collapse.
|
|
30
|
-
* @deprecated 設計師未定義,暫時標記為 deprecated
|
|
30
|
+
* @deprecated 設計師未定義,暫時標記為 deprecated.
|
|
31
|
+
* 目前 NavigationOption 與 Accordion 正在使用此元件.
|
|
31
32
|
*/
|
|
32
33
|
const Collapse = forwardRef(function Collapse(props, ref) {
|
|
33
34
|
const { appear, children, collapsedHeight: collapsedHeightProp = 0, delay = 0, duration = 'auto', easing = defaultEasing, in: inProp = false, lazyMount, keepMount, onEnter, onEntered, onEntering, onExit, onExiting, onExited, style, ...rest } = props;
|
package/Upload/Upload.js
CHANGED
|
@@ -14,11 +14,13 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
14
14
|
const { accept, className, disabled = false, mode = 'list', size = 'main', showFileSize = true, files: controlledFiles = [], onUpload, onDelete, onReload, onDownload, onZoomIn, onChange, id, name, multiple = false, maxFiles, hints, uploaderLabel, uploaderIcon, inputRef, inputProps, onMaxFilesExceeded, errorMessage, errorIcon, ...rest } = props;
|
|
15
15
|
const files = controlledFiles;
|
|
16
16
|
const filesRef = useRef(files);
|
|
17
|
+
const replaceFileIdRef = useRef(null);
|
|
18
|
+
const replaceInputRef = useRef(null);
|
|
17
19
|
useEffect(() => {
|
|
18
20
|
filesRef.current = files;
|
|
19
21
|
}, [files]);
|
|
20
22
|
// Default error icon when status is error and no errorIcon is provided
|
|
21
|
-
const defaultErrorIconElement = useMemo(() => errorIcon !== null && errorIcon !== void 0 ? errorIcon : jsx(Icon, { icon: DangerousFilledIcon, color: "error", size: 24 }), [errorIcon]);
|
|
23
|
+
const defaultErrorIconElement = useMemo(() => errorIcon !== null && errorIcon !== void 0 ? errorIcon : (jsx(Icon, { icon: DangerousFilledIcon, color: "error", size: 24 })), [errorIcon]);
|
|
22
24
|
// Auto-disable when maxFiles is reached
|
|
23
25
|
const isMaxFilesReached = useMemo(() => {
|
|
24
26
|
if (maxFiles === undefined)
|
|
@@ -37,6 +39,12 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
37
39
|
const handleUpload = useCallback(async (selectedFiles) => {
|
|
38
40
|
if (!selectedFiles.length)
|
|
39
41
|
return;
|
|
42
|
+
// If a replace operation is in progress, remove the old file first
|
|
43
|
+
if (replaceFileIdRef.current !== null) {
|
|
44
|
+
const fileIdToReplace = replaceFileIdRef.current;
|
|
45
|
+
replaceFileIdRef.current = null;
|
|
46
|
+
filesRef.current = filesRef.current.filter((f) => f.id !== fileIdToReplace);
|
|
47
|
+
}
|
|
40
48
|
// Check maxFiles limit
|
|
41
49
|
if (maxFiles !== undefined) {
|
|
42
50
|
const currentCount = filesRef.current.length;
|
|
@@ -100,7 +108,8 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
100
108
|
var _a, _b, _c;
|
|
101
109
|
const tempIndex = tempIdToIndex.get(file.id);
|
|
102
110
|
// If this is a temp file and we have a corresponding backend file
|
|
103
|
-
if (tempIndex !== undefined &&
|
|
111
|
+
if (tempIndex !== undefined &&
|
|
112
|
+
tempIndex < backendFiles.length) {
|
|
104
113
|
const backendFile = backendFiles[tempIndex];
|
|
105
114
|
// Replace temporary file with backend file (includes real ID and status)
|
|
106
115
|
// Apply default error message and icon if status is error and not provided
|
|
@@ -124,7 +133,8 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
124
133
|
const nextFiles = filesRef.current.map((file) => {
|
|
125
134
|
const tempIndex = tempIdToIndex.get(file.id);
|
|
126
135
|
// If this is a temp file and we have a corresponding backend ID
|
|
127
|
-
if (tempIndex !== undefined &&
|
|
136
|
+
if (tempIndex !== undefined &&
|
|
137
|
+
tempIndex < backendIds.length) {
|
|
128
138
|
const backendId = backendIds[tempIndex];
|
|
129
139
|
// Replace temporary ID with backend ID, set status to 'done'
|
|
130
140
|
return {
|
|
@@ -142,7 +152,11 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
142
152
|
else {
|
|
143
153
|
// Old API: backward compatibility - no return value, assume success
|
|
144
154
|
const nextFiles = filesRef.current.map((file) => tempFiles.some((tf) => tf.id === file.id)
|
|
145
|
-
? {
|
|
155
|
+
? {
|
|
156
|
+
...file,
|
|
157
|
+
status: 'done',
|
|
158
|
+
progress: 100,
|
|
159
|
+
}
|
|
146
160
|
: file);
|
|
147
161
|
emitChange(nextFiles);
|
|
148
162
|
}
|
|
@@ -172,7 +186,14 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
172
186
|
: file);
|
|
173
187
|
emitChange(nextFiles);
|
|
174
188
|
}
|
|
175
|
-
}, [
|
|
189
|
+
}, [
|
|
190
|
+
emitChange,
|
|
191
|
+
maxFiles,
|
|
192
|
+
onMaxFilesExceeded,
|
|
193
|
+
onUpload,
|
|
194
|
+
errorMessage,
|
|
195
|
+
defaultErrorIconElement,
|
|
196
|
+
]);
|
|
176
197
|
const handleDelete = useCallback((fileId) => {
|
|
177
198
|
const file = findFileById(fileId);
|
|
178
199
|
if (!file)
|
|
@@ -254,6 +275,7 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
254
275
|
const shouldUsePictureCard = useMemo(() => {
|
|
255
276
|
return /cards|card-wall/.test(mode);
|
|
256
277
|
}, [mode]);
|
|
278
|
+
const isSingleFileCardMode = shouldUsePictureCard && maxFiles === 1;
|
|
257
279
|
const renderUploadItem = useCallback((uploadFile) => {
|
|
258
280
|
// Skip rendering if neither file nor url is provided
|
|
259
281
|
if (!uploadFile.file && !uploadFile.url) {
|
|
@@ -262,18 +284,50 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
262
284
|
// Determine if it's an image using shared utility
|
|
263
285
|
const isImage = isImageFile(uploadFile.file, uploadFile.url);
|
|
264
286
|
// For images, use 'thumbnail' to show image preview; for non-images, use 'icon' to show file icon
|
|
265
|
-
const itemType = isImage
|
|
287
|
+
const itemType = isImage
|
|
288
|
+
? 'thumbnail'
|
|
289
|
+
: 'icon';
|
|
266
290
|
return (jsx(UploadItem, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, type: itemType, showFileSize: showFileSize, disabled: disabled, onDelete: () => handleDelete(uploadFile.id), onDownload: () => handleDownload(uploadFile.id), onReload: () => handleReload(uploadFile.id) }, uploadFile.id));
|
|
267
|
-
}, [
|
|
291
|
+
}, [
|
|
292
|
+
size,
|
|
293
|
+
showFileSize,
|
|
294
|
+
disabled,
|
|
295
|
+
handleDelete,
|
|
296
|
+
handleDownload,
|
|
297
|
+
handleReload,
|
|
298
|
+
]);
|
|
268
299
|
const uploaderElement = (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...uploaderConfig }));
|
|
269
300
|
const topUploaderElement = topUploaderConfig ? (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id ? `${id}-top` : undefined, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...topUploaderConfig })) : null;
|
|
270
301
|
const hintsElement = useMemo(() => {
|
|
271
|
-
if (!hints ||
|
|
302
|
+
if (!hints ||
|
|
303
|
+
hints.length === 0 ||
|
|
304
|
+
mode === 'list' ||
|
|
305
|
+
mode === 'card-wall')
|
|
272
306
|
return null;
|
|
273
307
|
const hintsClassName = mode === 'cards' ? uploadClasses.fillWidthHints : uploadClasses.hints;
|
|
274
308
|
return (jsx("ul", { className: hintsClassName, children: hints.map((hint) => (jsxs("li", { className: uploadClasses.hint(hint.type || 'info'), children: [jsx(Icon, { icon: hint.type === 'info' ? InfoFilledIcon : DangerousFilledIcon, color: hint.type === 'info' ? 'info' : 'error', size: 14 }), hint.label] }, hint.label))) }));
|
|
275
309
|
}, [hints, mode]);
|
|
276
|
-
return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, mode === 'button-list' && hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id),
|
|
310
|
+
return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, mode === 'button-list' && hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id), ...(!isSingleFileCardMode && {
|
|
311
|
+
onDownload: () => handleDownload(uploadFile.id),
|
|
312
|
+
onZoomIn: () => handleZoomIn(uploadFile.id),
|
|
313
|
+
}), ...(isSingleFileCardMode && {
|
|
314
|
+
onReplace: (e) => {
|
|
315
|
+
var _a;
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
replaceFileIdRef.current = uploadFile.id;
|
|
318
|
+
(_a = replaceInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
319
|
+
},
|
|
320
|
+
}) }, uploadFile.id))), !isSingleFileCardMode && uploaderElement, isSingleFileCardMode &&
|
|
321
|
+
imageFiles.length === 0 &&
|
|
322
|
+
uploaderElement, isSingleFileCardMode && (jsx("input", { ref: replaceInputRef, accept: accept, style: { display: 'none' }, type: "file", onChange: (e) => {
|
|
323
|
+
var _a;
|
|
324
|
+
const selectedFiles = Array.from((_a = e.target.files) !== null && _a !== void 0 ? _a : []);
|
|
325
|
+
e.target.value = '';
|
|
326
|
+
if (selectedFiles.length)
|
|
327
|
+
handleUpload(selectedFiles);
|
|
328
|
+
} }))] })), nonImageFiles.length > 0 && nonImageFiles.map(renderUploadItem), !shouldUsePictureCard &&
|
|
329
|
+
imageFiles.length > 0 &&
|
|
330
|
+
imageFiles.map(renderUploadItem)] }), mode === 'cards' && hintsElement] }));
|
|
277
331
|
});
|
|
278
332
|
|
|
279
333
|
export { Upload as default };
|