@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.
Files changed (61) hide show
  1. package/AutoComplete/AutoComplete.d.ts +25 -9
  2. package/AutoComplete/AutoComplete.js +84 -17
  3. package/AutoComplete/AutoCompleteInside.d.ts +54 -0
  4. package/AutoComplete/AutoCompleteInside.js +17 -0
  5. package/AutoComplete/useAutoCompleteKeyboard.d.ts +2 -1
  6. package/AutoComplete/useAutoCompleteKeyboard.js +4 -1
  7. package/Breadcrumb/BreadcrumbDropdown.d.ts +1 -1
  8. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +1 -1
  9. package/Breadcrumb/typings.d.ts +1 -1
  10. package/COMPONENTS.md +2 -2
  11. package/Checkbox/Checkbox.js +24 -3
  12. package/Cropper/Cropper.d.ts +1 -1
  13. package/Description/Description.d.ts +1 -1
  14. package/Description/Description.js +1 -1
  15. package/Description/DescriptionTitle.d.ts +6 -1
  16. package/Description/DescriptionTitle.js +2 -2
  17. package/Drawer/Drawer.d.ts +39 -34
  18. package/Drawer/Drawer.js +33 -35
  19. package/Dropdown/Dropdown.d.ts +16 -1
  20. package/Dropdown/Dropdown.js +156 -9
  21. package/Dropdown/DropdownItem.d.ts +26 -2
  22. package/Dropdown/DropdownItem.js +91 -43
  23. package/Dropdown/DropdownItemCard.d.ts +3 -2
  24. package/Dropdown/DropdownItemCard.js +8 -5
  25. package/Dropdown/dropdownKeydownHandler.d.ts +6 -0
  26. package/Dropdown/dropdownKeydownHandler.js +14 -7
  27. package/FilterArea/Filter.d.ts +25 -2
  28. package/FilterArea/Filter.js +23 -0
  29. package/FilterArea/FilterArea.d.ts +43 -4
  30. package/FilterArea/FilterArea.js +35 -2
  31. package/FilterArea/FilterLine.d.ts +19 -0
  32. package/FilterArea/FilterLine.js +19 -0
  33. package/Input/SpinnerButton/SpinnerButton.js +1 -1
  34. package/Modal/Modal.d.ts +22 -86
  35. package/Modal/Modal.js +4 -2
  36. package/Modal/ModalBodyForVerification.js +3 -1
  37. package/NotificationCenter/NotificationCenter.d.ts +21 -9
  38. package/NotificationCenter/NotificationCenter.js +22 -10
  39. package/NotificationCenter/NotificationCenterDrawer.d.ts +52 -1
  40. package/NotificationCenter/NotificationCenterDrawer.js +2 -2
  41. package/OverflowTooltip/OverflowTooltip.js +46 -5
  42. package/PageFooter/PageFooter.js +6 -14
  43. package/Pagination/PaginationPageSize.js +1 -1
  44. package/README.md +34 -4
  45. package/Radio/Radio.js +16 -2
  46. package/Table/Table.js +1 -1
  47. package/TimePicker/TimePicker.js +1 -1
  48. package/TimeRangePicker/TimeRangePicker.js +1 -1
  49. package/Toggle/Toggle.d.ts +1 -1
  50. package/Toggle/Toggle.js +1 -1
  51. package/Upload/Upload.d.ts +13 -7
  52. package/Upload/Upload.js +55 -20
  53. package/Upload/UploadItem.js +4 -1
  54. package/Upload/UploadPictureCard.d.ts +5 -0
  55. package/Upload/UploadPictureCard.js +8 -5
  56. package/Upload/Uploader.d.ts +32 -31
  57. package/Upload/Uploader.js +10 -9
  58. package/index.d.ts +3 -3
  59. package/index.js +1 -1
  60. package/llms.txt +128 -9
  61. 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;
@@ -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, 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' ||
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, children: Array.from({ length }).map((_, index) => (jsx("input", { ref: (el) => {
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
- * The react component for `mezzanine` notification center.
129
+ * 通知中心元件,支援即時提示(`notification`)與抽屜清單(`drawer`)兩種呈現模式。
130
130
  *
131
- * Trigger notifications imperatively via severity shorthand methods or `add`:
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/notification-center';
138
+ * import NotificationCenter from '@mezzanine-ui/react/NotificationCenter';
136
139
  *
137
- * NotificationCenter.success({ title: 'Saved!', description: 'Your changes have been saved.' });
138
- * NotificationCenter.error({ title: 'Error', description: 'Something went wrong.' });
140
+ * // 宣告式:嵌入畫面中的通知
141
+ * <NotificationCenter
142
+ * reference="notify-1"
143
+ * severity="success"
144
+ * title="儲存成功"
145
+ * type="notification"
146
+ * />
139
147
  *
140
- * // Dismiss programmatically
141
- * const key = NotificationCenter.add({ severity: 'info', title: 'Processing…', type: 'notification' });
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} for all available notification options.
146
- * @see {@link NotificationCenterDrawer} for the drawer list variant.
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 })) : (jsx(Fragment, {})), showConfirmButton ? (jsx(Button, { onClick: onConfirm, size: "minor", ...confirmButtonProps, children: confirmButtonText })) : (jsx(Fragment, {}))] })), type === 'drawer' && (jsxs(Fragment, { children: [isToday && (jsx(Popper, { anchor: timeStampAnchor, open: Boolean(timeStampAnchor), arrow: {
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
- * The react component for `mezzanine` notification center.
328
+ * 通知中心元件,支援即時提示(`notification`)與抽屜清單(`drawer`)兩種呈現模式。
329
329
  *
330
- * Trigger notifications imperatively via severity shorthand methods or `add`:
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/notification-center';
337
+ * import NotificationCenter from '@mezzanine-ui/react/NotificationCenter';
335
338
  *
336
- * NotificationCenter.success({ title: 'Saved!', description: 'Your changes have been saved.' });
337
- * NotificationCenter.error({ title: 'Error', description: 'Something went wrong.' });
339
+ * // 宣告式:嵌入畫面中的通知
340
+ * <NotificationCenter
341
+ * reference="notify-1"
342
+ * severity="success"
343
+ * title="儲存成功"
344
+ * type="notification"
345
+ * />
338
346
  *
339
- * // Dismiss programmatically
340
- * const key = NotificationCenter.add({ severity: 'info', title: 'Processing…', type: 'notification' });
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} for all available notification options.
345
- * @see {@link NotificationCenterDrawer} for the drawer list variant.
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, 'controlBarAllRadioLabel' | 'controlBarCustomButtonLabel' | 'controlBarDefaultValue' | 'controlBarOnCustomButtonClick' | 'controlBarOnRadioChange' | 'controlBarReadRadioLabel' | 'controlBarShow' | 'controlBarShowUnreadButton' | 'controlBarUnreadRadioLabel' | 'controlBarValue' | 'onClose' | 'open' | 'renderControlBar'> & {
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, controlBarAllRadioLabel, controlBarCustomButtonLabel, controlBarDefaultValue, controlBarOnCustomButtonClick, controlBarOnRadioChange, controlBarReadRadioLabel, controlBarShow, controlBarShowUnreadButton, controlBarUnreadRadioLabel, controlBarValue, drawerSize = 'narrow', earlierLabel, emptyNotificationTitle = '目前沒有新的通知', emptyNotificationDescription = '當有新的系統通知時,將會顯示在這裡。', notificationList, onClose, open, past7DaysLabel, renderControlBar, title, todayLabel, yesterdayLabel, ...restDrawerProps } = props;
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, controlBarAllRadioLabel: controlBarAllRadioLabel, controlBarCustomButtonLabel: controlBarCustomButtonLabel, controlBarDefaultValue: controlBarDefaultValue, controlBarIsEmpty: isEmpty, controlBarOnCustomButtonClick: controlBarOnCustomButtonClick, controlBarOnRadioChange: controlBarOnRadioChange, controlBarReadRadioLabel: controlBarReadRadioLabel, controlBarShow: controlBarShow, controlBarShowUnreadButton: controlBarShowUnreadButton, controlBarUnreadRadioLabel: controlBarUnreadRadioLabel, controlBarValue: controlBarValue, headerTitle: title, isHeaderDisplay: Boolean(title), onClose: onClose, open: open, renderControlBar: renderControlBar, size: drawerSize, ...restDrawerProps, children: jsx("div", { className: notificationClasses.notificationsContainer, children: renderNotifications }) }));
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 { getCSSVariableValue } from '../utils/get-css-variable-value.js';
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 = Number(getCSSVariableValue(`--${spacingPrefix}-gap-base`).replace('rem', '')) * 16;
20
- const arrowHeight = Number(getCSSVariableValue(`--${spacingPrefix}-size-element-tight`).replace('rem', '')) * 16;
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
- return (jsx(Popper, { ref: ref, anchor: anchor, open: popperOpen, arrow: { enabled: true, className: overflowTooltipClasses.arrow }, className: cx(overflowTooltipClasses.host, className), options: { placement, middleware }, children: jsx(Fade, { in: open, duration: {
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: {
@@ -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
- case 'overflow': {
27
- const { supportingActionIcon, dropdownProps } = props;
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
- Import the component styles from `@mezzanine-ui/core`, then use components from this package:
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
- // Import component styles (e.g. in your app entry or global stylesheet)
25
- import '@mezzanine-ui/core/button/styles';
51
+ // app entry (e.g. main.tsx / _app.tsx)
52
+ import './main.scss';
53
+ ```
54
+
55
+ ### 2. Use Components
26
56
 
27
- // Use the component
57
+ ```tsx
28
58
  import Button from '@mezzanine-ui/react/Button';
29
59
 
30
60
  function App() {