Skip to content

Commit

Permalink
feat(ui5-timeline-group-item): introduce new component (#9407)
Browse files Browse the repository at this point in the history
Introducing the new `<ui5-timeline-group-item>` web component.

It allows the application developers to group a `<ui5-timeline-item>`'s under one name, specified by the `groupName` property.

Mixed scenarios are possible, between `<ui5-timeline-item>`'s and `<ui5-timeline-group-item>`'s, where they are on the same level in the DOM.

Sample usage of groups in **mixed** scenarios could be:
```html
<ui5-timeline id="verticalWithGrps">
    <ui5-timeline-item></ui5-timeline-item>
    <ui5-timeline-item></ui5-timeline-item>
 
    /* Items in a group */
    <ui5-timeline-group-item group-name="Events">

        <ui5-timeline-item>Morning event</ui5-timeline-item>
	<ui5-timeline-item>
	     <ui5-avatar initials="SK"></ui5-avatar>
	     <ui5-label>Good morning</ui5-label>
	</ui5-timeline-item>
	<ui5-timeline-item><ui5-avatar initials="SK"></ui5-avatar></ui5-timeline-item>

    </ui5-timeline-group-item>

    <ui5-timeline-item></ui5-timeline-item>
    <ui5-timeline-item></ui5-timeline-item>
</ui5-timeline>
```

### Vertical layout
![2024-07-22_09-36-41](https://github.com/user-attachments/assets/af7ac9ee-4465-4be5-ab8f-d42d163b9ad5)


### Horizontal layout
![2024-07-22_09-43-54](https://github.com/user-attachments/assets/f6c767d3-d1c6-40fb-b5e0-e05a028c4286)
  • Loading branch information
hinzzx authored Aug 2, 2024
1 parent df445eb commit aea62ef
Show file tree
Hide file tree
Showing 34 changed files with 1,132 additions and 91 deletions.
129 changes: 104 additions & 25 deletions packages/fiori/src/Timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import { isTabNext, isTabPrevious } from "@ui5/webcomponents-base/dist/Keys.js";
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
import {
isTabNext,
isTabPrevious,
} from "@ui5/webcomponents-base/dist/Keys.js";
import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
import type ToggleButton from "@ui5/webcomponents/dist/ToggleButton.js";
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js";
import { getEventMark } from "@ui5/webcomponents-base/dist/MarkedEvents.js";
import { TIMELINE_ARIA_LABEL } from "./generated/i18n/i18n-defaults.js";
import TimelineTemplate from "./generated/templates/TimelineTemplate.lit.js";
import TimelineItem from "./TimelineItem.js";
import TimelineGroupItem from "./TimelineGroupItem.js";

// Styles
import TimelineCss from "./generated/themes/Timeline.css.js";
Expand All @@ -23,11 +28,18 @@ import TimelineLayout from "./types/TimelineLayout.js";
* @public
*/
interface ITimelineItem extends UI5Element, ITabbable {
layout: `${TimelineLayout}`,
icon?: string,
forcedLineWidth?: string,
nameClickable: boolean,
focusLink: () => void,
layout: `${TimelineLayout}`;
isGroupItem: boolean;
forcedLineWidth?: string;
icon?: string;
nameClickable?: boolean;
positionInGroup?: number;
collapsed?: boolean;
items?: Array<ITimelineItem>;
focusLink?(): void;
lastItem: boolean;
isNextItemGroup?: boolean;
firstItemInTimeline?: boolean;
}

const SHORT_LINE_WIDTH = "ShortLineWidth";
Expand All @@ -54,7 +66,7 @@ const LARGE_LINE_WIDTH = "LargeLineWidth";
renderer: litRender,
styles: TimelineCss,
template: TimelineTemplate,
dependencies: [TimelineItem],
dependencies: [TimelineItem, TimelineGroupItem],
})
class Timeline extends UI5Element {
/**
Expand Down Expand Up @@ -90,7 +102,7 @@ class Timeline extends UI5Element {
super();

this._itemNavigation = new ItemNavigation(this, {
getItemsCallback: () => this.items,
getItemsCallback: () => this._navigatableItems,
});
}

Expand All @@ -105,14 +117,22 @@ class Timeline extends UI5Element {
}

_onfocusin(e: FocusEvent) {
const target = e.target as TimelineItem;
let target = e.target as ITimelineItem | ToggleButton;

if ((target as ITimelineItem).isGroupItem) {
target = target.shadowRoot!.querySelector<ToggleButton>("[ui5-toggle-button]")!;
}

this._itemNavigation.setCurrentItem(target);
}

onBeforeRendering() {
this._itemNavigation._navigationMode = this.layout === TimelineLayout.Horizontal ? NavigationMode.Horizontal : NavigationMode.Vertical;

if (!this.items.length) {
return;
}

for (let i = 0; i < this.items.length; i++) {
this.items[i].layout = this.layout;
if (this.items[i + 1] && !!this.items[i + 1].icon) {
Expand All @@ -121,35 +141,94 @@ class Timeline extends UI5Element {
this.items[i].forcedLineWidth = LARGE_LINE_WIDTH;
}
}

this._setLastItem();
this._setIsNextItemGroup();
this.items[0].firstItemInTimeline = true;
}

_setLastItem() {
const items = this.items;

for (let i = 0; i < items.length; i++) {
items[i].lastItem = false;
}

if (items.length > 0) {
items[items.length - 1].lastItem = true;
}
}

_setIsNextItemGroup() {
for (let i = 0; i < this.items.length; i++) {
this.items[i].isNextItemGroup = false;
}

for (let i = 0; i < this.items.length; i++) {
if (this.items[i + 1] && this.items[i + 1].isGroupItem) {
this.items[i].isNextItemGroup = true;
}
}
}

_onkeydown(e: KeyboardEvent) {
const target = e.target as TimelineItem;
const target = e.target as ITimelineItem;

if (target.nameClickable && getEventMark(e) !== "link") {
return;
}

if (isTabNext(e)) {
if (!target.nameClickable || getEventMark(e) === "link") {
this._handleTabNextOrPrevious(e, isTabNext(e));
}
this._handleNextOrPreviousItem(e, true);
} else if (isTabPrevious(e)) {
this._handleTabNextOrPrevious(e);
this._handleNextOrPreviousItem(e);
}
}

_handleTabNextOrPrevious(e: KeyboardEvent, isNext?: boolean) {
const target = e.target as TimelineItem;
const nextTargetIndex = isNext ? this.items.indexOf(target) + 1 : this.items.indexOf(target) - 1;
const nextTarget = this.items[nextTargetIndex] as TimelineItem;
_handleNextOrPreviousItem(e: KeyboardEvent, isNext?: boolean) {
const target = e.target as ITimelineItem | ToggleButton;
let updatedTarget = target;

if ((target as ITimelineItem).isGroupItem) {
updatedTarget = target.shadowRoot!.querySelector<ToggleButton>("[ui5-toggle-button]")!;
}

const nextTargetIndex = isNext ? this._navigatableItems.indexOf(updatedTarget) + 1 : this._navigatableItems.indexOf(updatedTarget) - 1;
const nextTarget = this._navigatableItems[nextTargetIndex];

if (!nextTarget) {
return;
}
if (nextTarget.nameClickable && !isNext) {

if (nextTarget) {
e.preventDefault();
nextTarget.focusLink();
return;
nextTarget.focus();
this._itemNavigation.setCurrentItem(nextTarget);
}
e.preventDefault();
nextTarget.focus();
this._itemNavigation.setCurrentItem(nextTarget);
}

get _navigatableItems() {
const navigatableItems: Array<ITimelineItem | ToggleButton> = [];

if (!this.items.length) {
return [];
}

this.items.forEach(item => {
if (!item.isGroupItem) {
navigatableItems.push(item);
} else {
navigatableItems.push(item.shadowRoot!.querySelector<ToggleButton>("[ui5-toggle-button]")!);
}

if (item.isGroupItem && !item.collapsed) {
item.items?.forEach(groupItem => {
navigatableItems.push(groupItem);
});
}
});

return navigatableItems;
}
}

Expand Down
25 changes: 25 additions & 0 deletions packages/fiori/src/TimelineGroupItem.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="ui5-tlgi-root">
<div class="ui5-tlgi-btn-root">
<div class="ui5-tlgi-icon-placeholder">
<div class="ui5-tlgi-icon-dot"></div>
</div>

<div class="ui5-tlgi-line-placeholder">
<div class="ui5-tlgi-line"></div>
</div>

<ui5-toggle-button
icon="{{_groupItemIcon}}"
@click="{{onGroupItemClick}}"
class="ui5-tlgi-btn"
.pressed="{{collapsed}}"
>{{groupName}}</ui5-toggle-button>
</div>
<ul class="ui5-tl-group-item">
{{#each items}}
<li class="ui5-timeline-group-list-item">
<slot name="{{this._individualSlot}}"></slot>
</li>
{{/each}}
</ul>
</div>
Loading

0 comments on commit aea62ef

Please sign in to comment.