@mezzanine-ui/react 1.0.0-beta.3 → 1.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AutoComplete/AutoComplete.d.ts +23 -0
- package/AutoComplete/AutoComplete.js +23 -9
- package/Breadcrumb/Breadcrumb.js +16 -21
- package/Breadcrumb/BreadcrumbDropdown.d.ts +11 -0
- package/Breadcrumb/BreadcrumbDropdown.js +22 -0
- package/Breadcrumb/BreadcrumbItem.d.ts +2 -3
- package/Breadcrumb/BreadcrumbItem.js +13 -31
- package/Breadcrumb/BreadcrumbOverflowMenu.d.ts +7 -0
- package/Breadcrumb/BreadcrumbOverflowMenu.js +77 -0
- package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +11 -0
- package/Breadcrumb/BreadcrumbOverflowMenuDropdown.js +21 -0
- package/Breadcrumb/BreadcrumbOverflowMenuItem.d.ts +3 -0
- package/Breadcrumb/BreadcrumbOverflowMenuItem.js +27 -0
- package/Breadcrumb/typings.d.ts +21 -39
- package/Checkbox/index.d.ts +4 -5
- package/Checkbox/index.js +1 -5
- package/ContentHeader/ContentHeader.d.ts +160 -0
- package/ContentHeader/ContentHeader.js +54 -0
- package/ContentHeader/index.d.ts +2 -0
- package/ContentHeader/index.js +1 -0
- package/ContentHeader/utils.d.ts +23 -0
- package/ContentHeader/utils.js +215 -0
- package/Dropdown/Dropdown.d.ts +17 -0
- package/Dropdown/Dropdown.js +6 -2
- package/Dropdown/DropdownItem.d.ts +10 -0
- package/Dropdown/DropdownItem.js +37 -8
- package/Dropdown/DropdownItemCard.d.ts +2 -2
- package/Dropdown/DropdownItemCard.js +11 -8
- package/Empty/Empty.js +2 -1
- package/Empty/icons/EmptyMainNotificationIcon.d.ts +4 -0
- package/Empty/icons/EmptyMainNotificationIcon.js +9 -0
- package/Empty/typings.d.ts +2 -2
- package/FilterArea/Filter.d.ts +32 -0
- package/FilterArea/Filter.js +23 -0
- package/FilterArea/FilterArea.d.ts +58 -0
- package/FilterArea/FilterArea.js +31 -0
- package/FilterArea/FilterLine.d.ts +11 -0
- package/FilterArea/FilterLine.js +13 -0
- package/FilterArea/index.d.ts +6 -0
- package/FilterArea/index.js +3 -0
- package/Input/Input.d.ts +6 -4
- package/Input/Input.js +28 -10
- package/Input/index.d.ts +1 -1
- package/Modal/MediaPreviewModal.d.ts +54 -0
- package/Modal/MediaPreviewModal.js +158 -0
- package/Modal/Modal.js +1 -1
- package/Modal/index.d.ts +2 -0
- package/Modal/index.js +1 -0
- package/Navigation/Navigation.js +6 -5
- package/Navigation/NavigationOption.d.ts +6 -2
- package/Navigation/NavigationOption.js +19 -9
- package/Navigation/NavigationOverflowMenu.d.ts +6 -0
- package/Navigation/NavigationOverflowMenu.js +90 -0
- package/Navigation/NavigationOverflowMenuOption.d.ts +7 -0
- package/Navigation/NavigationOverflowMenuOption.js +68 -0
- package/Navigation/NavigationUserMenu.d.ts +4 -2
- package/Navigation/NavigationUserMenu.js +13 -5
- package/Navigation/context.d.ts +3 -2
- package/NotificationCenter/NotificationCenter.d.ts +1 -1
- package/NotificationCenter/NotificationCenter.js +34 -14
- package/NotificationCenter/NotificationCenterDrawer.d.ts +20 -0
- package/PageHeader/PageHeader.d.ts +32 -25
- package/PageHeader/PageHeader.js +49 -35
- package/ResultState/ResultState.d.ts +9 -0
- package/ResultState/ResultState.js +36 -4
- package/Scrollbar/Scrollbar.d.ts +9 -0
- package/Scrollbar/Scrollbar.js +78 -0
- package/Scrollbar/index.d.ts +2 -0
- package/Scrollbar/index.js +1 -0
- package/Scrollbar/typings.d.ts +47 -0
- package/Select/SelectTrigger.js +5 -4
- package/Select/typings.d.ts +6 -1
- package/Selection/Selection.js +1 -1
- package/Selection/SelectionGroup.d.ts +28 -0
- package/Table/Table.d.ts +2 -120
- package/Table/Table.js +148 -53
- package/Table/TableContext.d.ts +11 -12
- package/Table/components/TableActionsCell.js +12 -4
- package/Table/components/TableBody.js +2 -1
- package/Table/components/TableColGroup.d.ts +1 -4
- package/Table/components/TableColGroup.js +15 -16
- package/Table/components/TableCollectableCell.d.ts +17 -0
- package/Table/components/TableCollectableCell.js +54 -0
- package/Table/components/TableDragOrPinHandleCell.d.ts +20 -0
- package/Table/components/TableDragOrPinHandleCell.js +58 -0
- package/Table/components/TableExpandedRow.js +11 -2
- package/Table/components/TableHeader.js +12 -10
- package/Table/components/TableRow.js +38 -13
- package/Table/components/TableSelectionCell.js +1 -1
- package/Table/components/TableToggleableCell.d.ts +16 -0
- package/Table/components/TableToggleableCell.js +51 -0
- package/Table/components/index.d.ts +4 -1
- package/Table/components/index.js +3 -0
- package/Table/hooks/typings.d.ts +18 -4
- package/Table/hooks/useTableExpansion.d.ts +2 -2
- package/Table/hooks/useTableExpansion.js +5 -5
- package/Table/hooks/useTableFixedOffsets.d.ts +6 -2
- package/Table/hooks/useTableFixedOffsets.js +58 -24
- package/Table/hooks/useTableScroll.d.ts +9 -3
- package/Table/hooks/useTableScroll.js +34 -7
- package/Table/hooks/useTableVirtualization.d.ts +2 -1
- package/Table/hooks/useTableVirtualization.js +2 -8
- package/Table/index.d.ts +4 -3
- package/Table/index.js +3 -0
- package/Table/typings.d.ts +172 -0
- package/Transition/Slide.d.ts +9 -2
- package/Transition/Slide.js +7 -4
- package/Tree/TreeNode.js +1 -1
- package/index.d.ts +4 -2
- package/index.js +6 -3
- package/package.json +6 -4
- package/Navigation/CollapsedMenu.d.ts +0 -6
- package/Navigation/CollapsedMenu.js +0 -16
- package/PageToolbar/PageToolbar.d.ts +0 -110
- package/PageToolbar/PageToolbar.js +0 -23
- package/PageToolbar/index.d.ts +0 -2
- package/PageToolbar/index.js +0 -1
- package/PageToolbar/utils.d.ts +0 -23
- package/PageToolbar/utils.js +0 -157
- package/Table/components/TableDragHandleCell.d.ts +0 -11
- package/Table/components/TableDragHandleCell.js +0 -44
|
@@ -4,10 +4,10 @@ import cx from 'clsx';
|
|
|
4
4
|
import { useMemo, useState } from 'react';
|
|
5
5
|
import { dropdownClasses } from '@mezzanine-ui/core/dropdown/dropdown';
|
|
6
6
|
import { CheckedIcon } from '@mezzanine-ui/icons';
|
|
7
|
-
import Checkbox from '../Checkbox/Checkbox.js';
|
|
8
7
|
import Typography from '../Typography/Typography.js';
|
|
9
8
|
import { highlightText } from './highlightText.js';
|
|
10
9
|
import Icon from '../Icon/Icon.js';
|
|
10
|
+
import Checkbox from '../Checkbox/Checkbox.js';
|
|
11
11
|
|
|
12
12
|
function DropdownItemCard(props) {
|
|
13
13
|
const { active = false, appendIcon, appendContent, followText, id, label, level: levelProp, mode, name: _name, prependIcon, subTitle, validate, disabled, checked, defaultChecked, checkSite, onCheckedChange, onClick, className, onMouseEnter, showUnderline, } = props;
|
|
@@ -51,8 +51,8 @@ function DropdownItemCard(props) {
|
|
|
51
51
|
: [
|
|
52
52
|
{
|
|
53
53
|
text: cardLabel,
|
|
54
|
-
highlight: false
|
|
55
|
-
}
|
|
54
|
+
highlight: false,
|
|
55
|
+
},
|
|
56
56
|
];
|
|
57
57
|
}, [cardLabel, followText]);
|
|
58
58
|
const showPrependContent = useMemo(() => {
|
|
@@ -68,13 +68,15 @@ function DropdownItemCard(props) {
|
|
|
68
68
|
? [
|
|
69
69
|
{
|
|
70
70
|
text: subTitle,
|
|
71
|
-
highlight: false
|
|
72
|
-
}
|
|
71
|
+
highlight: false,
|
|
72
|
+
},
|
|
73
73
|
]
|
|
74
74
|
: [];
|
|
75
75
|
}, [subTitle, followText]);
|
|
76
76
|
const renderHighlightedText = (parts, className, id) => {
|
|
77
|
-
return (jsx(Typography, { className: className, id: id, children: parts.map((part, index) => (jsx("span", { className: part.highlight && validate !== 'danger'
|
|
77
|
+
return (jsx(Typography, { className: className, id: id, children: parts.map((part, index) => (jsx("span", { className: part.highlight && validate !== 'danger'
|
|
78
|
+
? dropdownClasses.cardHighlightedText
|
|
79
|
+
: '', children: part.text }, index))) }));
|
|
78
80
|
};
|
|
79
81
|
const toggleChecked = () => {
|
|
80
82
|
if (disabled)
|
|
@@ -109,8 +111,9 @@ function DropdownItemCard(props) {
|
|
|
109
111
|
[dropdownClasses.cardActive]: active || isChecked,
|
|
110
112
|
[dropdownClasses.cardDisabled]: disabled,
|
|
111
113
|
[dropdownClasses.cardDanger]: validate === 'danger',
|
|
112
|
-
}, 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
|
|
113
|
-
|
|
114
|
+
}, className), id: id, role: "option", tabIndex: -1, onMouseEnter: onMouseEnter, onClick: handleClick, onKeyDown: handleKeyDown, children: jsxs("div", { className: dropdownClasses.cardContainer, children: [showPrependContent && (jsxs("div", { className: dropdownClasses.cardPrependContent, children: [prependIcon && jsx(Icon, { icon: prependIcon, color: iconColor }), checkSite === 'prefix' && mode === 'multiple' && (jsx(Checkbox, { checked: isChecked, disabled: disabled, onChange: handleCheckboxChange }))] })), jsxs("div", { className: dropdownClasses.cardBody, children: [cardLabel &&
|
|
115
|
+
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("div", { className: dropdownClasses.cardUnderline })] }));
|
|
114
117
|
}
|
|
115
118
|
|
|
116
119
|
export { DropdownItemCard as default };
|
package/Empty/Empty.js
CHANGED
|
@@ -8,6 +8,7 @@ import { EmptyMainInitialDataIcon } from './icons/EmptyMainInitialDataIcon.js';
|
|
|
8
8
|
import { EmptyMainResultIcon } from './icons/EmptyMainResultIcon.js';
|
|
9
9
|
import { EmptyMainSystemIcon } from './icons/EmptyMainSystemIcon.js';
|
|
10
10
|
import { flattenChildren } from '../utils/flatten-children.js';
|
|
11
|
+
import { EmptyMainNotificationIcon } from './icons/EmptyMainNotificationIcon.js';
|
|
11
12
|
import Icon from '../Icon/Icon.js';
|
|
12
13
|
import cx from 'clsx';
|
|
13
14
|
|
|
@@ -21,7 +22,7 @@ const iconMap = {
|
|
|
21
22
|
const mainIconMap = {
|
|
22
23
|
custom: null,
|
|
23
24
|
'initial-data': jsx(EmptyMainInitialDataIcon, { className: emptyClasses.icon }),
|
|
24
|
-
notification:
|
|
25
|
+
notification: jsx(EmptyMainNotificationIcon, { className: emptyClasses.icon }),
|
|
25
26
|
result: jsx(EmptyMainResultIcon, { className: emptyClasses.icon }),
|
|
26
27
|
system: jsx(EmptyMainSystemIcon, { className: emptyClasses.icon }),
|
|
27
28
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export interface EmptyMainNotificationIconProps extends React.SVGProps<SVGSVGElement> {
|
|
2
|
+
size?: number;
|
|
3
|
+
}
|
|
4
|
+
export declare const EmptyMainNotificationIcon: import("react").ForwardRefExoticComponent<Omit<EmptyMainNotificationIconProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef } from 'react';
|
|
3
|
+
|
|
4
|
+
const EmptyMainNotificationIcon = forwardRef(function EmptyMainNotificationIcon(props, ref) {
|
|
5
|
+
const { className, size = 64, ...rest } = props;
|
|
6
|
+
return (jsxs("svg", { ...rest, className: className, fill: "none", height: size, ref: ref, viewBox: "0 0 44 47", width: size, xmlns: "http://www.w3.org/2000/svg", children: [jsxs("defs", { children: [jsxs("linearGradient", { gradientUnits: "userSpaceOnUse", id: "paint0_linear_8482_10235", x1: "22", x2: "20.7012", y1: "33", y2: "46.8785", children: [jsx("stop", { stopColor: "#9DA4AE" }), jsx("stop", { offset: "1", stopColor: "#E5E7EB" })] }), jsxs("linearGradient", { gradientUnits: "userSpaceOnUse", id: "paint1_linear_8482_10235", x1: "44", x2: "-3.06232", y1: "-1.43647e-06", y2: "35.9963", children: [jsx("stop", { stopColor: "#E5E7EB" }), jsx("stop", { offset: "1", stopColor: "#9DA4AE" })] })] }), jsx("circle", { cx: "22", cy: "40", fill: "url(#paint0_linear_8482_10235)", r: "7" }), jsx("path", { d: "M6.69446 15.305C6.69446 6.85228 13.5467 0 21.9995 0C30.4522 0 37.3045 6.85228 37.3045 15.305V26.5731C37.3045 26.6145 37.3141 26.6552 37.3326 26.6922L43.2746 38.5521C43.6077 39.217 43.1242 40 42.3805 40H1.61938C0.875674 40 0.392197 39.2171 0.725289 38.5521L6.66632 26.6922C6.68483 26.6552 6.69446 26.6145 6.69446 26.5731V15.305Z", fill: "url(#paint1_linear_8482_10235)" })] }));
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export { EmptyMainNotificationIcon };
|
package/Empty/typings.d.ts
CHANGED
|
@@ -13,14 +13,14 @@ export interface PresetPictogramEmptyProps {
|
|
|
13
13
|
* The type of empty state, which determines the icon and color theme.
|
|
14
14
|
* @default 'initial-data'
|
|
15
15
|
*/
|
|
16
|
-
type?: 'initial-data' | 'result' | 'system';
|
|
16
|
+
type?: 'initial-data' | 'result' | 'system' | 'notification';
|
|
17
17
|
/**
|
|
18
18
|
* Custom pictogram element.
|
|
19
19
|
*/
|
|
20
20
|
pictogram?: ReactNode;
|
|
21
21
|
}
|
|
22
22
|
export interface CustomPictogramEmptyProps {
|
|
23
|
-
type?: '
|
|
23
|
+
type?: 'custom';
|
|
24
24
|
pictogram?: ReactNode;
|
|
25
25
|
}
|
|
26
26
|
export interface MainOrSubEmptyProps {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { FilterAlign, FilterSpan } from '@mezzanine-ui/core/filter-area';
|
|
3
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
4
|
+
import { FormFieldProps } from '../Form';
|
|
5
|
+
export interface FilterProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'> {
|
|
6
|
+
/**
|
|
7
|
+
* Layout control - Vertical alignment of the field.
|
|
8
|
+
* @default 'stretch'
|
|
9
|
+
*/
|
|
10
|
+
align?: FilterAlign;
|
|
11
|
+
/**
|
|
12
|
+
* The content of the filter field.
|
|
13
|
+
*/
|
|
14
|
+
children: ReactElement<FormFieldProps> | ReactElement<FormFieldProps>[];
|
|
15
|
+
/**
|
|
16
|
+
* Layout control - Whether the field should automatically expand to fill the entire row (equivalent to span={12}).
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
grow?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Layout control - Minimum width of the field.
|
|
22
|
+
*/
|
|
23
|
+
minWidth?: string | number;
|
|
24
|
+
/**
|
|
25
|
+
* Layout control - Number of columns the field occupies in the Grid (1-12, Grid has 12 columns total).
|
|
26
|
+
* This property is ignored when grow is true.
|
|
27
|
+
* @default 2
|
|
28
|
+
*/
|
|
29
|
+
span?: FilterSpan;
|
|
30
|
+
}
|
|
31
|
+
declare const Filter: import("react").ForwardRefExoticComponent<FilterProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
32
|
+
export default Filter;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useMemo } from 'react';
|
|
4
|
+
import { filterAreaClasses, filterAreaPrefix } from '@mezzanine-ui/core/filter-area';
|
|
5
|
+
import cx from 'clsx';
|
|
6
|
+
|
|
7
|
+
const Filter = forwardRef(function Filter(props, ref) {
|
|
8
|
+
const { align = 'stretch', children, className, grow = false, minWidth, span = 2, ...rest } = props;
|
|
9
|
+
const filterClassName = useMemo(() => cx(filterAreaClasses.filter, {
|
|
10
|
+
[filterAreaClasses.filterGrow]: grow,
|
|
11
|
+
[filterAreaClasses.filterAlign(align)]: align,
|
|
12
|
+
}, className), [align, className, grow]);
|
|
13
|
+
const style = useMemo(() => ({
|
|
14
|
+
...(minWidth && { minWidth: typeof minWidth === 'number' ? `${minWidth}px` : minWidth }),
|
|
15
|
+
...(!grow && {
|
|
16
|
+
[`--${filterAreaPrefix}-filter-span`]: span,
|
|
17
|
+
}),
|
|
18
|
+
...rest.style,
|
|
19
|
+
}), [grow, minWidth, rest.style, span]);
|
|
20
|
+
return (jsx("div", { ...rest, ref: ref, className: filterClassName, style: style, children: children }));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export { Filter as default };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactElement } from 'react';
|
|
2
|
+
import { FilterAreaActionsAlign, FilterAreaSize } from '@mezzanine-ui/core/filter-area';
|
|
3
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
4
|
+
import { FilterLineProps } from './FilterLine';
|
|
5
|
+
export interface FilterAreaProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children' | 'onSubmit' | 'onReset'> {
|
|
6
|
+
/**
|
|
7
|
+
* The alignment of the actions.
|
|
8
|
+
* @default 'end'
|
|
9
|
+
*/
|
|
10
|
+
actionsAlign?: FilterAreaActionsAlign;
|
|
11
|
+
/**
|
|
12
|
+
* The content of the filter area, must be FilterLine component(s).
|
|
13
|
+
*/
|
|
14
|
+
children: ReactElement<FilterLineProps> | ReactElement<FilterLineProps>[];
|
|
15
|
+
/**
|
|
16
|
+
* Whether the form has been modified from its initial state.
|
|
17
|
+
* When false, the reset button will be disabled.
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
isDirty?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Callback function triggered when the form is reset.
|
|
23
|
+
* Used to clear all filter conditions and restore to initial state.
|
|
24
|
+
*/
|
|
25
|
+
onReset?: () => void;
|
|
26
|
+
/**
|
|
27
|
+
* Callback function triggered when the form is submitted.
|
|
28
|
+
* FilterArea itself does not manage form state; the parent component should collect
|
|
29
|
+
* filter values and handle submission logic. If using react-hook-form, values will be
|
|
30
|
+
* handled through FormProvider's handleSubmit.
|
|
31
|
+
*/
|
|
32
|
+
onSubmit?: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* The text of the reset button.
|
|
35
|
+
*/
|
|
36
|
+
resetText?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The size of the filter area.
|
|
39
|
+
* @default 'main'
|
|
40
|
+
*/
|
|
41
|
+
size?: FilterAreaSize;
|
|
42
|
+
/**
|
|
43
|
+
* The text of the submit button.
|
|
44
|
+
*/
|
|
45
|
+
submitText?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The type of the reset button.
|
|
48
|
+
* @default 'button'
|
|
49
|
+
*/
|
|
50
|
+
resetButtonType?: ComponentPropsWithoutRef<'button'>['type'];
|
|
51
|
+
/**
|
|
52
|
+
* The type of the submit button.
|
|
53
|
+
* @default 'button'
|
|
54
|
+
*/
|
|
55
|
+
submitButtonType?: ComponentPropsWithoutRef<'button'>['type'];
|
|
56
|
+
}
|
|
57
|
+
declare const FilterArea: import("react").ForwardRefExoticComponent<FilterAreaProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
58
|
+
export default FilterArea;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useMemo, Children, useState, useCallback } from 'react';
|
|
4
|
+
import { filterAreaClasses } from '@mezzanine-ui/core/filter-area';
|
|
5
|
+
import { ChevronUpIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
6
|
+
import Button from '../Button/Button.js';
|
|
7
|
+
import cx from 'clsx';
|
|
8
|
+
|
|
9
|
+
const FilterArea = forwardRef(function FilterArea(props, ref) {
|
|
10
|
+
const { actionsAlign = 'end', children, className, isDirty = true, onReset, onSubmit, resetText = 'Reset', size = 'main', submitText = 'Search', resetButtonType = 'button', submitButtonType = 'button', ...rest } = props;
|
|
11
|
+
const filterLines = useMemo(() => Children.toArray(children), [children]);
|
|
12
|
+
const hasMultipleLines = filterLines.length > 1;
|
|
13
|
+
const [expanded, setExpanded] = useState(false);
|
|
14
|
+
const handleToggleExpanded = useCallback(() => {
|
|
15
|
+
setExpanded((prev) => !prev);
|
|
16
|
+
}, []);
|
|
17
|
+
const handleSubmit = useCallback(() => {
|
|
18
|
+
if (onSubmit) {
|
|
19
|
+
onSubmit();
|
|
20
|
+
}
|
|
21
|
+
}, [onSubmit]);
|
|
22
|
+
const renderAction = () => {
|
|
23
|
+
return (jsxs("div", { className: cx(filterAreaClasses.actions, filterAreaClasses.actionsAlign(actionsAlign), { [filterAreaClasses.actionsExpanded]: expanded }), children: [jsx(Button, { onClick: handleSubmit, size: size, type: submitButtonType, children: submitText }), jsx(Button, { disabled: !isDirty, onClick: onReset, size: size, type: resetButtonType, variant: "base-secondary", children: resetText }), hasMultipleLines && (jsx(Button, { "aria-expanded": expanded, "aria-label": expanded ? 'Collapse filters' : 'Expand filters', icon: expanded ? ChevronUpIcon : ChevronDownIcon, iconType: "icon-only", onClick: handleToggleExpanded, size: size, title: expanded ? 'Collapse filters' : 'Expand filters', type: "button", variant: "base-ghost" }))] }));
|
|
24
|
+
};
|
|
25
|
+
const firstLine = filterLines[0];
|
|
26
|
+
return (jsx("div", { ...rest, ref: ref, className: cx(filterAreaClasses.host, className, {
|
|
27
|
+
[filterAreaClasses.size(size)]: size,
|
|
28
|
+
}), children: expanded ? (jsxs(Fragment, { children: [filterLines, renderAction()] })) : (jsx(Fragment, { children: firstLine && (jsxs("div", { className: filterAreaClasses.row, children: [firstLine, renderAction()] })) })) }));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export { FilterArea as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
3
|
+
import { FilterProps } from './Filter';
|
|
4
|
+
export interface FilterLineProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'> {
|
|
5
|
+
/**
|
|
6
|
+
* The content of the filter line, must be Filter component(s).
|
|
7
|
+
*/
|
|
8
|
+
children: ReactElement<FilterProps> | ReactElement<FilterProps>[];
|
|
9
|
+
}
|
|
10
|
+
declare const FilterLine: import("react").ForwardRefExoticComponent<FilterLineProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
11
|
+
export default FilterLine;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useMemo } from 'react';
|
|
4
|
+
import { filterAreaClasses } from '@mezzanine-ui/core/filter-area';
|
|
5
|
+
import cx from 'clsx';
|
|
6
|
+
|
|
7
|
+
const FilterLine = forwardRef(function FilterLine(props, ref) {
|
|
8
|
+
const { className, children, ...rest } = props;
|
|
9
|
+
const lineClassName = useMemo(() => cx(filterAreaClasses.line, className), [className]);
|
|
10
|
+
return (jsx("div", { ...rest, ref: ref, className: lineClassName, children: children }));
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export { FilterLine as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as FilterArea } from './FilterArea';
|
|
2
|
+
export type { FilterAreaProps } from './FilterArea';
|
|
3
|
+
export { default as FilterLine } from './FilterLine';
|
|
4
|
+
export type { FilterLineProps } from './FilterLine';
|
|
5
|
+
export { default as Filter } from './Filter';
|
|
6
|
+
export type { FilterProps } from './Filter';
|
package/Input/Input.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface InputBaseProps extends Omit<TextFieldProps, 'children' | 'clear
|
|
|
17
17
|
/**
|
|
18
18
|
* Formatter function to transform the value for display.
|
|
19
19
|
* Common use cases: currency formatting (1000 → "1,000"), phone numbers, etc.
|
|
20
|
+
* Default to formatting number with commas for "currency" variant.
|
|
20
21
|
* @example
|
|
21
22
|
* formatter={(value) => value.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
|
22
23
|
*/
|
|
@@ -49,6 +50,7 @@ export interface InputBaseProps extends Omit<TextFieldProps, 'children' | 'clear
|
|
|
49
50
|
/**
|
|
50
51
|
* Parser function to extract the raw value from formatted display value.
|
|
51
52
|
* Should reverse the formatter transformation.
|
|
53
|
+
* Default to removing commas for "currency" formatting.
|
|
52
54
|
* @example
|
|
53
55
|
* parser={(value) => value.replace(/,/g, '')}
|
|
54
56
|
*/
|
|
@@ -113,10 +115,10 @@ export type NumberInputProps = InputBaseProps & NumberInput & {
|
|
|
113
115
|
variant: 'number';
|
|
114
116
|
};
|
|
115
117
|
/**
|
|
116
|
-
* 5.
|
|
118
|
+
* 5. Currency Input - Input with unit text and spinner buttons
|
|
117
119
|
*/
|
|
118
|
-
export type
|
|
119
|
-
variant: '
|
|
120
|
+
export type CurrencyInputProps = InputBaseProps & NumberInput & TextFieldAffixProps & {
|
|
121
|
+
variant: 'currency';
|
|
120
122
|
/**
|
|
121
123
|
* Whether to show spinner buttons.
|
|
122
124
|
* @default false
|
|
@@ -201,7 +203,7 @@ export type WithPasswordStrengthIndicator = {
|
|
|
201
203
|
export type PasswordInputProps = InputBaseProps & ClearableInput & WithPasswordStrengthIndicator & {
|
|
202
204
|
variant: 'password';
|
|
203
205
|
};
|
|
204
|
-
export type InputProps = BaseInputProps | WithAffixInputProps | SearchInputProps | NumberInputProps |
|
|
206
|
+
export type InputProps = BaseInputProps | WithAffixInputProps | SearchInputProps | NumberInputProps | CurrencyInputProps | ActionInputProps | SelectInputProps | PasswordInputProps;
|
|
205
207
|
/**
|
|
206
208
|
* The react component for `mezzanine` input.
|
|
207
209
|
*/
|
package/Input/Input.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { inputClasses } from '@mezzanine-ui/core/input';
|
|
4
4
|
import { EyeIcon, EyeInvisibleIcon, SearchIcon } from '@mezzanine-ui/icons';
|
|
5
|
-
import { forwardRef, useRef, useState, useCallback } from 'react';
|
|
5
|
+
import { forwardRef, useRef, useState, useMemo, useCallback } from 'react';
|
|
6
6
|
import { useInputWithClearControlValue } from '../Form/useInputWithClearControlValue.js';
|
|
7
7
|
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
8
|
+
import { formatNumberWithCommas } from '../utils/format-number-with-commas.js';
|
|
9
|
+
import { parseNumberWithCommas } from '../utils/parse-number-with-commas.js';
|
|
8
10
|
import Icon from '../Icon/Icon.js';
|
|
9
11
|
import Dropdown from '../Dropdown/Dropdown.js';
|
|
10
12
|
import SelectButton from './SelectButton/SelectButton.js';
|
|
@@ -18,7 +20,7 @@ import cx from 'clsx';
|
|
|
18
20
|
* The react component for `mezzanine` input.
|
|
19
21
|
*/
|
|
20
22
|
const Input = forwardRef(function Input(props, ref) {
|
|
21
|
-
const { active, className, defaultValue, disabled = false, error = false, formatter, fullWidth = true, id, inputProps, inputType, inputRef: inputRefProp, name, onChange: onChangeProp, parser, placeholder, readonly, size = 'main', typing, variant = 'base', value: valueProp, } = props;
|
|
23
|
+
const { active, className, defaultValue, disabled = false, error = false, formatter: formatterProp, fullWidth = true, id, inputProps, inputType, inputRef: inputRefProp, name, onChange: onChangeProp, parser: parserProp, placeholder, readonly, size = 'main', typing, variant = 'base', value: valueProp, } = props;
|
|
22
24
|
const inputRef = useRef(null);
|
|
23
25
|
const [showPassword, setShowPassword] = useState(false);
|
|
24
26
|
const [value, onChange, onClearFromHook] = useInputWithClearControlValue({
|
|
@@ -28,6 +30,22 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
28
30
|
value: valueProp,
|
|
29
31
|
});
|
|
30
32
|
// Handle formatter/parser logic
|
|
33
|
+
const formatter = useMemo(() => {
|
|
34
|
+
if (formatterProp)
|
|
35
|
+
return formatterProp;
|
|
36
|
+
if (variant === 'currency') {
|
|
37
|
+
return (value) => formatNumberWithCommas(value);
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}, [formatterProp, variant]);
|
|
41
|
+
const parser = useMemo(() => {
|
|
42
|
+
if (parserProp)
|
|
43
|
+
return parserProp;
|
|
44
|
+
if (variant === 'currency') {
|
|
45
|
+
return (value) => { var _a, _b; return (_b = (_a = parseNumberWithCommas(value)) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''; };
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}, [parserProp, variant]);
|
|
31
49
|
const handleChange = useCallback((event) => {
|
|
32
50
|
let newValue = event.target.value;
|
|
33
51
|
// Parse the formatted value back to raw value if parser is provided
|
|
@@ -106,20 +124,20 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
106
124
|
};
|
|
107
125
|
break;
|
|
108
126
|
}
|
|
109
|
-
case '
|
|
110
|
-
const
|
|
111
|
-
const { step = 1, max, min, onSpinUp, onSpinDown } =
|
|
127
|
+
case 'currency': {
|
|
128
|
+
const currencyProps = props;
|
|
129
|
+
const { step = 1, max, min, onSpinUp, onSpinDown } = currencyProps;
|
|
112
130
|
// 預設置右對齊
|
|
113
131
|
inputStyle = { textAlign: 'right' };
|
|
114
132
|
// 允許填入 prefix/suffix
|
|
115
|
-
prefix =
|
|
116
|
-
suffix =
|
|
133
|
+
prefix = currencyProps.prefix;
|
|
134
|
+
suffix = currencyProps.suffix;
|
|
117
135
|
defaultInputProps = {
|
|
118
136
|
min: min,
|
|
119
137
|
max: max,
|
|
120
138
|
step: step,
|
|
121
139
|
};
|
|
122
|
-
if (
|
|
140
|
+
if (currencyProps.showSpinner) {
|
|
123
141
|
const handleSpinUp = () => {
|
|
124
142
|
const currentValue = parseFloat(value || '0');
|
|
125
143
|
const newValue = currentValue + step;
|
|
@@ -140,7 +158,7 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
140
158
|
}
|
|
141
159
|
onSpinDown === null || onSpinDown === void 0 ? void 0 : onSpinDown();
|
|
142
160
|
};
|
|
143
|
-
suffix = (jsxs(Fragment, { children: [
|
|
161
|
+
suffix = (jsxs(Fragment, { children: [currencyProps.suffix, jsxs("div", { className: inputClasses.spinners, children: [jsx(SpinnerButton, { type: "up", size: size, disabled: disabled, onClick: handleSpinUp }), jsx(SpinnerButton, { type: "down", size: size, disabled: disabled, onClick: handleSpinDown })] })] }));
|
|
144
162
|
}
|
|
145
163
|
break;
|
|
146
164
|
}
|
|
@@ -159,7 +177,7 @@ const Input = forwardRef(function Input(props, ref) {
|
|
|
159
177
|
}
|
|
160
178
|
case 'select': {
|
|
161
179
|
const selectProps = props;
|
|
162
|
-
const { selectButton, options, dropdownWidth = 120, dropdownMaxHeight = 114 } = selectProps;
|
|
180
|
+
const { selectButton, options, dropdownWidth = 120, dropdownMaxHeight = 114, } = selectProps;
|
|
163
181
|
const defaultOptions = options || [];
|
|
164
182
|
const selectedOptions = defaultOptions.length > 0
|
|
165
183
|
? defaultOptions.map((option) => ({
|
package/Input/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { InputSize, InputStrength } from '@mezzanine-ui/core/input';
|
|
2
|
-
export type { InputBaseProps, ClearableInput, NumberInput, BaseInputProps, WithAffixInputProps, SearchInputProps, NumberInputProps,
|
|
2
|
+
export type { InputBaseProps, ClearableInput, NumberInput, BaseInputProps, WithAffixInputProps, SearchInputProps, NumberInputProps, CurrencyInputProps, ActionInputProps, SelectInputProps, WithPasswordStrengthIndicator, PasswordInputProps, InputProps, } from './Input';
|
|
3
3
|
export { default } from './Input';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
2
|
+
import { ModalContainerProps } from './useModalContainer';
|
|
3
|
+
export interface MediaPreviewModalProps extends Omit<ModalContainerProps, 'children'>, NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
4
|
+
/**
|
|
5
|
+
* The current index of the media being displayed (controlled mode).
|
|
6
|
+
* If provided along with onNext/onPrev, the component operates in controlled mode.
|
|
7
|
+
*/
|
|
8
|
+
currentIndex?: number;
|
|
9
|
+
/**
|
|
10
|
+
* The default index when the modal opens (uncontrolled mode).
|
|
11
|
+
* @default 0
|
|
12
|
+
*/
|
|
13
|
+
defaultIndex?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to disable the next navigation button.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
disableNext?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether to disable the previous navigation button.
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
disablePrev?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Array of media items to display.
|
|
26
|
+
* Each item should be a valid image URL or React node.
|
|
27
|
+
*/
|
|
28
|
+
mediaItems: (string | React.ReactNode)[];
|
|
29
|
+
/**
|
|
30
|
+
* Callback fired when the index changes (uncontrolled mode).
|
|
31
|
+
*/
|
|
32
|
+
onIndexChange?: (index: number) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Callback fired when the next navigation button is clicked (controlled mode).
|
|
35
|
+
* If provided, the component operates in controlled mode.
|
|
36
|
+
*/
|
|
37
|
+
onNext?: () => void;
|
|
38
|
+
/**
|
|
39
|
+
* Callback fired when the previous navigation button is clicked (controlled mode).
|
|
40
|
+
* If provided, the component operates in controlled mode.
|
|
41
|
+
*/
|
|
42
|
+
onPrev?: () => void;
|
|
43
|
+
/**
|
|
44
|
+
* Whether to show the pagination indicator.
|
|
45
|
+
* @default true
|
|
46
|
+
*/
|
|
47
|
+
showPaginationIndicator?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The react component for `mezzanine` media preview modal.
|
|
51
|
+
* Displays media items with navigation controls and a close button.
|
|
52
|
+
*/
|
|
53
|
+
declare const MediaPreviewModal: import("react").ForwardRefExoticComponent<MediaPreviewModalProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
54
|
+
export default MediaPreviewModal;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useState, useEffect, useRef, useMemo } from 'react';
|
|
4
|
+
import { ChevronLeftIcon, ChevronRightIcon } from '@mezzanine-ui/icons';
|
|
5
|
+
import { modalClasses } from '@mezzanine-ui/core/modal';
|
|
6
|
+
import { MOTION_DURATION, MOTION_EASING } from '@mezzanine-ui/system/motion';
|
|
7
|
+
import useModalContainer from './useModalContainer.js';
|
|
8
|
+
import ClearActions from '../ClearActions/ClearActions.js';
|
|
9
|
+
import Icon from '../Icon/Icon.js';
|
|
10
|
+
import Fade from '../Transition/Fade.js';
|
|
11
|
+
import cx from 'clsx';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The react component for `mezzanine` media preview modal.
|
|
15
|
+
* Displays media items with navigation controls and a close button.
|
|
16
|
+
*/
|
|
17
|
+
const MediaPreviewModal = forwardRef(function MediaPreviewModal(props, ref) {
|
|
18
|
+
const { className, container, currentIndex: controlledIndex, defaultIndex = 0, disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disableNext = false, disablePortal = false, disablePrev = false, mediaItems, onBackdropClick, onClose, onIndexChange, onNext, onPrev, open, showPaginationIndicator = true, ...rest } = props;
|
|
19
|
+
const { Container: ModalContainer } = useModalContainer();
|
|
20
|
+
// Determine if component is in controlled mode
|
|
21
|
+
const isControlled = controlledIndex !== undefined;
|
|
22
|
+
// Internal state for uncontrolled mode
|
|
23
|
+
const [internalIndex, setInternalIndex] = useState(defaultIndex);
|
|
24
|
+
// Use controlled index if provided, otherwise use internal state
|
|
25
|
+
const currentIndex = isControlled ? controlledIndex : internalIndex;
|
|
26
|
+
// Reset internal index when modal opens in uncontrolled mode
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (open && !isControlled) {
|
|
29
|
+
setInternalIndex(defaultIndex);
|
|
30
|
+
}
|
|
31
|
+
}, [open, isControlled, defaultIndex]);
|
|
32
|
+
// Built-in navigation handlers for uncontrolled mode
|
|
33
|
+
const handleNext = () => {
|
|
34
|
+
if (onNext) {
|
|
35
|
+
// Controlled mode: use provided callback
|
|
36
|
+
onNext();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Uncontrolled mode: update internal state
|
|
40
|
+
const nextIndex = Math.min(currentIndex + 1, mediaItems.length - 1);
|
|
41
|
+
setInternalIndex(nextIndex);
|
|
42
|
+
onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(nextIndex);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const handlePrev = () => {
|
|
46
|
+
if (onPrev) {
|
|
47
|
+
// Controlled mode: use provided callback
|
|
48
|
+
onPrev();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Uncontrolled mode: update internal state
|
|
52
|
+
const prevIndex = Math.max(currentIndex - 1, 0);
|
|
53
|
+
setInternalIndex(prevIndex);
|
|
54
|
+
onIndexChange === null || onIndexChange === void 0 ? void 0 : onIndexChange(prevIndex);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
// Auto-calculate disable states for uncontrolled mode
|
|
58
|
+
const isNextDisabled = disableNext || currentIndex >= mediaItems.length - 1;
|
|
59
|
+
const isPrevDisabled = disablePrev || currentIndex <= 0;
|
|
60
|
+
const [displayedIndices, setDisplayedIndices] = useState([
|
|
61
|
+
currentIndex,
|
|
62
|
+
]);
|
|
63
|
+
const [activeIndex, setActiveIndex] = useState(currentIndex);
|
|
64
|
+
const preloadedUrls = useRef(new Set());
|
|
65
|
+
// Helper function to preload a single image
|
|
66
|
+
const preloadImage = (url) => {
|
|
67
|
+
if (preloadedUrls.current.has(url))
|
|
68
|
+
return;
|
|
69
|
+
const img = new Image();
|
|
70
|
+
img.src = url;
|
|
71
|
+
preloadedUrls.current.add(url);
|
|
72
|
+
};
|
|
73
|
+
// Preload images: prioritize current and adjacent, then load others
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (!open)
|
|
76
|
+
return;
|
|
77
|
+
// Priority 1: Current and adjacent images
|
|
78
|
+
const priorityIndices = [
|
|
79
|
+
currentIndex - 1,
|
|
80
|
+
currentIndex,
|
|
81
|
+
currentIndex + 1,
|
|
82
|
+
].filter((idx) => idx >= 0 && idx < mediaItems.length);
|
|
83
|
+
priorityIndices.forEach((idx) => {
|
|
84
|
+
const media = mediaItems[idx];
|
|
85
|
+
if (typeof media === 'string') {
|
|
86
|
+
preloadImage(media);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// Priority 2: All other images (load after a short delay)
|
|
90
|
+
const loadRemainingTimer = setTimeout(() => {
|
|
91
|
+
mediaItems.forEach((media, idx) => {
|
|
92
|
+
if (typeof media === 'string' && !priorityIndices.includes(idx)) {
|
|
93
|
+
preloadImage(media);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}, 500);
|
|
97
|
+
return () => clearTimeout(loadRemainingTimer);
|
|
98
|
+
}, [open, currentIndex, mediaItems]);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (currentIndex !== activeIndex) {
|
|
101
|
+
// First, add new index to displayedIndices (will render with in=false)
|
|
102
|
+
setDisplayedIndices((prev) => prev.includes(currentIndex) ? prev : [...prev, currentIndex]);
|
|
103
|
+
// Use requestAnimationFrame to ensure DOM is updated before triggering animation
|
|
104
|
+
let rafId1 = null;
|
|
105
|
+
let rafId2 = null;
|
|
106
|
+
rafId1 = requestAnimationFrame(() => {
|
|
107
|
+
rafId2 = requestAnimationFrame(() => {
|
|
108
|
+
setActiveIndex(currentIndex);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
// Clean up old images after transition completes
|
|
112
|
+
const cleanupTimer = setTimeout(() => {
|
|
113
|
+
setDisplayedIndices([currentIndex]);
|
|
114
|
+
}, MOTION_DURATION.fast + 100);
|
|
115
|
+
return () => {
|
|
116
|
+
clearTimeout(cleanupTimer);
|
|
117
|
+
if (rafId1 !== null) {
|
|
118
|
+
cancelAnimationFrame(rafId1);
|
|
119
|
+
}
|
|
120
|
+
if (rafId2 !== null) {
|
|
121
|
+
cancelAnimationFrame(rafId2);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}, [currentIndex, activeIndex]);
|
|
127
|
+
// Memoize media elements to prevent unnecessary re-renders
|
|
128
|
+
const mediaElements = useMemo(() => {
|
|
129
|
+
return mediaItems.map((media, index) => {
|
|
130
|
+
if (typeof media === 'string') {
|
|
131
|
+
return {
|
|
132
|
+
index,
|
|
133
|
+
element: (jsx("img", { alt: `Media ${index + 1}`, className: modalClasses.mediaPreviewImage, src: media })),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
index,
|
|
138
|
+
element: jsx("div", { className: modalClasses.mediaPreviewImage, children: media }),
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
}, [mediaItems]);
|
|
142
|
+
const renderMedia = (index) => {
|
|
143
|
+
const mediaElement = mediaElements.find((item) => item.index === index);
|
|
144
|
+
if (!mediaElement)
|
|
145
|
+
return null;
|
|
146
|
+
const isCurrent = index === activeIndex;
|
|
147
|
+
return (jsx(Fade, { duration: {
|
|
148
|
+
enter: MOTION_DURATION.fast,
|
|
149
|
+
exit: MOTION_DURATION.fast,
|
|
150
|
+
}, easing: {
|
|
151
|
+
enter: MOTION_EASING.entrance,
|
|
152
|
+
exit: MOTION_EASING.exit,
|
|
153
|
+
}, in: isCurrent, children: mediaElement.element }, index));
|
|
154
|
+
};
|
|
155
|
+
return (jsxs(ModalContainer, { className: modalClasses.overlay, container: container, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: [jsx("div", { ...rest, className: cx(modalClasses.host, modalClasses.mediaPreview, className), role: "dialog", children: jsx("div", { className: modalClasses.mediaPreviewContent, children: jsx("div", { className: modalClasses.mediaPreviewMediaContainer, children: displayedIndices.map((index) => renderMedia(index)) }) }) }), jsx(ClearActions, { className: modalClasses.mediaPreviewCloseButton, onClick: onClose, type: "embedded", variant: "contrast" }), mediaItems.length > 1 && (jsx("button", { "aria-disabled": isPrevDisabled, "aria-label": "Previous media", className: cx(modalClasses.mediaPreviewNavButton, modalClasses.mediaPreviewNavButtonPrev), disabled: isPrevDisabled, onClick: handlePrev, title: "Previous", type: "button", children: jsx(Icon, { icon: ChevronLeftIcon, size: 16, color: "fixed-light" }) })), mediaItems.length > 1 && (jsx("button", { "aria-disabled": isNextDisabled, "aria-label": "Next media", className: cx(modalClasses.mediaPreviewNavButton, modalClasses.mediaPreviewNavButtonNext), disabled: isNextDisabled, onClick: handleNext, title: "Next", type: "button", children: jsx(Icon, { icon: ChevronRightIcon, size: 16, color: "fixed-light" }) })), showPaginationIndicator && mediaItems.length > 1 && (jsxs("div", { "aria-label": `Page ${currentIndex + 1} of ${mediaItems.length}`, className: modalClasses.mediaPreviewPaginationIndicator, children: [currentIndex + 1, "/", mediaItems.length] }))] }));
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export { MediaPreviewModal as default };
|