Skip to content

Commit 01cd6f9

Browse files
niyapMapTo0
andauthored
feat(ui5-wizard): improve progress navigator responsiveness (#2590)
* [WIP] ui5-wizard: improve progress navigator responsiveness * feat(ui5-wizard): responsive behaviour * ui5-wizard: selection-change fired as expected * ui5-wizard: minor enhancements * ui5-wizard: CSS adjustments * ui5-wizard: CSS adjustments * [WIP] ui5-wizard: improve progress navigator responsiveness * feat(ui5-wizard): responsive behaviour * ui5-wizard: selection-change fired as expected * ui5-wizard: minor enhancements * ui5-wizard: CSS adjustments * css improvements * ui5-wizard: IE fix and other minor adjustments * ui5-wizard: attributes names changed Co-authored-by: Martin Hristov <[email protected]>
1 parent 7b78c16 commit 01cd6f9

File tree

7 files changed

+352
-64
lines changed

7 files changed

+352
-64
lines changed

packages/fiori/src/Wizard.hbs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="ui5-wiz-root" aria-label="{{ariaLabelText}}">
2-
<div class="ui5-wiz-nav" role="navigation" aria-roledescription="{{navAriaRoleDescription}}" tabindex="-1">
2+
<nav class="ui5-wiz-nav" role="navigation" aria-roledescription="{{navAriaRoleDescription}}" tabindex="-1">
33
<ul class="ui5-wiz-nav-list" role="list">
44
{{#each _stepsInHeader}}
55
<ui5-wizard-tab
@@ -22,13 +22,15 @@
2222
_tab-index="{{tabIndex}}"
2323
@selection-change-requested="{{../onSelectionChangeRequested}}"
2424
@focused="{{../onStepInHeaderFocused}}"
25+
@click="{{../_onGroupedTabClick}}"
26+
style={{styles}}
2527
></ui5-wizard-tab>
2628
{{/each}}
2729
</ul>
28-
</div>
30+
</nav>
2931

3032
<div class="ui5-wiz-content" @scroll="{{onScroll}}">
31-
{{#each _steps}}
33+
{{#each _steps}}
3234
<div class="ui5-wiz-content-item"
3335
?hidden="{{disabled}}"
3436
?selected="{{selected}}"

packages/fiori/src/Wizard.js

+203-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js
77
import Float from "@ui5/webcomponents-base/dist/types/Float.js";
88
import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
99
import { isPhone } from "@ui5/webcomponents-base/dist/Device.js";
10+
import Button from "@ui5/webcomponents/dist/Button.js";
11+
import ResponsivePopover from "@ui5/webcomponents/dist/ResponsivePopover.js";
1012

1113
// Texts
1214
import {
@@ -20,7 +22,18 @@ import WizardStep from "./WizardStep.js";
2022

2123
// Template and Styles
2224
import WizardTemplate from "./generated/templates/WizardTemplate.lit.js";
25+
import WizardPopoverTemplate from "./generated/templates/WizardPopoverTemplate.lit.js";
2326
import WizardCss from "./generated/themes/Wizard.css.js";
27+
import WizardPopoverCss from "./generated/themes/WizardPopover.css.js";
28+
29+
30+
const MIN_STEP_WIDTH_NO_TITLE = 64;
31+
const MIN_STEP_WIDTH_WITH_TITLE = 200;
32+
33+
const EXPANDED_STEP = "data-ui5-wizard-expanded-tab";
34+
const AFTER_EXPANDED_STEP = "data-ui5-wizard-expanded-tab-next";
35+
const AFTER_CURRENT_STEP = "data-ui5-wizard-after-current-tab";
36+
const BEFORE_EXPANDED_STEP = "data-ui5-wizard-expanded-tab-prev";
2437

2538
/**
2639
* @public
@@ -48,6 +61,11 @@ const metadata = {
4861
width: {
4962
type: Float,
5063
},
64+
65+
_groupedTabs: {
66+
type: String,
67+
multiple: true,
68+
},
5169
},
5270
slots: /** @lends sap.ui.webcomponents.fiori.Wizard.prototype */ {
5371
/**
@@ -165,6 +183,9 @@ class Wizard extends UI5Element {
165183
// e.g. the steps' starting point.
166184
this.stepScrollOffsets = [];
167185

186+
// Stores references to the grouped steps.
187+
this._groupedTabs = [];
188+
168189
// Keeps track of the selected step index.
169190
this.selectedStepIndex = 0;
170191

@@ -194,16 +215,35 @@ class Wizard extends UI5Element {
194215
return litRender;
195216
}
196217

218+
get classes() {
219+
return {
220+
popover: {
221+
"ui5-wizard-responsive-popover": true,
222+
"ui5-wizard-popover": !isPhone(),
223+
"ui5-wizard-dialog": isPhone(),
224+
},
225+
};
226+
}
227+
197228
static get styles() {
198229
return WizardCss;
199230
}
200231

232+
static get staticAreaStyles() {
233+
return WizardPopoverCss;
234+
}
235+
201236
static get template() {
202237
return WizardTemplate;
203238
}
204239

205240
static get dependencies() {
206-
return [WizardTab, WizardStep];
241+
return [
242+
WizardTab,
243+
WizardStep,
244+
ResponsivePopover,
245+
Button,
246+
];
207247
}
208248

209249
static async onDefine() {
@@ -222,6 +262,10 @@ class Wizard extends UI5Element {
222262
return 80;
223263
}
224264

265+
static get staticAreaTemplate() {
266+
return WizardPopoverTemplate;
267+
}
268+
225269
onEnterDOM() {
226270
ResizeHandler.register(this, this._onResize);
227271
}
@@ -364,6 +408,145 @@ class Wizard extends UI5Element {
364408
*/
365409
onResize() {
366410
this.width = this.getBoundingClientRect().width;
411+
412+
if (this.responsivePopover && this.responsivePopover.opened) {
413+
this._closeRespPopover();
414+
}
415+
}
416+
417+
/**
418+
* Updates the expanded attribute for each ui5-wizard-tab based on the ui5-wizard width
419+
* @private
420+
*/
421+
_adjustHeaderOverflow() {
422+
let counter = 0;
423+
let isForward = true;
424+
const iWidth = this.width;
425+
const iCurrStep = this.getSelectedStepIndex();
426+
const iStepsToShow = this.steps.length ? Math.floor(iWidth / MIN_STEP_WIDTH_WITH_TITLE) : Math.floor(iWidth / MIN_STEP_WIDTH_NO_TITLE);
427+
428+
const tabs = this.shadowRoot.querySelectorAll("ui5-wizard-tab");
429+
430+
if (!tabs.length) {
431+
return;
432+
}
433+
434+
[].forEach.call(tabs, (step, index) => {
435+
step.setAttribute(EXPANDED_STEP, false);
436+
step.setAttribute(BEFORE_EXPANDED_STEP, false);
437+
step.setAttribute(AFTER_EXPANDED_STEP, false);
438+
439+
// Add "data-ui5-wizard-after-current-tab" to all tabs after the current one
440+
if (index > iCurrStep) {
441+
tabs[index].setAttribute(AFTER_CURRENT_STEP, true);
442+
} else {
443+
tabs[index].removeAttribute(AFTER_CURRENT_STEP);
444+
}
445+
});
446+
447+
// Add "data-ui5-wizard-expanded-tab" to the current step
448+
if (tabs[iCurrStep]) {
449+
tabs[iCurrStep].setAttribute(EXPANDED_STEP, true);
450+
}
451+
452+
// Set the "data-ui5-wizard-expanded-tab" to the steps that are expanded
453+
// The algorithm is as follows:
454+
// 1. A step towards the end is expanded
455+
// 1.2. If there are no available steps towards the end a step towards the beginning is expanded
456+
// 2. A step towards the beginning is expanded
457+
// 2.2. If there are no available steps towards the beginning a step towards the end is expanded
458+
for (let i = 1; i < iStepsToShow; i++) {
459+
if (isForward) {
460+
counter += 1;
461+
}
462+
463+
if (isForward && tabs[iCurrStep + counter]) {
464+
tabs[iCurrStep + counter].setAttribute(EXPANDED_STEP, true);
465+
isForward = !isForward;
466+
} else if (!isForward && tabs[iCurrStep - counter]) {
467+
tabs[iCurrStep - counter].setAttribute(EXPANDED_STEP, true);
468+
isForward = !isForward;
469+
} else if (tabs[iCurrStep + counter + 1]) {
470+
counter += 1;
471+
tabs[iCurrStep + counter].setAttribute(EXPANDED_STEP, true);
472+
isForward = true;
473+
} else if (tabs[iCurrStep - counter]) {
474+
tabs[iCurrStep - counter].setAttribute(EXPANDED_STEP, true);
475+
counter += 1;
476+
isForward = false;
477+
}
478+
}
479+
480+
// mark the topmost steps of both groups (in the beginning and the end),
481+
// using the "data-ui5-wizard-after-current-tab" and "data-ui5-wizard-expanded-tab-prev" attributes
482+
for (let i = 0; i < tabs.length; i++) {
483+
if (tabs[i].getAttribute(EXPANDED_STEP) === "true" && tabs[i - 1] && tabs[i - 1].getAttribute(EXPANDED_STEP) === "false") {
484+
tabs[i - 1].setAttribute(BEFORE_EXPANDED_STEP, true);
485+
}
486+
487+
if (tabs[i].getAttribute(EXPANDED_STEP) === "false" && tabs[i - 1] && tabs[i - 1].getAttribute(EXPANDED_STEP) === "true") {
488+
tabs[i].setAttribute(AFTER_EXPANDED_STEP, true);
489+
break;
490+
}
491+
}
492+
}
493+
494+
_isGroupAtStart(selectedStep) {
495+
const iStepNumber = this.stepsInHeaderDOM.indexOf(selectedStep);
496+
497+
return selectedStep.getAttribute(EXPANDED_STEP) === "false" && selectedStep.getAttribute(BEFORE_EXPANDED_STEP) === "true" && iStepNumber > 0;
498+
}
499+
500+
_isGroupAtEnd(selectedStep) {
501+
const iStepNumber = this.stepsInHeaderDOM.indexOf(selectedStep);
502+
503+
return selectedStep.getAttribute(EXPANDED_STEP) === "false" && selectedStep.getAttribute(AFTER_EXPANDED_STEP) === "true" && (iStepNumber + 1 < this.steps.length);
504+
}
505+
506+
async _showPopover(oDomTarget, bAtStart) {
507+
const tabs = Array.from(this.shadowRoot.querySelectorAll("ui5-wizard-tab"));
508+
this._groupedTabs = [];
509+
510+
const iFromStep = bAtStart ? 0 : this.stepsInHeaderDOM.indexOf(oDomTarget);
511+
const iToStep = bAtStart ? this.stepsInHeaderDOM.indexOf(oDomTarget) : tabs.length - 1;
512+
513+
for (let i = iFromStep; i <= iToStep; i++) {
514+
this._groupedTabs.push(tabs[i]);
515+
}
516+
517+
this.responsivePopover = await this._respPopover();
518+
this.responsivePopover.open(oDomTarget);
519+
}
520+
521+
async _onGroupedTabClick(event) {
522+
if (this._isGroupAtStart(event.target)) {
523+
return this._showPopover(event.target, true);
524+
}
525+
526+
if (this._isGroupAtEnd(event.target)) {
527+
return this._showPopover(event.target, false);
528+
}
529+
}
530+
531+
_onOverflowStepButtonClick(event) {
532+
const tabs = Array.from(this.shadowRoot.querySelectorAll("ui5-wizard-tab"));
533+
const stepRefId = event.target.getAttribute("data-ui5-header-tab-ref-id");
534+
const stepToSelect = this.slottedSteps[stepRefId - 1];
535+
const selectedStep = this.selectedStep;
536+
const newlySelectedIndex = this.slottedSteps.indexOf(stepToSelect);
537+
538+
this.switchSelectionFromOldToNewStep(selectedStep, stepToSelect, newlySelectedIndex);
539+
this._closeRespPopover();
540+
tabs[newlySelectedIndex].focus();
541+
}
542+
543+
_closeRespPopover() {
544+
this.responsivePopover.close();
545+
}
546+
547+
async _respPopover() {
548+
const staticAreaItem = await this.getStaticAreaItemDomRef();
549+
return staticAreaItem.querySelector(`.ui5-wizard-responsive-popover`);
367550
}
368551

369552
/**
@@ -400,6 +583,8 @@ class Wizard extends UI5Element {
400583
const stepRefId = stepInHeader.getAttribute("data-ui5-content-ref-id");
401584
const selectedStep = this.selectedStep;
402585
const stepToSelect = this.getStepByRefId(stepRefId);
586+
const bExpanded = stepInHeader.getAttribute(EXPANDED_STEP) === "true";
587+
const newlySelectedIndex = this.slottedSteps.indexOf(stepToSelect);
403588

404589
// If the currently selected (active) step is clicked,
405590
// just scroll to its starting point and stop.
@@ -408,9 +593,10 @@ class Wizard extends UI5Element {
408593
return;
409594
}
410595

411-
// Change selection and fire "selection-change".
412-
const newlySelectedIndex = this.slottedSteps.indexOf(stepToSelect);
413-
this.switchSelectionFromOldToNewStep(selectedStep, stepToSelect, newlySelectedIndex);
596+
if (bExpanded || (!bExpanded && (newlySelectedIndex === 0 || newlySelectedIndex === this.steps.length - 1))) {
597+
// Change selection and fire "selection-change".
598+
this.switchSelectionFromOldToNewStep(selectedStep, stepToSelect, newlySelectedIndex);
599+
}
414600
}
415601

416602
get _stepsInHeader() {
@@ -503,22 +689,25 @@ class Wizard extends UI5Element {
503689
getStepsInfo() {
504690
const lastEnabledStepIndex = this.getLastEnabledStepIndex();
505691
const stepsCount = this.stepsCount;
692+
const selectedStepIndex = this.getSelectedStepIndex();
693+
let inintialZIndex = this.steps.length + 10;
694+
695+
this._adjustHeaderOverflow();
506696

507697
return this.steps.map((step, idx) => {
508698
const pos = idx + 1;
509699

510-
// Hide separator if:
511-
// (1) its size is under the phone breakpoint
512-
// (2) it's the last step and it's not a branching one
513-
const hideSeparator = this.phoneMode || ((idx === stepsCount - 1) && !step.branching);
700+
// Hide separator if it's the last step and it's not a branching one
701+
const hideSeparator = (idx === stepsCount - 1) && !step.branching;
514702

515703
// Calculate the step's aria-roledectioption: "1. heading" or "Step 1".
516704
const roleDescription = step.heading ? `${pos}. ${step.heading}` : `${this.navStepDefaultHeading} ${pos}`;
705+
const isAfterCurrent = (idx > selectedStepIndex);
517706

518707
return {
519708
icon: step.icon,
520-
heading: this.phoneMode ? "" : step.heading,
521-
subheading: this.phoneMode ? "" : step.subheading,
709+
heading: step.heading,
710+
subheading: step.subheading,
522711
number: pos,
523712
selected: step.selected,
524713
disabled: step.disabled,
@@ -531,6 +720,7 @@ class Wizard extends UI5Element {
531720
ariaLabel: getEffectiveAriaLabelText(step),
532721
refStepId: step._id,
533722
tabIndex: this.selectedStepIndex === idx ? "0" : "-1",
723+
styles: `z-index: ${isAfterCurrent ? --inintialZIndex : 1}`,
534724
};
535725
});
536726
}
@@ -643,10 +833,10 @@ class Wizard extends UI5Element {
643833
*
644834
* @param {HTMLElement} selectedStep the old step
645835
* @param {HTMLElement} stepToSelect the step to be selected
646-
* @param {Integer} selectedStepIndex the index of the newly selected step
836+
* @param {Integer} stepToSelectIndex the index of the newly selected step
647837
* @private
648838
*/
649-
switchSelectionFromOldToNewStep(selectedStep, stepToSelect, selectedStepIndex) {
839+
switchSelectionFromOldToNewStep(selectedStep, stepToSelect, stepToSelectIndex) {
650840
if (selectedStep && stepToSelect) {
651841
selectedStep.selected = false;
652842
stepToSelect.selected = true;
@@ -656,7 +846,7 @@ class Wizard extends UI5Element {
656846
previouslySelectedStep: selectedStep,
657847
});
658848

659-
this.selectedStepIndex = selectedStepIndex;
849+
this.selectedStepIndex = stepToSelectIndex;
660850
}
661851
}
662852

packages/fiori/src/WizardPopover.hbs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<ui5-responsive-popover
2+
horizontal-align="Center"
3+
placement-type="Bottom"
4+
class="{{classes.popover}}"
5+
@ui5-after-close={{_afterClosePopover}}
6+
content-only-on-desktop
7+
prevent-focus-restore
8+
with-padding
9+
_hide-header
10+
>
11+
<ul class="ui5-wizard-responsive-popover-list">
12+
{{#each _groupedTabs}}
13+
<li>
14+
<ui5-button
15+
icon="{{this.icon}}"
16+
?disabled="{{this.disabled}}"
17+
design="Transparent"
18+
data-ui5-header-tab-ref-id="{{this.ariaPosinset}}"
19+
@click="{{../_onOverflowStepButtonClick}}"
20+
>
21+
{{this.heading}}
22+
</ui5-button>
23+
</li>
24+
{{/each}}
25+
</ul>
26+
<div slot="footer" class="ui5-responsive-popover-footer">
27+
<ui5-button
28+
design="Transparent"
29+
@click="{{_closeRespPopover}}"
30+
>Cancel</ui5-button>
31+
</div>
32+
</ui5-responsive-popover>

0 commit comments

Comments
 (0)