@mezzanine-ui/react 1.0.0-rc.6 → 1.0.0-rc.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AutoComplete/AutoComplete.d.ts +25 -9
- package/AutoComplete/AutoComplete.js +84 -17
- package/AutoComplete/AutoCompleteInside.d.ts +54 -0
- package/AutoComplete/AutoCompleteInside.js +17 -0
- package/AutoComplete/useAutoCompleteKeyboard.d.ts +2 -1
- package/AutoComplete/useAutoCompleteKeyboard.js +4 -1
- package/Breadcrumb/BreadcrumbDropdown.d.ts +1 -1
- package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +1 -1
- package/Breadcrumb/typings.d.ts +1 -1
- package/COMPONENTS.md +2 -2
- package/Checkbox/Checkbox.js +24 -3
- package/Cropper/Cropper.d.ts +1 -1
- package/Description/Description.d.ts +1 -1
- package/Description/Description.js +1 -1
- package/Description/DescriptionTitle.d.ts +6 -1
- package/Description/DescriptionTitle.js +2 -2
- package/Drawer/Drawer.d.ts +39 -34
- package/Drawer/Drawer.js +33 -35
- package/Dropdown/Dropdown.d.ts +16 -1
- package/Dropdown/Dropdown.js +156 -9
- package/Dropdown/DropdownItem.d.ts +26 -2
- package/Dropdown/DropdownItem.js +91 -43
- package/Dropdown/DropdownItemCard.d.ts +3 -2
- package/Dropdown/DropdownItemCard.js +8 -5
- package/Dropdown/dropdownKeydownHandler.d.ts +6 -0
- package/Dropdown/dropdownKeydownHandler.js +14 -7
- package/FilterArea/Filter.d.ts +25 -2
- package/FilterArea/Filter.js +23 -0
- package/FilterArea/FilterArea.d.ts +43 -4
- package/FilterArea/FilterArea.js +35 -2
- package/FilterArea/FilterLine.d.ts +19 -0
- package/FilterArea/FilterLine.js +19 -0
- package/Input/SpinnerButton/SpinnerButton.js +1 -1
- package/Modal/Modal.d.ts +22 -86
- package/Modal/Modal.js +4 -2
- package/Modal/ModalBodyForVerification.js +3 -1
- package/NotificationCenter/NotificationCenter.d.ts +21 -9
- package/NotificationCenter/NotificationCenter.js +22 -10
- package/NotificationCenter/NotificationCenterDrawer.d.ts +52 -1
- package/NotificationCenter/NotificationCenterDrawer.js +2 -2
- package/OverflowTooltip/OverflowTooltip.js +46 -5
- package/PageFooter/PageFooter.js +6 -14
- package/Pagination/PaginationPageSize.js +1 -1
- package/README.md +34 -4
- package/Radio/Radio.js +16 -2
- package/Table/Table.js +1 -1
- package/TimePicker/TimePicker.js +1 -1
- package/TimeRangePicker/TimeRangePicker.js +1 -1
- package/Toggle/Toggle.d.ts +1 -1
- package/Toggle/Toggle.js +1 -1
- package/Upload/Upload.d.ts +13 -7
- package/Upload/Upload.js +55 -20
- package/Upload/UploadItem.js +4 -1
- package/Upload/UploadPictureCard.d.ts +5 -0
- package/Upload/UploadPictureCard.js +8 -5
- package/Upload/Uploader.d.ts +32 -31
- package/Upload/Uploader.js +10 -9
- package/index.d.ts +3 -3
- package/index.js +1 -1
- package/llms.txt +128 -9
- package/package.json +5 -4
|
@@ -7,5 +7,24 @@ export interface FilterLineProps extends Omit<NativeElementPropsWithoutKeyAndRef
|
|
|
7
7
|
*/
|
|
8
8
|
children: ReactElement<FilterProps> | ReactElement<FilterProps>[];
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* 篩選器中的單行條件列,包含一或多個 Filter 欄位。
|
|
12
|
+
*
|
|
13
|
+
* 透過 Grid 排版將 Filter 子元件橫向排列;
|
|
14
|
+
* 需作為 FilterArea 的直接子元件使用。
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* import { Filter, FilterLine } from '@mezzanine-ui/react';
|
|
19
|
+
*
|
|
20
|
+
* <FilterLine>
|
|
21
|
+
* <Filter span={2}>...</Filter>
|
|
22
|
+
* <Filter span={3}>...</Filter>
|
|
23
|
+
* </FilterLine>
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @see {@link FilterArea} 管理多個 FilterLine 的容器元件
|
|
27
|
+
* @see {@link Filter} 包裝單一篩選欄位的元件
|
|
28
|
+
*/
|
|
10
29
|
declare const FilterLine: import("react").ForwardRefExoticComponent<FilterLineProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
11
30
|
export default FilterLine;
|
package/FilterArea/FilterLine.js
CHANGED
|
@@ -4,6 +4,25 @@ import { forwardRef, useMemo } from 'react';
|
|
|
4
4
|
import { filterAreaClasses } from '@mezzanine-ui/core/filter-area';
|
|
5
5
|
import cx from 'clsx';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* 篩選器中的單行條件列,包含一或多個 Filter 欄位。
|
|
9
|
+
*
|
|
10
|
+
* 透過 Grid 排版將 Filter 子元件橫向排列;
|
|
11
|
+
* 需作為 FilterArea 的直接子元件使用。
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import { Filter, FilterLine } from '@mezzanine-ui/react';
|
|
16
|
+
*
|
|
17
|
+
* <FilterLine>
|
|
18
|
+
* <Filter span={2}>...</Filter>
|
|
19
|
+
* <Filter span={3}>...</Filter>
|
|
20
|
+
* </FilterLine>
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @see {@link FilterArea} 管理多個 FilterLine 的容器元件
|
|
24
|
+
* @see {@link Filter} 包裝單一篩選欄位的元件
|
|
25
|
+
*/
|
|
7
26
|
const FilterLine = forwardRef(function FilterLine(props, ref) {
|
|
8
27
|
const { className, children, ...rest } = props;
|
|
9
28
|
const lineClassName = useMemo(() => cx(filterAreaClasses.line, className), [className]);
|
|
@@ -8,7 +8,7 @@ import cx from 'clsx';
|
|
|
8
8
|
|
|
9
9
|
const SpinnerButton = forwardRef(function SpinnerButton(props, ref) {
|
|
10
10
|
const { className, disabled, size = 'main', type, ...rest } = props;
|
|
11
|
-
return (jsx("button", { ref: ref, type: "button", disabled: disabled, className: cx(inputSpinnerButtonClasses.host, disabled && inputSpinnerButtonClasses.disabled, size === 'main' ? inputSpinnerButtonClasses.main : inputSpinnerButtonClasses.sub, className), title: type === 'up' ? 'Increase value' : 'Decrease value', ...rest, children: jsx(Icon, { icon: type === 'up' ? CaretUpFlatIcon : CaretDownFlatIcon }) }));
|
|
11
|
+
return (jsx("button", { ref: ref, type: "button", disabled: disabled, className: cx(inputSpinnerButtonClasses.host, disabled && inputSpinnerButtonClasses.disabled, size === 'main' ? inputSpinnerButtonClasses.main : inputSpinnerButtonClasses.sub, className), "aria-label": type === 'up' ? 'Increase value' : 'Decrease value', title: type === 'up' ? 'Increase value' : 'Decrease value', ...rest, children: jsx(Icon, { "aria-hidden": "true", icon: type === 'up' ? CaretUpFlatIcon : CaretDownFlatIcon }) }));
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
export { SpinnerButton as default };
|
package/Modal/Modal.d.ts
CHANGED
|
@@ -1,61 +1,9 @@
|
|
|
1
1
|
import { ModalSize, ModalStatusType } from '@mezzanine-ui/core/modal';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
3
2
|
import { ModalContainerProps } from './useModalContainer';
|
|
4
3
|
import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
|
|
4
|
+
import { ModalHeaderProps } from './ModalHeader';
|
|
5
5
|
import { ModalFooterProps } from './ModalFooter';
|
|
6
|
-
export type ModalHeaderLayoutProps =
|
|
7
|
-
/**
|
|
8
|
-
* Layout of the status type icon relative to title.
|
|
9
|
-
* - 'horizontal': Icon to the left of title
|
|
10
|
-
*/
|
|
11
|
-
statusTypeIconLayout: 'horizontal';
|
|
12
|
-
/**
|
|
13
|
-
* Alignment of the supporting text.
|
|
14
|
-
* Only 'left' is allowed when statusTypeIconLayout is 'horizontal'.
|
|
15
|
-
* @default 'left'
|
|
16
|
-
*/
|
|
17
|
-
supportingTextAlign?: 'left';
|
|
18
|
-
/**
|
|
19
|
-
* Alignment of the title.
|
|
20
|
-
* Only 'left' is allowed when statusTypeIconLayout is 'horizontal'.
|
|
21
|
-
* @default 'left'
|
|
22
|
-
*/
|
|
23
|
-
titleAlign?: 'left';
|
|
24
|
-
} | {
|
|
25
|
-
/**
|
|
26
|
-
* Layout of the status type icon relative to title.
|
|
27
|
-
* - 'vertical': Icon above title
|
|
28
|
-
* @default 'vertical'
|
|
29
|
-
*/
|
|
30
|
-
statusTypeIconLayout?: 'vertical';
|
|
31
|
-
/**
|
|
32
|
-
* Alignment of the supporting text.
|
|
33
|
-
* Only 'left' is allowed when titleAlign is 'left'.
|
|
34
|
-
* @default 'left'
|
|
35
|
-
*/
|
|
36
|
-
supportingTextAlign?: 'left';
|
|
37
|
-
/**
|
|
38
|
-
* Alignment of the title.
|
|
39
|
-
* @default 'left'
|
|
40
|
-
*/
|
|
41
|
-
titleAlign?: 'left';
|
|
42
|
-
} | {
|
|
43
|
-
/**
|
|
44
|
-
* Layout of the status type icon relative to title.
|
|
45
|
-
* - 'vertical': Icon above title
|
|
46
|
-
* @default 'vertical'
|
|
47
|
-
*/
|
|
48
|
-
statusTypeIconLayout?: 'vertical';
|
|
49
|
-
/**
|
|
50
|
-
* Alignment of the supporting text.
|
|
51
|
-
* @default 'left'
|
|
52
|
-
*/
|
|
53
|
-
supportingTextAlign?: 'left' | 'center';
|
|
54
|
-
/**
|
|
55
|
-
* Alignment of the title.
|
|
56
|
-
*/
|
|
57
|
-
titleAlign: 'center';
|
|
58
|
-
};
|
|
6
|
+
export type ModalHeaderLayoutProps = Pick<ModalHeaderProps, 'statusTypeIconLayout' | 'titleAlign' | 'supportingTextAlign'>;
|
|
59
7
|
interface CommonModalProps extends Omit<ModalContainerProps, 'children'>, Pick<NativeElementPropsWithoutKeyAndRef<'div'>, 'children'>, Partial<Omit<ModalFooterProps, 'cancelText' | 'children' | 'className' | 'confirmText' | 'showCancelButton'>> {
|
|
60
8
|
/**
|
|
61
9
|
* The custom class name applied to the modal container.
|
|
@@ -77,16 +25,6 @@ interface CommonModalProps extends Omit<ModalContainerProps, 'children'>, Pick<N
|
|
|
77
25
|
* @default 'info'
|
|
78
26
|
*/
|
|
79
27
|
modalStatusType?: ModalStatusType;
|
|
80
|
-
/**
|
|
81
|
-
* Text content of the cancel button.
|
|
82
|
-
* Required when cancel button is shown (showCancelButton is true or not provided).
|
|
83
|
-
*/
|
|
84
|
-
cancelText?: ReactNode;
|
|
85
|
-
/**
|
|
86
|
-
* Whether to show the cancel button.
|
|
87
|
-
* @default true
|
|
88
|
-
*/
|
|
89
|
-
showCancelButton?: boolean;
|
|
90
28
|
/**
|
|
91
29
|
* Controls whether or not to show dismiss button at top-end.
|
|
92
30
|
* @default true
|
|
@@ -113,6 +51,13 @@ interface ExtendedSplitModalProps extends CommonModalProps {
|
|
|
113
51
|
* Required when modalType is 'extendedSplit'.
|
|
114
52
|
*/
|
|
115
53
|
extendedSplitRightSideContent: React.ReactNode;
|
|
54
|
+
/**
|
|
55
|
+
* Controls which side the sidebar panel (narrow column with footer) appears on.
|
|
56
|
+
* - `'left'`: sidebar on the left, wide content on the right
|
|
57
|
+
* - `'right'`: wide content on the left, sidebar on the right
|
|
58
|
+
* @default 'right'
|
|
59
|
+
*/
|
|
60
|
+
extendedSplitSidebarPosition?: 'left' | 'right';
|
|
116
61
|
/**
|
|
117
62
|
* Controls the type/layout of the modal.
|
|
118
63
|
* - 'extendedSplit': Modal with split layout (footer inside left content)
|
|
@@ -136,6 +81,10 @@ interface OtherModalProps extends CommonModalProps {
|
|
|
136
81
|
* Cannot be provided when modalType is not 'extendedSplit'.
|
|
137
82
|
*/
|
|
138
83
|
extendedSplitRightSideContent?: never;
|
|
84
|
+
/**
|
|
85
|
+
* 此模式下不適用。
|
|
86
|
+
*/
|
|
87
|
+
extendedSplitSidebarPosition?: never;
|
|
139
88
|
/**
|
|
140
89
|
* Controls the type/layout of the modal.
|
|
141
90
|
* - 'standard': Default modal with body container
|
|
@@ -174,28 +123,7 @@ type ModalHeaderPropsWithoutHeader = {
|
|
|
174
123
|
*/
|
|
175
124
|
title?: never;
|
|
176
125
|
};
|
|
177
|
-
export type ModalFooterCancelProps =
|
|
178
|
-
/**
|
|
179
|
-
* Text content of the cancel button.
|
|
180
|
-
* Required when cancel button is shown (showCancelButton is true or not provided).
|
|
181
|
-
*/
|
|
182
|
-
cancelText: ReactNode;
|
|
183
|
-
/**
|
|
184
|
-
* Whether to show the cancel button.
|
|
185
|
-
* @default true
|
|
186
|
-
*/
|
|
187
|
-
showCancelButton?: true;
|
|
188
|
-
} | {
|
|
189
|
-
/**
|
|
190
|
-
* Text content of the cancel button.
|
|
191
|
-
* Cannot be provided when showCancelButton is false.
|
|
192
|
-
*/
|
|
193
|
-
cancelText?: never;
|
|
194
|
-
/**
|
|
195
|
-
* Whether to show the cancel button.
|
|
196
|
-
*/
|
|
197
|
-
showCancelButton: false;
|
|
198
|
-
};
|
|
126
|
+
export type ModalFooterCancelProps = Pick<ModalFooterProps, 'cancelText' | 'showCancelButton'>;
|
|
199
127
|
type ModalFooterPropsWithFooter = {
|
|
200
128
|
/**
|
|
201
129
|
* Whether to show modal footer.
|
|
@@ -217,6 +145,14 @@ type ModalFooterPropsWithoutFooter = {
|
|
|
217
145
|
* Cannot be provided when showModalFooter is false.
|
|
218
146
|
*/
|
|
219
147
|
confirmText?: never;
|
|
148
|
+
/**
|
|
149
|
+
* 此模式下不適用。
|
|
150
|
+
*/
|
|
151
|
+
cancelText?: never;
|
|
152
|
+
/**
|
|
153
|
+
* 此模式下不適用。
|
|
154
|
+
*/
|
|
155
|
+
showCancelButton?: never;
|
|
220
156
|
};
|
|
221
157
|
export type ModalProps = BaseModalProps & ModalHeaderLayoutProps & ((ModalHeaderPropsWithHeader & ModalFooterPropsWithFooter) | (ModalHeaderPropsWithHeader & ModalFooterPropsWithoutFooter) | (ModalHeaderPropsWithoutHeader & ModalFooterPropsWithFooter) | (ModalHeaderPropsWithoutHeader & ModalFooterPropsWithoutFooter));
|
|
222
158
|
/**
|
package/Modal/Modal.js
CHANGED
|
@@ -54,7 +54,7 @@ import cx from 'clsx';
|
|
|
54
54
|
* @see {@link useModalContainer} 用於自訂 Modal 掛載容器的 Hook
|
|
55
55
|
*/
|
|
56
56
|
const Modal = forwardRef(function Modal(props, ref) {
|
|
57
|
-
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;
|
|
57
|
+
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, extendedSplitSidebarPosition = 'right', 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;
|
|
58
58
|
const bodyContentRef = useRef(null);
|
|
59
59
|
const [hasTopSeparator, setHasTopSeparator] = useState(false);
|
|
60
60
|
const [hasBottomSeparator, setHasBottomSeparator] = useState(false);
|
|
@@ -128,7 +128,9 @@ const Modal = forwardRef(function Modal(props, ref) {
|
|
|
128
128
|
supportingTextAlign,
|
|
129
129
|
title: title,
|
|
130
130
|
titleAlign,
|
|
131
|
-
} })), modalType === 'extendedSplit' && (jsxs("div", { className: modalClasses.modalBodyContainerExtendedSplit,
|
|
131
|
+
} })), modalType === 'extendedSplit' && (jsxs("div", { className: cx(modalClasses.modalBodyContainerExtendedSplit, {
|
|
132
|
+
[modalClasses.modalBodyContainerExtendedSplitSidebarLeft]: extendedSplitSidebarPosition === 'left',
|
|
133
|
+
}), 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' ||
|
|
132
134
|
modalType === 'verification' ||
|
|
133
135
|
modalType === 'extended' ||
|
|
134
136
|
modalType === 'mediaPreview') && (jsxs(Fragment, { children: [children && (jsx("div", { ref: handleBodyContainerRef, className: cx(modalClasses.modalBodyContainer, {
|
|
@@ -89,7 +89,9 @@ const ModalBodyForVerification = forwardRef(function ModalBodyForVerification(pr
|
|
|
89
89
|
const handleInputChange = (index, e) => {
|
|
90
90
|
handleChange(index, e.target.value);
|
|
91
91
|
};
|
|
92
|
-
return (jsxs("div", { ...rest, ref: ref, className: cx(modalClasses.modalBodyVerification, className), children: [jsx("div", { className: modalClasses.modalBodyVerificationInputs,
|
|
92
|
+
return (jsxs("div", { ...rest, ref: ref, className: cx(modalClasses.modalBodyVerification, className), children: [jsx("div", { className: cx(modalClasses.modalBodyVerificationInputs, {
|
|
93
|
+
[modalClasses.modalBodyVerificationInputsExtended]: length > 4,
|
|
94
|
+
}), children: Array.from({ length }).map((_, index) => (jsx("input", { ref: (el) => {
|
|
93
95
|
inputRefs.current[index] = el;
|
|
94
96
|
}, type: "text", inputMode: "numeric", maxLength: 1, value: codes[index] || '', onChange: (e) => handleInputChange(index, e), onKeyDown: (e) => handleKeyDown(index, e), onPaste: handlePaste, className: cx(modalClasses.modalBodyVerificationInput, {
|
|
95
97
|
[modalClasses.modalBodyVerificationInputError]: error,
|
|
@@ -126,24 +126,36 @@ export interface NotificationCenterSeverityMethods {
|
|
|
126
126
|
warning: (props?: NotificationCenterShorthandProps) => Key;
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
129
|
+
* 通知中心元件,支援即時提示(`notification`)與抽屜清單(`drawer`)兩種呈現模式。
|
|
130
130
|
*
|
|
131
|
-
*
|
|
131
|
+
* 以 `<NotificationCenter type="notification" />` 宣告式渲染單筆浮動通知;
|
|
132
|
+
* 或使用靜態方法 `NotificationCenter.add()`、`.success()`、`.error()` 等命令式 API
|
|
133
|
+
* 觸發通知,並可透過回傳的 key 呼叫 `remove()` 收回。
|
|
134
|
+
* `type: 'drawer'` 模式下,請搭配 `NotificationCenterDrawer` 以清單方式顯示。
|
|
132
135
|
*
|
|
133
136
|
* @example
|
|
134
137
|
* ```tsx
|
|
135
|
-
* import NotificationCenter from '@mezzanine-ui/react/
|
|
138
|
+
* import NotificationCenter from '@mezzanine-ui/react/NotificationCenter';
|
|
136
139
|
*
|
|
137
|
-
*
|
|
138
|
-
* NotificationCenter
|
|
140
|
+
* // 宣告式:嵌入畫面中的通知
|
|
141
|
+
* <NotificationCenter
|
|
142
|
+
* reference="notify-1"
|
|
143
|
+
* severity="success"
|
|
144
|
+
* title="儲存成功"
|
|
145
|
+
* type="notification"
|
|
146
|
+
* />
|
|
139
147
|
*
|
|
140
|
-
* //
|
|
141
|
-
*
|
|
148
|
+
* // 命令式:觸發浮動通知
|
|
149
|
+
* NotificationCenter.success({ title: '儲存成功', description: '資料已更新。' });
|
|
150
|
+
* NotificationCenter.error({ title: '操作失敗', description: '請稍後再試。' });
|
|
151
|
+
*
|
|
152
|
+
* // 手動控制生命週期
|
|
153
|
+
* const key = NotificationCenter.add({ severity: 'info', title: '處理中…', type: 'notification' });
|
|
142
154
|
* NotificationCenter.remove(key);
|
|
143
155
|
* ```
|
|
144
156
|
*
|
|
145
|
-
* @see {@link NotificationData}
|
|
146
|
-
* @see {@link NotificationCenterDrawer}
|
|
157
|
+
* @see {@link NotificationData} 所有可用的通知設定項
|
|
158
|
+
* @see {@link NotificationCenterDrawer} 抽屜清單模式的容器元件
|
|
147
159
|
*/
|
|
148
160
|
declare const NotificationCenter: FC<PropsWithChildren<NotificationData>> & {
|
|
149
161
|
add: (notif: NotificationData & {
|
|
@@ -276,7 +276,7 @@ const NotificationCenterFC = (props) => {
|
|
|
276
276
|
const showCancelButton = Boolean(cancelButtonText && (onCancelProp || onCloseProp));
|
|
277
277
|
const hideButtons = !(type === 'notification' &&
|
|
278
278
|
(showConfirmButton || showCancelButton));
|
|
279
|
-
const notificationContent = (jsxs("div", { className: cx(notificationClasses.host, notificationClasses.severity(severity), notificationClasses.type(type)), onMouseEnter: handleNotificationMouseEnter, onMouseLeave: handleNotificationMouseLeave, children: [targetIcon ? (jsx("div", { className: notificationClasses.iconContainer, children: jsx(Icon, { icon: targetIcon, className: notificationClasses.severityIcon, size: 32 }) })) : null, jsxs("div", { className: notificationClasses.body, children: [jsxs("div", { className: notificationClasses.bodyContent, children: [jsx("h4", { className: notificationClasses.title, children: title }), jsx(Typography, { className: notificationClasses.content, children: description })] }), !hideButtons && (jsxs(ButtonGroup, { className: notificationClasses.action, children: [showCancelButton ? (jsx(Button, { onClick: onCancel || onClose, size: "minor", variant: "base-secondary", ...cancelButtonProps, children: cancelButtonText })) :
|
|
279
|
+
const notificationContent = (jsxs("div", { className: cx(notificationClasses.host, notificationClasses.severity(severity), notificationClasses.type(type)), onMouseEnter: handleNotificationMouseEnter, onMouseLeave: handleNotificationMouseLeave, children: [targetIcon ? (jsx("div", { className: notificationClasses.iconContainer, children: jsx(Icon, { icon: targetIcon, className: notificationClasses.severityIcon, size: 32 }) })) : null, jsxs("div", { className: notificationClasses.body, children: [jsxs("div", { className: notificationClasses.bodyContent, children: [jsx("h4", { className: notificationClasses.title, children: title }), jsx(Typography, { className: notificationClasses.content, children: description })] }), !hideButtons && (jsxs(ButtonGroup, { className: notificationClasses.action, children: [showCancelButton ? (jsx(Button, { onClick: onCancel || onClose, size: "minor", variant: "base-secondary", ...cancelButtonProps, children: cancelButtonText })) : null, showConfirmButton ? (jsx(Button, { onClick: onConfirm, size: "minor", ...confirmButtonProps, children: confirmButtonText })) : null] })), type === 'drawer' && (jsxs(Fragment, { children: [isToday && (jsx(Popper, { anchor: timeStampAnchor, open: Boolean(timeStampAnchor), arrow: {
|
|
280
280
|
className: notificationClasses.timeStampPopperArrow,
|
|
281
281
|
enabled: true,
|
|
282
282
|
padding: 0,
|
|
@@ -325,24 +325,36 @@ const { add: addNotifier, config, destroy, remove, } = createNotifier({
|
|
|
325
325
|
},
|
|
326
326
|
});
|
|
327
327
|
/**
|
|
328
|
-
*
|
|
328
|
+
* 通知中心元件,支援即時提示(`notification`)與抽屜清單(`drawer`)兩種呈現模式。
|
|
329
329
|
*
|
|
330
|
-
*
|
|
330
|
+
* 以 `<NotificationCenter type="notification" />` 宣告式渲染單筆浮動通知;
|
|
331
|
+
* 或使用靜態方法 `NotificationCenter.add()`、`.success()`、`.error()` 等命令式 API
|
|
332
|
+
* 觸發通知,並可透過回傳的 key 呼叫 `remove()` 收回。
|
|
333
|
+
* `type: 'drawer'` 模式下,請搭配 `NotificationCenterDrawer` 以清單方式顯示。
|
|
331
334
|
*
|
|
332
335
|
* @example
|
|
333
336
|
* ```tsx
|
|
334
|
-
* import NotificationCenter from '@mezzanine-ui/react/
|
|
337
|
+
* import NotificationCenter from '@mezzanine-ui/react/NotificationCenter';
|
|
335
338
|
*
|
|
336
|
-
*
|
|
337
|
-
* NotificationCenter
|
|
339
|
+
* // 宣告式:嵌入畫面中的通知
|
|
340
|
+
* <NotificationCenter
|
|
341
|
+
* reference="notify-1"
|
|
342
|
+
* severity="success"
|
|
343
|
+
* title="儲存成功"
|
|
344
|
+
* type="notification"
|
|
345
|
+
* />
|
|
338
346
|
*
|
|
339
|
-
* //
|
|
340
|
-
*
|
|
347
|
+
* // 命令式:觸發浮動通知
|
|
348
|
+
* NotificationCenter.success({ title: '儲存成功', description: '資料已更新。' });
|
|
349
|
+
* NotificationCenter.error({ title: '操作失敗', description: '請稍後再試。' });
|
|
350
|
+
*
|
|
351
|
+
* // 手動控制生命週期
|
|
352
|
+
* const key = NotificationCenter.add({ severity: 'info', title: '處理中…', type: 'notification' });
|
|
341
353
|
* NotificationCenter.remove(key);
|
|
342
354
|
* ```
|
|
343
355
|
*
|
|
344
|
-
* @see {@link NotificationData}
|
|
345
|
-
* @see {@link NotificationCenterDrawer}
|
|
356
|
+
* @see {@link NotificationData} 所有可用的通知設定項
|
|
357
|
+
* @see {@link NotificationCenterDrawer} 抽屜清單模式的容器元件
|
|
346
358
|
*/
|
|
347
359
|
const NotificationCenter = Object.assign(NotificationCenterFC, {
|
|
348
360
|
add: (notif) => {
|
|
@@ -6,7 +6,58 @@ type NotificationDataForDrawer = NotificationData & {
|
|
|
6
6
|
key: Key;
|
|
7
7
|
type: 'drawer';
|
|
8
8
|
};
|
|
9
|
-
type NotificationCenterDrawerPropsBase = Pick<DrawerProps, '
|
|
9
|
+
type NotificationCenterDrawerPropsBase = Pick<DrawerProps, 'onClose' | 'open'> & {
|
|
10
|
+
/**
|
|
11
|
+
* Label for the "all" filter option in the filter bar.
|
|
12
|
+
*/
|
|
13
|
+
filterBarAllRadioLabel?: DrawerProps['filterAreaAllRadioLabel'];
|
|
14
|
+
/**
|
|
15
|
+
* Label for the custom action button in the filter bar.
|
|
16
|
+
*/
|
|
17
|
+
filterBarCustomButtonLabel?: DrawerProps['filterAreaCustomButtonLabel'];
|
|
18
|
+
/**
|
|
19
|
+
* Default selected filter value in the filter bar.
|
|
20
|
+
*/
|
|
21
|
+
filterBarDefaultValue?: DrawerProps['filterAreaDefaultValue'];
|
|
22
|
+
/**
|
|
23
|
+
* Callback fired when the custom action button is clicked.
|
|
24
|
+
*/
|
|
25
|
+
filterBarOnCustomButtonClick?: DrawerProps['filterAreaOnCustomButtonClick'];
|
|
26
|
+
/**
|
|
27
|
+
* Callback fired when the filter option changes.
|
|
28
|
+
*/
|
|
29
|
+
filterBarOnRadioChange?: DrawerProps['filterAreaOnRadioChange'];
|
|
30
|
+
/**
|
|
31
|
+
* Label for the "read" filter option in the filter bar.
|
|
32
|
+
*/
|
|
33
|
+
filterBarReadRadioLabel?: DrawerProps['filterAreaReadRadioLabel'];
|
|
34
|
+
/**
|
|
35
|
+
* Whether to show the filter bar.
|
|
36
|
+
*/
|
|
37
|
+
filterBarShow?: DrawerProps['filterAreaShow'];
|
|
38
|
+
/**
|
|
39
|
+
* Whether to show the "unread only" shortcut button.
|
|
40
|
+
*/
|
|
41
|
+
filterBarShowUnreadButton?: DrawerProps['filterAreaShowUnreadButton'];
|
|
42
|
+
/**
|
|
43
|
+
* Label for the "unread" filter option in the filter bar.
|
|
44
|
+
*/
|
|
45
|
+
filterBarUnreadRadioLabel?: DrawerProps['filterAreaUnreadRadioLabel'];
|
|
46
|
+
/**
|
|
47
|
+
* Controlled selected filter value in the filter bar.
|
|
48
|
+
*/
|
|
49
|
+
filterBarValue?: DrawerProps['filterAreaValue'];
|
|
50
|
+
/**
|
|
51
|
+
* Options for the filter bar dropdown.
|
|
52
|
+
* When non-empty, the right-side filter area button is replaced by a Dropdown
|
|
53
|
+
* triggered by a `DotHorizontalIcon` icon button.
|
|
54
|
+
*/
|
|
55
|
+
filterBarOptions?: DrawerProps['filterAreaOptions'];
|
|
56
|
+
/**
|
|
57
|
+
* Callback fired when a filter bar dropdown option is selected.
|
|
58
|
+
* Only used when `filterBarOptions` is non-empty.
|
|
59
|
+
*/
|
|
60
|
+
filterBarOnSelect?: DrawerProps['filterAreaOnSelect'];
|
|
10
61
|
/**
|
|
11
62
|
* The size of the drawer.
|
|
12
63
|
* @default 'narrow'
|
|
@@ -55,7 +55,7 @@ const getTimeGroup = (timestamp, now) => {
|
|
|
55
55
|
return 'earlier';
|
|
56
56
|
};
|
|
57
57
|
const NotificationCenterDrawer = (props) => {
|
|
58
|
-
const { children,
|
|
58
|
+
const { children, filterBarAllRadioLabel, filterBarCustomButtonLabel, filterBarDefaultValue, filterBarOnCustomButtonClick, filterBarOnRadioChange, filterBarOnSelect, filterBarReadRadioLabel, filterBarShow, filterBarShowUnreadButton, filterBarUnreadRadioLabel, filterBarValue, filterBarOptions, drawerSize = 'narrow', earlierLabel, emptyNotificationTitle = '目前沒有新的通知', emptyNotificationDescription = '當有新的系統通知時,將會顯示在這裡。', notificationList, onClose, open, past7DaysLabel, title, todayLabel, yesterdayLabel, ...restDrawerProps } = props;
|
|
59
59
|
const isEmpty = useMemo(() => {
|
|
60
60
|
if (notificationList) {
|
|
61
61
|
return notificationList.length === 0;
|
|
@@ -120,7 +120,7 @@ const NotificationCenterDrawer = (props) => {
|
|
|
120
120
|
todayLabel,
|
|
121
121
|
yesterdayLabel,
|
|
122
122
|
]);
|
|
123
|
-
return (jsx(Drawer, { className: notificationClasses.drawer,
|
|
123
|
+
return (jsx(Drawer, { className: notificationClasses.drawer, filterAreaAllRadioLabel: filterBarAllRadioLabel, filterAreaCustomButtonLabel: filterBarCustomButtonLabel, filterAreaDefaultValue: filterBarDefaultValue, filterAreaIsEmpty: isEmpty, filterAreaOnCustomButtonClick: filterBarOnCustomButtonClick, filterAreaOnRadioChange: filterBarOnRadioChange, filterAreaOnSelect: filterBarOnSelect, filterAreaReadRadioLabel: filterBarReadRadioLabel, filterAreaShow: filterBarShow, filterAreaShowUnreadButton: filterBarShowUnreadButton, filterAreaUnreadRadioLabel: filterBarUnreadRadioLabel, filterAreaValue: filterBarValue, filterAreaOptions: filterBarOptions, headerTitle: title, isHeaderDisplay: Boolean(title), onClose: onClose, open: open, size: drawerSize, ...restDrawerProps, children: jsx("div", { className: notificationClasses.notificationsContainer, children: renderNotifications }) }));
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
export { NotificationCenterDrawer as default };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useState, useEffect } from 'react';
|
|
3
|
+
import { forwardRef, useState, useEffect, useRef } from 'react';
|
|
4
4
|
import { overflowTooltipClasses } from '@mezzanine-ui/core/overflow-tooltip';
|
|
5
|
-
import {
|
|
5
|
+
import { getNumericCSSVariablePixelValue } from '../utils/get-css-variable-value.js';
|
|
6
6
|
import { offset, flip, shift } from '@floating-ui/react-dom';
|
|
7
7
|
import { spacingPrefix } from '@mezzanine-ui/system/spacing';
|
|
8
8
|
import { MOTION_EASING, MOTION_DURATION } from '@mezzanine-ui/system/motion';
|
|
@@ -16,8 +16,9 @@ import cx from 'clsx';
|
|
|
16
16
|
*/
|
|
17
17
|
const OverflowTooltip = forwardRef(function OverflowTooltip(props, ref) {
|
|
18
18
|
const { anchor, className, onTagDismiss, open, placement = 'top-start', readOnly, tags, tagSize, } = props;
|
|
19
|
-
const offsetValue =
|
|
20
|
-
const arrowHeight =
|
|
19
|
+
const offsetValue = getNumericCSSVariablePixelValue(`--${spacingPrefix}-gap-base`);
|
|
20
|
+
const arrowHeight = getNumericCSSVariablePixelValue(`--${spacingPrefix}-size-element-tight`);
|
|
21
|
+
const arrowPadding = getNumericCSSVariablePixelValue(`--${spacingPrefix}-padding-horizontal-comfort`);
|
|
21
22
|
const middleware = [offset({ mainAxis: offsetValue + arrowHeight })];
|
|
22
23
|
const flipMiddleware = flip({
|
|
23
24
|
crossAxis: 'alignment',
|
|
@@ -36,7 +37,47 @@ const OverflowTooltip = forwardRef(function OverflowTooltip(props, ref) {
|
|
|
36
37
|
setPopperOpen(true);
|
|
37
38
|
}
|
|
38
39
|
}, [open]);
|
|
39
|
-
|
|
40
|
+
const contentRef = useRef(null);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!popperOpen)
|
|
43
|
+
return;
|
|
44
|
+
const rafId = requestAnimationFrame(() => {
|
|
45
|
+
const content = contentRef.current;
|
|
46
|
+
if (!content)
|
|
47
|
+
return;
|
|
48
|
+
content.style.width = '';
|
|
49
|
+
const children = Array.from(content.children);
|
|
50
|
+
if (!children.length)
|
|
51
|
+
return;
|
|
52
|
+
const rows = new Map();
|
|
53
|
+
children.forEach((child) => {
|
|
54
|
+
const top = Math.round(child.getBoundingClientRect().top);
|
|
55
|
+
if (!rows.has(top))
|
|
56
|
+
rows.set(top, []);
|
|
57
|
+
const arr = rows.get(top);
|
|
58
|
+
if (arr) {
|
|
59
|
+
arr.push(child);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const style = getComputedStyle(content);
|
|
63
|
+
const paddingLeft = parseFloat(style.paddingLeft);
|
|
64
|
+
const paddingRight = parseFloat(style.paddingRight);
|
|
65
|
+
const contentLeft = content.getBoundingClientRect().left;
|
|
66
|
+
let maxRowWidth = 0;
|
|
67
|
+
rows.forEach((rowItems) => {
|
|
68
|
+
const lastItem = rowItems[rowItems.length - 1];
|
|
69
|
+
const rowWidth = lastItem.getBoundingClientRect().right - contentLeft - paddingLeft;
|
|
70
|
+
maxRowWidth = Math.max(maxRowWidth, rowWidth);
|
|
71
|
+
});
|
|
72
|
+
content.style.width = `${maxRowWidth + paddingLeft + paddingRight}px`;
|
|
73
|
+
});
|
|
74
|
+
return () => cancelAnimationFrame(rafId);
|
|
75
|
+
}, [tags, popperOpen, tagSize, readOnly]);
|
|
76
|
+
return (jsx(Popper, { ref: ref, anchor: anchor, open: popperOpen, arrow: {
|
|
77
|
+
enabled: true,
|
|
78
|
+
className: overflowTooltipClasses.arrow,
|
|
79
|
+
padding: arrowPadding,
|
|
80
|
+
}, className: cx(overflowTooltipClasses.host, className), options: { placement, middleware }, children: jsx(Fade, { ref: contentRef, in: open, duration: {
|
|
40
81
|
enter: MOTION_DURATION.fast,
|
|
41
82
|
exit: MOTION_DURATION.fast,
|
|
42
83
|
}, easing: {
|
package/PageFooter/PageFooter.js
CHANGED
|
@@ -10,29 +10,21 @@ import cx from 'clsx';
|
|
|
10
10
|
|
|
11
11
|
const PageFooter = forwardRef(function PageFooter(props, ref) {
|
|
12
12
|
var _a;
|
|
13
|
-
const { actions, annotationClassName, className, type = 'standard', warningMessage, ...rest } = props;
|
|
14
|
-
// Filter out onAnnotationClick from rest props to avoid React warnings
|
|
15
|
-
if ('onAnnotationClick' in rest) {
|
|
16
|
-
delete rest.onAnnotationClick;
|
|
17
|
-
}
|
|
13
|
+
const { actions, annotationClassName, annotation, className, dropdownProps, supportingActionIcon, supportingActionName, supportingActionOnClick, supportingActionType, supportingActionVariant = 'base-ghost', type = 'standard', warningMessage, ...rest } = props;
|
|
18
14
|
const { children: primaryButtonText, ...restPrimaryButtonProps } = (_a = actions === null || actions === void 0 ? void 0 : actions.primaryButton) !== null && _a !== void 0 ? _a : {};
|
|
19
15
|
// Render annotation based on type
|
|
20
16
|
const renderAnnotation = () => {
|
|
21
17
|
switch (type) {
|
|
22
|
-
case 'standard':
|
|
23
|
-
const { supportingActionName, supportingActionType, supportingActionOnClick, supportingActionVariant = 'base-ghost', } = props;
|
|
18
|
+
case 'standard':
|
|
24
19
|
return (jsx(Button, { size: "main", type: supportingActionType, onClick: supportingActionOnClick, variant: supportingActionVariant, children: supportingActionName }));
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
case 'overflow':
|
|
21
|
+
if (!dropdownProps)
|
|
22
|
+
return null;
|
|
28
23
|
return (jsx(Dropdown, { ...dropdownProps, options: dropdownProps.options || [], placement: dropdownProps.placement || 'top', children: jsx(Button, { type: "button", iconType: "icon-only", icon: supportingActionIcon || DotHorizontalIcon, size: "main", variant: "base-ghost" }) }));
|
|
29
|
-
|
|
30
|
-
case 'information': {
|
|
31
|
-
const { annotation } = props;
|
|
24
|
+
case 'information':
|
|
32
25
|
if (!annotation)
|
|
33
26
|
return null;
|
|
34
27
|
return (jsx(Typography, { color: "text-neutral", variant: "caption", children: annotation }));
|
|
35
|
-
}
|
|
36
28
|
default:
|
|
37
29
|
return null;
|
|
38
30
|
}
|
|
@@ -25,7 +25,7 @@ const PaginationPageSize = forwardRef((props, ref) => {
|
|
|
25
25
|
onChange === null || onChange === void 0 ? void 0 : onChange(Number(option.id));
|
|
26
26
|
setOpen(false);
|
|
27
27
|
};
|
|
28
|
-
return (jsxs("div", { ...rest, ref: ref, className: cx(paginationPageSizeClasses.host, className), children: [label ? (jsx(Typography, { component: "div", ellipsis: true, variant: "label-primary", children: label })) : null, jsx(Dropdown, { disabled: disabled, onSelect: dropDownOnSelect, onVisibilityChange: setOpen, open: open, options: selectOptions, sameWidth: true, children: jsx(SelectTrigger, { className: paginationPageSizeClasses.select, disabled: disabled, size: "sub", value: currentValue }) })] }));
|
|
28
|
+
return (jsxs("div", { ...rest, ref: ref, className: cx(paginationPageSizeClasses.host, className), children: [label ? (jsx(Typography, { component: "div", ellipsis: true, variant: "label-primary", children: label })) : null, jsx(Dropdown, { disabled: disabled, minWidth: 0, onSelect: dropDownOnSelect, onVisibilityChange: setOpen, open: open, options: selectOptions, sameWidth: true, value: currentValue === null || currentValue === void 0 ? void 0 : currentValue.id, children: jsx(SelectTrigger, { className: paginationPageSizeClasses.select, disabled: disabled, size: "sub", value: currentValue }) })] }));
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
export { PaginationPageSize as default };
|
package/README.md
CHANGED
|
@@ -18,13 +18,43 @@ npm install @mezzanine-ui/react @mezzanine-ui/core @mezzanine-ui/icons @mezzanin
|
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
### 1. Setup Styles
|
|
22
|
+
|
|
23
|
+
Create a `main.scss` and import it at your app entry point:
|
|
24
|
+
|
|
25
|
+
```scss
|
|
26
|
+
@use '@mezzanine-ui/system' as mzn-system;
|
|
27
|
+
@use '@mezzanine-ui/core' as mzn-core;
|
|
28
|
+
|
|
29
|
+
:root {
|
|
30
|
+
@include mzn-system.common-variables('default');
|
|
31
|
+
@include mzn-system.colors();
|
|
32
|
+
@include mzn-system.palette-variables(light);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Optional: dark mode */
|
|
36
|
+
[data-theme='dark'] {
|
|
37
|
+
@include mzn-system.palette-variables(dark);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Optional: compact density */
|
|
41
|
+
[data-density='compact'] {
|
|
42
|
+
@include mzn-system.common-variables(compact);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@include mzn-core.styles();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> **Note:** The `~` prefix (e.g. `~@mezzanine-ui/system`) was required by older webpack 4 + sass-loader setups. Modern tooling (Next.js 13+, Vite) resolves node_modules without it.
|
|
22
49
|
|
|
23
50
|
```tsx
|
|
24
|
-
//
|
|
25
|
-
import '
|
|
51
|
+
// app entry (e.g. main.tsx / _app.tsx)
|
|
52
|
+
import './main.scss';
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Use Components
|
|
26
56
|
|
|
27
|
-
|
|
57
|
+
```tsx
|
|
28
58
|
import Button from '@mezzanine-ui/react/Button';
|
|
29
59
|
|
|
30
60
|
function App() {
|