@mezzanine-ui/react 1.0.0-canary.2 → 1.0.0-canary.3
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/AlertBanner/AlertBanner.d.ts +93 -0
- package/AlertBanner/AlertBanner.js +135 -0
- package/AlertBanner/index.d.ts +2 -0
- package/AlertBanner/index.js +1 -0
- package/{Overlay/Overlay.d.ts → Backdrop/Backdrop.d.ts} +12 -16
- package/Backdrop/Backdrop.js +33 -0
- package/Backdrop/index.d.ts +2 -0
- package/Backdrop/index.js +1 -0
- package/Badge/Badge.d.ts +20 -14
- package/Badge/Badge.js +14 -16
- package/Badge/BadgeContainer.d.ts +1 -1
- package/Badge/BadgeContainer.js +1 -1
- package/Badge/index.d.ts +1 -2
- package/Badge/typings.d.ts +42 -0
- package/Button/Button.js +2 -2
- package/Calendar/CalendarContext.js +1 -1
- package/DismissButton/DismissButton.d.ts +34 -0
- package/DismissButton/DismissButton.js +19 -0
- package/DismissButton/index.d.ts +3 -0
- package/DismissButton/index.js +5 -0
- package/Drawer/Drawer.js +2 -2
- package/Dropdown/Dropdown.js +12 -22
- package/InlineMessage/InlineMessage.d.ts +33 -0
- package/InlineMessage/InlineMessage.js +37 -0
- package/InlineMessage/InlineMessageGroup.d.ts +24 -0
- package/InlineMessage/InlineMessageGroup.js +21 -0
- package/InlineMessage/index.d.ts +4 -0
- package/InlineMessage/index.js +2 -0
- package/Message/Message.d.ts +4 -2
- package/Message/Message.js +85 -8
- package/Message/MessageTimerController.d.ts +14 -0
- package/Message/MessageTimerController.js +27 -0
- package/Modal/Modal.js +2 -2
- package/Modal/useModalContainer.d.ts +1 -1
- package/Modal/useModalContainer.js +2 -4
- package/Navigation/NavigationSubMenu.js +15 -26
- package/Notifier/NotifierManager.d.ts +15 -2
- package/Notifier/NotifierManager.js +60 -18
- package/Notifier/createNotifier.d.ts +9 -0
- package/Notifier/createNotifier.js +3 -2
- package/Notifier/typings.d.ts +3 -1
- package/PageFooter/PageFooter.d.ts +58 -8
- package/PageFooter/PageFooter.js +34 -3
- package/Popover/Popover.js +4 -8
- package/Popper/Popper.d.ts +15 -10
- package/Popper/Popper.js +88 -17
- package/Popper/index.d.ts +1 -1
- package/Portal/Portal.d.ts +13 -5
- package/Portal/Portal.js +25 -10
- package/Portal/index.d.ts +2 -0
- package/Portal/index.js +1 -0
- package/Portal/portalRegistry.d.ts +17 -0
- package/Portal/portalRegistry.js +99 -0
- package/Provider/ConfigProvider.d.ts +1 -0
- package/Provider/ConfigProvider.js +1 -0
- package/ResultState/ResultState.d.ts +36 -0
- package/ResultState/ResultState.js +24 -0
- package/ResultState/index.d.ts +2 -0
- package/ResultState/index.js +1 -0
- package/Skeleton/Skeleton.d.ts +10 -3
- package/Skeleton/Skeleton.js +12 -4
- package/Spin/Spin.d.ts +39 -0
- package/{Loading/Loading.js → Spin/Spin.js} +8 -8
- package/Spin/index.d.ts +2 -0
- package/Spin/index.js +1 -0
- package/Stepper/Step.d.ts +1 -35
- package/Stepper/Step.js +43 -10
- package/Stepper/Stepper.d.ts +1 -14
- package/Stepper/Stepper.js +49 -15
- package/Stepper/index.d.ts +3 -2
- package/Stepper/index.js +1 -0
- package/Stepper/typings.d.ts +77 -0
- package/Stepper/useStepDistance.d.ts +10 -0
- package/Stepper/useStepDistance.js +81 -0
- package/Stepper/useStepper.d.ts +21 -0
- package/Stepper/useStepper.js +26 -0
- package/Table/Table.js +2 -2
- package/Table/TableBody.js +2 -2
- package/Tag/Tag.d.ts +2 -25
- package/Tag/Tag.js +14 -13
- package/Tag/index.d.ts +1 -1
- package/Tag/typings.d.ts +79 -0
- package/Toggle/Toggle.d.ts +46 -0
- package/Toggle/Toggle.js +29 -0
- package/Toggle/index.d.ts +3 -0
- package/Toggle/index.js +1 -0
- package/Tooltip/Tooltip.d.ts +13 -2
- package/Tooltip/Tooltip.js +48 -10
- package/Tooltip/useDelayMouseEnterLeave.d.ts +1 -2
- package/Tooltip/useDelayMouseEnterLeave.js +0 -4
- package/Tree/TreeNode.js +5 -5
- package/_internal/InputTriggerPopper/InputTriggerPopper.js +14 -24
- package/_internal/SlideFadeOverlay/SlideFadeOverlay.d.ts +2 -2
- package/_internal/SlideFadeOverlay/SlideFadeOverlay.js +4 -27
- package/hooks/useScrollLock.d.ts +28 -0
- package/hooks/useScrollLock.js +134 -0
- package/index.d.ts +87 -80
- package/index.js +74 -69
- package/package.json +7 -7
- package/utils/get-css-variable-value.d.ts +1 -0
- package/utils/get-css-variable-value.js +9 -0
- package/Loading/Loading.d.ts +0 -33
- package/Loading/index.d.ts +0 -2
- package/Loading/index.js +0 -1
- package/Overlay/Overlay.js +0 -31
- package/Overlay/index.d.ts +0 -2
- package/Overlay/index.js +0 -1
- package/Switch/Switch.d.ts +0 -43
- package/Switch/Switch.js +0 -29
- package/Switch/index.d.ts +0 -3
- package/Switch/index.js +0 -1
- package/utils/scroll-lock.d.ts +0 -2
- package/utils/scroll-lock.js +0 -24
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { InlineMessageSeverity } from '@mezzanine-ui/core/inline-message';
|
|
2
|
+
import { IconDefinition } from '@mezzanine-ui/icons';
|
|
3
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
4
|
+
export interface InlineMessageProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
5
|
+
/**
|
|
6
|
+
* The content of the inline message value(only string is supported).
|
|
7
|
+
*/
|
|
8
|
+
content: string;
|
|
9
|
+
/**
|
|
10
|
+
* The class name of the inline message element.
|
|
11
|
+
*/
|
|
12
|
+
className?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The icon of the inline message.
|
|
15
|
+
*/
|
|
16
|
+
icon?: IconDefinition;
|
|
17
|
+
/**
|
|
18
|
+
* Called when user clicks the close button.
|
|
19
|
+
*/
|
|
20
|
+
onClose?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* The inline message severity (`'info' | 'warning' | 'error'`).
|
|
23
|
+
*/
|
|
24
|
+
severity: InlineMessageSeverity;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The react component for `mezzanine` inline message.
|
|
28
|
+
*
|
|
29
|
+
* Use `InlineMessage` directly in your layout to display contextual feedback,
|
|
30
|
+
* and provide `onClose` callback when you need to react to dismiss events.
|
|
31
|
+
*/
|
|
32
|
+
declare const InlineMessage: import("react").ForwardRefExoticComponent<InlineMessageProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
33
|
+
export default InlineMessage;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useState, useCallback, useMemo } from 'react';
|
|
4
|
+
import { inlineMessageClasses, inlineMessageIcons } from '@mezzanine-ui/core/inline-message';
|
|
5
|
+
import DismissButton from '../DismissButton/DismissButton.js';
|
|
6
|
+
import Icon from '../Icon/Icon.js';
|
|
7
|
+
import cx from 'clsx';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The react component for `mezzanine` inline message.
|
|
11
|
+
*
|
|
12
|
+
* Use `InlineMessage` directly in your layout to display contextual feedback,
|
|
13
|
+
* and provide `onClose` callback when you need to react to dismiss events.
|
|
14
|
+
*/
|
|
15
|
+
const InlineMessage = forwardRef(function InlineMessage(props, ref) {
|
|
16
|
+
const { content, className, icon, onClose, severity } = props;
|
|
17
|
+
const [visible, setVisible] = useState(true);
|
|
18
|
+
const handleClose = useCallback(() => {
|
|
19
|
+
setVisible(false);
|
|
20
|
+
if (onClose) {
|
|
21
|
+
onClose();
|
|
22
|
+
}
|
|
23
|
+
}, [onClose]);
|
|
24
|
+
const iconNode = useMemo(() => {
|
|
25
|
+
if (icon) {
|
|
26
|
+
return jsx(Icon, { className: inlineMessageClasses.icon, icon: icon });
|
|
27
|
+
}
|
|
28
|
+
const severityIcon = inlineMessageIcons[severity];
|
|
29
|
+
return jsx(Icon, { className: inlineMessageClasses.icon, icon: severityIcon });
|
|
30
|
+
}, [icon, severity]);
|
|
31
|
+
if (!visible) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return (jsxs("div", { ref: ref, "aria-live": "polite", className: cx(inlineMessageClasses.host, inlineMessageClasses.severity(severity), className), role: "status", children: [jsxs("div", { className: inlineMessageClasses.contentContainer, children: [iconNode, jsx("span", { className: inlineMessageClasses.content, children: content })] }), severity === 'info' ? (jsx(DismissButton, { onClick: handleClose, type: "standard", variant: "base" })) : null] }));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export { InlineMessage as default };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Key, ReactNode } from 'react';
|
|
2
|
+
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
3
|
+
import { InlineMessageProps } from './InlineMessage';
|
|
4
|
+
export interface InlineMessageGroupItem extends Omit<InlineMessageProps, 'content'> {
|
|
5
|
+
key: Key;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InlineMessageGroupProps extends Omit<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'> {
|
|
9
|
+
/**
|
|
10
|
+
* The inline messages within the group.
|
|
11
|
+
* Ignored when `children` provided.
|
|
12
|
+
*/
|
|
13
|
+
items?: InlineMessageGroupItem[];
|
|
14
|
+
/**
|
|
15
|
+
* Custom inline message nodes.
|
|
16
|
+
*/
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* Callback invoked when any item triggers `onClose`.
|
|
20
|
+
*/
|
|
21
|
+
onItemClose?: (key: Key) => void;
|
|
22
|
+
}
|
|
23
|
+
declare const InlineMessageGroup: import("react").ForwardRefExoticComponent<InlineMessageGroupProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
24
|
+
export default InlineMessageGroup;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { inlineMessageGroupClasses } from '@mezzanine-ui/core/inline-message';
|
|
3
|
+
import { forwardRef, useMemo } from 'react';
|
|
4
|
+
import InlineMessage from './InlineMessage.js';
|
|
5
|
+
import cx from 'clsx';
|
|
6
|
+
|
|
7
|
+
const mapItemsToMessages = (items, onItemClose) => items === null || items === void 0 ? void 0 : items.map(({ key, content, onClose, ...restItemProps }) => (jsx(InlineMessage, { ...restItemProps, content: content, onClose: () => {
|
|
8
|
+
if (onClose) {
|
|
9
|
+
onClose();
|
|
10
|
+
}
|
|
11
|
+
if (onItemClose) {
|
|
12
|
+
onItemClose(key);
|
|
13
|
+
}
|
|
14
|
+
} }, key)));
|
|
15
|
+
const InlineMessageGroup = forwardRef(function InlineMessageGroup(props, ref) {
|
|
16
|
+
const { children: childrenProp, className, items, onItemClose, ...rest } = props;
|
|
17
|
+
const children = useMemo(() => childrenProp !== null && childrenProp !== void 0 ? childrenProp : mapItemsToMessages(items, onItemClose), [childrenProp, items, onItemClose]);
|
|
18
|
+
return (jsx("div", { ...rest, ref: ref, className: cx(inlineMessageGroupClasses.host, className), "aria-live": "polite", role: "region", children: children }));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export { InlineMessageGroup as default };
|
package/Message/Message.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MessageSeverity } from '@mezzanine-ui/core/message';
|
|
2
2
|
import { IconDefinition } from '@mezzanine-ui/icons';
|
|
3
|
-
import { FC, Key } from 'react';
|
|
3
|
+
import type { FC, Key } from 'react';
|
|
4
4
|
import { Notifier, NotifierData, NotifierConfig } from '../Notifier';
|
|
5
5
|
import { SlideFadeProps } from '../Transition';
|
|
6
6
|
export interface MessageConfigProps extends Pick<NotifierConfig, 'duration'>, Pick<SlideFadeProps, 'onEnter' | 'onEntering' | 'onEntered' | 'onExit' | 'onExiting' | 'onExited' | 'easing' | 'direction'> {
|
|
@@ -25,7 +25,9 @@ export interface MessageData extends Omit<NotifierData, 'onClose'>, MessageConfi
|
|
|
25
25
|
*/
|
|
26
26
|
severity?: MessageSeverity;
|
|
27
27
|
}
|
|
28
|
-
export type MessageType = FC<MessageData> & Notifier<MessageData, MessageConfigProps> & Record<string, (message: MessageData['children'], props?: Omit<MessageData, 'children' | 'severity' | 'icon'>
|
|
28
|
+
export type MessageType = FC<MessageData> & Notifier<MessageData, MessageConfigProps> & Record<string, (message: MessageData['children'], props?: Omit<MessageData, 'children' | 'severity' | 'icon'> & {
|
|
29
|
+
key?: Key;
|
|
30
|
+
}) => Key>;
|
|
29
31
|
/**
|
|
30
32
|
* The react component for `mezzanine` message.
|
|
31
33
|
*
|
package/Message/Message.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useRef, useCallback, useEffect, createElement } from 'react';
|
|
2
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
4
|
import { messageClasses, messageIcons } from '@mezzanine-ui/core/message';
|
|
5
|
+
import { messageTimerController } from './MessageTimerController.js';
|
|
4
6
|
import SlideFade from '../Transition/SlideFade.js';
|
|
5
7
|
import Icon from '../Icon/Icon.js';
|
|
6
8
|
import { createNotifier } from '../Notifier/createNotifier.js';
|
|
@@ -15,16 +17,72 @@ import cx from 'clsx';
|
|
|
15
17
|
const Message = ((props) => {
|
|
16
18
|
const { children, duration, icon, reference, severity, onExited: onExitedProp, ...restTransitionProps } = props;
|
|
17
19
|
const [open, setOpen] = useState(true);
|
|
20
|
+
const timerRef = useRef(null);
|
|
21
|
+
const remainingTimeRef = useRef(duration || 0);
|
|
22
|
+
const startTimeRef = useRef(Date.now());
|
|
23
|
+
// 清理計時器
|
|
24
|
+
const clearTimer = useCallback(() => {
|
|
25
|
+
if (timerRef.current) {
|
|
26
|
+
window.clearTimeout(timerRef.current);
|
|
27
|
+
timerRef.current = null;
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
// 開始計時器
|
|
31
|
+
const startTimer = useCallback((time) => {
|
|
32
|
+
clearTimer();
|
|
33
|
+
if (time > 0) {
|
|
34
|
+
startTimeRef.current = Date.now();
|
|
35
|
+
remainingTimeRef.current = time;
|
|
36
|
+
timerRef.current = window.setTimeout(() => {
|
|
37
|
+
setOpen(false);
|
|
38
|
+
}, time);
|
|
39
|
+
}
|
|
40
|
+
}, [clearTimer]);
|
|
41
|
+
// 暫停計時器
|
|
42
|
+
const pauseTimer = useCallback(() => {
|
|
43
|
+
if (timerRef.current) {
|
|
44
|
+
clearTimer();
|
|
45
|
+
const elapsed = Date.now() - startTimeRef.current;
|
|
46
|
+
remainingTimeRef.current = Math.max(0, remainingTimeRef.current - elapsed);
|
|
47
|
+
}
|
|
48
|
+
}, [clearTimer]);
|
|
49
|
+
// 恢復計時器
|
|
50
|
+
const resumeTimer = useCallback(() => {
|
|
51
|
+
if (remainingTimeRef.current > 0) {
|
|
52
|
+
startTimer(remainingTimeRef.current);
|
|
53
|
+
}
|
|
54
|
+
}, [startTimer]);
|
|
55
|
+
// 初始設定計時器
|
|
18
56
|
useEffect(() => {
|
|
19
57
|
if (open && duration) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
58
|
+
startTimer(duration);
|
|
59
|
+
}
|
|
60
|
+
else if (open && duration === false) {
|
|
61
|
+
// duration 為 false 時,清除計時器(不自動關閉)
|
|
62
|
+
clearTimer();
|
|
63
|
+
}
|
|
64
|
+
return () => {
|
|
65
|
+
clearTimer();
|
|
66
|
+
};
|
|
67
|
+
}, [open, duration, clearTimer, startTimer]);
|
|
68
|
+
// 註冊到全域控制器
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (reference && duration) {
|
|
71
|
+
messageTimerController.register(reference, {
|
|
72
|
+
pause: pauseTimer,
|
|
73
|
+
resume: resumeTimer,
|
|
74
|
+
});
|
|
23
75
|
return () => {
|
|
24
|
-
|
|
76
|
+
messageTimerController.unregister(reference);
|
|
25
77
|
};
|
|
26
78
|
}
|
|
27
|
-
}, [
|
|
79
|
+
}, [reference, duration, pauseTimer, resumeTimer]);
|
|
80
|
+
// 清理計時器(組件卸載時)
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
return () => {
|
|
83
|
+
clearTimer();
|
|
84
|
+
};
|
|
85
|
+
}, [clearTimer]);
|
|
28
86
|
const onExited = (node) => {
|
|
29
87
|
if (onExitedProp) {
|
|
30
88
|
onExitedProp(node);
|
|
@@ -32,11 +90,24 @@ const Message = ((props) => {
|
|
|
32
90
|
if (reference)
|
|
33
91
|
Message.remove(reference);
|
|
34
92
|
};
|
|
35
|
-
|
|
93
|
+
const handleMouseEnter = () => {
|
|
94
|
+
messageTimerController.pause();
|
|
95
|
+
};
|
|
96
|
+
const handleMouseLeave = () => {
|
|
97
|
+
messageTimerController.resume();
|
|
98
|
+
};
|
|
99
|
+
const handleFocus = () => {
|
|
100
|
+
messageTimerController.pause();
|
|
101
|
+
};
|
|
102
|
+
const handleBlur = () => {
|
|
103
|
+
messageTimerController.resume();
|
|
104
|
+
};
|
|
105
|
+
return (jsx(SlideFade, { in: open, appear: true, onExited: onExited, direction: "up", ...restTransitionProps, children: jsxs("div", { className: cx(messageClasses.host, severity ? messageClasses.severity(severity) : ''), onBlur: handleBlur, onFocus: handleFocus, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, role: "status", children: [icon ? (jsx(Icon, { className: messageClasses.icon, icon: icon, spin: severity === 'loading' })) : null, jsx("span", { className: messageClasses.content, children: children })] }) }));
|
|
36
106
|
});
|
|
37
107
|
const { add, config, destroy, remove } = createNotifier({
|
|
38
108
|
duration: 3000,
|
|
39
|
-
|
|
109
|
+
maxCount: 4,
|
|
110
|
+
render: (message) => (createElement(Message, { ...message, reference: message.key, key: undefined })),
|
|
40
111
|
setRoot: (root) => {
|
|
41
112
|
root === null || root === void 0 ? void 0 : root.setAttribute('class', messageClasses.root);
|
|
42
113
|
},
|
|
@@ -62,12 +133,17 @@ const severities = [
|
|
|
62
133
|
key: 'info',
|
|
63
134
|
icon: messageIcons.info,
|
|
64
135
|
},
|
|
136
|
+
{
|
|
137
|
+
key: 'loading',
|
|
138
|
+
icon: messageIcons.loading,
|
|
139
|
+
},
|
|
65
140
|
];
|
|
66
141
|
const validSeverities = [
|
|
67
142
|
'success',
|
|
68
143
|
'warning',
|
|
69
144
|
'error',
|
|
70
145
|
'info',
|
|
146
|
+
'loading',
|
|
71
147
|
];
|
|
72
148
|
severities.forEach((severity) => {
|
|
73
149
|
Message[severity.key] = (message, props) => Message.add({
|
|
@@ -77,6 +153,7 @@ severities.forEach((severity) => {
|
|
|
77
153
|
? severity.key
|
|
78
154
|
: undefined,
|
|
79
155
|
icon: severity.icon,
|
|
156
|
+
duration: severity.key === 'loading' ? false : props === null || props === void 0 ? void 0 : props.duration,
|
|
80
157
|
});
|
|
81
158
|
});
|
|
82
159
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Key } from 'react';
|
|
2
|
+
declare class MessageTimerController {
|
|
3
|
+
private isPaused;
|
|
4
|
+
private timers;
|
|
5
|
+
pause(): void;
|
|
6
|
+
register(key: Key, callbacks: {
|
|
7
|
+
pause: () => void;
|
|
8
|
+
resume: () => void;
|
|
9
|
+
}): void;
|
|
10
|
+
resume(): void;
|
|
11
|
+
unregister(key: Key): void;
|
|
12
|
+
}
|
|
13
|
+
export declare const messageTimerController: MessageTimerController;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class MessageTimerController {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.isPaused = false;
|
|
4
|
+
this.timers = new Map();
|
|
5
|
+
}
|
|
6
|
+
pause() {
|
|
7
|
+
if (this.isPaused)
|
|
8
|
+
return;
|
|
9
|
+
this.isPaused = true;
|
|
10
|
+
this.timers.forEach((timer) => timer.pause());
|
|
11
|
+
}
|
|
12
|
+
register(key, callbacks) {
|
|
13
|
+
this.timers.set(key, callbacks);
|
|
14
|
+
}
|
|
15
|
+
resume() {
|
|
16
|
+
if (!this.isPaused)
|
|
17
|
+
return;
|
|
18
|
+
this.isPaused = false;
|
|
19
|
+
this.timers.forEach((timer) => timer.resume());
|
|
20
|
+
}
|
|
21
|
+
unregister(key) {
|
|
22
|
+
this.timers.delete(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const messageTimerController = new MessageTimerController();
|
|
26
|
+
|
|
27
|
+
export { messageTimerController };
|
package/Modal/Modal.js
CHANGED
|
@@ -11,13 +11,13 @@ import cx from 'clsx';
|
|
|
11
11
|
* The react component for `mezzanine` modal.
|
|
12
12
|
*/
|
|
13
13
|
const Modal = forwardRef(function Modal(props, ref) {
|
|
14
|
-
const { children, className, container, direction = 'down', disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, fullScreen = false,
|
|
14
|
+
const { children, className, container, direction = 'down', disableCloseOnBackdropClick = false, disableCloseOnEscapeKeyDown = false, disablePortal = false, fullScreen = false, hideCloseIcon = false, loading = false, onBackdropClick, onClose, open, severity = 'info', size = 'medium', ...rest } = props;
|
|
15
15
|
const modalControl = useMemo(() => ({
|
|
16
16
|
loading,
|
|
17
17
|
severity,
|
|
18
18
|
}), [loading, severity]);
|
|
19
19
|
const { Container: ModalContainer } = useModalContainer();
|
|
20
|
-
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className: modalClasses.overlay, container: container, direction: direction, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal,
|
|
20
|
+
return (jsx(ModalControlContext.Provider, { value: modalControl, children: jsx(ModalContainer, { className: modalClasses.overlay, container: container, direction: direction, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: jsxs("div", { ...rest, className: cx(modalClasses.host, modalClasses.severity(severity), modalClasses.size(size), {
|
|
21
21
|
[modalClasses.fullScreen]: fullScreen,
|
|
22
22
|
[modalClasses.withCloseIcon]: !hideCloseIcon,
|
|
23
23
|
}, className), role: "dialog", children: [children, !hideCloseIcon && (jsx(Icon, { className: modalClasses.closeIcon, icon: TimesIcon, onClick: onClose }))] }) }) }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SlideFadeOverlayProps } from '../_internal/SlideFadeOverlay';
|
|
2
2
|
export default function useModalContainer(): {
|
|
3
3
|
Container: import("react").ForwardRefExoticComponent<SlideFadeOverlayProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
4
|
-
defaultOptions: Pick<SlideFadeOverlayProps, "className" | "open" | "disablePortal" | "direction" | "disableCloseOnBackdropClick" | "
|
|
4
|
+
defaultOptions: Pick<SlideFadeOverlayProps, "className" | "open" | "disablePortal" | "direction" | "disableCloseOnBackdropClick" | "disableCloseOnEscapeKeyDown">;
|
|
5
5
|
};
|
|
@@ -9,13 +9,11 @@ const defaultOptions = {
|
|
|
9
9
|
disableCloseOnBackdropClick: false,
|
|
10
10
|
disableCloseOnEscapeKeyDown: false,
|
|
11
11
|
disablePortal: false,
|
|
12
|
-
hideBackdrop: false,
|
|
13
|
-
invisibleBackdrop: false,
|
|
14
12
|
open: false,
|
|
15
13
|
};
|
|
16
14
|
const ModalContainer = forwardRef((props, ref) => {
|
|
17
|
-
const { className = defaultOptions.className, children, container, direction = defaultOptions.direction, disableCloseOnBackdropClick = defaultOptions.disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown = defaultOptions.disableCloseOnEscapeKeyDown, disablePortal = defaultOptions.disablePortal,
|
|
18
|
-
return (jsx(SlideFadeOverlay, { className: className, container: container, direction: direction, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal,
|
|
15
|
+
const { className = defaultOptions.className, children, container, direction = defaultOptions.direction, disableCloseOnBackdropClick = defaultOptions.disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown = defaultOptions.disableCloseOnEscapeKeyDown, disablePortal = defaultOptions.disablePortal, onBackdropClick, onClose, open = defaultOptions.open, } = props;
|
|
16
|
+
return (jsx(SlideFadeOverlay, { className: className, container: container, direction: direction, disableCloseOnBackdropClick: disableCloseOnBackdropClick, disableCloseOnEscapeKeyDown: disableCloseOnEscapeKeyDown, disablePortal: disablePortal, onBackdropClick: onBackdropClick, onClose: onClose, open: open, ref: ref, children: children }));
|
|
19
17
|
});
|
|
20
18
|
function useModalContainer() {
|
|
21
19
|
return {
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
3
3
|
import { forwardRef, useState, useRef, useContext } from 'react';
|
|
4
4
|
import { navigationSubMenuClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import { ChevronUpIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
6
|
+
import { offset, size } from '@floating-ui/react-dom';
|
|
6
7
|
import { useClickAway } from '../hooks/useClickAway.js';
|
|
7
8
|
import { useComposeRefs } from '../hooks/useComposeRefs.js';
|
|
8
9
|
import NavigationItem from './NavigationItem.js';
|
|
@@ -12,31 +13,14 @@ import Popper from '../Popper/Popper.js';
|
|
|
12
13
|
import Collapse from '../Transition/Collapse.js';
|
|
13
14
|
import cx from 'clsx';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
name: 'sameWidth',
|
|
26
|
-
enabled: true,
|
|
27
|
-
phase: 'beforeWrite',
|
|
28
|
-
requires: ['computeStyles'],
|
|
29
|
-
fn: ({ state }) => {
|
|
30
|
-
const reassignState = state;
|
|
31
|
-
reassignState.styles.popper.width = `${state.rects.reference.width}px`;
|
|
32
|
-
},
|
|
33
|
-
effect: ({ state }) => {
|
|
34
|
-
const reassignState = state;
|
|
35
|
-
reassignState.elements.popper.style.width = `${state.elements.reference.getBoundingClientRect().width}px`;
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
};
|
|
16
|
+
// Middleware to make the submenu have the same width as the reference element
|
|
17
|
+
const sameWidthMiddleware = size({
|
|
18
|
+
apply({ rects, elements }) {
|
|
19
|
+
Object.assign(elements.floating.style, {
|
|
20
|
+
width: `${rects.reference.width}px`,
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
40
24
|
const NavigationSubMenu = forwardRef((props, ref) => {
|
|
41
25
|
const { active, className, children = [], defaultOpen = false, icon, title, ...rest } = props;
|
|
42
26
|
const [open, setOpen] = useState(defaultOpen);
|
|
@@ -53,7 +37,12 @@ const NavigationSubMenu = forwardRef((props, ref) => {
|
|
|
53
37
|
};
|
|
54
38
|
}, nodeRef, [open, orientation]);
|
|
55
39
|
const WrapChildren = jsx("ul", { className: navigationSubMenuClasses.group, children: children });
|
|
56
|
-
return (jsxs(NavigationItem, { ...rest, ref: composedNodeRef, className: cx(navigationSubMenuClasses.host, active && navigationSubMenuClasses.active, open && navigationSubMenuClasses.open, icon && orientation === 'vertical' && navigationSubMenuClasses.indent, className), onClick: () => setOpen(!open), children: [jsxs("div", { className: navigationSubMenuClasses.title, children: [icon && jsx(Icon, { className: navigationSubMenuClasses.icon, icon: icon }), title, jsx(Icon, { className: navigationSubMenuClasses.toggleIcon, icon: GroupToggleIcon })] }), orientation === 'horizontal' && (jsx(Popper, { anchor: nodeRef, disablePortal: true, open: !!open, options:
|
|
40
|
+
return (jsxs(NavigationItem, { ...rest, ref: composedNodeRef, className: cx(navigationSubMenuClasses.host, active && navigationSubMenuClasses.active, open && navigationSubMenuClasses.open, icon && orientation === 'vertical' && navigationSubMenuClasses.indent, className), onClick: () => setOpen(!open), children: [jsxs("div", { className: navigationSubMenuClasses.title, children: [icon && jsx(Icon, { className: navigationSubMenuClasses.icon, icon: icon }), title, jsx(Icon, { className: navigationSubMenuClasses.toggleIcon, icon: GroupToggleIcon })] }), orientation === 'horizontal' && (jsx(Popper, { anchor: nodeRef, disablePortal: true, open: !!open, options: {
|
|
41
|
+
middleware: [
|
|
42
|
+
offset({ mainAxis: 0, crossAxis: 0 }),
|
|
43
|
+
sameWidthMiddleware,
|
|
44
|
+
],
|
|
45
|
+
}, children: WrapChildren })), orientation === 'vertical' && (jsx(Collapse, { style: {
|
|
57
46
|
width: '100%',
|
|
58
47
|
}, in: !!open, children: WrapChildren }))] }));
|
|
59
48
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
1
2
|
import { Key, RefObject } from 'react';
|
|
2
|
-
import {
|
|
3
|
+
import { NotifierConfig, NotifierData, RenderNotifier } from './typings';
|
|
3
4
|
export interface NotifierController<N extends NotifierData> {
|
|
4
5
|
add: (notifier: N & {
|
|
5
6
|
key: Key;
|
|
@@ -12,6 +13,18 @@ export interface NotifierManagerProps<N extends NotifierData> extends Pick<Notif
|
|
|
12
13
|
key: Key;
|
|
13
14
|
})[];
|
|
14
15
|
render: RenderNotifier<N>;
|
|
16
|
+
/**
|
|
17
|
+
* Custom wrapper for rendered notifiers (e.g. AlertBanner group container).
|
|
18
|
+
*/
|
|
19
|
+
renderContainer?: (children: ReactNode) => ReactNode;
|
|
20
|
+
/**
|
|
21
|
+
* Sorting hook to enforce display/queue ordering before updates.
|
|
22
|
+
*/
|
|
23
|
+
sortBeforeUpdate?: (notifiers: (N & {
|
|
24
|
+
key: Key;
|
|
25
|
+
})[]) => (N & {
|
|
26
|
+
key: Key;
|
|
27
|
+
})[];
|
|
15
28
|
}
|
|
16
|
-
declare function NotifierManager<N extends NotifierData>(props: NotifierManagerProps<N>): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
declare function NotifierManager<N extends NotifierData>(props: NotifierManagerProps<N>): string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
|
|
17
30
|
export default NotifierManager;
|
|
@@ -1,31 +1,73 @@
|
|
|
1
|
-
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { useState, useMemo, useImperativeHandle, Fragment
|
|
1
|
+
import { jsx, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
+
import { useCallback, useState, useEffect, useMemo, useImperativeHandle, Fragment } from 'react';
|
|
3
3
|
|
|
4
4
|
function NotifierManager(props) {
|
|
5
|
-
const { controllerRef, defaultNotifiers = [], maxCount, render } = props;
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const { controllerRef, defaultNotifiers = [], maxCount, render, renderContainer, sortBeforeUpdate, } = props;
|
|
6
|
+
// 只有在呼叫端有提供 sortBeforeUpdate 時才執行排序,否則維持原本順序。
|
|
7
|
+
const sortNotifiers = useCallback((notifiers) => sortBeforeUpdate ? sortBeforeUpdate(notifiers) : notifiers, [sortBeforeUpdate]);
|
|
8
|
+
const [displayedNotifiers, setDisplayedNotifiers] = useState(() => sortNotifiers(defaultNotifiers));
|
|
9
|
+
const [queuedNotifiers, setQueuedNotifiers] = useState([]);
|
|
10
|
+
// 當有空位時,從 queue 中補上
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (queuedNotifiers.length > 0) {
|
|
13
|
+
const hasMaxCount = typeof maxCount === 'number';
|
|
14
|
+
const availableSlots = hasMaxCount
|
|
15
|
+
? maxCount - displayedNotifiers.length
|
|
16
|
+
: Infinity;
|
|
17
|
+
if (availableSlots > 0) {
|
|
18
|
+
const notifiersToDisplay = queuedNotifiers.slice(0, availableSlots);
|
|
19
|
+
setDisplayedNotifiers((prev) => sortNotifiers([...prev, ...notifiersToDisplay]));
|
|
20
|
+
setQueuedNotifiers((prev) => prev.slice(availableSlots));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, [displayedNotifiers.length, queuedNotifiers, maxCount, sortNotifiers]);
|
|
10
24
|
const controller = useMemo(() => ({
|
|
11
25
|
add(notifier) {
|
|
12
|
-
|
|
26
|
+
setDisplayedNotifiers((prev) => {
|
|
13
27
|
const notifierIndex = prev.findIndex(({ key }) => key === notifier.key);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
// 如果已存在,則更新該訊息
|
|
29
|
+
if (~notifierIndex) {
|
|
30
|
+
const next = [...prev];
|
|
31
|
+
next[notifierIndex] = notifier;
|
|
32
|
+
return sortNotifiers(next);
|
|
33
|
+
}
|
|
34
|
+
// 新訊息:檢查是否超過 maxCount
|
|
35
|
+
const hasMaxCount = typeof maxCount === 'number';
|
|
36
|
+
if (hasMaxCount && prev.length >= maxCount) {
|
|
37
|
+
if (sortBeforeUpdate) {
|
|
38
|
+
// 需要排序時,先把舊有與新增併在一起排序,
|
|
39
|
+
// 取前 maxCount 個顯示,其餘補進 queue。
|
|
40
|
+
const allNotifiers = sortNotifiers([...prev, notifier]);
|
|
41
|
+
const toDisplay = allNotifiers.slice(0, maxCount);
|
|
42
|
+
const toQueue = allNotifiers.slice(maxCount);
|
|
43
|
+
if (toQueue.length > 0) {
|
|
44
|
+
setQueuedNotifiers((queue) => [...queue, ...toQueue]);
|
|
45
|
+
}
|
|
46
|
+
return toDisplay;
|
|
47
|
+
}
|
|
48
|
+
// 超過上限,加入 queue
|
|
49
|
+
setQueuedNotifiers((queue) => [...queue, notifier]);
|
|
50
|
+
return prev;
|
|
51
|
+
}
|
|
52
|
+
// 未超過上限,直接加到最後
|
|
53
|
+
// 直接加入並依需求排序。
|
|
54
|
+
return sortNotifiers([...prev, notifier]);
|
|
21
55
|
});
|
|
22
56
|
},
|
|
23
57
|
remove(key) {
|
|
24
|
-
|
|
58
|
+
setDisplayedNotifiers((prev) => prev.filter((m) => m.key !== key));
|
|
59
|
+
setQueuedNotifiers((prev) => prev.filter((m) => m.key !== key));
|
|
25
60
|
},
|
|
26
|
-
}), []);
|
|
61
|
+
}), [maxCount, sortBeforeUpdate, sortNotifiers]);
|
|
27
62
|
useImperativeHandle(controllerRef, () => controller, [controller]);
|
|
28
|
-
|
|
63
|
+
const renderedNotifiers = displayedNotifiers.map((notifier) => (jsx(Fragment, { children: render(notifier) }, notifier.key)));
|
|
64
|
+
if (renderContainer) {
|
|
65
|
+
if (!renderedNotifiers.length) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return renderContainer(renderedNotifiers);
|
|
69
|
+
}
|
|
70
|
+
return (jsx(Fragment$1, { children: renderedNotifiers }));
|
|
29
71
|
}
|
|
30
72
|
|
|
31
73
|
export { NotifierManager as default };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type NotifierManagerProps } from './NotifierManager';
|
|
1
2
|
import { Notifier, NotifierConfig, NotifierData, RenderNotifier } from './typings';
|
|
2
3
|
export interface CreateNotifierProps<N extends NotifierData, C extends NotifierConfig> extends NotifierConfig {
|
|
3
4
|
/**
|
|
@@ -12,6 +13,14 @@ export interface CreateNotifierProps<N extends NotifierData, C extends NotifierC
|
|
|
12
13
|
* The method to set attributes or listeners to root.
|
|
13
14
|
*/
|
|
14
15
|
setRoot?: (root: HTMLDivElement) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Custom wrapper for rendered notifiers (e.g. AlertBanner group container).
|
|
18
|
+
*/
|
|
19
|
+
renderContainer?: NotifierManagerProps<N>['renderContainer'];
|
|
20
|
+
/**
|
|
21
|
+
* Sorting hook to enforce display/queue ordering before updates.
|
|
22
|
+
*/
|
|
23
|
+
sortBeforeUpdate?: NotifierManagerProps<N>['sortBeforeUpdate'];
|
|
15
24
|
}
|
|
16
25
|
/**
|
|
17
26
|
* The utility factory for `mezzanine` to create a notifier.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx } from 'react/jsx-runtime';
|
|
2
3
|
import { createRef } from 'react';
|
|
3
4
|
import { createRoot } from 'react-dom/client';
|
|
@@ -9,7 +10,7 @@ import NotifierManager from './NotifierManager.js';
|
|
|
9
10
|
* When APIs are called, Notifier will dynamically render a new react instance by `ReactDOM.render` method.
|
|
10
11
|
*/
|
|
11
12
|
function createNotifier(props) {
|
|
12
|
-
const { config: configProp, render: renderNotifier, setRoot, duration, maxCount, ...restNotifierProps } = props;
|
|
13
|
+
const { config: configProp, render: renderNotifier, setRoot, duration, maxCount, renderContainer, sortBeforeUpdate, ...restNotifierProps } = props;
|
|
13
14
|
const container = typeof document === 'undefined' ? null : document.createElement('div');
|
|
14
15
|
const root = container ? createRoot(container) : null;
|
|
15
16
|
const controllerRef = createRef();
|
|
@@ -40,7 +41,7 @@ function createNotifier(props) {
|
|
|
40
41
|
controllerRef.current.add(resolvedNotifier);
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
|
-
root === null || root === void 0 ? void 0 : root.render(jsx(NotifierManager, { controllerRef: controllerRef, defaultNotifiers: [resolvedNotifier], maxCount: currentConfig.maxCount, render: renderNotifier }));
|
|
44
|
+
root === null || root === void 0 ? void 0 : root.render(jsx(NotifierManager, { controllerRef: controllerRef, defaultNotifiers: [resolvedNotifier], maxCount: currentConfig.maxCount, render: renderNotifier, renderContainer: renderContainer, sortBeforeUpdate: sortBeforeUpdate }));
|
|
44
45
|
}
|
|
45
46
|
return resolvedNotifier.key;
|
|
46
47
|
},
|
package/Notifier/typings.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export interface NotifierData extends Pick<NotifierConfig, 'duration'> {
|
|
|
16
16
|
*/
|
|
17
17
|
onClose?: (key: Key) => void;
|
|
18
18
|
}
|
|
19
|
-
export type RenderNotifier<N extends NotifierData> = (notifier: N
|
|
19
|
+
export type RenderNotifier<N extends NotifierData> = (notifier: N & {
|
|
20
|
+
key: Key;
|
|
21
|
+
}) => ReactNode;
|
|
20
22
|
export interface Notifier<N extends NotifierData, C extends NotifierConfig = NotifierConfig> {
|
|
21
23
|
add: (notifier: N & {
|
|
22
24
|
key?: Key;
|