diff --git a/packages/main/src/components/MessageView/MessageView.cy.tsx b/packages/main/src/components/MessageView/MessageView.cy.tsx index 9060fedc806..6f5c2a906ff 100644 --- a/packages/main/src/components/MessageView/MessageView.cy.tsx +++ b/packages/main/src/components/MessageView/MessageView.cy.tsx @@ -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( + <> + External description + + + Error + + + , + ); + cy.get('[ui5-list]') + .should('have.attr', 'accessible-description', 'Inline description') + .and('have.attr', 'accessible-description-ref', 'desc'); + }); }); diff --git a/packages/main/src/components/MessageView/MessageView.mdx b/packages/main/src/components/MessageView/MessageView.mdx index 4d18db23517..870f096bdb6 100644 --- a/packages/main/src/components/MessageView/MessageView.mdx +++ b/packages/main/src/components/MessageView/MessageView.mdx @@ -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. + -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 ( + + {/* MessageItems with Links */} + + ); +} +``` ### MessageView in a Dialog diff --git a/packages/main/src/components/MessageView/MessageView.stories.tsx b/packages/main/src/components/MessageView/MessageView.stories.tsx index 457ed414ee7..6d199b36add 100644 --- a/packages/main/src/components/MessageView/MessageView.stories.tsx +++ b/packages/main/src/components/MessageView/MessageView.stories.tsx @@ -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'; @@ -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, @@ -76,23 +67,6 @@ const meta = { Informative message , , - { - 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! - - } - type={ValueState.Negative} - counter={3} - />, ], }, tags: ['package:@ui5/webcomponents-react'], @@ -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 ( + + { + e.stopPropagation(); + alert('Link pressed!'); + }} + > + Error message + + } + subtitleText="This message contains a link" + type={ValueState.Negative} + /> + { + e.stopPropagation(); + alert('Link pressed!'); + }} + > + Click here to view the error log. + + } + type={ValueState.Negative} + /> + { + 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! + + } + type={ValueState.Negative} + counter={3} + /> + + + ); + }, +}; diff --git a/packages/main/src/components/MessageView/index.tsx b/packages/main/src/components/MessageView/index.tsx index bb09fc5838f..ee4e0439387 100644 --- a/packages/main/src/components/MessageView/index.tsx +++ b/packages/main/src/components/MessageView/index.tsx @@ -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. */ @@ -67,7 +85,7 @@ export interface MessageViewPropTypes extends CommonProps { const withAnimation = getAnimationMode() !== 'none'; -export const resolveMessageTypes = (children: ReactElement[]) => { +export const resolveMessageTypes = (children: ReactElement[]): Record => { return (children ?? []) .map((message) => message?.props?.type) .reduce( @@ -83,7 +101,7 @@ export const resolveMessageTypes = (children: ReactElement [ValueState.Critical]: 0, [ValueState.Positive]: 0, [ValueState.Information]: 0, - }, + } as Record, ); }; @@ -111,9 +129,32 @@ export const resolveMessageGroups = (children: ReactElement 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((props, ref) => { - const { children, groupItems, showDetailsPageHeader, className, onItemSelect, ...rest } = props; + const { + children, + groupItems, + showDetailsPageHeader, + className, + onItemSelect, + listAccessibleDescriptionRef, + listAccessibleDescription, + ...rest + } = props; const navBtnRef = useRef(null); const listRef = useRef(null); const transitionTrigger = useRef<'btn' | 'list' | null>(null); @@ -224,8 +265,7 @@ const MessageView = forwardRef((props, {i18nBundle.getText(ALL)} - {/* @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; } @@ -252,6 +292,8 @@ const MessageView = forwardRef((props, onItemClick={handleListItemClick} noDataText={i18nBundle.getText(LIST_NO_DATA)} separators={ListSeparator.Inner} + accessibleDescriptionRef={listAccessibleDescriptionRef} + accessibleDescription={listAccessibleDescription} > {groupItems ? groupedMessages.map(([groupName, items]) => { diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index 39c64c0d918..956ee6d4fd1 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -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.