@ui5/webcomponents-react
Version:
React Wrapper for UI5 Web Components and additional components
251 lines (248 loc) • 10.9 kB
JavaScript
'use client';
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
import ListSeparator from '@ui5/webcomponents/dist/types/ListSeparator.js';
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
import WrappingType from '@ui5/webcomponents/dist/types/WrappingType.js';
import { getAnimationMode } from '@ui5/webcomponents-base/dist/config/AnimationMode.js';
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
import announce from '@ui5/webcomponents-base/dist/util/InvisibleMessage.js';
import iconSlimArrowLeft from '@ui5/webcomponents-icons/dist/slim-arrow-left.js';
import { useI18nBundle, useStylesheet, useSyncRef } from '@ui5/webcomponents-react-base';
import { clsx } from 'clsx';
import { useRef, Children, forwardRef, Fragment, isValidElement, useCallback, useEffect, useState } from 'react';
import { FlexBoxDirection } from '../../enums/FlexBoxDirection.js';
import { ALL, LIST_NO_DATA, NAVIGATE_BACK, MESSAGE_DETAILS, MESSAGE_TYPES } from '../../i18n/i18n-defaults.js';
import { MessageViewContext } from '../../internal/MessageViewContext.js';
import { Bar } from '../../webComponents/Bar/index.js';
import { Button } from '../../webComponents/Button/index.js';
import { Icon } from '../../webComponents/Icon/index.js';
import { List } from '../../webComponents/List/index.js';
import { ListItemGroup } from '../../webComponents/ListItemGroup/index.js';
import { SegmentedButton } from '../../webComponents/SegmentedButton/index.js';
import { SegmentedButtonItem } from '../../webComponents/SegmentedButtonItem/index.js';
import { Title } from '../../webComponents/Title/index.js';
import { FlexBox } from '../FlexBox/index.js';
import { classNames, styleData } from './MessageView.module.css.js';
import { getIconNameForType, getValueStateMap, resolveTitleTextStr } from './utils.js';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const withAnimation = getAnimationMode() !== 'none';
export const resolveMessageTypes = children => {
return (children ?? []).map(message => message?.props?.type).reduce((acc, type) => {
const finalType = type === ValueState.None ? ValueState.Information : type;
if (Object.prototype.hasOwnProperty.call(acc, finalType)) {
acc[finalType]++;
}
return acc;
}, {
[ValueState.Negative]: 0,
[ValueState.Critical]: 0,
[ValueState.Positive]: 0,
[ValueState.Information]: 0
});
};
export const resolveMessageGroups = children => {
const groups = (children ?? []).reduce((acc, val) => {
const groupName = val?.props?.groupName ?? '';
if (Object.prototype.hasOwnProperty.call(acc, groupName)) {
acc[groupName].push(val);
} else {
acc[groupName] = [val];
}
return acc;
}, {});
return Object.entries(groups).sort(([keyA], [keyB]) => {
if (keyA === '' && keyB !== '') {
return -1;
}
if (keyA !== '' && keyB === '') {
return 1;
}
return 0;
});
};
/**
* The `MessageView` is used to display a summarized list of different types of messages (error, warning, success, and information messages).
*/
const MessageView = /*#__PURE__*/forwardRef((props, ref) => {
const {
children,
groupItems,
showDetailsPageHeader,
className,
onItemSelect,
...rest
} = props;
const navBtnRef = useRef(null);
const listRef = useRef(null);
const transitionTrigger = useRef(null);
const prevSelectedMessage = useRef(null);
useStylesheet(styleData, MessageView.displayName);
const [componentRef, internalRef] = useSyncRef(ref);
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
const [listFilter, setListFilter] = useState('All');
const [selectedMessage, setSelectedMessage] = useState(null);
const childrenArray = Children.toArray(children);
const messageTypes = resolveMessageTypes(childrenArray);
const filledTypes = Object.values(messageTypes).filter(count => count > 0).length;
// fallback to All if selected filter is removed
const effectiveListFilter = listFilter !== 'All' && messageTypes[listFilter] === 0 ? 'All' : listFilter;
// collapse details pane if the open MessageItem is no longer in children
const effectiveSelectedMessage = selectedMessage !== null && childrenArray.some(child => {
if (! /*#__PURE__*/isValidElement(child)) {
return false;
}
return resolveTitleTextStr(child.props.titleText) === selectedMessage.titleTextStr && child.props.type === selectedMessage.type;
}) ? selectedMessage : null;
const filteredChildren = effectiveListFilter === 'All' ? childrenArray : childrenArray.filter(message => {
if (! /*#__PURE__*/isValidElement(message)) {
return false;
}
const castMessage = message;
if (effectiveListFilter === ValueState.Information) {
return castMessage?.props?.type === ValueState.Information || castMessage?.props?.type === ValueState.None;
}
return castMessage?.props?.type === effectiveListFilter;
});
const groupedMessages = resolveMessageGroups(filteredChildren);
const navigateBack = useCallback(() => {
transitionTrigger.current = 'btn';
prevSelectedMessage.current = selectedMessage;
setSelectedMessage(null);
}, [setSelectedMessage, selectedMessage]);
useEffect(() => {
if (internalRef.current) {
internalRef.current.navigateBack = navigateBack;
}
}, [internalRef, navigateBack]);
const handleListFilterChange = e => {
setListFilter(e.detail.selectedItems.at(0).dataset.key);
};
const handleTransitionEnd = e => {
if (typeof props?.onTransitionEnd === 'function') {
props.onTransitionEnd(e);
}
if (showDetailsPageHeader && transitionTrigger.current === 'list') {
requestAnimationFrame(() => {
void navBtnRef.current?.focus();
});
setTimeout(() => {
announce(i18nBundle.getText(MESSAGE_DETAILS), 'Polite');
}, 300);
}
if (transitionTrigger.current === 'btn') {
requestAnimationFrame(() => {
const selectedItem = listRef.current.querySelector(`[data-title="${CSS.escape(prevSelectedMessage.current.titleTextStr)}"]`);
void selectedItem?.focus();
});
}
transitionTrigger.current = null;
};
const handleListItemClick = e => {
transitionTrigger.current = 'list';
if (typeof onItemSelect === 'function') {
onItemSelect(e);
}
};
const outerClasses = clsx(classNames.container, className, effectiveSelectedMessage && classNames.showDetails);
return /*#__PURE__*/_jsx("div", {
ref: componentRef,
...rest,
className: outerClasses,
onTransitionEnd: handleTransitionEnd,
"data-with-animation": `${withAnimation}`,
children: /*#__PURE__*/_jsxs(MessageViewContext.Provider, {
value: {
selectMessage: setSelectedMessage
},
children: [/*#__PURE__*/_jsx("div", {
style: {
visibility: effectiveSelectedMessage ? 'hidden' : 'visible',
opacity: effectiveSelectedMessage ? 0.3 : 1
},
className: classNames.messagesContainer,
children: !effectiveSelectedMessage && /*#__PURE__*/_jsxs(_Fragment, {
children: [filledTypes > 1 && /*#__PURE__*/_jsx(Bar, {
startContent: /*#__PURE__*/_jsxs(SegmentedButton, {
onSelectionChange: handleListFilterChange,
accessibleName: i18nBundle.getText(MESSAGE_TYPES),
children: [/*#__PURE__*/_jsx(SegmentedButtonItem, {
"data-key": "All",
selected: effectiveListFilter === 'All',
children: i18nBundle.getText(ALL)
}), Object.entries(messageTypes).map(([valueState, count]) => {
if (count === 0) {
return null;
}
return /*#__PURE__*/_jsx(SegmentedButtonItem, {
"data-key": valueState,
selected: effectiveListFilter === valueState,
icon: getIconNameForType(valueState),
className: classNames.button,
tooltip: getValueStateMap(i18nBundle)[valueState],
accessibleName: `${count} ${getValueStateMap(i18nBundle)[valueState]}`,
children: count
}, valueState);
})]
})
}), /*#__PURE__*/_jsx(List, {
ref: listRef,
onItemClick: handleListItemClick,
noDataText: i18nBundle.getText(LIST_NO_DATA),
separators: ListSeparator.Inner,
children: groupItems ? groupedMessages.map(([groupName, items]) => {
if (!groupName) {
return items;
}
return /*#__PURE__*/_jsx(Fragment, {
children: /*#__PURE__*/_jsx(ListItemGroup, {
headerText: groupName,
children: items
})
}, groupName);
}) : filteredChildren
})]
})
}), /*#__PURE__*/_jsx("div", {
className: classNames.detailsContainer,
style: {
opacity: effectiveSelectedMessage ? 1 : 0.3
},
"data-component-name": "MessageViewDetailsContainer",
children: childrenArray.length > 0 ? /*#__PURE__*/_jsxs(_Fragment, {
children: [showDetailsPageHeader && effectiveSelectedMessage && /*#__PURE__*/_jsx(Bar, {
startContent: /*#__PURE__*/_jsx(Button, {
ref: navBtnRef,
design: ButtonDesign.Transparent,
icon: iconSlimArrowLeft,
onClick: navigateBack,
tooltip: i18nBundle.getText(NAVIGATE_BACK),
accessibleName: i18nBundle.getText(NAVIGATE_BACK),
"data-component-name": "MessageViewDetailsNavBackBtn"
})
}), effectiveSelectedMessage && /*#__PURE__*/_jsxs(FlexBox, {
className: classNames.details,
children: [/*#__PURE__*/_jsx(Icon, {
"data-type": effectiveSelectedMessage.type ?? ValueState.Negative,
name: getIconNameForType(effectiveSelectedMessage.type),
className: classNames.detailsIcon
}), /*#__PURE__*/_jsxs(FlexBox, {
direction: FlexBoxDirection.Column,
className: classNames.detailsTextContainer,
children: [/*#__PURE__*/_jsx(Title, {
level: TitleLevel.H5,
className: classNames.detailsTitle,
wrappingType: WrappingType.Normal,
children: effectiveSelectedMessage.titleText
}), /*#__PURE__*/_jsx("div", {
className: classNames.detailsText,
children: effectiveSelectedMessage.children
})]
})]
})]
}) : null
})]
})
});
});
MessageView.displayName = 'MessageView';
export { MessageView };