@mezzanine-ui/react 1.0.0-beta.5 → 1.0.0-beta.6
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 +5 -0
- package/AutoComplete/AutoComplete.js +3 -2
- package/Breadcrumb/BreadcrumbDropdown.js +1 -1
- package/Breadcrumb/BreadcrumbItem.js +1 -1
- package/Breadcrumb/BreadcrumbOverflowMenuItem.js +1 -1
- package/DateTimePicker/DateTimePicker.js +8 -4
- package/DateTimeRangePicker/DateTimeRangePicker.d.ts +34 -0
- package/DateTimeRangePicker/DateTimeRangePicker.js +118 -0
- package/DateTimeRangePicker/index.d.ts +2 -0
- package/DateTimeRangePicker/index.js +1 -0
- package/Drawer/Drawer.d.ts +132 -1
- package/Drawer/Drawer.js +47 -3
- package/Dropdown/Dropdown.d.ts +3 -3
- package/Dropdown/Dropdown.js +7 -29
- package/Form/FormField.d.ts +14 -2
- package/Form/FormField.js +3 -3
- package/Input/Input.js +3 -19
- package/Input/SelectButton/SelectButton.d.ts +25 -4
- package/Input/SelectButton/SelectButton.js +21 -9
- package/Modal/MediaPreviewModal.d.ts +11 -0
- package/Modal/MediaPreviewModal.js +24 -7
- package/Modal/Modal.d.ts +1 -1
- package/Modal/Modal.js +1 -1
- package/Modal/useModalContainer.js +6 -2
- package/MultipleDatePicker/MultipleDatePicker.d.ts +62 -0
- package/MultipleDatePicker/MultipleDatePicker.js +176 -0
- package/MultipleDatePicker/MultipleDatePickerTrigger.d.ts +56 -0
- package/MultipleDatePicker/MultipleDatePickerTrigger.js +92 -0
- package/MultipleDatePicker/index.d.ts +6 -0
- package/MultipleDatePicker/index.js +3 -0
- package/MultipleDatePicker/useMultipleDatePickerValue.d.ts +55 -0
- package/MultipleDatePicker/useMultipleDatePickerValue.js +68 -0
- package/NotificationCenter/NotificationCenterDrawer.d.ts +10 -52
- package/NotificationCenter/NotificationCenterDrawer.js +128 -0
- package/NotificationCenter/index.d.ts +2 -0
- package/NotificationCenter/index.js +1 -0
- package/OverflowTooltip/index.d.ts +2 -2
- package/Picker/RangePickerTrigger.js +1 -1
- package/Section/Section.d.ts +32 -0
- package/Section/Section.js +62 -0
- package/Section/index.d.ts +2 -0
- package/Select/Select.d.ts +9 -4
- package/Select/Select.js +2 -2
- package/Select/TreeSelect.d.ts +10 -5
- package/Select/TreeSelect.js +12 -5
- package/TimePanel/TimePanelColumn.js +16 -14
- package/TimeRangePicker/TimeRangePicker.d.ts +29 -0
- package/TimeRangePicker/TimeRangePicker.js +96 -0
- package/TimeRangePicker/index.d.ts +3 -0
- package/TimeRangePicker/index.js +2 -0
- package/TimeRangePicker/useTimeRangePickerValue.d.ts +30 -0
- package/TimeRangePicker/useTimeRangePickerValue.js +92 -0
- package/Transition/Rotate.js +2 -5
- package/Tree/TreeNode.js +2 -2
- package/index.d.ts +9 -6
- package/index.js +7 -6
- package/package.json +4 -4
- package/AppBar/AppBar.d.ts +0 -14
- package/AppBar/AppBar.js +0 -33
- package/AppBar/AppBarBrand.d.ts +0 -4
- package/AppBar/AppBarBrand.js +0 -11
- package/AppBar/AppBarMain.d.ts +0 -4
- package/AppBar/AppBarMain.js +0 -11
- package/AppBar/AppBarSupport.d.ts +0 -4
- package/AppBar/AppBarSupport.js +0 -11
- package/AppBar/index.d.ts +0 -8
- package/AppBar/index.js +0 -4
- package/Popconfirm/Popconfirm.d.ts +0 -16
- package/Popconfirm/Popconfirm.js +0 -15
- package/Popconfirm/index.d.ts +0 -2
- package/Popconfirm/index.js +0 -1
- package/Popover/Popover.d.ts +0 -23
- package/Popover/Popover.js +0 -35
- package/Popover/index.d.ts +0 -2
- package/Popover/index.js +0 -1
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useRef, useMemo, useCallback } from 'react';
|
|
4
|
+
import { multipleDatePickerClasses } from '@mezzanine-ui/core/multiple-date-picker';
|
|
5
|
+
import TagGroup from '../Tag/TagGroup.js';
|
|
6
|
+
import { useSelectTriggerTags } from '../Select/useSelectTriggerTags.js';
|
|
7
|
+
import Tag from '../Tag/Tag.js';
|
|
8
|
+
import OverflowCounterTag from '../OverflowTooltip/OverflowCounterTag.js';
|
|
9
|
+
import TextField from '../TextField/TextField.js';
|
|
10
|
+
import cx from 'clsx';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The trigger component for MultipleDatePicker.
|
|
14
|
+
* Displays selected dates as tags within a TextField.
|
|
15
|
+
*/
|
|
16
|
+
const MultipleDatePickerTrigger = forwardRef(function MultipleDatePickerTrigger(props, ref) {
|
|
17
|
+
const { active = false, className, clearable = true, disabled = false, error = false, fullWidth = false, onTagClose, overflowStrategy = 'counter', placeholder, readOnly = false, required = false, size = 'main', suffix, value = [], ...restTextFieldProps } = props;
|
|
18
|
+
const tagsContainerRef = useRef(null);
|
|
19
|
+
const tagsRef = useRef(null);
|
|
20
|
+
const tagSize = size === 'main' ? 'main' : 'sub';
|
|
21
|
+
// Convert DateValue[] to SelectValue[] for useSelectTriggerTags
|
|
22
|
+
const selectValues = useMemo(() => value.map((v) => ({ id: v.id, name: v.name })), [value]);
|
|
23
|
+
const { overflowSelections, renderFakeTags, visibleSelections } = useSelectTriggerTags({
|
|
24
|
+
containerRef: tagsContainerRef,
|
|
25
|
+
enabled: overflowStrategy === 'counter',
|
|
26
|
+
size: tagSize,
|
|
27
|
+
tagsRef,
|
|
28
|
+
value: selectValues,
|
|
29
|
+
});
|
|
30
|
+
const displaySelections = useMemo(() => (overflowStrategy === 'counter' ? visibleSelections : selectValues), [overflowStrategy, selectValues, visibleSelections]);
|
|
31
|
+
// Find the original DateValue by id
|
|
32
|
+
const findDateValue = useCallback((id) => value.find((v) => v.id === id), [value]);
|
|
33
|
+
const handleTagClose = useCallback((id) => (e) => {
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
const dateValue = findDateValue(id);
|
|
36
|
+
if (dateValue && onTagClose) {
|
|
37
|
+
onTagClose(dateValue.date);
|
|
38
|
+
}
|
|
39
|
+
}, [findDateValue, onTagClose]);
|
|
40
|
+
const tagChildren = useMemo(() => {
|
|
41
|
+
const tags = displaySelections.map((selection) => {
|
|
42
|
+
if (readOnly) {
|
|
43
|
+
return (jsx(Tag, { label: selection.name, readOnly: true, size: tagSize, type: "static" }, selection.id));
|
|
44
|
+
}
|
|
45
|
+
return (jsx(Tag, { disabled: disabled, label: selection.name, onClose: handleTagClose(selection.id), size: tagSize, type: "dismissable" }, selection.id));
|
|
46
|
+
});
|
|
47
|
+
if (overflowStrategy === 'counter' && overflowSelections.length) {
|
|
48
|
+
tags.push(jsx(OverflowCounterTag, { disabled: disabled, onClick: (e) => {
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
}, onTagDismiss: (tagIndex) => {
|
|
51
|
+
const target = overflowSelections[tagIndex];
|
|
52
|
+
if (!target)
|
|
53
|
+
return;
|
|
54
|
+
const dateValue = findDateValue(target.id);
|
|
55
|
+
if (dateValue && onTagClose) {
|
|
56
|
+
onTagClose(dateValue.date);
|
|
57
|
+
}
|
|
58
|
+
}, readOnly: readOnly, tagSize: tagSize, tags: overflowSelections.map((s) => s.name) }, "overflow-counter"));
|
|
59
|
+
}
|
|
60
|
+
return tags;
|
|
61
|
+
}, [
|
|
62
|
+
disabled,
|
|
63
|
+
displaySelections,
|
|
64
|
+
findDateValue,
|
|
65
|
+
handleTagClose,
|
|
66
|
+
onTagClose,
|
|
67
|
+
overflowSelections,
|
|
68
|
+
overflowStrategy,
|
|
69
|
+
readOnly,
|
|
70
|
+
tagSize,
|
|
71
|
+
]);
|
|
72
|
+
const hasValue = value.length > 0;
|
|
73
|
+
// TextField requires disabled and readonly to be mutually exclusive
|
|
74
|
+
let interactiveProps = {};
|
|
75
|
+
if (disabled) {
|
|
76
|
+
interactiveProps = { disabled: true };
|
|
77
|
+
}
|
|
78
|
+
else if (readOnly) {
|
|
79
|
+
interactiveProps = { readonly: true };
|
|
80
|
+
}
|
|
81
|
+
return (jsx(TextField, { ...restTextFieldProps, ...interactiveProps, active: active, className: cx(multipleDatePickerClasses.trigger, {
|
|
82
|
+
[multipleDatePickerClasses.triggerSelected]: hasValue,
|
|
83
|
+
[multipleDatePickerClasses.triggerDisabled]: disabled,
|
|
84
|
+
[multipleDatePickerClasses.triggerReadOnly]: readOnly,
|
|
85
|
+
}, className), clearable: !readOnly && clearable && hasValue, error: error, fullWidth: fullWidth, ref: ref, size: size, suffix: suffix, children: jsx("div", { ref: tagsContainerRef, className: cx(multipleDatePickerClasses.triggerTagsWrapper, {
|
|
86
|
+
[multipleDatePickerClasses.triggerTagsWrapperEllipsis]: overflowStrategy === 'counter',
|
|
87
|
+
}), children: hasValue ? (jsxs(Fragment, { children: [jsxs("div", { className: cx(multipleDatePickerClasses.triggerTags, {
|
|
88
|
+
[multipleDatePickerClasses.triggerTagsEllipsis]: overflowStrategy === 'counter',
|
|
89
|
+
}), ref: tagsRef, children: [jsx(TagGroup, { children: tagChildren }), overflowStrategy === 'counter' ? renderFakeTags() : null] }), jsx("input", { "aria-disabled": disabled, "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, className: cx(multipleDatePickerClasses.triggerInput, multipleDatePickerClasses.triggerInputAbsolute), disabled: disabled, readOnly: true, tabIndex: -1, type: "text", value: "" })] })) : (jsx("input", { "aria-disabled": disabled, "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, className: multipleDatePickerClasses.triggerInput, disabled: disabled, placeholder: placeholder, readOnly: true, tabIndex: -1, type: "text", value: "" })) }) }));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export { MultipleDatePickerTrigger as default };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default } from './MultipleDatePicker';
|
|
2
|
+
export type { MultipleDatePickerProps } from './MultipleDatePicker';
|
|
3
|
+
export { default as MultipleDatePickerTrigger } from './MultipleDatePickerTrigger';
|
|
4
|
+
export type { DateValue as MultipleDatePickerDateValue, MultipleDatePickerTriggerProps, } from './MultipleDatePickerTrigger';
|
|
5
|
+
export { useMultipleDatePickerValue } from './useMultipleDatePickerValue';
|
|
6
|
+
export type { UseMultipleDatePickerValueProps, UseMultipleDatePickerValueReturn, } from './useMultipleDatePickerValue';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DateType } from '@mezzanine-ui/core/calendar';
|
|
2
|
+
import { MultipleDatePickerValue } from '@mezzanine-ui/core/multiple-date-picker';
|
|
3
|
+
export interface UseMultipleDatePickerValueProps {
|
|
4
|
+
/**
|
|
5
|
+
* The format pattern for displaying dates (e.g., "YYYY-MM-DD")
|
|
6
|
+
*/
|
|
7
|
+
format: string;
|
|
8
|
+
/**
|
|
9
|
+
* Maximum number of dates that can be selected
|
|
10
|
+
*/
|
|
11
|
+
maxSelections?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Controlled value
|
|
14
|
+
*/
|
|
15
|
+
value?: MultipleDatePickerValue;
|
|
16
|
+
}
|
|
17
|
+
export interface UseMultipleDatePickerValueReturn {
|
|
18
|
+
/**
|
|
19
|
+
* The internal value (pending changes)
|
|
20
|
+
*/
|
|
21
|
+
internalValue: MultipleDatePickerValue;
|
|
22
|
+
/**
|
|
23
|
+
* Toggle a date in/out of selection
|
|
24
|
+
*/
|
|
25
|
+
toggleDate: (date: DateType) => void;
|
|
26
|
+
/**
|
|
27
|
+
* Remove a specific date from selection
|
|
28
|
+
*/
|
|
29
|
+
removeDate: (date: DateType) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Clear all selected dates
|
|
32
|
+
*/
|
|
33
|
+
clearAll: () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a date is currently selected
|
|
36
|
+
*/
|
|
37
|
+
isDateSelected: (date: DateType) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Check if selection has reached max limit
|
|
40
|
+
*/
|
|
41
|
+
isMaxReached: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Confirm the current selection (returns the value to be passed to onChange)
|
|
44
|
+
*/
|
|
45
|
+
getConfirmValue: () => MultipleDatePickerValue;
|
|
46
|
+
/**
|
|
47
|
+
* Cancel and revert to original value
|
|
48
|
+
*/
|
|
49
|
+
revertToValue: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* Format a date to display string
|
|
52
|
+
*/
|
|
53
|
+
formatDate: (date: DateType) => string;
|
|
54
|
+
}
|
|
55
|
+
export declare function useMultipleDatePickerValue({ format, maxSelections, value, }: UseMultipleDatePickerValueProps): UseMultipleDatePickerValueReturn;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useState, useEffect } from 'react';
|
|
3
|
+
import { useCalendarContext } from '../Calendar/CalendarContext.js';
|
|
4
|
+
|
|
5
|
+
function useMultipleDatePickerValue({ format, maxSelections, value = [], }) {
|
|
6
|
+
const { formatToString, isBefore, isSameDate, locale } = useCalendarContext();
|
|
7
|
+
// Sort dates in chronological order
|
|
8
|
+
const sortDates = useCallback((dates) => {
|
|
9
|
+
return [...dates].sort((a, b) => {
|
|
10
|
+
if (isSameDate(a, b))
|
|
11
|
+
return 0;
|
|
12
|
+
return isBefore(a, b) ? -1 : 1;
|
|
13
|
+
});
|
|
14
|
+
}, [isBefore, isSameDate]);
|
|
15
|
+
// Internal state for pending changes
|
|
16
|
+
const [internalValue, setInternalValue] = useState(() => sortDates(value));
|
|
17
|
+
// Sync internal value when controlled value changes
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setInternalValue(sortDates(value));
|
|
20
|
+
}, [sortDates, value]);
|
|
21
|
+
const formatDate = useCallback((date) => {
|
|
22
|
+
return formatToString(locale, date, format);
|
|
23
|
+
}, [formatToString, locale, format]);
|
|
24
|
+
const isDateSelected = useCallback((date) => {
|
|
25
|
+
return internalValue.some((d) => isSameDate(d, date));
|
|
26
|
+
}, [internalValue, isSameDate]);
|
|
27
|
+
const isMaxReached = typeof maxSelections === 'number' && internalValue.length >= maxSelections;
|
|
28
|
+
const toggleDate = useCallback((date) => {
|
|
29
|
+
setInternalValue((prev) => {
|
|
30
|
+
const existingIndex = prev.findIndex((d) => isSameDate(d, date));
|
|
31
|
+
if (existingIndex >= 0) {
|
|
32
|
+
// Remove the date
|
|
33
|
+
return prev.filter((_, index) => index !== existingIndex);
|
|
34
|
+
}
|
|
35
|
+
// Check max limit before adding
|
|
36
|
+
if (typeof maxSelections === 'number' && prev.length >= maxSelections) {
|
|
37
|
+
return prev;
|
|
38
|
+
}
|
|
39
|
+
// Add the date and sort
|
|
40
|
+
return sortDates([...prev, date]);
|
|
41
|
+
});
|
|
42
|
+
}, [isSameDate, maxSelections, sortDates]);
|
|
43
|
+
const removeDate = useCallback((date) => {
|
|
44
|
+
setInternalValue((prev) => prev.filter((d) => !isSameDate(d, date)));
|
|
45
|
+
}, [isSameDate]);
|
|
46
|
+
const clearAll = useCallback(() => {
|
|
47
|
+
setInternalValue([]);
|
|
48
|
+
}, []);
|
|
49
|
+
const getConfirmValue = useCallback(() => {
|
|
50
|
+
return internalValue;
|
|
51
|
+
}, [internalValue]);
|
|
52
|
+
const revertToValue = useCallback(() => {
|
|
53
|
+
setInternalValue(sortDates(value));
|
|
54
|
+
}, [sortDates, value]);
|
|
55
|
+
return {
|
|
56
|
+
clearAll,
|
|
57
|
+
formatDate,
|
|
58
|
+
getConfirmValue,
|
|
59
|
+
internalValue,
|
|
60
|
+
isDateSelected,
|
|
61
|
+
isMaxReached,
|
|
62
|
+
removeDate,
|
|
63
|
+
revertToValue,
|
|
64
|
+
toggleDate,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { useMultipleDatePickerValue };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ComponentProps, type Key, type ReactElement } from 'react';
|
|
2
2
|
import { DrawerSize } from '@mezzanine-ui/core/drawer';
|
|
3
3
|
import { type IconDefinition } from '@mezzanine-ui/icons';
|
|
4
4
|
import { type DrawerProps } from '../Drawer';
|
|
@@ -7,24 +7,17 @@ type NotificationDataForDrawer = NotificationData & {
|
|
|
7
7
|
key: Key;
|
|
8
8
|
type: 'drawer';
|
|
9
9
|
};
|
|
10
|
-
type NotificationCenterDrawerPropsBase = Pick<DrawerProps, '
|
|
11
|
-
/**
|
|
12
|
-
* The label of the all radio.
|
|
13
|
-
*/
|
|
14
|
-
allRadioLabel?: string;
|
|
15
|
-
/**
|
|
16
|
-
* The label of the custom radio.
|
|
17
|
-
*/
|
|
18
|
-
customRadioLabel?: string;
|
|
19
|
-
/**
|
|
20
|
-
* The default value of the radio group.
|
|
21
|
-
*/
|
|
22
|
-
defaultValue?: string;
|
|
10
|
+
type NotificationCenterDrawerPropsBase = Pick<DrawerProps, 'controlBarAllRadioLabel' | 'controlBarCustomButtonLabel' | 'controlBarDefaultValue' | 'controlBarOnCustomButtonClick' | 'controlBarOnRadioChange' | 'controlBarReadRadioLabel' | 'controlBarShow' | 'controlBarShowUnreadButton' | 'controlBarUnreadRadioLabel' | 'controlBarValue' | 'onClose' | 'open' | 'renderControlBar'> & {
|
|
23
11
|
/**
|
|
24
12
|
* The size of the drawer.
|
|
25
13
|
* @default 'narrow'
|
|
26
14
|
*/
|
|
27
15
|
drawerSize?: DrawerSize;
|
|
16
|
+
/**
|
|
17
|
+
* The label of the "earlier" time group.
|
|
18
|
+
* @default '更早'
|
|
19
|
+
*/
|
|
20
|
+
earlierLabel?: string;
|
|
28
21
|
/**
|
|
29
22
|
* The icon of the empty notification.
|
|
30
23
|
*/
|
|
@@ -34,49 +27,14 @@ type NotificationCenterDrawerPropsBase = Pick<DrawerProps, 'open' | 'onClose'> &
|
|
|
34
27
|
*/
|
|
35
28
|
emptyNotificationTitle?: string;
|
|
36
29
|
/**
|
|
37
|
-
* The
|
|
38
|
-
|
|
39
|
-
onCustomButtonClick?: VoidFunction;
|
|
40
|
-
/**
|
|
41
|
-
* The callback function when the radio group value changes.
|
|
42
|
-
*/
|
|
43
|
-
onRadioChange?: ChangeEventHandler<HTMLInputElement>;
|
|
44
|
-
/**
|
|
45
|
-
* The label of the read radio.
|
|
46
|
-
*/
|
|
47
|
-
readRadioLabel?: string;
|
|
48
|
-
/**
|
|
49
|
-
* Controls whether to display the toolbar.
|
|
50
|
-
* @default true
|
|
51
|
-
*/
|
|
52
|
-
showToolbar?: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Controls whether to display the unread button.
|
|
55
|
-
* @default false
|
|
30
|
+
* The label for the "past 7 days" time group.
|
|
31
|
+
* @default '過去七天'
|
|
56
32
|
*/
|
|
57
|
-
|
|
33
|
+
past7DaysLabel?: string;
|
|
58
34
|
/**
|
|
59
35
|
* The title of the drawer.
|
|
60
36
|
*/
|
|
61
37
|
title?: string;
|
|
62
|
-
/**
|
|
63
|
-
* The label of the unread radio.
|
|
64
|
-
*/
|
|
65
|
-
unreadRadioLabel?: string;
|
|
66
|
-
/**
|
|
67
|
-
* The value of the radio group.
|
|
68
|
-
*/
|
|
69
|
-
value?: string;
|
|
70
|
-
/**
|
|
71
|
-
* The label for the "earlier" time group.
|
|
72
|
-
* @default '更早'
|
|
73
|
-
*/
|
|
74
|
-
earlierLabel?: string;
|
|
75
|
-
/**
|
|
76
|
-
* The label for the "past 7 days" time group.
|
|
77
|
-
* @default '過去七天'
|
|
78
|
-
*/
|
|
79
|
-
past7DaysLabel?: string;
|
|
80
38
|
/**
|
|
81
39
|
* The label for the "today" time group.
|
|
82
40
|
* @default '今天'
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { useMemo } from 'react';
|
|
4
|
+
import { notificationClasses } from '@mezzanine-ui/core/notification-center';
|
|
5
|
+
import { NotificationIcon } from '@mezzanine-ui/icons';
|
|
6
|
+
import Typography from '../Typography/Typography.js';
|
|
7
|
+
import NotificationCenter from './NotificationCenter.js';
|
|
8
|
+
import Drawer from '../Drawer/Drawer.js';
|
|
9
|
+
import Icon from '../Icon/Icon.js';
|
|
10
|
+
|
|
11
|
+
const isValidTime = (timestamp) => {
|
|
12
|
+
if (!timestamp)
|
|
13
|
+
return false;
|
|
14
|
+
const date = new Date(timestamp);
|
|
15
|
+
return !Number.isNaN(date.getTime());
|
|
16
|
+
};
|
|
17
|
+
const getValidTime = (timestamp) => {
|
|
18
|
+
if (!isValidTime(timestamp))
|
|
19
|
+
return 0;
|
|
20
|
+
const date = new Date(timestamp);
|
|
21
|
+
return date.getTime();
|
|
22
|
+
};
|
|
23
|
+
const DEFAULT_TIME_GROUP_LABELS = {
|
|
24
|
+
today: '今天',
|
|
25
|
+
yesterday: '昨天',
|
|
26
|
+
past7Days: '過去七天',
|
|
27
|
+
earlier: '更早',
|
|
28
|
+
};
|
|
29
|
+
const TIME_GROUP_ORDER = [
|
|
30
|
+
'today',
|
|
31
|
+
'yesterday',
|
|
32
|
+
'past7Days',
|
|
33
|
+
'earlier',
|
|
34
|
+
];
|
|
35
|
+
const getTimeGroup = (timestamp, now) => {
|
|
36
|
+
if (!isValidTime(timestamp))
|
|
37
|
+
return 'earlier';
|
|
38
|
+
const notificationDate = new Date(timestamp);
|
|
39
|
+
const nowStartOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
40
|
+
const notificationStartOfDay = new Date(notificationDate.getFullYear(), notificationDate.getMonth(), notificationDate.getDate());
|
|
41
|
+
// Today: same calendar day
|
|
42
|
+
if (notificationStartOfDay.getTime() === nowStartOfDay.getTime()) {
|
|
43
|
+
return 'today';
|
|
44
|
+
}
|
|
45
|
+
// Yesterday: previous calendar day
|
|
46
|
+
const yesterdayStartOfDay = new Date(nowStartOfDay);
|
|
47
|
+
yesterdayStartOfDay.setDate(yesterdayStartOfDay.getDate() - 1);
|
|
48
|
+
if (notificationStartOfDay.getTime() === yesterdayStartOfDay.getTime()) {
|
|
49
|
+
return 'yesterday';
|
|
50
|
+
}
|
|
51
|
+
// Past 7 days: within 7 days but not today or yesterday
|
|
52
|
+
const diffInDays = (now.getTime() - notificationDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
53
|
+
if (diffInDays <= 7) {
|
|
54
|
+
return 'past7Days';
|
|
55
|
+
}
|
|
56
|
+
// Earlier: more than 7 days ago
|
|
57
|
+
return 'earlier';
|
|
58
|
+
};
|
|
59
|
+
const NotificationCenterDrawer = (props) => {
|
|
60
|
+
const { children, controlBarAllRadioLabel, controlBarCustomButtonLabel, controlBarDefaultValue, controlBarOnCustomButtonClick, controlBarOnRadioChange, controlBarReadRadioLabel, controlBarShow, controlBarShowUnreadButton, controlBarUnreadRadioLabel, controlBarValue, drawerSize = 'narrow', earlierLabel, emptyNotificationIcon = NotificationIcon, emptyNotificationTitle = '目前沒有新的通知', notificationList, onClose, open, past7DaysLabel, renderControlBar, title, todayLabel, yesterdayLabel, ...restDrawerProps } = props;
|
|
61
|
+
const isEmpty = useMemo(() => {
|
|
62
|
+
if (notificationList) {
|
|
63
|
+
return notificationList.length === 0;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(children)) {
|
|
66
|
+
return children.length === 0;
|
|
67
|
+
}
|
|
68
|
+
return !children;
|
|
69
|
+
}, [notificationList, children]);
|
|
70
|
+
const renderNotifications = useMemo(() => {
|
|
71
|
+
const renderEmptyNotifications = () => {
|
|
72
|
+
return (jsxs("div", { className: notificationClasses.emptyNotifications, children: [jsx(Icon, { icon: emptyNotificationIcon, size: 28 }), jsx(Typography, { children: emptyNotificationTitle })] }));
|
|
73
|
+
};
|
|
74
|
+
if (isEmpty) {
|
|
75
|
+
return renderEmptyNotifications();
|
|
76
|
+
}
|
|
77
|
+
// Check if notificationList is provided
|
|
78
|
+
if (notificationList) {
|
|
79
|
+
// Sort once by timestamp (newest first), then group while maintaining order
|
|
80
|
+
const sorted = [...notificationList].sort((a, b) => {
|
|
81
|
+
const aTime = getValidTime(a.timeStamp);
|
|
82
|
+
const bTime = getValidTime(b.timeStamp);
|
|
83
|
+
return bTime - aTime;
|
|
84
|
+
});
|
|
85
|
+
// Group sorted notifications
|
|
86
|
+
const now = new Date();
|
|
87
|
+
const grouped = sorted.reduce((acc, notification) => {
|
|
88
|
+
var _a;
|
|
89
|
+
const group = getTimeGroup(notification.timeStamp, now);
|
|
90
|
+
((_a = acc[group]) !== null && _a !== void 0 ? _a : (acc[group] = [])).push(notification);
|
|
91
|
+
return acc;
|
|
92
|
+
}, {});
|
|
93
|
+
// Get time group labels from props or use defaults
|
|
94
|
+
const timeGroupLabels = {
|
|
95
|
+
earlier: earlierLabel !== null && earlierLabel !== void 0 ? earlierLabel : DEFAULT_TIME_GROUP_LABELS.earlier,
|
|
96
|
+
past7Days: past7DaysLabel !== null && past7DaysLabel !== void 0 ? past7DaysLabel : DEFAULT_TIME_GROUP_LABELS.past7Days,
|
|
97
|
+
today: todayLabel !== null && todayLabel !== void 0 ? todayLabel : DEFAULT_TIME_GROUP_LABELS.today,
|
|
98
|
+
yesterday: yesterdayLabel !== null && yesterdayLabel !== void 0 ? yesterdayLabel : DEFAULT_TIME_GROUP_LABELS.yesterday,
|
|
99
|
+
};
|
|
100
|
+
// Render notifications with prependTips for first item in each group
|
|
101
|
+
return TIME_GROUP_ORDER.flatMap((group) => {
|
|
102
|
+
const notifications = grouped[group];
|
|
103
|
+
if (!(notifications === null || notifications === void 0 ? void 0 : notifications.length)) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
return notifications.map((notification, index) => {
|
|
107
|
+
const { key, ...restNotification } = notification;
|
|
108
|
+
return (jsx(NotificationCenter, { ...restNotification, prependTips: index === 0 ? timeGroupLabels[group] : undefined, reference: key, type: "drawer" }, key));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Return children (can be single element or array)
|
|
113
|
+
return Array.isArray(children) ? children : children;
|
|
114
|
+
}, [
|
|
115
|
+
isEmpty,
|
|
116
|
+
notificationList,
|
|
117
|
+
children,
|
|
118
|
+
emptyNotificationIcon,
|
|
119
|
+
emptyNotificationTitle,
|
|
120
|
+
earlierLabel,
|
|
121
|
+
past7DaysLabel,
|
|
122
|
+
todayLabel,
|
|
123
|
+
yesterdayLabel,
|
|
124
|
+
]);
|
|
125
|
+
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: renderNotifications }));
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export { NotificationCenterDrawer as default };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export type { NotificationSeverity } from '@mezzanine-ui/core/notification-center';
|
|
2
2
|
export { default } from './NotificationCenter';
|
|
3
3
|
export type { NotificationConfigProps, NotificationData, } from './NotificationCenter';
|
|
4
|
+
export { default as NotificationCenterDrawer } from './NotificationCenterDrawer';
|
|
5
|
+
export type { NotificationCenterDrawerProps } from './NotificationCenterDrawer';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default, OverflowTooltipProps } from './OverflowTooltip';
|
|
2
|
-
export { default as OverflowCounterTag, OverflowCounterTagProps, } from './OverflowCounterTag';
|
|
1
|
+
export { default, type OverflowTooltipProps } from './OverflowTooltip';
|
|
2
|
+
export { default as OverflowCounterTag, type OverflowCounterTagProps, } from './OverflowCounterTag';
|
|
@@ -69,7 +69,7 @@ const RangePickerTrigger = forwardRef(function RangePickerTrigger(props, ref) {
|
|
|
69
69
|
const handleToBlur = useCallback((e) => {
|
|
70
70
|
onToBlur === null || onToBlur === void 0 ? void 0 : onToBlur(e);
|
|
71
71
|
}, [onToBlur]);
|
|
72
|
-
return (jsxs(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref,
|
|
72
|
+
return (jsxs(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, className), clearable: !readOnly && clearable, suffix: suffix !== null && suffix !== void 0 ? suffix : defaultSuffix, children: [jsx(FormattedInput, { ...inputFromProps, ref: fromRef, "aria-disabled": disabled, "aria-label": "Start date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesFrom, format: format, onBlur: handleFromBlur, onChange: handleFromChange, onFocus: handleFromFocus, placeholder: inputFromPlaceholder, readOnly: readOnly, required: required, validate: validateFrom, value: inputFromValue }), jsx(Icon, { icon: LongTailArrowRightIcon, className: pickerClasses.arrowIcon, "aria-hidden": "true" }), jsx(FormattedInput, { ...inputToProps, ref: toRef, "aria-disabled": disabled, "aria-label": "End date", "aria-multiline": false, "aria-readonly": readOnly, "aria-required": required, disabled: disabled, errorMessages: errorMessagesTo, format: format, onBlur: handleToBlur, onChange: handleToChange, onFocus: handleToFocus, placeholder: inputToPlaceholder, readOnly: readOnly, required: required, validate: validateTo, value: inputToValue })] }));
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
export { RangePickerTrigger as default };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ReactElement } from 'react';
|
|
2
|
+
import { ContentHeaderProps } from '../ContentHeader';
|
|
3
|
+
import { FilterAreaProps } from '../FilterArea';
|
|
4
|
+
import { TabProps } from '../Tab';
|
|
5
|
+
export interface SectionProps {
|
|
6
|
+
/**
|
|
7
|
+
* Additional style for the section container.
|
|
8
|
+
*/
|
|
9
|
+
className?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Accept `<ContentHeader />` component from `mezzanine`.
|
|
12
|
+
* Other components will trigger a warning.
|
|
13
|
+
*/
|
|
14
|
+
contentHeader?: ReactElement<ContentHeaderProps>;
|
|
15
|
+
/**
|
|
16
|
+
* Accept `<FilterArea />` component from `mezzanine`.
|
|
17
|
+
* Other components will trigger a warning.
|
|
18
|
+
*/
|
|
19
|
+
filterArea?: ReactElement<FilterAreaProps>;
|
|
20
|
+
/**
|
|
21
|
+
* Accept `<Tab />` component from `mezzanine`.
|
|
22
|
+
* Other components will trigger a warning.
|
|
23
|
+
*/
|
|
24
|
+
tab?: ReactElement<TabProps>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The react component for `mezzanine` section.
|
|
28
|
+
*/
|
|
29
|
+
declare const Section: import("react").ForwardRefExoticComponent<SectionProps & {
|
|
30
|
+
children?: import("react").ReactNode | undefined;
|
|
31
|
+
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
32
|
+
export default Section;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { forwardRef, cloneElement, isValidElement } from 'react';
|
|
3
|
+
import { sectionClasses } from '@mezzanine-ui/core/section';
|
|
4
|
+
import ContentHeader from '../ContentHeader/ContentHeader.js';
|
|
5
|
+
import FilterArea from '../FilterArea/FilterArea.js';
|
|
6
|
+
import Tab from '../Tab/Tab.js';
|
|
7
|
+
import cx from 'clsx';
|
|
8
|
+
|
|
9
|
+
function getDisplayName(element) {
|
|
10
|
+
const type = element.type;
|
|
11
|
+
if (typeof type === 'string') {
|
|
12
|
+
return type;
|
|
13
|
+
}
|
|
14
|
+
return type.displayName
|
|
15
|
+
|| type.name
|
|
16
|
+
|| 'Unknown';
|
|
17
|
+
}
|
|
18
|
+
function isContentHeaderElement(element) {
|
|
19
|
+
return isValidElement(element) && element.type === ContentHeader;
|
|
20
|
+
}
|
|
21
|
+
function isFilterAreaElement(element) {
|
|
22
|
+
return isValidElement(element) && element.type === FilterArea;
|
|
23
|
+
}
|
|
24
|
+
function isTabElement(element) {
|
|
25
|
+
return isValidElement(element) && element.type === Tab;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The react component for `mezzanine` section.
|
|
29
|
+
*/
|
|
30
|
+
const Section = forwardRef(function Section(props, ref) {
|
|
31
|
+
const { children, className, contentHeader, filterArea, tab, } = props;
|
|
32
|
+
let renderedContentHeader = null;
|
|
33
|
+
let renderedFilterArea = null;
|
|
34
|
+
let renderedTab = null;
|
|
35
|
+
if (contentHeader) {
|
|
36
|
+
if (isContentHeaderElement(contentHeader)) {
|
|
37
|
+
renderedContentHeader = cloneElement(contentHeader, { size: 'sub' });
|
|
38
|
+
}
|
|
39
|
+
else if (isValidElement(contentHeader)) {
|
|
40
|
+
console.warn(`[Section] Invalid contentHeader type: <${getDisplayName(contentHeader)}>. Only <ContentHeader /> component from @mezzanine-ui/react is allowed.`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (filterArea) {
|
|
44
|
+
if (isFilterAreaElement(filterArea)) {
|
|
45
|
+
renderedFilterArea = cloneElement(filterArea, { size: 'sub' });
|
|
46
|
+
}
|
|
47
|
+
else if (isValidElement(filterArea)) {
|
|
48
|
+
console.warn(`[Section] Invalid filterArea type: <${getDisplayName(filterArea)}>. Only <FilterArea /> component from @mezzanine-ui/react is allowed.`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (tab) {
|
|
52
|
+
if (isTabElement(tab)) {
|
|
53
|
+
renderedTab = tab;
|
|
54
|
+
}
|
|
55
|
+
else if (isValidElement(tab)) {
|
|
56
|
+
console.warn(`[Section] Invalid tab type: <${getDisplayName(tab)}>. Only <Tab /> component from @mezzanine-ui/react is allowed.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return (jsxs("div", { ref: ref, className: cx(sectionClasses.host, className), children: [renderedContentHeader, renderedFilterArea, renderedTab, children] }));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export { Section as default };
|
package/Select/Select.d.ts
CHANGED
|
@@ -23,10 +23,6 @@ export interface SelectBaseProps extends Omit<SelectTriggerProps, 'active' | 'in
|
|
|
23
23
|
* The other native props for input element.
|
|
24
24
|
*/
|
|
25
25
|
inputProps?: Omit<SelectTriggerInputProps, 'onBlur' | 'onChange' | 'onFocus' | 'placeholder' | 'role' | 'value' | `aria-${'controls' | 'expanded' | 'owns'}`>;
|
|
26
|
-
/**
|
|
27
|
-
* Whether to disable portal.
|
|
28
|
-
*/
|
|
29
|
-
disablePortal?: boolean;
|
|
30
26
|
/**
|
|
31
27
|
* The max height of the dropdown list.
|
|
32
28
|
*/
|
|
@@ -60,6 +56,15 @@ export interface SelectBaseProps extends Omit<SelectTriggerProps, 'active' | 'in
|
|
|
60
56
|
* The size of input.
|
|
61
57
|
*/
|
|
62
58
|
size?: SelectInputSize;
|
|
59
|
+
/**
|
|
60
|
+
* The z-index of the dropdown.
|
|
61
|
+
*/
|
|
62
|
+
dropdownZIndex?: number | string;
|
|
63
|
+
/**
|
|
64
|
+
* Whether to enable portal for the dropdown.
|
|
65
|
+
* @default true
|
|
66
|
+
*/
|
|
67
|
+
globalPortal?: boolean;
|
|
63
68
|
}
|
|
64
69
|
export type SelectMultipleProps = SelectBaseProps & {
|
|
65
70
|
/**
|
package/Select/Select.js
CHANGED
|
@@ -14,7 +14,7 @@ import cx from 'clsx';
|
|
|
14
14
|
|
|
15
15
|
const Select = forwardRef(function Select(props, ref) {
|
|
16
16
|
const { disabled: disabledFromFormControl, fullWidth: fullWidthFromFormControl, required: requiredFromFormControl, severity, } = useContext(FormControlContext) || {};
|
|
17
|
-
const { children, className, clearable = false, defaultValue, disabled = disabledFromFormControl || false,
|
|
17
|
+
const { children, className, clearable = false, defaultValue, disabled = disabledFromFormControl || false, error = severity === 'error' || false, fullWidth = fullWidthFromFormControl || false, inputProps, inputRef, menuMaxHeight, mode = 'single', onBlur, onChange: onChangeProp, onClear: onClearProp, onFocus, onScroll, options: optionsProp, placeholder = '', prefix, readOnly = false, renderValue, required = requiredFromFormControl || false, size, suffixActionIcon, type = 'default', value: valueProp, dropdownZIndex, globalPortal = true, } = props;
|
|
18
18
|
const [open, toggleOpen] = useState(false);
|
|
19
19
|
const onOpen = useCallback(() => {
|
|
20
20
|
// Prevent opening when readOnly is true
|
|
@@ -232,7 +232,7 @@ const Select = forwardRef(function Select(props, ref) {
|
|
|
232
232
|
onChange,
|
|
233
233
|
value,
|
|
234
234
|
}), [onChange, value]);
|
|
235
|
-
return (jsx(SelectControlContext.Provider, { value: context, children: jsx("div", { ref: nodeRef, className: cx(selectClasses.host, fullWidth && selectClasses.hostFullWidth), children: jsx(Dropdown, { disabled: readOnly || disabled,
|
|
235
|
+
return (jsx(SelectControlContext.Provider, { value: context, children: jsx("div", { ref: nodeRef, className: cx(selectClasses.host, fullWidth && selectClasses.hostFullWidth, mode && selectClasses.hostMode(mode)), children: jsx(Dropdown, { disabled: readOnly || disabled, maxHeight: menuMaxHeight, mode: mode, onScroll: onScroll, onSelect: handleDropdownSelect, onVisibilityChange: handleVisibilityChange, open: readOnly ? false : open, options: options, sameWidth: true, type: dropdownType, value: dropdownValue, zIndex: dropdownZIndex, globalPortal: globalPortal, children: jsx(SelectTrigger, { ref: composedRef, active: !readOnly && open, className: className, clearable: clearable, disabled: disabled, error: error, fullWidth: fullWidth, inputRef: inputRef, mode: mode, onTagClose: onChange, onClear: onClear, onKeyDown: onKeyDownTextField, prefix: prefix, readOnly: readOnly, ...(mode === 'single' && renderValue ? { renderValue } : {}), required: required, inputProps: resolvedInputProps, size: size, suffixActionIcon: suffixActionIcon, value: value === null ? undefined : value }) }) }) }));
|
|
236
236
|
});
|
|
237
237
|
|
|
238
238
|
export { Select as default };
|