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
16 changes: 16 additions & 0 deletions packages/main/src/components/MessageView/MessageView.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,20 @@ describe('MessageView', () => {
cy.get('@select').should('have.been.calledTwice');
cy.get('[name="slim-arrow-left"]').should('not.exist');
});

it('listAccessibleDescription & listAccessibleDescriptionRef', () => {
cy.mount(
<>
<span id="desc">External description</span>
<MessageView listAccessibleDescription="Inline description" listAccessibleDescriptionRef="desc">
<MessageItem titleText="Error" type={ValueState.Negative}>
Error
</MessageItem>
</MessageView>
</>,
);
cy.get('[ui5-list]')
.should('have.attr', 'accessible-description', 'Inline description')
.and('have.attr', 'accessible-description-ref', 'desc');
});
});
31 changes: 21 additions & 10 deletions packages/main/src/components/MessageView/MessageView.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,32 @@ This component exposes public methods. You can invoke them directly on the insta
| ------------------ | ---------- | --------------------------------------------------------------------------- |
| **`navigateBack`** | — | Navigates back to the list view if you are in the details view of a message |

## Usage Notes
## More Examples

The `MessageView` can be used in several scenarios, but most likely in Dialogs or Popovers.
### MessageView with Interactive Links

If used in a Popover it is recommended that the `MessageViewButton` is used as an opener.
The `type` of the button should always reflect the highest severity (Error -> Warning -> Success -> Information).
When `MessageItem` components contain `Link`s, make sure to set the `listAccessibleDescription` or `listAccessibleDescriptionRef` prop to inform screen reader users about interactive content and how to access it (F2 key).

If the `MessageView` is used in a standalone way, you can set the prop `showDetailsPageHeader` to `true`.
This will add a bar to the details page where a back button is contained.
<Canvas of={ComponentStories.WithLinks} />

When used in a `Dialog` or a `Popover`, we recommend not setting the `showDetailsPageHeader` prop but rather listen
to the `onItemSelect` event and add a back button to your Dialog or Popover header and use the `navigateBack()` method
to get back to the list view.
#### Reusing i18n texts from @ui5/webcomponents-react

## More Examples
You can reuse the built-in translation texts with the `useI18nBundle` hook:

```tsx
import { useI18nBundle } from '@ui5/webcomponents-react-base/hooks';

function MyComponent() {
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
const listAccessibleDescription = `${i18nBundle.getText({ key: 'LIST_INTERACTIVE_ITEMS', defaultText: 'List with interactive items.' })} ${i18nBundle.getText({ key: 'MOVE_TO_CONTENT_F2', defaultText: 'To move to the content press F2.' })}`;

return (
<MessageView listAccessibleDescription={listAccessibleDescription}>
{/* MessageItems with Links */}
</MessageView>
);
}
```

### MessageView in a Dialog

Expand Down
92 changes: 65 additions & 27 deletions packages/main/src/components/MessageView/MessageView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
import WrappingType from '@ui5/webcomponents/dist/types/WrappingType.js';
import ValueState from '@ui5/webcomponents-base/dist/types/ValueState.js';
import arrowLeftIcon from '@ui5/webcomponents-icons/dist/slim-arrow-left.js';
import { useI18nBundle } from '@ui5/webcomponents-react-base/hooks';
import { useRef, useState } from 'react';
import { FlexBoxAlignItems, FlexBoxJustifyContent } from '../../enums/index.js';
import { Bar } from '../../webComponents/Bar/index.js';
Expand All @@ -22,16 +23,6 @@ import { MessageView } from './index.js';
const meta = {
title: 'User Feedback / MessageView',
component: MessageView,
argTypes: {
showDetailsPageHeader: { description: 'Defines whether the header of the details page will be shown.' },
groupItems: { description: 'Defines whether the messages are grouped or not.' },
onItemSelect: { description: 'Event is fired when the details of a message are shown.', action: 'onItemSelect' },
children: {
description: `A list with message items. If only one item is provided, the initial page will be the details page for the item.\n\n
**Note:** Although this prop accepts all HTML Elements, it is strongly recommended that you only use \`Message\` in order to preserve the intended design.`,
control: { disable: true },
},
},
args: {
showDetailsPageHeader: true,
groupItems: false,
Expand Down Expand Up @@ -76,23 +67,6 @@ const meta = {
Informative message
</MessageItem>,
<MessageItem key={7} titleText={'Error Message Type (2)'} type={ValueState.Negative} counter={3} />,
<MessageItem
key={8}
titleText={
<Link
wrappingType={WrappingType.None}
onClick={(e) => {
e.stopPropagation();
}}
>
Long Error Message Type without children/details including a Link as `titleText` which has
wrappingType="None" applied. - The details view is only available if the `titleText` is not fully visible.
It is NOT recommended to use long titles!
</Link>
}
type={ValueState.Negative}
counter={3}
/>,
],
},
tags: ['package:@ui5/webcomponents-react'],
Expand Down Expand Up @@ -270,3 +244,67 @@ export const WithMessageViewButton: Story = {
);
},
};

export const WithLinks: Story = {
name: 'with Links',
render(args) {
const i18nBundle = useI18nBundle('@ui5/webcomponents-react');
const listAccessibleDescription = `${i18nBundle.getText({ key: 'LIST_INTERACTIVE_ITEMS', defaultText: 'List with interactive items.' })} ${i18nBundle.getText({ key: 'MOVE_TO_CONTENT_F2', defaultText: 'To move to the content press F2.' })}`;

return (
<MessageView {...args} listAccessibleDescription={listAccessibleDescription}>
<MessageItem
titleText={
<Link
wrappingType={WrappingType.None}
onClick={(e) => {
e.stopPropagation();
alert('Link pressed!');
}}
>
Error message
</Link>
}
subtitleText="This message contains a link"
type={ValueState.Negative}
/>
<MessageItem
titleText={
<Link
wrappingType={WrappingType.None}
onClick={(e) => {
e.stopPropagation();
alert('Link pressed!');
}}
>
Click here to view the error log.
</Link>
}
type={ValueState.Negative}
/>
<MessageItem
titleText={
<Link
wrappingType={WrappingType.None}
onClick={(e) => {
e.stopPropagation();
alert('Link pressed!');
}}
>
Long Error Message Type without children/details including a Link as `titleText` which has
wrappingType="None" applied. - The details view is only available if the `titleText` is not fully visible.
It is NOT recommended to use long titles!
</Link>
}
type={ValueState.Negative}
counter={3}
/>
<MessageItem
titleText="Information Message"
subtitleText="No interactive elements"
type={ValueState.Information}
/>
</MessageView>
);
},
};
52 changes: 47 additions & 5 deletions packages/main/src/components/MessageView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,24 @@ export interface MessageViewPropTypes extends CommonProps {
*/
children: ReactNode | ReactNode[];

/**
* Defines the accessible description of the `List` (`ui5-list`).
*
* __Note:__ It is recommended to set this prop when `MessageItem` components contain `Link`s, to inform screen reader users about the interactive content and how to access it (F2 key).
*
* @since 2.20.0
*/
listAccessibleDescription?: ListPropTypes['accessibleDescription'];

/**
* Defines the IDs of the elements that describe the `List` (`ui5-list`).
*
* __Note:__ It is recommended to set this prop when `MessageItem` components contain `Link`s, to inform screen reader users about the interactive content and how to access it (F2 key).
*
* @since 2.20.0
*/
listAccessibleDescriptionRef?: ListPropTypes['accessibleDescriptionRef'];

/**
* Event is fired when the details of a message are shown.
*/
Expand All @@ -67,7 +85,7 @@ export interface MessageViewPropTypes extends CommonProps {

const withAnimation = getAnimationMode() !== 'none';

export const resolveMessageTypes = (children: ReactElement<MessageItemPropTypes>[]) => {
export const resolveMessageTypes = (children: ReactElement<MessageItemPropTypes>[]): Record<ValueState, number> => {
return (children ?? [])
.map((message) => message?.props?.type)
.reduce(
Expand All @@ -83,7 +101,7 @@ export const resolveMessageTypes = (children: ReactElement<MessageItemPropTypes>
[ValueState.Critical]: 0,
[ValueState.Positive]: 0,
[ValueState.Information]: 0,
},
} as Record<ValueState, number>,
);
};

Expand Down Expand Up @@ -111,9 +129,32 @@ export const resolveMessageGroups = (children: ReactElement<MessageItemPropTypes

/**
* The `MessageView` is used to display a summarized list of different types of messages (error, warning, success, and information messages).
*
* ### Usage
*
* The `MessageView` can be used in several scenarios, but most likely in Dialogs or Popovers.
*
* If used in a Popover it is recommended that the `MessageViewButton` is used as an opener.
* The `type` of the button should always reflect the highest severity (Error -> Warning -> Success -> Information).
*
* If the `MessageView` is used in a standalone way, you can set the prop `showDetailsPageHeader` to `true`.
* This will add a bar to the details page where a back button is contained.
*
* When used in a `Dialog` or a `Popover`, we recommend not setting the `showDetailsPageHeader` prop but rather listen
* to the `onItemSelect` event and add a back button to your Dialog or Popover header and use the `navigateBack()` method
* to get back to the list view.
*/
const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props, ref) => {
const { children, groupItems, showDetailsPageHeader, className, onItemSelect, ...rest } = props;
const {
children,
groupItems,
showDetailsPageHeader,
className,
onItemSelect,
listAccessibleDescriptionRef,
listAccessibleDescription,
...rest
} = props;
const navBtnRef = useRef<ButtonDomRef>(null);
const listRef = useRef<ListDomRef>(null);
const transitionTrigger = useRef<'btn' | 'list' | null>(null);
Expand Down Expand Up @@ -224,8 +265,7 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
<SegmentedButtonItem data-key="All" selected={listFilter === '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*/}
{Object.entries(messageTypes).map(([valueState, count]: [ValueState, number]) => {
{(Object.entries(messageTypes) as [ValueState, number][]).map(([valueState, count]) => {
if (count === 0) {
return null;
}
Expand All @@ -252,6 +292,8 @@ const MessageView = forwardRef<MessageViewDomRef, MessageViewPropTypes>((props,
onItemClick={handleListItemClick}
noDataText={i18nBundle.getText(LIST_NO_DATA)}
separators={ListSeparator.Inner}
accessibleDescriptionRef={listAccessibleDescriptionRef}
accessibleDescription={listAccessibleDescription}
>
{groupItems
? groupedMessages.map(([groupName, items]) => {
Expand Down
6 changes: 6 additions & 0 deletions packages/main/src/i18n/messagebundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,9 @@ SELECTED_ITEMS=Selected Items {0}

#XACT: Screen reader announcement for table cell that includes interactive element/s. The placeholder is the name of the element (e.g. "Input").
INCLUDES_X=Includes {0}

#XACT: Screen reader announcement for a list containing interactive elements.
LIST_INTERACTIVE_ITEMS=List with interactive items.

#XACT: Keyboard navigation instruction for focusing interactive content in a list.
MOVE_TO_CONTENT_F2=To move to the content press F2.