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
361 changes: 331 additions & 30 deletions packages/fiori/cypress/specs/Timeline.cy.tsx

Large diffs are not rendered by default.

101 changes: 95 additions & 6 deletions packages/fiori/src/Timeline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import type { DefaultSlot } from "@ui5/webcomponents-base/dist/UI5Element.js";
import type { DefaultSlot, Slot } from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot-strict.js";
Expand All @@ -21,7 +21,10 @@ import type ToggleButton from "@ui5/webcomponents/dist/ToggleButton.js";
import "./TimelineItem.js";
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js";
import { TIMELINE_ARIA_LABEL, TIMELINE_LOAD_MORE_BUTTON_TEXT } from "./generated/i18n/i18n-defaults.js";
import {
TIMELINE_ARIA_LABEL,
TIMELINE_LOAD_MORE_BUTTON_TEXT,
} from "./generated/i18n/i18n-defaults.js";
import TimelineTemplate from "./TimelineTemplate.js";
import event from "@ui5/webcomponents-base/dist/decorators/event-strict.js";
import debounce from "@ui5/webcomponents-base/dist/util/debounce.js";
Expand All @@ -35,6 +38,8 @@ import TimelineLayout from "./types/TimelineLayout.js";
import TimelineGrowingMode from "./types/TimelineGrowingMode.js";
import { getFirstFocusableElement } from "@ui5/webcomponents-base/dist/util/FocusableElements.js";
import getActiveElement from "@ui5/webcomponents-base/dist/util/getActiveElement.js";
import type TimelineHeaderBar from "./TimelineHeaderBar.js";
import type { TimelineHeaderBarSearchEventDetail, TimelineHeaderBarFilterEventDetail, TimelineHeaderBarSortEventDetail } from "./TimelineHeaderBar.js";

/**
* Interface for components that may be slotted inside `ui5-timeline` as items
Expand All @@ -54,8 +59,17 @@ interface ITimelineItem extends UI5Element, ITabbable {
isNextItemGroup?: boolean;
firstItemInTimeline?: boolean;
effectiveRole?: string;
titleText?: string;
name?: string;
subtitleText?: string;
}

type TimelineSearchEventDetail = TimelineHeaderBarSearchEventDetail;

type TimelineFilterEventDetail = TimelineHeaderBarFilterEventDetail;

type TimelineSortEventDetail = TimelineHeaderBarSortEventDetail;

const SHORT_LINE_WIDTH = "ShortLineWidth";
const LARGE_LINE_WIDTH = "LargeLineWidth";
const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms
Expand All @@ -70,6 +84,15 @@ const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms
* These entries can be generated by the system (for example, value XY changed from A to B), or added manually.
* There are two distinct variants of the timeline: basic and social. The basic timeline is read-only,
* while the social timeline offers a high level of interaction and collaboration, and is integrated within SAP Jam.
*
* ### Header Bar
*
* The Timeline supports a `header-bar` slot for search, filter, and sort functionality.
* Use the `ui5-timeline-header-bar` component in this slot.
* The Timeline fires `search`, `filter`, and `sort` events that the application should handle
* by adding, removing, or reordering items in the DOM. The Timeline itself does not perform
* filtering or sorting — it renders whatever items are provided in the default slot.
*
* @constructor
* @extends UI5Element
* @public
Expand All @@ -94,9 +117,55 @@ const GROWING_WITH_SCROLL_DEBOUNCE_RATE = 250; // ms
bubbles: true,
})

/**
* Fired when the user performs a search in the header bar.
*
* **Note:** The Timeline does not perform filtering. The application should handle
* this event and add/remove items from the DOM to reflect the search results.
*
* @param {string} value The search value entered by the user.
* @public
* @since 2.22.0
*/
@event("search", {
bubbles: true,
})

/**
* Fired when the user changes filter selection in the header bar.
*
* **Note:** The Timeline does not perform filtering. The application should handle
* this event and add/remove items from the DOM to reflect the filter selection.
*
* @param {string} filterBy The filter category.
* @param {string[]} selectedOptions The selected filter option texts.
* @public
* @since 2.22.0
*/
@event("filter", {
bubbles: true,
})

/**
* Fired when the user changes sort order in the header bar.
*
* **Note:** The Timeline does not perform sorting. The application should handle
* this event and reorder the items in the DOM accordingly.
*
* @param {string} sortOrder The sort order ("Ascending" or "Descending").
* @public
* @since 2.22.0
*/
@event("sort", {
bubbles: true,
})

class Timeline extends UI5Element {
eventDetails!: {
"load-more": void,
"search": TimelineSearchEventDetail,
"filter": TimelineFilterEventDetail,
"sort": TimelineSortEventDetail,
}
/**
* Defines the items orientation.
Expand Down Expand Up @@ -167,6 +236,19 @@ class Timeline extends UI5Element {
@slot({ type: HTMLElement, individualSlots: true, "default": true })
items!: DefaultSlot<ITimelineItem>;

/**
* Defines the header bar of the timeline.
* Use `ui5-timeline-header-bar` for filtering, sorting, and search functionality.
*
* **Note:** The Timeline fires `search`, `filter`, and `sort` events when the user interacts
* with the header bar. The application should handle these events to filter/sort the items.
*
* @public
* @since 2.22.0
*/
@slot()
headerBar!: Slot<TimelineHeaderBar>;

@query(".ui5-timeline-end-marker")
timelineEndMarker!: HTMLElement;

Expand Down Expand Up @@ -215,6 +297,14 @@ class Timeline extends UI5Element {
return this.growing === TimelineGrowingMode.Button;
}

get _hasHeaderBar(): boolean {
return this.headerBar.length > 0;
}

onExitDOM() {
this.unobserveTimelineEnd();
}

onAfterRendering() {
if (this.growsOnScroll) {
this.observeTimelineEnd();
Expand All @@ -225,10 +315,6 @@ class Timeline extends UI5Element {
this.growingIntersectionObserver = this.getIntersectionObserver();
}

onExitDOM() {
this.unobserveTimelineEnd();
}

async observeTimelineEnd() {
if (!this.timeLineEndObserved) {
await renderFinished();
Expand Down Expand Up @@ -473,4 +559,7 @@ Timeline.define();
export default Timeline;
export type {
ITimelineItem,
TimelineSearchEventDetail,
TimelineFilterEventDetail,
TimelineSortEventDetail,
};
47 changes: 47 additions & 0 deletions packages/fiori/src/TimelineFilterOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";

/**
* @class
*
* ### Overview
*
* The `ui5-timeline-filter-option` component defines individual filter values within a `ui5-timeline-header-bar`.
* It represents a single selectable option that users can choose to filter timeline items.
*
* ### Usage
*
* The `ui5-timeline-filter-option` is used as a child component within `ui5-timeline-header-bar`.
* Each option represents a specific value that can be used for filtering.
*
* ### ES6 Module Import
*
* `import "@ui5/webcomponents-fiori/dist/TimelineFilterOption.js";`
* @constructor
* @extends UI5Element
* @since 2.22.0
* @public
*/
@customElement("ui5-timeline-filter-option")
class TimelineFilterOption extends UI5Element {
/**
* Defines the text of the filter option.
* @default ""
* @public
*/
@property()
text = "";

/**
* Defines if the filter option is selected.
* @default false
* @public
*/
@property({ type: Boolean })
selected = false;
}

TimelineFilterOption.define();

export default TimelineFilterOption;
Loading
Loading