diff --git a/packages/fiori/cypress/specs/Timeline.cy.ts b/packages/fiori/cypress/specs/Timeline.cy.ts new file mode 100644 index 000000000000..b2c84ed3efa8 --- /dev/null +++ b/packages/fiori/cypress/specs/Timeline.cy.ts @@ -0,0 +1,55 @@ +import { html } from "lit"; +import "../../src/Timeline.js"; +import "../../src/TimelineItem.js"; +import "@ui5/webcomponents-icons/dist/accept.js"; +import "@ui5/webcomponents-icons/dist/message-information.js"; +import "@ui5/webcomponents-icons/dist/decline.js"; +import "@ui5/webcomponents-icons/dist/message-warning.js"; + +describe("Accessibility", () => { + beforeEach(() => { + cy.mount(html` + + + + Compilation succeeded. + + + Lint completed with minor issues. + + + + `); + + cy.get("#test-timeline").as("timeline"); + }); + + it("item with state attribute has aria-description, item without state does not", () => { + cy.get(`ui5-timeline-item[state="Positive"]`).each($itemWithState => { + cy.wrap($itemWithState) + .shadow() + .find(".ui5-tli-bubble") + .should("have.attr", "aria-description"); + }); + + cy.get(`ui5-timeline-item:not([state="Positive"])`).each($itemWithoutState => { + cy.wrap($itemWithoutState) + .shadow() + .find(".ui5-tli-bubble") + .should("not.have.attr", "aria-description"); + }); + }); +}); diff --git a/packages/fiori/src/TimelineGroupItem.ts b/packages/fiori/src/TimelineGroupItem.ts index b73bced44a6f..727d1b55c481 100644 --- a/packages/fiori/src/TimelineGroupItem.ts +++ b/packages/fiori/src/TimelineGroupItem.ts @@ -4,9 +4,8 @@ import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; -import TimelineLayout from "./types/TimelineLayout.js"; +import type TimelineLayout from "./types/TimelineLayout.js"; import type { ITimelineItem } from "./Timeline.js"; - import TimelineGroupItemTemplate from "./TimelineGroupItemTemplate.js"; // Styles @@ -162,14 +161,6 @@ class TimelineGroupItem extends UI5Element implements ITimelineItem { get _groupName() { return this.groupName; } - - get _groupItemIcon() { - if (this.layout === TimelineLayout.Vertical) { - return this.collapsed ? "slim-arrow-left" : "slim-arrow-down"; - } - - return this.collapsed ? "slim-arrow-up" : "slim-arrow-right"; - } } TimelineGroupItem.define(); diff --git a/packages/fiori/src/TimelineGroupItemTemplate.tsx b/packages/fiori/src/TimelineGroupItemTemplate.tsx index a174721236cb..ae2d22d309fb 100644 --- a/packages/fiori/src/TimelineGroupItemTemplate.tsx +++ b/packages/fiori/src/TimelineGroupItemTemplate.tsx @@ -1,5 +1,10 @@ import type TimelineGroupItem from "./TimelineGroupItem.js"; +import TimelineLayout from "./types/TimelineLayout.js"; import ToggleButton from "@ui5/webcomponents/dist/ToggleButton.js"; +import slimArrowleft from "@ui5/webcomponents-icons/dist/slim-arrow-left.js"; +import slimArrowRight from "@ui5/webcomponents-icons/dist/slim-arrow-right.js"; +import slimArrowDown from "@ui5/webcomponents-icons/dist/slim-arrow-down.js"; +import slimArrowup from "@ui5/webcomponents-icons/dist/slim-arrow-up.js"; export default function TimelineGroupItemTemplate(this: TimelineGroupItem) { return ( @@ -15,7 +20,7 @@ export default function TimelineGroupItemTemplate(this: TimelineGroupItem) { @@ -32,3 +37,11 @@ export default function TimelineGroupItemTemplate(this: TimelineGroupItem) { ); } + +function getEffectiveGroupIcon(layout: `${TimelineLayout}`, collapsed: boolean): string { + if (layout === TimelineLayout.Vertical) { + return collapsed ? slimArrowleft : slimArrowDown; + } + + return collapsed ? slimArrowup : slimArrowRight; +} diff --git a/packages/fiori/src/TimelineItem.ts b/packages/fiori/src/TimelineItem.ts index 352e6a683249..df4f8162ed24 100644 --- a/packages/fiori/src/TimelineItem.ts +++ b/packages/fiori/src/TimelineItem.ts @@ -3,11 +3,23 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; +import i18n from "@ui5/webcomponents-base/dist/decorators/i18n.js"; import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js"; import type Link from "@ui5/webcomponents/dist/Link.js"; +import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import type { I18nText } from "@ui5/webcomponents-base/dist/i18nBundle.js"; import type { ITimelineItem } from "./Timeline.js"; +import type ValueState from "@ui5/webcomponents-base/dist/types/ValueState.js"; import TimelineItemTemplate from "./TimelineItemTemplate.js"; import type TimelineLayout from "./types/TimelineLayout.js"; + +import { + TIMELINE_ITEM_INFORMATION_STATE_TEXT, + TIMELINE_ITEM_POSITIVE_STATE_TEXT, + TIMELINE_ITEM_NEGATIVE_STATE_TEXT, + TIMELINE_ITEM_CRITICAL_STATE_TEXT, +} from "./generated/i18n/i18n-defaults.js"; + // Styles import TimelineItemCss from "./generated/themes/TimelineItem.css.js"; @@ -86,6 +98,15 @@ class TimelineItem extends UI5Element implements ITimelineItem { @property() subtitleText?: string; + /** + * Defines the state of the icon displayed in the `ui5-timeline-item`. + * @default "None" + * @public + * @since 2.7.0 + */ + @property() + state: `${ValueState}` = "None"; + /** * Defines the content of the `ui5-timeline-item`. * @public @@ -149,6 +170,9 @@ class TimelineItem extends UI5Element implements ITimelineItem { @property({ type: Number }) positionInGroup?: number; + @i18n("@ui5/webcomponents") + static i18nBundle: I18nBundle; + constructor() { super(); } @@ -164,6 +188,19 @@ class TimelineItem extends UI5Element implements ITimelineItem { this.shadowRoot!.querySelector("[ui5-link]")?.focus(); } + static typeTextMappings(): Record { + return { + "Information": TIMELINE_ITEM_INFORMATION_STATE_TEXT, + "Positive": TIMELINE_ITEM_POSITIVE_STATE_TEXT, + "Negative": TIMELINE_ITEM_NEGATIVE_STATE_TEXT, + "Critical": TIMELINE_ITEM_CRITICAL_STATE_TEXT, + }; + } + + get timelineItemStateText() { + return this.state !== "None" ? TimelineItem.i18nBundle.getText(TimelineItem.typeTextMappings()[this.state]) : undefined; + } + get isGroupItem() { return false; } diff --git a/packages/fiori/src/TimelineItemTemplate.tsx b/packages/fiori/src/TimelineItemTemplate.tsx index cb90a7933248..d92126f2e9dd 100644 --- a/packages/fiori/src/TimelineItemTemplate.tsx +++ b/packages/fiori/src/TimelineItemTemplate.tsx @@ -16,7 +16,7 @@ export default function TimelineItemTemplate(this: TimelineItem) {
{ this.icon ? - + :
} @@ -28,6 +28,7 @@ export default function TimelineItemTemplate(this: TimelineItem) { data-sap-focus-ref class="ui5-tli-bubble" tabindex={parseInt(this.forcedTabIndex)} + aria-description={this.timelineItemStateText} >
{ this.name && name.call(this) } diff --git a/packages/fiori/src/i18n/messagebundle.properties b/packages/fiori/src/i18n/messagebundle.properties index ca7675c8c770..9d7c69b01ee3 100644 --- a/packages/fiori/src/i18n/messagebundle.properties +++ b/packages/fiori/src/i18n/messagebundle.properties @@ -98,6 +98,12 @@ NOTIFICATION_LIST_GROUP_EXPANDED=Expanded #XACT: ARIA announcement for timeline label TIMELINE_ARIA_LABEL=Timeline +#XACT: ARIA announcement for the TimelineItem state +TIMELINE_ITEM_INFORMATION_STATE_TEXT=Information State +TIMELINE_ITEM_POSITIVE_STATE_TEXT=Positive State +TIMELINE_ITEM_NEGATIVE_STATE_TEXT=Negative State +TIMELINE_ITEM_CRITICAL_STATE_TEXT=Critical State + #XBUT: Button text for cancel button in the UploadCollectionItem UPLOADCOLLECTIONITEM_CANCELBUTTON_TEXT=Cancel diff --git a/packages/fiori/src/themes/TimelineItem.css b/packages/fiori/src/themes/TimelineItem.css index 9b145271891a..3d5490a95629 100644 --- a/packages/fiori/src/themes/TimelineItem.css +++ b/packages/fiori/src/themes/TimelineItem.css @@ -33,6 +33,22 @@ inset-inline-start: 50%; } +:host([state="Positive"]) .ui5-tli-icon { + color: var(--sapPositiveElementColor); +} + +:host([state="Negative"]) .ui5-tli-icon { + color: var(--sapNegativeElementColor); +} + +:host([state="Critical"]) .ui5-tli-icon { + color: var(--sapCriticalElementColor); +} + +:host([state="Information"]) .ui5-tli-icon { + color: var(--sapInformativeElementColor); +} + :host(:not([icon])[layout="Vertical"]) .ui5-tli-indicator::before { inset-block-start: 1.75rem; } diff --git a/packages/fiori/test/pages/Timeline.html b/packages/fiori/test/pages/Timeline.html index 15e6abfca042..527b9a4bea50 100644 --- a/packages/fiori/test/pages/Timeline.html +++ b/packages/fiori/test/pages/Timeline.html @@ -286,6 +286,33 @@

Advanced Timeline - Horizontal With Groups and Diverse Components

+ +
+

Timeline with Various Timeline Item States

+
+ + + + Compilation succeeded. + + + Lint completed with minor issues. + + + + + Unit tests failed. + + + Integration tests have warnings. + + + End-to-end tests passed. + + + +
+
+ + + +