UNPKG

@ui5/webcomponents-react

Version:

React Wrapper for UI5 Web Components and additional components

251 lines (248 loc) 10.9 kB
'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 };