Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions packages/main/src/components/MessageItem/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
import iconArrowRight from '@ui5/webcomponents-icons/dist/slim-arrow-right.js';
import { useI18nBundle, useStylesheet } from '@ui5/webcomponents-react-base';
import { clsx } from 'clsx';
import { Children, isValidElement, forwardRef, useContext, useEffect, useRef, useState } from 'react';
import { Children, forwardRef, useContext, useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { FlexBoxAlignItems } from '../../enums/FlexBoxAlignItems.js';
import { FlexBoxDirection } from '../../enums/FlexBoxDirection.js';
Expand All @@ -16,11 +16,10 @@ import { MessageViewContext } from '../../internal/MessageViewContext.js';
import type { CommonProps } from '../../types/index.js';
import { Icon } from '../../webComponents/Icon/index.js';
import { Label } from '../../webComponents/Label/index.js';
import type { LinkPropTypes } from '../../webComponents/Link/index.js';
import type { ListItemCustomDomRef, ListItemCustomPropTypes } from '../../webComponents/ListItemCustom/index.js';
import { ListItemCustom } from '../../webComponents/ListItemCustom/index.js';
import { FlexBox } from '../FlexBox/index.js';
import { getIconNameForType, getValueStateMap } from '../MessageView/utils.js';
import { getIconNameForType, getValueStateMap, resolveTitleTextStr } from '../MessageView/utils.js';
import { classNames, styleData } from './MessageItem.module.css.js';

export interface MessageItemPropTypes
Expand Down Expand Up @@ -69,14 +68,7 @@ const MessageItem = forwardRef<ListItemCustomDomRef, MessageItemPropTypes>((prop
const titleTextRef = useRef<HTMLSpanElement>(null);
const hasDetails = !!(children || isTitleTextOverflowing);
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
const titleTextStr = (() => {
if (typeof titleText === 'string') {
return titleText;
} else if (isValidElement<LinkPropTypes>(titleText) && typeof titleText.props.children === 'string') {
return titleText.props.children;
}
return '';
})();
const titleTextStr = resolveTitleTextStr(titleText);

useStylesheet(styleData, MessageItem.displayName);

Expand Down
52 changes: 51 additions & 1 deletion packages/main/src/components/MessageView/MessageView.cy.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import WrappingType from '@ui5/webcomponents/dist/types/WrappingType.js';
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
import { Link } from '@ui5/webcomponents-react';
import { useRef } from 'react';
import { useRef, useState } from 'react';
import { Dialog } from '../../webComponents/Dialog/index.js';
import { MessageItem } from '../MessageItem/index.js';
import { MessageView } from './index.js';
Expand Down Expand Up @@ -184,6 +184,56 @@ describe('MessageView', () => {
cy.findByText('1337').should('not.exist');
});

it('remove MessageItems', () => {
const TestComp = () => {
const [showInfo, setShowInfo] = useState(true);
const [showNeg, setShowNeg] = useState(true);
return (
<>
<button data-testid="remove-info" onClick={() => setShowInfo(false)}>
remove info
</button>
<button data-testid="remove-neg" onClick={() => setShowNeg(false)}>
remove neg
</button>
<MessageView showDetailsPageHeader>
{showInfo && (
<MessageItem titleText="InfoTitle" type={ValueState.Information}>
Info Body
</MessageItem>
)}
{showNeg && (
<MessageItem titleText="NegTitle" type={ValueState.Negative}>
Neg Body
</MessageItem>
)}
<MessageItem titleText="Neg2Title" type={ValueState.Negative}>
Neg2 Body
</MessageItem>
</MessageView>
</>
);
};
cy.mount(<TestComp />);

cy.get('[icon="information"]').click();
cy.findByText('InfoTitle').click();
cy.findByText('Info Body').should('be.visible');
cy.get('[data-component-name="MessageViewDetailsNavBackBtn"]').should('exist');

// removing an unrelated item leaves both filter + details intact
cy.findByTestId('remove-neg').click();
cy.findByText('Info Body').should('be.visible');
cy.get('[data-component-name="MessageViewDetailsNavBackBtn"]').should('exist');

// removing the open + filtered-type item: details collapse, filter falls back, Bar unmounts
cy.findByTestId('remove-info').click();
cy.findByText('Info Body').should('not.exist');
cy.get('[data-component-name="MessageViewDetailsNavBackBtn"]').should('not.exist');
cy.findByText('Neg2Title').should('be.visible');
cy.get('[ui5-bar]').should('not.exist');
});

it('MessageItem - titleText overflow', () => {
const selectSpy = cy.spy().as('select');
cy.mount(
Expand Down
53 changes: 37 additions & 16 deletions packages/main/src/components/MessageView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { Title } from '../../webComponents/Title/index.js';
import { FlexBox } from '../FlexBox/index.js';
import type { MessageItemPropTypes } from '../MessageItem/index.js';
import { classNames, styleData } from './MessageView.module.css.js';
import { getIconNameForType, getValueStateMap } from './utils.js';
import { getIconNameForType, getValueStateMap, resolveTitleTextStr } from './utils.js';

export interface MessageViewDomRef extends HTMLDivElement {
/**
Expand Down Expand Up @@ -131,19 +131,37 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
const childrenArray = Children.toArray(children);
const messageTypes = resolveMessageTypes(childrenArray as ReactElement<MessageItemPropTypes>[]);
const filledTypes = Object.values(messageTypes).filter((count) => count > 0).length;
// fallback to All if selected filter is removed
const effectiveListFilter: ValueState | 'All' =
listFilter !== 'All' && messageTypes[listFilter] === 0 ? 'All' : listFilter;

// collapse details pane if the open MessageItem is no longer in children
const effectiveSelectedMessage: SelectedMessage | null =
selectedMessage !== null &&
childrenArray.some((child) => {
if (!isValidElement<MessageItemPropTypes>(child)) {
return false;
}
return (
resolveTitleTextStr(child.props.titleText) === selectedMessage.titleTextStr &&
child.props.type === selectedMessage.type
);
})
? selectedMessage
: null;

const filteredChildren =
listFilter === 'All'
effectiveListFilter === 'All'
? childrenArray
: childrenArray.filter((message) => {
if (!isValidElement(message)) {
return false;
}
const castMessage = message as ReactElement<MessageItemPropTypes>;
if (listFilter === ValueState.Information) {
if (effectiveListFilter === ValueState.Information) {
return castMessage?.props?.type === ValueState.Information || castMessage?.props?.type === ValueState.None;
}
return castMessage?.props?.type === listFilter;
return castMessage?.props?.type === effectiveListFilter;
});

const groupedMessages = resolveMessageGroups(filteredChildren as ReactElement<MessageItemPropTypes>[]);
Expand Down Expand Up @@ -194,7 +212,7 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
}
};

const outerClasses = clsx(classNames.container, className, selectedMessage && classNames.showDetails);
const outerClasses = clsx(classNames.container, className, effectiveSelectedMessage && classNames.showDetails);
return (
<div
ref={componentRef}
Expand All @@ -209,10 +227,13 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
}}
>
<div
style={{ visibility: selectedMessage ? 'hidden' : 'visible', opacity: selectedMessage ? 0.3 : 1 }}
style={{
visibility: effectiveSelectedMessage ? 'hidden' : 'visible',
opacity: effectiveSelectedMessage ? 0.3 : 1,
}}
className={classNames.messagesContainer}
>
{!selectedMessage && (
{!effectiveSelectedMessage && (
<>
{filledTypes > 1 && (
<Bar
Expand All @@ -221,7 +242,7 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
onSelectionChange={handleListFilterChange}
accessibleName={i18nBundle.getText(MESSAGE_TYPES)}
>
<SegmentedButtonItem data-key="All" selected={listFilter === 'All'}>
<SegmentedButtonItem data-key="All" selected={effectiveListFilter === 'All'}>
{i18nBundle.getText(ALL)}
</SegmentedButtonItem>
{/* @ts-expect-error: The key can't be typed, it's always `string`, but since the `ValueState` enum only contains strings it's fine to use here*/}
Expand All @@ -233,7 +254,7 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
<SegmentedButtonItem
key={valueState}
data-key={valueState}
selected={listFilter === valueState}
selected={effectiveListFilter === valueState}
icon={getIconNameForType(valueState)}
className={classNames.button}
tooltip={getValueStateMap(i18nBundle)[valueState]}
Expand Down Expand Up @@ -271,12 +292,12 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
</div>
<div
className={classNames.detailsContainer}
style={{ opacity: selectedMessage ? 1 : 0.3 }}
style={{ opacity: effectiveSelectedMessage ? 1 : 0.3 }}
data-component-name="MessageViewDetailsContainer"
>
{childrenArray.length > 0 ? (
<>
{showDetailsPageHeader && selectedMessage && (
{showDetailsPageHeader && effectiveSelectedMessage && (
<Bar
startContent={
<Button
Expand All @@ -291,18 +312,18 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
}
/>
)}
{selectedMessage && (
{effectiveSelectedMessage && (
<FlexBox className={classNames.details}>
<Icon
data-type={selectedMessage.type ?? ValueState.Negative}
name={getIconNameForType(selectedMessage.type)}
data-type={effectiveSelectedMessage.type ?? ValueState.Negative}
name={getIconNameForType(effectiveSelectedMessage.type)}
className={classNames.detailsIcon}
/>
<FlexBox direction={FlexBoxDirection.Column} className={classNames.detailsTextContainer}>
<Title level={TitleLevel.H5} className={classNames.detailsTitle} wrappingType={WrappingType.Normal}>
{selectedMessage.titleText}
{effectiveSelectedMessage.titleText}
</Title>
<div className={classNames.detailsText}>{selectedMessage.children}</div>
<div className={classNames.detailsText}>{effectiveSelectedMessage.children}</div>
</FlexBox>
</FlexBox>
)}
Expand Down
16 changes: 16 additions & 0 deletions packages/main/src/components/MessageView/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import iconAlert from '@ui5/webcomponents-icons/dist/alert.js';
import iconError from '@ui5/webcomponents-icons/dist/error.js';
import iconInformation from '@ui5/webcomponents-icons/dist/information.js';
import iconSysEnter from '@ui5/webcomponents-icons/dist/sys-enter-2.js';
import { isValidElement } from 'react';
import type { ReactNode } from 'react';
import { ERROR, WARNING, SUCCESS, INFORMATION } from '../../i18n/i18n-defaults.js';
import type { LinkPropTypes } from '../../webComponents/Link/index.js';

export const getIconNameForType = (type: ValueState | keyof typeof ValueState): string => {
switch (type) {
Expand All @@ -29,3 +32,16 @@ export const getValueStateMap = (i18nBundle: I18nBundle) => ({
[ValueState.Information]: i18nBundle.getText(INFORMATION),
[ValueState.None]: i18nBundle.getText(INFORMATION),
});

export const resolveTitleTextStr = (titleText: ReactNode): string => {
if (typeof titleText === 'string' || typeof titleText === 'number') {
return String(titleText);
}
if (isValidElement<LinkPropTypes>(titleText)) {
const linkChild = titleText.props.children;
if (typeof linkChild === 'string' || typeof linkChild === 'number') {
return String(linkChild);
}
}
return '';
};
Loading