@mezzanine-ui/react 1.0.0-rc.0 → 1.0.0-rc.1
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/Badge/Badge.d.ts +4 -0
- package/Badge/Badge.js +2 -2
- package/Badge/typings.d.ts +13 -1
- package/Calendar/CalendarDays.js +4 -3
- package/Calendar/CalendarWeeks.js +4 -3
- package/Description/Description.d.ts +6 -1
- package/Description/Description.js +11 -4
- package/Description/DescriptionContent.d.ts +9 -3
- package/Description/DescriptionContent.js +4 -1
- package/Description/DescriptionContext.d.ts +6 -0
- package/Description/DescriptionContext.js +9 -0
- package/Description/index.d.ts +2 -0
- package/Description/index.js +1 -0
- package/Form/FormField.d.ts +6 -0
- package/Form/FormField.js +2 -2
- package/Form/FormHintText.d.ts +12 -0
- package/Form/FormHintText.js +3 -2
- package/Navigation/Navigation.d.ts +4 -0
- package/Navigation/Navigation.js +39 -3
- package/Navigation/NavigationFooter.js +19 -2
- package/Navigation/NavigationOption.d.ts +4 -0
- package/Navigation/NavigationOption.js +40 -19
- package/Navigation/NavigationOverflowMenuOption.js +11 -7
- package/Navigation/NavigationUserMenu.d.ts +1 -0
- package/Navigation/NavigationUserMenu.js +24 -5
- package/Navigation/context.d.ts +2 -0
- package/Navigation/context.js +4 -1
- package/Picker/RangePickerTrigger.js +1 -1
- package/Section/Section.js +6 -6
- package/Transition/Collapse.d.ts +2 -1
- package/Transition/Collapse.js +2 -1
- package/Upload/Upload.js +63 -9
- package/Upload/UploadPictureCard.d.ts +25 -15
- package/Upload/UploadPictureCard.js +14 -6
- package/package.json +4 -4
package/Badge/Badge.d.ts
CHANGED
|
@@ -6,24 +6,28 @@ declare const Badge: import("react").ForwardRefExoticComponent<(Omit<Omit<Native
|
|
|
6
6
|
children?: never;
|
|
7
7
|
count: number;
|
|
8
8
|
overflowCount?: number;
|
|
9
|
+
size?: never;
|
|
9
10
|
text?: never;
|
|
10
11
|
variant: BadgeCountVariant;
|
|
11
12
|
}, "ref"> | Omit<Omit<NativeElementPropsWithoutKeyAndRef<"span">, "children"> & {
|
|
12
13
|
children?: never;
|
|
13
14
|
count?: never;
|
|
14
15
|
overflowCount?: never;
|
|
16
|
+
size?: import("@mezzanine-ui/core/badge").BadgeTextSize;
|
|
15
17
|
text?: string;
|
|
16
18
|
variant: import("@mezzanine-ui/core/badge").BadgeDotVariant;
|
|
17
19
|
}, "ref"> | Omit<Omit<NativeElementPropsWithoutKeyAndRef<"span">, "children"> & {
|
|
18
20
|
children?: import("react").ReactNode;
|
|
19
21
|
count?: never;
|
|
20
22
|
overflowCount?: never;
|
|
23
|
+
size?: never;
|
|
21
24
|
text?: never;
|
|
22
25
|
variant: import("@mezzanine-ui/core/badge").BadgeDotVariant;
|
|
23
26
|
}, "ref"> | Omit<Omit<NativeElementPropsWithoutKeyAndRef<"span">, "children"> & {
|
|
24
27
|
children?: never;
|
|
25
28
|
count?: never;
|
|
26
29
|
overflowCount?: never;
|
|
30
|
+
size?: import("@mezzanine-ui/core/badge").BadgeTextSize;
|
|
27
31
|
text: string;
|
|
28
32
|
variant: import("@mezzanine-ui/core/badge").BadgeTextVariant;
|
|
29
33
|
}, "ref">) & import("react").RefAttributes<HTMLSpanElement>>;
|
package/Badge/Badge.js
CHANGED
|
@@ -14,8 +14,8 @@ const isCountVariant = (variant) => [
|
|
|
14
14
|
* The react component for `mezzanine` badge.
|
|
15
15
|
*/
|
|
16
16
|
const Badge = forwardRef(function Badge(props, ref) {
|
|
17
|
-
const { children, count, className, overflowCount, text, variant, ...rest } = props;
|
|
18
|
-
return (jsxs("div", { className: badgeClasses.container(!!children), children: [children, jsx("span", { ...rest, ref: ref, className: cx(badgeClasses.host, badgeClasses.variant(variant), { [badgeClasses.hide]: isCountVariant(variant) && count === 0 }, className), children: isCountVariant(variant)
|
|
17
|
+
const { children, count, className, overflowCount, size, text, variant, ...rest } = props;
|
|
18
|
+
return (jsxs("div", { className: badgeClasses.container(!!children), children: [children, jsx("span", { ...rest, ref: ref, className: cx(badgeClasses.host, badgeClasses.variant(variant), { [badgeClasses.hide]: isCountVariant(variant) && count === 0 }, size && badgeClasses.size(size), className), children: isCountVariant(variant)
|
|
19
19
|
? overflowCount && count > overflowCount
|
|
20
20
|
? `${overflowCount}+`
|
|
21
21
|
: count
|
package/Badge/typings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BadgeCountVariant, BadgeDotVariant, BadgeTextVariant } from '@mezzanine-ui/core/badge';
|
|
1
|
+
import { BadgeCountVariant, BadgeDotVariant, BadgeTextSize, BadgeTextVariant } from '@mezzanine-ui/core/badge';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { NativeElementPropsWithoutKeyAndRef } from 'react/src/utils/jsx-types';
|
|
4
4
|
export type BadgeProps = Omit<NativeElementPropsWithoutKeyAndRef<'span'>, 'children'> & BadgeVariantProps;
|
|
@@ -21,6 +21,11 @@ type BadgeCountProps = {
|
|
|
21
21
|
* it will show overflowCount suffixed with a "+".
|
|
22
22
|
*/
|
|
23
23
|
overflowCount?: number;
|
|
24
|
+
/**
|
|
25
|
+
* ONLY AVAILABLE FOR DOT WITH TEXT BADGE.
|
|
26
|
+
* Controls the size of the dot and text.
|
|
27
|
+
*/
|
|
28
|
+
size?: never;
|
|
24
29
|
/**
|
|
25
30
|
* ONLY AVAILABLE FOR DOT WITH TEXT BADGE.
|
|
26
31
|
* String displayed next to the dot badge.
|
|
@@ -35,6 +40,7 @@ type BadgeTextProps = {
|
|
|
35
40
|
children?: never;
|
|
36
41
|
count?: never;
|
|
37
42
|
overflowCount?: never;
|
|
43
|
+
size?: BadgeTextSize;
|
|
38
44
|
text: string;
|
|
39
45
|
variant: BadgeTextVariant;
|
|
40
46
|
};
|
|
@@ -42,6 +48,11 @@ type BadgeDotWithTextProps = {
|
|
|
42
48
|
children?: never;
|
|
43
49
|
count?: never;
|
|
44
50
|
overflowCount?: never;
|
|
51
|
+
/**
|
|
52
|
+
* Controls the size of the text.
|
|
53
|
+
* @default 'main'
|
|
54
|
+
*/
|
|
55
|
+
size?: BadgeTextSize;
|
|
45
56
|
text?: string;
|
|
46
57
|
variant: BadgeDotVariant;
|
|
47
58
|
};
|
|
@@ -49,6 +60,7 @@ type BadgeDotProps = {
|
|
|
49
60
|
children?: ReactNode;
|
|
50
61
|
count?: never;
|
|
51
62
|
overflowCount?: never;
|
|
63
|
+
size?: never;
|
|
52
64
|
text?: never;
|
|
53
65
|
variant: BadgeDotVariant;
|
|
54
66
|
};
|
package/Calendar/CalendarDays.js
CHANGED
|
@@ -14,10 +14,11 @@ import cx from 'clsx';
|
|
|
14
14
|
* You may use it to compose your own calendar.
|
|
15
15
|
*/
|
|
16
16
|
function CalendarDays(props) {
|
|
17
|
-
const { locale, getCalendarGrid, getDate, getMonth, getNow, isDateIncluded, isSameDate, setDate, setMonth, setHour, setMinute, setSecond, setMillisecond, } = useCalendarContext();
|
|
17
|
+
const { locale, getCalendarGrid, getDate, getMonth, getNow, getWeekends, isDateIncluded, isSameDate, setDate, setMonth, setHour, setMinute, setSecond, setMillisecond, } = useCalendarContext();
|
|
18
18
|
const { className, displayWeekDayLocale = locale, isYearDisabled, isMonthDisabled, isDateDisabled, isDateInRange, onClick: onClickProp, onDateHover, renderAnnotations, referenceDate, value, ...rest } = props;
|
|
19
|
+
const weekends = useMemo(() => getWeekends(displayWeekDayLocale), [getWeekends, displayWeekDayLocale]);
|
|
19
20
|
const daysGrid = useMemo(() => getCalendarGrid(referenceDate, displayWeekDayLocale), [getCalendarGrid, displayWeekDayLocale, referenceDate]);
|
|
20
|
-
return (jsx("div", { ...rest, className: cx(calendarClasses.board, className), children: jsxs("div", { className: calendarClasses.daysGrid, children: [jsx(CalendarDayOfWeek, { displayWeekDayLocale: displayWeekDayLocale }), daysGrid.map((week, index) => (jsx("div", { className: calendarClasses.row, children: week.map((dateNum) => {
|
|
21
|
+
return (jsx("div", { ...rest, className: cx(calendarClasses.board, className), children: jsxs("div", { className: calendarClasses.daysGrid, children: [jsx(CalendarDayOfWeek, { displayWeekDayLocale: displayWeekDayLocale }), daysGrid.map((week, index) => (jsx("div", { className: calendarClasses.row, children: week.map((dateNum, dayIndex) => {
|
|
21
22
|
const isPrevMonth = index === 0 && dateNum > 7;
|
|
22
23
|
const isNextMonth = index > 3 && dateNum <= 14;
|
|
23
24
|
const thisMonth = getMonth(referenceDate);
|
|
@@ -66,7 +67,7 @@ function CalendarDays(props) {
|
|
|
66
67
|
]
|
|
67
68
|
.filter(Boolean)
|
|
68
69
|
.join(', ');
|
|
69
|
-
return (jsx(CalendarCell, { mode: "day", today: isSameDate(date, getNow()),
|
|
70
|
+
return (jsx(CalendarCell, { active: active, disabled: isPrevMonth || isNextMonth, isRangeEnd: inRangeEnd, isRangeStart: inRangeStart, isWeekend: weekends[dayIndex], mode: "day", today: isSameDate(date, getNow()), withAnnotation: Boolean(renderAnnotations), children: jsxs("button", { type: "button", "aria-disabled": disabled, disabled: disabled, "aria-label": ariaLabel, "aria-pressed": active, "aria-current": isToday ? 'date' : undefined, onMouseEnter: onMouseEnter, className: cx(calendarClasses.button, {
|
|
70
71
|
[calendarClasses.buttonInRange]: inRange,
|
|
71
72
|
[calendarClasses.buttonActive]: active,
|
|
72
73
|
[calendarClasses.buttonDisabled]: disabled,
|
|
@@ -13,8 +13,9 @@ import cx from 'clsx';
|
|
|
13
13
|
* You may use it to compose your own calendar.
|
|
14
14
|
*/
|
|
15
15
|
function CalendarWeeks(props) {
|
|
16
|
-
const { locale, getCalendarGrid, getWeek, getDate, getMonth, getNow, isInMonth, isSameDate, isWeekIncluded, setDate, setMonth, setHour, setMinute, setSecond, setMillisecond, getCurrentWeekFirstDate, } = useCalendarContext();
|
|
16
|
+
const { locale, getCalendarGrid, getWeek, getDate, getMonth, getNow, getWeekends, isInMonth, isSameDate, isWeekIncluded, setDate, setMonth, setHour, setMinute, setSecond, setMillisecond, getCurrentWeekFirstDate, } = useCalendarContext();
|
|
17
17
|
const { className, displayWeekDayLocale = locale, isYearDisabled, isMonthDisabled, isWeekDisabled, isWeekInRange, onClick: onClickProp, onWeekHover, referenceDate, value, ...rest } = props;
|
|
18
|
+
const weekends = useMemo(() => getWeekends(displayWeekDayLocale), [getWeekends, displayWeekDayLocale]);
|
|
18
19
|
const daysGrid = useMemo(() => getCalendarGrid(referenceDate, displayWeekDayLocale), [getCalendarGrid, referenceDate, displayWeekDayLocale]);
|
|
19
20
|
// Pre-calculate all weeks data including dates and week first dates
|
|
20
21
|
const weeksData = useMemo(() => {
|
|
@@ -143,8 +144,8 @@ function CalendarWeeks(props) {
|
|
|
143
144
|
isSameDate(currentDate, lastWeekDatesMap.lastWeekDates[6]);
|
|
144
145
|
cellActive = isFirstWeekFirstDate || isLastWeekLastDate;
|
|
145
146
|
}
|
|
146
|
-
return (jsx(CalendarCell, {
|
|
147
|
-
!isInMonth(dates[dateIndex], getMonth(referenceDate)),
|
|
147
|
+
return (jsx(CalendarCell, { active: cellActive, disabled: disabled ||
|
|
148
|
+
!isInMonth(dates[dateIndex], getMonth(referenceDate)), isRangeEnd: isLastWeekLastDate, isRangeStart: isFirstWeekFirstDate, isWeekend: weekends[dateIndex], mode: "week", today: isSameDate(dates[dateIndex], getNow()), children: jsx("div", { className: cx(calendarClasses.button, {
|
|
148
149
|
[calendarClasses.buttonInRange]: weekIncluded,
|
|
149
150
|
[calendarClasses.buttonActive]: cellActive,
|
|
150
151
|
}), style: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
-
import { DescriptionOrientation } from '@mezzanine-ui/core/description';
|
|
2
|
+
import { DescriptionOrientation, DescriptionSize } from '@mezzanine-ui/core/description';
|
|
3
3
|
import { DescriptionTitleProps } from './DescriptionTitle';
|
|
4
4
|
import { DescriptionContentProps } from './DescriptionContent';
|
|
5
5
|
import { BadgeProps } from '../Badge/typings';
|
|
@@ -21,6 +21,11 @@ export type DescriptionProps = DistributiveOmit<DescriptionTitleProps, 'classNam
|
|
|
21
21
|
* @default 'horizontal'
|
|
22
22
|
*/
|
|
23
23
|
orientation?: DescriptionOrientation;
|
|
24
|
+
/**
|
|
25
|
+
* Controls the text size of the description content
|
|
26
|
+
* @default 'main'
|
|
27
|
+
*/
|
|
28
|
+
size?: DescriptionSize;
|
|
24
29
|
/**
|
|
25
30
|
* title text for description
|
|
26
31
|
*/
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import { forwardRef } from 'react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, isValidElement, cloneElement } from 'react';
|
|
4
4
|
import { descriptionClasses } from '@mezzanine-ui/core/description';
|
|
5
5
|
import DescriptionTitle from './DescriptionTitle.js';
|
|
6
|
+
import { DescriptionContext } from './DescriptionContext.js';
|
|
6
7
|
import cx from 'clsx';
|
|
7
8
|
|
|
8
9
|
const Description = forwardRef(function Description(props, ref) {
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
var _a;
|
|
11
|
+
const { children, className, orientation = 'horizontal', size = 'main', title, ...rest } = props;
|
|
12
|
+
const injectedChildren = isValidElement(children)
|
|
13
|
+
? cloneElement(children, {
|
|
14
|
+
size: (_a = children.props.size) !== null && _a !== void 0 ? _a : size,
|
|
15
|
+
})
|
|
16
|
+
: children;
|
|
17
|
+
return (jsx(DescriptionContext.Provider, { value: { size }, children: jsxs("div", { className: cx(descriptionClasses.host, descriptionClasses.orientation(orientation), className), ref: ref, children: [jsx(DescriptionTitle, { ...rest, children: title }), injectedChildren] }) }));
|
|
11
18
|
});
|
|
12
19
|
|
|
13
20
|
export { Description as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IconDefinition } from '@mezzanine-ui/icons';
|
|
2
|
-
import {
|
|
2
|
+
import { DescriptionContentVariant, DescriptionSize } from '@mezzanine-ui/core/description';
|
|
3
3
|
interface DescriptionContentBaseProps {
|
|
4
4
|
/**
|
|
5
5
|
* Custom class name for content
|
|
@@ -10,8 +10,9 @@ interface DescriptionContentBaseProps {
|
|
|
10
10
|
*/
|
|
11
11
|
children: string;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Controls the text size of the content. When provided, overrides the size
|
|
14
|
+
* inherited from a parent <Description> component.
|
|
15
|
+
* @default context value or 'main'
|
|
15
16
|
*/
|
|
16
17
|
size?: DescriptionSize;
|
|
17
18
|
/**
|
|
@@ -31,6 +32,11 @@ interface DescriptionContentBaseProps {
|
|
|
31
32
|
interface DescriptionContentWithClickableIcon {
|
|
32
33
|
className?: string;
|
|
33
34
|
children: string;
|
|
35
|
+
/**
|
|
36
|
+
* Controls the text size of the content. When provided, overrides the size
|
|
37
|
+
* inherited from a parent <Description> component.
|
|
38
|
+
* @default context value or 'main'
|
|
39
|
+
*/
|
|
34
40
|
size?: DescriptionSize;
|
|
35
41
|
variant: Extract<DescriptionContentVariant, 'with-icon'>;
|
|
36
42
|
icon: IconDefinition;
|
|
@@ -3,11 +3,14 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
3
3
|
import { forwardRef } from 'react';
|
|
4
4
|
import { CaretUpIcon, CaretDownIcon } from '@mezzanine-ui/icons';
|
|
5
5
|
import { descriptionClasses } from '@mezzanine-ui/core/description';
|
|
6
|
+
import { useDescriptionContext } from './DescriptionContext.js';
|
|
6
7
|
import Icon from '../Icon/Icon.js';
|
|
7
8
|
import cx from 'clsx';
|
|
8
9
|
|
|
9
10
|
const DescriptionContent = forwardRef(function DescriptionContent(props, ref) {
|
|
10
|
-
const { className, children, icon, onClickIcon, size
|
|
11
|
+
const { className, children, icon, onClickIcon, size: sizeProp, variant = 'normal', } = props;
|
|
12
|
+
const { size: contextSize } = useDescriptionContext();
|
|
13
|
+
const size = sizeProp !== null && sizeProp !== void 0 ? sizeProp : contextSize;
|
|
11
14
|
return (jsxs("span", { className: cx(descriptionClasses.contentHost, descriptionClasses.contentVariant(variant), descriptionClasses.contentSize(size), className), ref: ref, children: [variant === 'trend-up' && (jsx(Icon, { className: descriptionClasses.contentTrendUp, icon: CaretUpIcon, size: 16 })), variant === 'trend-down' && (jsx(Icon, { className: descriptionClasses.contentTrendDown, icon: CaretDownIcon, size: 16 })), children, variant === 'with-icon' && icon && (jsx(Icon, { className: descriptionClasses.contentIcon, icon: icon, size: 16, onClick: onClickIcon }))] }));
|
|
12
15
|
});
|
|
13
16
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DescriptionSize } from '@mezzanine-ui/core/description';
|
|
2
|
+
export interface DescriptionContextValue {
|
|
3
|
+
size: DescriptionSize;
|
|
4
|
+
}
|
|
5
|
+
export declare const DescriptionContext: import("react").Context<DescriptionContextValue>;
|
|
6
|
+
export declare const useDescriptionContext: () => DescriptionContextValue;
|
package/Description/index.d.ts
CHANGED
|
@@ -6,3 +6,5 @@ export { default as DescriptionContent } from './DescriptionContent';
|
|
|
6
6
|
export type { DescriptionContentProps } from './DescriptionContent';
|
|
7
7
|
export { default as DescriptionGroup } from './DescriptionGroup';
|
|
8
8
|
export type { DescriptionGroupProps } from './DescriptionGroup';
|
|
9
|
+
export { DescriptionContext } from './DescriptionContext';
|
|
10
|
+
export type { DescriptionContextValue } from './DescriptionContext';
|
package/Description/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { default as Description } from './Description.js';
|
|
|
2
2
|
export { default as DescriptionTitle } from './DescriptionTitle.js';
|
|
3
3
|
export { default as DescriptionContent } from './DescriptionContent.js';
|
|
4
4
|
export { default as DescriptionGroup } from './DescriptionGroup.js';
|
|
5
|
+
export { DescriptionContext } from './DescriptionContext.js';
|
package/Form/FormField.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ export interface FormFieldProps extends NativeElementPropsWithoutKeyAndRef<'div'
|
|
|
37
37
|
* The icon to display alongside the hint text.
|
|
38
38
|
*/
|
|
39
39
|
hintTextIcon?: IconDefinition;
|
|
40
|
+
/**
|
|
41
|
+
* Whether to display the hint text icon.
|
|
42
|
+
* When false, neither the custom icon nor the default severity icon will be shown.
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
showHintTextIcon?: boolean;
|
|
40
46
|
/**
|
|
41
47
|
* The label text for the form field.
|
|
42
48
|
*/
|
package/Form/FormField.js
CHANGED
|
@@ -10,7 +10,7 @@ import cx from 'clsx';
|
|
|
10
10
|
* The React component for `mezzanine` form field.
|
|
11
11
|
*/
|
|
12
12
|
const FormField = forwardRef(function FormField(props, ref) {
|
|
13
|
-
const { children, className, counter, counterColor, controlFieldSlotLayout = ControlFieldSlotLayout.MAIN, density, disabled = false, fullWidth = false, hintText, hintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker, labelSpacing = FormFieldLabelSpacing.MAIN, layout = FormFieldLayout.HORIZONTAL, name, required = false, severity = 'info', ...rest } = props;
|
|
13
|
+
const { children, className, counter, counterColor, controlFieldSlotLayout = ControlFieldSlotLayout.MAIN, density, disabled = false, fullWidth = false, hintText, hintTextIcon, showHintTextIcon, label, labelInformationIcon, labelInformationText, labelOptionalMarker, labelSpacing = FormFieldLabelSpacing.MAIN, layout = FormFieldLayout.HORIZONTAL, name, required = false, severity = 'info', ...rest } = props;
|
|
14
14
|
const formControl = {
|
|
15
15
|
disabled,
|
|
16
16
|
fullWidth,
|
|
@@ -30,7 +30,7 @@ const FormField = forwardRef(function FormField(props, ref) {
|
|
|
30
30
|
[formFieldClasses.fullWidth]: fullWidth,
|
|
31
31
|
}, className), children: jsxs(FormControlContext.Provider, { value: formControl, children: [label && (jsx(FormLabel, { className: cx(formFieldClasses.labelArea, labelSpacingClass), htmlFor: name, informationIcon: labelInformationIcon, informationText: labelInformationText, labelText: label, optionalMarker: labelOptionalMarker })), jsxs("div", { className: cx(formFieldClasses.dataEntry), children: [jsx("div", { className: cx(`${formFieldClasses.controlFieldSlot}--${controlFieldSlotLayout}`), children: children }), hintText || hintTextIcon || counter ? (jsxs("div", { className: cx(formFieldClasses.hintTextAndCounterArea, {
|
|
32
32
|
[formFieldClasses.hintTextAndCounterArea + '--align-right']: !(hintText || hintTextIcon) && counter,
|
|
33
|
-
}), children: [(hintText || hintTextIcon) && (jsx(FormHintText, { hintText: hintText, hintTextIcon: hintTextIcon, severity: severity })), counter && (jsx("span", { className: cx(formFieldClasses.counter, formFieldClasses.counterColor(counterColor || FormFieldCounterColor.INFO)), children: counter }))] })) : null] })] }) }));
|
|
33
|
+
}), children: [(hintText || hintTextIcon) && (jsx(FormHintText, { hintText: hintText, hintTextIcon: hintTextIcon, severity: severity, showHintTextIcon: showHintTextIcon })), counter && (jsx("span", { className: cx(formFieldClasses.counter, formFieldClasses.counterColor(counterColor || FormFieldCounterColor.INFO)), children: counter }))] })) : null] })] }) }));
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
export { FormField as default };
|
package/Form/FormHintText.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export type FormHintTextProps = NativeElementPropsWithoutKeyAndRef<'span'> & {
|
|
|
17
17
|
* if not provided, no icon will be displayed.
|
|
18
18
|
*/
|
|
19
19
|
severity?: keyof typeof formHintIcons | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to display the hint text icon.
|
|
22
|
+
* When false, neither the custom icon nor the default severity icon will be shown.
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
showHintTextIcon?: boolean;
|
|
20
26
|
};
|
|
21
27
|
/**
|
|
22
28
|
* The React component for `mezzanine` form message.
|
|
@@ -37,5 +43,11 @@ declare const FormHintText: import("react").ForwardRefExoticComponent<NativeElem
|
|
|
37
43
|
* if not provided, no icon will be displayed.
|
|
38
44
|
*/
|
|
39
45
|
severity?: keyof typeof formHintIcons | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Whether to display the hint text icon.
|
|
48
|
+
* When false, neither the custom icon nor the default severity icon will be shown.
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
showHintTextIcon?: boolean;
|
|
40
52
|
} & import("react").RefAttributes<HTMLSpanElement>>;
|
|
41
53
|
export default FormHintText;
|
package/Form/FormHintText.js
CHANGED
|
@@ -9,9 +9,10 @@ import cx from 'clsx';
|
|
|
9
9
|
* The React component for `mezzanine` form message.
|
|
10
10
|
*/
|
|
11
11
|
const FormHintText = forwardRef(function FormHintText(props, ref) {
|
|
12
|
-
const { className, hintText, hintTextIcon, severity = 'info', ...rest } = props;
|
|
12
|
+
const { className, hintText, hintTextIcon, severity = 'info', showHintTextIcon = true, ...rest } = props;
|
|
13
13
|
const defaultIcon = severity ? formHintIcons[severity] : null;
|
|
14
|
-
return (jsxs("span", { ...rest, ref: ref, className: cx(formFieldClasses.hintText, severity ? formFieldClasses.hintTextSeverity(severity) : undefined, className), children: [
|
|
14
|
+
return (jsxs("span", { ...rest, ref: ref, className: cx(formFieldClasses.hintText, severity ? formFieldClasses.hintTextSeverity(severity) : undefined, className), children: [showHintTextIcon &&
|
|
15
|
+
(hintTextIcon ? (jsx(Icon, { className: formFieldClasses.hintTextIcon, icon: hintTextIcon, color: severity })) : (defaultIcon && (jsx(Icon, { className: formFieldClasses.hintTextIcon, icon: defaultIcon, color: severity })))), hintText] }));
|
|
15
16
|
});
|
|
16
17
|
|
|
17
18
|
export { FormHintText as default };
|
|
@@ -32,6 +32,10 @@ export interface NavigationProps extends Omit<NativeElementPropsWithoutKeyAndRef
|
|
|
32
32
|
* Called when a navigation option is clicked.
|
|
33
33
|
*/
|
|
34
34
|
onOptionClick?: (activePath?: string[]) => void;
|
|
35
|
+
/**
|
|
36
|
+
* Custom component for rendering navigation options which have an href prop.
|
|
37
|
+
*/
|
|
38
|
+
optionsAnchorComponent?: React.ElementType;
|
|
35
39
|
}
|
|
36
40
|
declare const Navigation: import("react").ForwardRefExoticComponent<NavigationProps & import("react").RefAttributes<HTMLElement>>;
|
|
37
41
|
export default Navigation;
|
package/Navigation/Navigation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef, useState, useCallback, useMemo, Children, isValidElement } from 'react';
|
|
3
|
+
import { forwardRef, useState, useCallback, useMemo, Children, isValidElement, useRef, useEffect } from 'react';
|
|
4
4
|
import { navigationClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import NavigationOption from './NavigationOption.js';
|
|
6
6
|
import NavigationHeader from './NavigationHeader.js';
|
|
@@ -15,17 +15,19 @@ import Input from '../Input/Input.js';
|
|
|
15
15
|
import cx from 'clsx';
|
|
16
16
|
|
|
17
17
|
const Navigation = forwardRef((props, ref) => {
|
|
18
|
-
const { activatedPath, children = [], className, collapsed: collapsedProp, filter, onCollapseChange, onOptionClick, ...rest } = props;
|
|
18
|
+
const { activatedPath, children = [], className, collapsed: collapsedProp, filter, onCollapseChange, onOptionClick, optionsAnchorComponent, ...rest } = props;
|
|
19
19
|
const [collapsedState, setCollapsedState] = useState(collapsedProp || false);
|
|
20
20
|
const collapsed = collapsedProp !== null && collapsedProp !== void 0 ? collapsedProp : collapsedState;
|
|
21
21
|
const handleCollapseChange = useCallback((newCollapsed) => {
|
|
22
22
|
setCollapsedState(newCollapsed);
|
|
23
23
|
onCollapseChange === null || onCollapseChange === void 0 ? void 0 : onCollapseChange(newCollapsed);
|
|
24
24
|
}, [onCollapseChange]);
|
|
25
|
-
const [innerActivatedPath, setInnerActivatedPath] = useState([]);
|
|
25
|
+
const [innerActivatedPath, setInnerActivatedPath] = useState(activatedPath || []);
|
|
26
|
+
const [activatedPathKey, setActivatedPathKey] = useState(activatedPath ? activatedPath.join('::') : '');
|
|
26
27
|
const combineSetActivatedPath = useCallback((newActivatedPath) => {
|
|
27
28
|
onOptionClick === null || onOptionClick === void 0 ? void 0 : onOptionClick(newActivatedPath);
|
|
28
29
|
setInnerActivatedPath(newActivatedPath);
|
|
30
|
+
setActivatedPathKey(newActivatedPath.join('::'));
|
|
29
31
|
}, [onOptionClick]);
|
|
30
32
|
const currentPathname = useCurrentPathname();
|
|
31
33
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
@@ -65,6 +67,38 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
65
67
|
});
|
|
66
68
|
return { headerComponent, footerComponent, items, level1Items };
|
|
67
69
|
}, [flattenedChildren]);
|
|
70
|
+
const hrefActivated = useRef(false);
|
|
71
|
+
// Scan level1Items and its descendants (up to level3) to find out whether href matches to determine whether to preset expansion and activatedPath
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (hrefActivated.current || !currentPathname) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const checkActivatedPathKey = (items, path) => {
|
|
77
|
+
for (const item of items) {
|
|
78
|
+
if (!isValidElement(item) || item.type !== NavigationOption) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const newKey = item.props.id || item.props.title || item.props.href;
|
|
82
|
+
if (!newKey) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const newPath = [...path, newKey];
|
|
86
|
+
if (item.props.href && item.props.href === currentPathname) {
|
|
87
|
+
combineSetActivatedPath(newPath);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (item.props.children) {
|
|
91
|
+
const flattenedChildren = flattenChildren(item.props.children);
|
|
92
|
+
if (checkActivatedPathKey(flattenedChildren, newPath)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
checkActivatedPathKey(level1Items, []);
|
|
100
|
+
hrefActivated.current = true;
|
|
101
|
+
}, [combineSetActivatedPath, currentPathname, level1Items]);
|
|
68
102
|
const { contentRef, visibleCount } = useVisibleItems(items, collapsed);
|
|
69
103
|
const { collapsedItems, collapsedMenuItems } = useMemo(() => {
|
|
70
104
|
return {
|
|
@@ -77,11 +111,13 @@ const Navigation = forwardRef((props, ref) => {
|
|
|
77
111
|
const [filterText, setFilterText] = useState('');
|
|
78
112
|
return (jsx("nav", { ...rest, ref: ref, className: cx(navigationClasses.host, collapsed ? navigationClasses.collapsed : navigationClasses.expand, className), children: jsxs(NavigationActivatedContext.Provider, { value: {
|
|
79
113
|
activatedPath: activatedPath || innerActivatedPath,
|
|
114
|
+
activatedPathKey,
|
|
80
115
|
collapsed,
|
|
81
116
|
currentPathname,
|
|
82
117
|
filterText,
|
|
83
118
|
handleCollapseChange,
|
|
84
119
|
setActivatedPath: combineSetActivatedPath,
|
|
120
|
+
optionsAnchorComponent,
|
|
85
121
|
}, children: [headerComponent, jsx(NavigationOptionLevelContext.Provider, { value: navigationOptionLevelContextDefaultValues, children: jsxs("div", { ref: contentRef, className: navigationClasses.content, children: [filter && (jsx(Input, { size: "sub", variant: "search", className: cx(navigationClasses.searchInput), value: filterText, onChange: (e) => setFilterText(e.target.value) })), jsxs("ul", { children: [collapsed ? collapsedItems : items, collapsed &&
|
|
86
122
|
visibleCount !== null &&
|
|
87
123
|
visibleCount < level1Items.length && (jsx(NavigationOverflowMenu, { items: collapsedMenuItems }))] }, collapsed ? 'collapsed' : 'expand')] }) }), footerComponent] }) }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, use, Children, isValidElement } from 'react';
|
|
2
|
+
import { forwardRef, use, useRef, useState, useEffect, Children, isValidElement } from 'react';
|
|
3
3
|
import { navigationFooterClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import NavigationUserMenu from './NavigationUserMenu.js';
|
|
5
5
|
import { NavigationActivatedContext } from './context.js';
|
|
@@ -22,7 +22,24 @@ const NavigationFooter = forwardRef((props, ref) => {
|
|
|
22
22
|
const { children, className, ...rest } = props;
|
|
23
23
|
const { collapsed } = use(NavigationActivatedContext);
|
|
24
24
|
const { userMenu, otherChildren } = resolveChildren(children);
|
|
25
|
-
|
|
25
|
+
const iconsRef = useRef(null);
|
|
26
|
+
const [iconsWidth, setIconsWidth] = useState(0);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!iconsRef.current)
|
|
29
|
+
return;
|
|
30
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
31
|
+
if (iconsRef.current) {
|
|
32
|
+
setIconsWidth(iconsRef.current.offsetWidth);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
resizeObserver.observe(iconsRef.current);
|
|
36
|
+
return () => {
|
|
37
|
+
resizeObserver.disconnect();
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
return (jsxs("footer", { ...rest, ref: ref, className: cx(navigationFooterClasses.host, collapsed && navigationFooterClasses.collapsed, className), style: {
|
|
41
|
+
['--icons-width']: `${iconsWidth}px`,
|
|
42
|
+
}, children: [userMenu, jsx("span", { ref: iconsRef, className: navigationFooterClasses.icons, children: otherChildren })] }));
|
|
26
43
|
});
|
|
27
44
|
|
|
28
45
|
export { NavigationFooter as default };
|
|
@@ -13,6 +13,10 @@ export interface NavigationOptionProps extends Omit<NativeElementPropsWithoutKey
|
|
|
13
13
|
* Strict children with `NavigationOption`.
|
|
14
14
|
*/
|
|
15
15
|
children?: NavigationOptionChildren;
|
|
16
|
+
/**
|
|
17
|
+
* Custom component to render, it should support `href` and `onClick` props if provided.
|
|
18
|
+
*/
|
|
19
|
+
anchorComponent?: React.ElementType;
|
|
16
20
|
/**
|
|
17
21
|
* Icon of the item.
|
|
18
22
|
*/
|
|
@@ -1,34 +1,32 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
-
import { forwardRef,
|
|
3
|
+
import { forwardRef, use, useState, useId, useMemo, Children, isValidElement, useEffect, useRef } from 'react';
|
|
4
4
|
import { navigationOptionClasses } from '@mezzanine-ui/core/navigation';
|
|
5
5
|
import { ChevronUpIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
6
|
-
import {
|
|
6
|
+
import { NavigationActivatedContext, NavigationOptionLevelContext } from './context.js';
|
|
7
7
|
import { flattenChildren } from '../utils/flatten-children.js';
|
|
8
8
|
import Badge from '../Badge/Badge.js';
|
|
9
9
|
import Tooltip from '../Tooltip/Tooltip.js';
|
|
10
10
|
import Icon from '../Icon/Icon.js';
|
|
11
|
+
import Fade from '../Transition/Fade.js';
|
|
11
12
|
import Collapse from '../Transition/Collapse.js';
|
|
12
13
|
import cx from 'clsx';
|
|
13
14
|
|
|
14
15
|
const NavigationOption = forwardRef((props, ref) => {
|
|
15
|
-
|
|
16
|
-
const
|
|
16
|
+
var _a;
|
|
17
|
+
const { active, children, className, anchorComponent, defaultOpen, href, icon, id, onTriggerClick, title, ...rest } = props;
|
|
18
|
+
const { activatedPathKey, activatedPath, collapsed, filterText, handleCollapseChange, setActivatedPath, optionsAnchorComponent, } = use(NavigationActivatedContext);
|
|
19
|
+
const [open, setOpen] = useState(defaultOpen !== null && defaultOpen !== void 0 ? defaultOpen : false);
|
|
17
20
|
const GroupToggleIcon = open ? ChevronUpIcon : ChevronDownIcon;
|
|
18
21
|
const { level, path: parentPath } = use(NavigationOptionLevelContext);
|
|
19
22
|
const currentLevel = level + 1; // start as 1
|
|
20
23
|
const uuid = useId();
|
|
21
24
|
const currentKey = id || title || href || uuid;
|
|
22
25
|
const currentPath = useMemo(() => [...parentPath, currentKey], [parentPath, currentKey]);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
setOpen(true);
|
|
28
|
-
}
|
|
29
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
-
}, []);
|
|
31
|
-
const Component = href ? 'a' : 'div';
|
|
26
|
+
const currentPathKey = currentPath.join('::');
|
|
27
|
+
const Component = href && !children
|
|
28
|
+
? ((_a = anchorComponent !== null && anchorComponent !== void 0 ? anchorComponent : optionsAnchorComponent) !== null && _a !== void 0 ? _a : 'a')
|
|
29
|
+
: 'div';
|
|
32
30
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
33
31
|
const { badge, items } = useMemo(() => {
|
|
34
32
|
let badgeComponent = null;
|
|
@@ -51,6 +49,13 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
51
49
|
});
|
|
52
50
|
return { badge: badgeComponent, items };
|
|
53
51
|
}, [flattenedChildren]);
|
|
52
|
+
// Default open if current path is activated
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (activatedPathKey === currentPathKey ||
|
|
55
|
+
activatedPathKey.startsWith(`${currentPathKey}::`)) {
|
|
56
|
+
setOpen(true);
|
|
57
|
+
}
|
|
58
|
+
}, [activatedPathKey, currentLevel, currentPathKey]);
|
|
54
59
|
const [filter, setFilter] = useState(true);
|
|
55
60
|
useEffect(() => {
|
|
56
61
|
var _a;
|
|
@@ -60,10 +65,28 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
60
65
|
}
|
|
61
66
|
setFilter((_a = (title.includes(filterText) || (href === null || href === void 0 ? void 0 : href.includes(filterText)))) !== null && _a !== void 0 ? _a : false);
|
|
62
67
|
}, [currentPath, filterText, href, title]);
|
|
68
|
+
const titleRef = useRef(null);
|
|
69
|
+
const [titleOverflow, setTitleOverflow] = useState(false);
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!titleRef.current)
|
|
72
|
+
return;
|
|
73
|
+
const checkOverflow = () => {
|
|
74
|
+
if (!titleRef.current)
|
|
75
|
+
return;
|
|
76
|
+
const { scrollWidth, clientWidth } = titleRef.current;
|
|
77
|
+
setTitleOverflow(scrollWidth > clientWidth);
|
|
78
|
+
};
|
|
79
|
+
checkOverflow();
|
|
80
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
81
|
+
resizeObserver.observe(titleRef.current);
|
|
82
|
+
return () => {
|
|
83
|
+
resizeObserver.disconnect();
|
|
84
|
+
};
|
|
85
|
+
}, [title]);
|
|
63
86
|
return (jsxs("li", { ...rest, ref: ref, className: cx(navigationOptionClasses.host, open && navigationOptionClasses.open, !children && navigationOptionClasses.basic, (active !== null && active !== void 0 ? active : (activatedPath === null || activatedPath === void 0 ? void 0 : activatedPath[currentLevel - 1]) === currentKey) &&
|
|
64
|
-
navigationOptionClasses.active, collapsed && navigationOptionClasses.collapsed, !collapsed && !filter && navigationOptionClasses.hidden, className), "data-id": currentKey, children: [jsx(Tooltip, { options: {
|
|
65
|
-
placement: 'right',
|
|
66
|
-
}, title: collapsed ? title : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipChildRef }) => (jsxs(Component, { className: cx(navigationOptionClasses.content, navigationOptionClasses.level(currentLevel)),
|
|
87
|
+
navigationOptionClasses.active, collapsed && navigationOptionClasses.collapsed, !collapsed && !filter && navigationOptionClasses.hidden, className), "data-id": currentKey, children: [jsx(Tooltip, { disablePortal: false, options: {
|
|
88
|
+
placement: collapsed ? 'right' : 'top',
|
|
89
|
+
}, title: collapsed || titleOverflow ? title : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipChildRef }) => (jsxs(Component, { className: cx(navigationOptionClasses.content, navigationOptionClasses.level(currentLevel)), href: Component === 'div' ? undefined : href, onClick: () => {
|
|
67
90
|
setOpen(!open);
|
|
68
91
|
onTriggerClick === null || onTriggerClick === void 0 ? void 0 : onTriggerClick(currentPath, currentKey, href);
|
|
69
92
|
if (collapsed) {
|
|
@@ -78,9 +101,7 @@ const NavigationOption = forwardRef((props, ref) => {
|
|
|
78
101
|
if (!children)
|
|
79
102
|
setActivatedPath(currentPath);
|
|
80
103
|
}
|
|
81
|
-
}, role: "menuitem", tabIndex: 0, children: [icon && jsx(Icon, { className: navigationOptionClasses.icon, icon: icon }), jsx("span", { className: navigationOptionClasses.title, children: title }), badge, children && (jsx(Icon, { className: navigationOptionClasses.toggleIcon, icon: GroupToggleIcon }))] })) }), children && !collapsed && (jsx(Collapse, { className: navigationOptionClasses.childrenWrapper,
|
|
82
|
-
width: '100%',
|
|
83
|
-
}, in: !!open, children: jsx(NavigationOptionLevelContext.Provider, { value: {
|
|
104
|
+
}, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: tooltipChildRef, role: "menuitem", tabIndex: 0, children: [icon && jsx(Icon, { className: navigationOptionClasses.icon, icon: icon }), jsx(Fade, { ref: titleRef, in: collapsed === false || !icon, children: jsx("span", { className: navigationOptionClasses.title, children: title }) }), badge, children && (jsx(Icon, { className: navigationOptionClasses.toggleIcon, icon: GroupToggleIcon }))] })) }), children && !collapsed && (jsx(Collapse, { lazyMount: true, className: cx(navigationOptionClasses.childrenWrapper), in: open, children: jsx(NavigationOptionLevelContext.Provider, { value: {
|
|
84
105
|
level: currentLevel,
|
|
85
106
|
path: currentPath,
|
|
86
107
|
}, children: jsx("ul", { className: navigationOptionClasses.group, children: items }) }) }))] }));
|
|
@@ -11,22 +11,26 @@ import Icon from '../Icon/Icon.js';
|
|
|
11
11
|
import cx from 'clsx';
|
|
12
12
|
|
|
13
13
|
const NavigationOverflowMenuOption = forwardRef((props, ref) => {
|
|
14
|
-
|
|
14
|
+
var _a;
|
|
15
|
+
const { active, children, className, anchorComponent, defaultOpen = false, href, icon, id, onTriggerClick, title, ...rest } = props;
|
|
15
16
|
const [open, setOpen] = useState(defaultOpen);
|
|
16
17
|
const { level, path: parentPath } = use(NavigationOptionLevelContext);
|
|
17
18
|
const currentLevel = level + 1; // start as 1
|
|
18
19
|
const uuid = useId();
|
|
19
20
|
const currentKey = id || title || href || uuid;
|
|
20
21
|
const currentPath = useMemo(() => [...parentPath, currentKey], [parentPath, currentKey]);
|
|
21
|
-
const
|
|
22
|
+
const currentPathKey = currentPath.join('::');
|
|
23
|
+
const { activatedPath, activatedPathKey, setActivatedPath, optionsAnchorComponent, } = use(NavigationActivatedContext);
|
|
24
|
+
// Default open if current path is activated
|
|
22
25
|
useEffect(() => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
26
|
+
if (activatedPathKey === currentPathKey ||
|
|
27
|
+
activatedPathKey.startsWith(`${currentPathKey}::`)) {
|
|
25
28
|
setOpen(true);
|
|
26
29
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
}, [activatedPathKey, currentLevel, currentPathKey]);
|
|
31
|
+
const Component = href
|
|
32
|
+
? ((_a = anchorComponent !== null && anchorComponent !== void 0 ? anchorComponent : optionsAnchorComponent) !== null && _a !== void 0 ? _a : 'a')
|
|
33
|
+
: 'div';
|
|
30
34
|
const flattenedChildren = useMemo(() => flattenChildren(children), [children]);
|
|
31
35
|
const { badge, items } = useMemo(() => {
|
|
32
36
|
let badgeComponent = null;
|
|
@@ -3,6 +3,7 @@ import { DropdownProps } from '../Dropdown';
|
|
|
3
3
|
export interface NavigationUserMenuProps extends Omit<DropdownProps, 'children' | 'type'> {
|
|
4
4
|
children?: ReactNode;
|
|
5
5
|
className?: string;
|
|
6
|
+
collapsedPlacement?: DropdownProps['placement'];
|
|
6
7
|
imgSrc?: string;
|
|
7
8
|
onClick?: () => void;
|
|
8
9
|
}
|
|
@@ -1,26 +1,45 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { forwardRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, useState, use, useRef, useEffect } from 'react';
|
|
3
3
|
import { navigationUserMenuClasses } from '@mezzanine-ui/core/navigation';
|
|
4
4
|
import { UserIcon, ChevronDownIcon } from '@mezzanine-ui/icons';
|
|
5
|
+
import { NavigationActivatedContext } from './context.js';
|
|
5
6
|
import Dropdown from '../Dropdown/Dropdown.js';
|
|
7
|
+
import Tooltip from '../Tooltip/Tooltip.js';
|
|
6
8
|
import Icon from '../Icon/Icon.js';
|
|
7
9
|
import cx from 'clsx';
|
|
8
10
|
|
|
9
11
|
const NavigationUserMenu = forwardRef((props, ref) => {
|
|
10
|
-
// shared props
|
|
11
12
|
const { children, className, imgSrc, onClick, ...rest } = props;
|
|
12
|
-
const { open: openProp, onClose, placement = 'top-end', onVisibilityChange, ...dropdownRest } = rest;
|
|
13
|
+
const { open: openProp, onClose, placement = 'top-end', collapsedPlacement = 'right-end', onVisibilityChange, ...dropdownRest } = rest;
|
|
13
14
|
const [imgError, setImgError] = useState(false);
|
|
14
15
|
const [_open, setOpen] = useState(false);
|
|
16
|
+
const { collapsed } = use(NavigationActivatedContext);
|
|
15
17
|
const open = openProp !== null && openProp !== void 0 ? openProp : _open;
|
|
16
|
-
|
|
18
|
+
const userNameRef = useRef(null);
|
|
19
|
+
const [userNameOverflow, setUserNameOverflow] = useState(false);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (!userNameRef.current)
|
|
22
|
+
return;
|
|
23
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
24
|
+
if (userNameRef.current) {
|
|
25
|
+
setUserNameOverflow(userNameRef.current.scrollWidth > userNameRef.current.offsetWidth);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
resizeObserver.observe(userNameRef.current);
|
|
29
|
+
return () => {
|
|
30
|
+
resizeObserver.disconnect();
|
|
31
|
+
};
|
|
32
|
+
}, []);
|
|
33
|
+
return (jsx(Dropdown, { ...dropdownRest, open: open, placement: collapsed ? collapsedPlacement : placement, onVisibilityChange: () => {
|
|
17
34
|
setOpen(!open);
|
|
18
35
|
onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(!open);
|
|
19
36
|
onClick === null || onClick === void 0 ? void 0 : onClick();
|
|
20
37
|
}, onClose: () => {
|
|
21
38
|
setOpen(false);
|
|
22
39
|
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
23
|
-
}, children:
|
|
40
|
+
}, children: jsx("button", { className: cx(navigationUserMenuClasses.host, open && navigationUserMenuClasses.open, className), ref: ref, type: "button", children: jsx(Tooltip, { disablePortal: false, options: {
|
|
41
|
+
placement: collapsed ? 'right' : 'top',
|
|
42
|
+
}, title: (collapsed || userNameOverflow) && !open ? children : undefined, children: ({ onMouseEnter, onMouseLeave, ref: tooltipRef }) => (jsxs("span", { className: navigationUserMenuClasses.content, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, ref: tooltipRef, children: [jsx("span", { className: navigationUserMenuClasses.avatar, children: imgError || !imgSrc ? (jsx(Icon, { icon: UserIcon })) : (jsx("img", { alt: "User avatar", className: navigationUserMenuClasses.avatar, src: imgSrc, onError: () => setImgError(true) })) }), children && (jsx("span", { className: navigationUserMenuClasses.userName, children: jsx("span", { ref: userNameRef, children: children }) })), jsx(Icon, { className: navigationUserMenuClasses.icon, icon: ChevronDownIcon })] })) }) }) }));
|
|
24
43
|
});
|
|
25
44
|
|
|
26
45
|
export { NavigationUserMenu as default };
|
package/Navigation/context.d.ts
CHANGED
|
@@ -8,9 +8,11 @@ export declare const NavigationOptionLevelContext: import("react").Context<{
|
|
|
8
8
|
}>;
|
|
9
9
|
export declare const NavigationActivatedContext: import("react").Context<{
|
|
10
10
|
activatedPath: string[];
|
|
11
|
+
activatedPathKey: string;
|
|
11
12
|
collapsed: boolean;
|
|
12
13
|
currentPathname: string | null;
|
|
13
14
|
filterText: string;
|
|
14
15
|
handleCollapseChange: (newCollapsed: boolean) => void;
|
|
15
16
|
setActivatedPath: (path: string[]) => void;
|
|
17
|
+
optionsAnchorComponent?: React.ElementType;
|
|
16
18
|
}>;
|
package/Navigation/context.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createContext } from 'react';
|
|
2
2
|
|
|
3
|
-
const navigationOptionLevelContextDefaultValues = {
|
|
3
|
+
const navigationOptionLevelContextDefaultValues = {
|
|
4
|
+
level: 0,
|
|
5
|
+
path: [],
|
|
6
|
+
};
|
|
4
7
|
const NavigationOptionLevelContext = createContext(navigationOptionLevelContextDefaultValues);
|
|
5
8
|
const NavigationActivatedContext = createContext(null);
|
|
6
9
|
|
|
@@ -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, 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 })] }));
|
|
72
|
+
return (jsxs(TextField, { ...restTextFieldProps, ...defaultTextFieldProps, ref: ref, className: cx(pickerClasses.host, pickerClasses.hostRange, 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 };
|
package/Section/Section.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { forwardRef, cloneElement, isValidElement } from 'react';
|
|
3
3
|
import { sectionClasses } from '@mezzanine-ui/core/section';
|
|
4
4
|
import ContentHeader from '../ContentHeader/ContentHeader.js';
|
|
@@ -11,9 +11,9 @@ function getDisplayName(element) {
|
|
|
11
11
|
if (typeof type === 'string') {
|
|
12
12
|
return type;
|
|
13
13
|
}
|
|
14
|
-
return type.displayName
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
return (type.displayName ||
|
|
15
|
+
type.name ||
|
|
16
|
+
'Unknown');
|
|
17
17
|
}
|
|
18
18
|
function isContentHeaderElement(element) {
|
|
19
19
|
return isValidElement(element) && element.type === ContentHeader;
|
|
@@ -28,7 +28,7 @@ function isTabElement(element) {
|
|
|
28
28
|
* The react component for `mezzanine` section.
|
|
29
29
|
*/
|
|
30
30
|
const Section = forwardRef(function Section(props, ref) {
|
|
31
|
-
const { children, className, contentHeader, filterArea, tab
|
|
31
|
+
const { children, className, contentHeader, filterArea, tab } = props;
|
|
32
32
|
let renderedContentHeader = null;
|
|
33
33
|
let renderedFilterArea = null;
|
|
34
34
|
let renderedTab = null;
|
|
@@ -56,7 +56,7 @@ const Section = forwardRef(function Section(props, ref) {
|
|
|
56
56
|
console.warn(`[Section] Invalid tab type: <${getDisplayName(tab)}>. Only <Tab /> component from @mezzanine-ui/react is allowed.`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
-
return (jsxs("div", { ref: ref, className: cx(sectionClasses.host, className), children: [renderedContentHeader, renderedFilterArea, renderedTab, children] }));
|
|
59
|
+
return (jsxs("div", { ref: ref, className: cx(sectionClasses.host, className), children: [renderedContentHeader, renderedFilterArea, renderedTab, jsx("div", { className: sectionClasses.hostContent, children: children })] }));
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
export { Section as default };
|
package/Transition/Collapse.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ export interface CollapseProps extends TransitionImplementationProps, Omit<Nativ
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* The react component for `mezzanine` transition collapse.
|
|
13
|
-
* @deprecated 設計師未定義,暫時標記為 deprecated
|
|
13
|
+
* @deprecated 設計師未定義,暫時標記為 deprecated.
|
|
14
|
+
* 目前 NavigationOption 與 Accordion 正在使用此元件.
|
|
14
15
|
*/
|
|
15
16
|
declare const Collapse: import("react").ForwardRefExoticComponent<CollapseProps & import("react").RefAttributes<HTMLElement>>;
|
|
16
17
|
export default Collapse;
|
package/Transition/Collapse.js
CHANGED
|
@@ -27,7 +27,8 @@ const defaultEasing = {
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* The react component for `mezzanine` transition collapse.
|
|
30
|
-
* @deprecated 設計師未定義,暫時標記為 deprecated
|
|
30
|
+
* @deprecated 設計師未定義,暫時標記為 deprecated.
|
|
31
|
+
* 目前 NavigationOption 與 Accordion 正在使用此元件.
|
|
31
32
|
*/
|
|
32
33
|
const Collapse = forwardRef(function Collapse(props, ref) {
|
|
33
34
|
const { appear, children, collapsedHeight: collapsedHeightProp = 0, delay = 0, duration = 'auto', easing = defaultEasing, in: inProp = false, lazyMount, keepMount, onEnter, onEntered, onEntering, onExit, onExiting, onExited, style, ...rest } = props;
|
package/Upload/Upload.js
CHANGED
|
@@ -14,11 +14,13 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
14
14
|
const { accept, className, disabled = false, mode = 'list', size = 'main', showFileSize = true, files: controlledFiles = [], onUpload, onDelete, onReload, onDownload, onZoomIn, onChange, id, name, multiple = false, maxFiles, hints, uploaderLabel, uploaderIcon, inputRef, inputProps, onMaxFilesExceeded, errorMessage, errorIcon, ...rest } = props;
|
|
15
15
|
const files = controlledFiles;
|
|
16
16
|
const filesRef = useRef(files);
|
|
17
|
+
const replaceFileIdRef = useRef(null);
|
|
18
|
+
const replaceInputRef = useRef(null);
|
|
17
19
|
useEffect(() => {
|
|
18
20
|
filesRef.current = files;
|
|
19
21
|
}, [files]);
|
|
20
22
|
// Default error icon when status is error and no errorIcon is provided
|
|
21
|
-
const defaultErrorIconElement = useMemo(() => errorIcon !== null && errorIcon !== void 0 ? errorIcon : jsx(Icon, { icon: DangerousFilledIcon, color: "error", size: 24 }), [errorIcon]);
|
|
23
|
+
const defaultErrorIconElement = useMemo(() => errorIcon !== null && errorIcon !== void 0 ? errorIcon : (jsx(Icon, { icon: DangerousFilledIcon, color: "error", size: 24 })), [errorIcon]);
|
|
22
24
|
// Auto-disable when maxFiles is reached
|
|
23
25
|
const isMaxFilesReached = useMemo(() => {
|
|
24
26
|
if (maxFiles === undefined)
|
|
@@ -37,6 +39,12 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
37
39
|
const handleUpload = useCallback(async (selectedFiles) => {
|
|
38
40
|
if (!selectedFiles.length)
|
|
39
41
|
return;
|
|
42
|
+
// If a replace operation is in progress, remove the old file first
|
|
43
|
+
if (replaceFileIdRef.current !== null) {
|
|
44
|
+
const fileIdToReplace = replaceFileIdRef.current;
|
|
45
|
+
replaceFileIdRef.current = null;
|
|
46
|
+
filesRef.current = filesRef.current.filter((f) => f.id !== fileIdToReplace);
|
|
47
|
+
}
|
|
40
48
|
// Check maxFiles limit
|
|
41
49
|
if (maxFiles !== undefined) {
|
|
42
50
|
const currentCount = filesRef.current.length;
|
|
@@ -100,7 +108,8 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
100
108
|
var _a, _b, _c;
|
|
101
109
|
const tempIndex = tempIdToIndex.get(file.id);
|
|
102
110
|
// If this is a temp file and we have a corresponding backend file
|
|
103
|
-
if (tempIndex !== undefined &&
|
|
111
|
+
if (tempIndex !== undefined &&
|
|
112
|
+
tempIndex < backendFiles.length) {
|
|
104
113
|
const backendFile = backendFiles[tempIndex];
|
|
105
114
|
// Replace temporary file with backend file (includes real ID and status)
|
|
106
115
|
// Apply default error message and icon if status is error and not provided
|
|
@@ -124,7 +133,8 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
124
133
|
const nextFiles = filesRef.current.map((file) => {
|
|
125
134
|
const tempIndex = tempIdToIndex.get(file.id);
|
|
126
135
|
// If this is a temp file and we have a corresponding backend ID
|
|
127
|
-
if (tempIndex !== undefined &&
|
|
136
|
+
if (tempIndex !== undefined &&
|
|
137
|
+
tempIndex < backendIds.length) {
|
|
128
138
|
const backendId = backendIds[tempIndex];
|
|
129
139
|
// Replace temporary ID with backend ID, set status to 'done'
|
|
130
140
|
return {
|
|
@@ -142,7 +152,11 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
142
152
|
else {
|
|
143
153
|
// Old API: backward compatibility - no return value, assume success
|
|
144
154
|
const nextFiles = filesRef.current.map((file) => tempFiles.some((tf) => tf.id === file.id)
|
|
145
|
-
? {
|
|
155
|
+
? {
|
|
156
|
+
...file,
|
|
157
|
+
status: 'done',
|
|
158
|
+
progress: 100,
|
|
159
|
+
}
|
|
146
160
|
: file);
|
|
147
161
|
emitChange(nextFiles);
|
|
148
162
|
}
|
|
@@ -172,7 +186,14 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
172
186
|
: file);
|
|
173
187
|
emitChange(nextFiles);
|
|
174
188
|
}
|
|
175
|
-
}, [
|
|
189
|
+
}, [
|
|
190
|
+
emitChange,
|
|
191
|
+
maxFiles,
|
|
192
|
+
onMaxFilesExceeded,
|
|
193
|
+
onUpload,
|
|
194
|
+
errorMessage,
|
|
195
|
+
defaultErrorIconElement,
|
|
196
|
+
]);
|
|
176
197
|
const handleDelete = useCallback((fileId) => {
|
|
177
198
|
const file = findFileById(fileId);
|
|
178
199
|
if (!file)
|
|
@@ -254,6 +275,7 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
254
275
|
const shouldUsePictureCard = useMemo(() => {
|
|
255
276
|
return /cards|card-wall/.test(mode);
|
|
256
277
|
}, [mode]);
|
|
278
|
+
const isSingleFileCardMode = shouldUsePictureCard && maxFiles === 1;
|
|
257
279
|
const renderUploadItem = useCallback((uploadFile) => {
|
|
258
280
|
// Skip rendering if neither file nor url is provided
|
|
259
281
|
if (!uploadFile.file && !uploadFile.url) {
|
|
@@ -262,18 +284,50 @@ const Upload = forwardRef(function Upload(props, ref) {
|
|
|
262
284
|
// Determine if it's an image using shared utility
|
|
263
285
|
const isImage = isImageFile(uploadFile.file, uploadFile.url);
|
|
264
286
|
// For images, use 'thumbnail' to show image preview; for non-images, use 'icon' to show file icon
|
|
265
|
-
const itemType = isImage
|
|
287
|
+
const itemType = isImage
|
|
288
|
+
? 'thumbnail'
|
|
289
|
+
: 'icon';
|
|
266
290
|
return (jsx(UploadItem, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, type: itemType, showFileSize: showFileSize, disabled: disabled, onDelete: () => handleDelete(uploadFile.id), onDownload: () => handleDownload(uploadFile.id), onReload: () => handleReload(uploadFile.id) }, uploadFile.id));
|
|
267
|
-
}, [
|
|
291
|
+
}, [
|
|
292
|
+
size,
|
|
293
|
+
showFileSize,
|
|
294
|
+
disabled,
|
|
295
|
+
handleDelete,
|
|
296
|
+
handleDownload,
|
|
297
|
+
handleReload,
|
|
298
|
+
]);
|
|
268
299
|
const uploaderElement = (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...uploaderConfig }));
|
|
269
300
|
const topUploaderElement = topUploaderConfig ? (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id ? `${id}-top` : undefined, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...topUploaderConfig })) : null;
|
|
270
301
|
const hintsElement = useMemo(() => {
|
|
271
|
-
if (!hints ||
|
|
302
|
+
if (!hints ||
|
|
303
|
+
hints.length === 0 ||
|
|
304
|
+
mode === 'list' ||
|
|
305
|
+
mode === 'card-wall')
|
|
272
306
|
return null;
|
|
273
307
|
const hintsClassName = mode === 'cards' ? uploadClasses.fillWidthHints : uploadClasses.hints;
|
|
274
308
|
return (jsx("ul", { className: hintsClassName, children: hints.map((hint) => (jsxs("li", { className: uploadClasses.hint(hint.type || 'info'), children: [jsx(Icon, { icon: hint.type === 'info' ? InfoFilledIcon : DangerousFilledIcon, color: hint.type === 'info' ? 'info' : 'error', size: 14 }), hint.label] }, hint.label))) }));
|
|
275
309
|
}, [hints, mode]);
|
|
276
|
-
return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, mode === 'button-list' && hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id),
|
|
310
|
+
return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, mode === 'button-list' && hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id), ...(!isSingleFileCardMode && {
|
|
311
|
+
onDownload: () => handleDownload(uploadFile.id),
|
|
312
|
+
onZoomIn: () => handleZoomIn(uploadFile.id),
|
|
313
|
+
}), ...(isSingleFileCardMode && {
|
|
314
|
+
onReplace: (e) => {
|
|
315
|
+
var _a;
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
replaceFileIdRef.current = uploadFile.id;
|
|
318
|
+
(_a = replaceInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
319
|
+
},
|
|
320
|
+
}) }, uploadFile.id))), !isSingleFileCardMode && uploaderElement, isSingleFileCardMode &&
|
|
321
|
+
imageFiles.length === 0 &&
|
|
322
|
+
uploaderElement, isSingleFileCardMode && (jsx("input", { ref: replaceInputRef, accept: accept, style: { display: 'none' }, type: "file", onChange: (e) => {
|
|
323
|
+
var _a;
|
|
324
|
+
const selectedFiles = Array.from((_a = e.target.files) !== null && _a !== void 0 ? _a : []);
|
|
325
|
+
e.target.value = '';
|
|
326
|
+
if (selectedFiles.length)
|
|
327
|
+
handleUpload(selectedFiles);
|
|
328
|
+
} }))] })), nonImageFiles.length > 0 && nonImageFiles.map(renderUploadItem), !shouldUsePictureCard &&
|
|
329
|
+
imageFiles.length > 0 &&
|
|
330
|
+
imageFiles.map(renderUploadItem)] }), mode === 'cards' && hintsElement] }));
|
|
277
331
|
});
|
|
278
332
|
|
|
279
333
|
export { Upload as default };
|
|
@@ -9,30 +9,35 @@ export interface UploadPictureCardAriaLabels {
|
|
|
9
9
|
*/
|
|
10
10
|
cancelUpload?: string;
|
|
11
11
|
/**
|
|
12
|
-
* Aria label for
|
|
13
|
-
* @default '
|
|
12
|
+
* Aria label for the click-to-replace overlay label.
|
|
13
|
+
* @default 'Click to Replace'
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
clickToReplace?: string;
|
|
16
16
|
/**
|
|
17
|
-
* Aria label for
|
|
18
|
-
* @default '
|
|
17
|
+
* Aria label for delete button.
|
|
18
|
+
* @default 'Delete file'
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
delete?: string;
|
|
21
21
|
/**
|
|
22
22
|
* Aria label for download button.
|
|
23
23
|
* @default 'Download file'
|
|
24
24
|
*/
|
|
25
25
|
download?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Aria label for delete button.
|
|
28
|
-
* @default 'Delete file'
|
|
29
|
-
*/
|
|
30
|
-
delete?: string;
|
|
31
26
|
/**
|
|
32
27
|
* Aria label for reload/retry button.
|
|
33
28
|
* @default 'Retry upload'
|
|
34
29
|
*/
|
|
35
30
|
reload?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Aria label for uploading status.
|
|
33
|
+
* @default 'Uploading'
|
|
34
|
+
*/
|
|
35
|
+
uploading?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Aria label for zoom in button.
|
|
38
|
+
* @default 'Zoom in image'
|
|
39
|
+
*/
|
|
40
|
+
zoomIn?: string;
|
|
36
41
|
}
|
|
37
42
|
export interface UploadPictureCardProps extends NativeElementPropsWithoutKeyAndRef<'div'> {
|
|
38
43
|
/**
|
|
@@ -90,10 +95,6 @@ export interface UploadPictureCardProps extends NativeElementPropsWithoutKeyAndR
|
|
|
90
95
|
* When delete icon is clicked, this callback will be fired.
|
|
91
96
|
*/
|
|
92
97
|
onDelete?: MouseEventHandler;
|
|
93
|
-
/**
|
|
94
|
-
* When zoom in icon is clicked, this callback will be fired.
|
|
95
|
-
*/
|
|
96
|
-
onZoomIn?: MouseEventHandler;
|
|
97
98
|
/**
|
|
98
99
|
* When download icon is clicked, this callback will be fired.
|
|
99
100
|
*/
|
|
@@ -102,6 +103,15 @@ export interface UploadPictureCardProps extends NativeElementPropsWithoutKeyAndR
|
|
|
102
103
|
* When reload icon is clicked, this callback will be fired.
|
|
103
104
|
*/
|
|
104
105
|
onReload?: MouseEventHandler;
|
|
106
|
+
/**
|
|
107
|
+
* When provided, the card becomes a replacement trigger in done status.
|
|
108
|
+
* On hover, shows a "Click to Replace" overlay label. Fired when the card body is clicked.
|
|
109
|
+
*/
|
|
110
|
+
onReplace?: MouseEventHandler;
|
|
111
|
+
/**
|
|
112
|
+
* When zoom in icon is clicked, this callback will be fired.
|
|
113
|
+
*/
|
|
114
|
+
onZoomIn?: MouseEventHandler;
|
|
105
115
|
}
|
|
106
116
|
/**
|
|
107
117
|
* The react component for `mezzanine` upload picture card.
|
|
@@ -15,14 +15,15 @@ import cx from 'clsx';
|
|
|
15
15
|
*/
|
|
16
16
|
const UploadPictureCard = forwardRef(function UploadPictureCard(props, ref) {
|
|
17
17
|
var _a;
|
|
18
|
-
const { ariaLabels, className, file, url, status = 'loading', imageFit = 'cover', size = 'main', disabled = false, errorMessage, errorIcon, onDelete,
|
|
18
|
+
const { ariaLabels, className, file, url, status = 'loading', imageFit = 'cover', size = 'main', disabled = false, errorMessage, errorIcon, onDelete, onDownload, onReload, onReplace, onZoomIn, ...rest } = props;
|
|
19
19
|
const defaultAriaLabels = {
|
|
20
20
|
cancelUpload: 'Cancel upload',
|
|
21
|
-
|
|
22
|
-
zoomIn: 'Zoom in image',
|
|
23
|
-
download: 'Download file',
|
|
21
|
+
clickToReplace: 'Click to Replace',
|
|
24
22
|
delete: 'Delete file',
|
|
23
|
+
download: 'Download file',
|
|
25
24
|
reload: 'Retry upload',
|
|
25
|
+
uploading: 'Uploading',
|
|
26
|
+
zoomIn: 'Zoom in image',
|
|
26
27
|
};
|
|
27
28
|
const labels = { ...defaultAriaLabels, ...ariaLabels };
|
|
28
29
|
const isImage = useMemo(() => {
|
|
@@ -92,10 +93,17 @@ const UploadPictureCard = forwardRef(function UploadPictureCard(props, ref) {
|
|
|
92
93
|
console.warn('UploadPictureCard: minor size is not supported for non-image files');
|
|
93
94
|
return null;
|
|
94
95
|
}
|
|
95
|
-
return (jsx("div", { className: cx(uploadPictureCardClasses.host, uploadPictureCardClasses.size(size), disabled && uploadPictureCardClasses.disabled,
|
|
96
|
+
return (jsx("div", { "aria-disabled": disabled, className: cx(uploadPictureCardClasses.host, uploadPictureCardClasses.size(size), disabled && uploadPictureCardClasses.disabled, onReplace && status === 'done' && uploadPictureCardClasses.replaceMode, className), onClick: onReplace && status === 'done' ? onReplace : undefined, onKeyDown: onReplace && status === 'done'
|
|
97
|
+
? (e) => {
|
|
98
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
e.currentTarget.click();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
: undefined, ref: ref, role: "group", tabIndex: disabled ? -1 : 0, ...rest, children: jsxs("div", { className: uploadPictureCardClasses.container, children: [isImage && imageUrl && status !== 'error' && (jsx("img", { alt: fileName, src: imageUrl, style: {
|
|
96
104
|
objectFit: imageFit,
|
|
97
105
|
objectPosition: 'center',
|
|
98
|
-
} })), status === 'done' && size !== 'minor' && !isImage && (jsxs("div", { className: uploadPictureCardClasses.content, children: [jsx(Icon, { icon: FileIcon, color: "brand", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.name, ellipsis: true, children: fileName })] })), status === 'error' && size !== 'minor' && (jsxs("div", { className: uploadPictureCardClasses.errorMessage, role: "alert", "aria-live": "polite", children: [jsx(Icon, { icon: errorIconContent, color: "error", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.errorMessageText, children: errorMessageContent })] })), jsxs("div", { className: cx(uploadPictureCardClasses.actions, uploadPictureCardClasses.actionsStatus(status)), children: [status === 'loading' && size !== 'minor' && (jsxs(Fragment, { children: [jsx(ClearActions, { type: "embedded", variant: "contrast", onClick: onDelete, className: uploadPictureCardClasses.clearActionsIcon, "aria-label": labels.cancelUpload }), jsx("div", { className: uploadPictureCardClasses.loadingIcon, "aria-label": labels.uploading, children: jsx(Icon, { icon: SpinnerIcon, color: "fixed-light", spin: true, size: 32 }) })] })), status === 'done' && size !== 'minor' && (
|
|
106
|
+
} })), status === 'done' && size !== 'minor' && !isImage && (jsxs("div", { className: uploadPictureCardClasses.content, children: [jsx(Icon, { icon: FileIcon, color: "brand", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.name, ellipsis: true, children: fileName })] })), status === 'error' && size !== 'minor' && (jsxs("div", { className: uploadPictureCardClasses.errorMessage, role: "alert", "aria-live": "polite", children: [jsx(Icon, { icon: errorIconContent, color: "error", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.errorMessageText, children: errorMessageContent })] })), jsxs("div", { className: cx(uploadPictureCardClasses.actions, uploadPictureCardClasses.actionsStatus(status)), children: [status === 'loading' && size !== 'minor' && (jsxs(Fragment, { children: [jsx(ClearActions, { type: "embedded", variant: "contrast", onClick: onDelete, className: uploadPictureCardClasses.clearActionsIcon, "aria-label": labels.cancelUpload }), jsx("div", { className: uploadPictureCardClasses.loadingIcon, "aria-label": labels.uploading, children: jsx(Icon, { icon: SpinnerIcon, color: "fixed-light", spin: true, size: 32 }) })] })), status === 'done' && size !== 'minor' && (jsxs(Fragment, { children: [jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [onZoomIn && (jsx(Button, { variant: "base-secondary", size: "minor", icon: ZoomInIcon, iconType: "icon-only", onClick: onZoomIn, "aria-label": labels.zoomIn })), onDownload && (jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: DownloadIcon, onClick: onDownload, "aria-label": labels.download })), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: onDelete, "aria-label": labels.delete })] }) }), onReplace && (jsx("span", { className: uploadPictureCardClasses.replaceLabel, children: labels.clickToReplace }))] })), status === 'error' && size !== 'minor' && (jsx(Fragment, { children: jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: ResetIcon, onClick: onReload, "aria-label": labels.reload }), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: onDelete, "aria-label": labels.delete })] }) }) })), size === 'minor' && (jsx(Icon, { icon: ZoomInIcon, color: "fixed-light", size: 24 }))] })] }) }));
|
|
99
107
|
});
|
|
100
108
|
|
|
101
109
|
export { UploadPictureCard as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mezzanine-ui/react",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"description": "React components for mezzanine-ui",
|
|
5
5
|
"author": "Mezzanine",
|
|
6
6
|
"repository": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@floating-ui/dom": "^1.7.4",
|
|
32
32
|
"@floating-ui/react-dom": "^2.1.6",
|
|
33
33
|
"@hello-pangea/dnd": "^18.0.1",
|
|
34
|
-
"@mezzanine-ui/core": "1.0.0-rc.
|
|
35
|
-
"@mezzanine-ui/icons": "1.0.0-rc.
|
|
36
|
-
"@mezzanine-ui/system": "1.0.0-rc.
|
|
34
|
+
"@mezzanine-ui/core": "1.0.0-rc.1",
|
|
35
|
+
"@mezzanine-ui/icons": "1.0.0-rc.1",
|
|
36
|
+
"@mezzanine-ui/system": "1.0.0-rc.1",
|
|
37
37
|
"@tanstack/react-virtual": "^3.13.13",
|
|
38
38
|
"@types/react-transition-group": "^4.4.12",
|
|
39
39
|
"clsx": "^2.1.1",
|