Skip to content

Commit e6bda4b

Browse files
feat(ui5-navigation-layout): introduce breakpoint-based responsive behavior (#12817)
Responsiveness has been changed from device-based detection to screen-width breakpoints. Jira: 3570
1 parent 5997ded commit e6bda4b

File tree

7 files changed

+149
-96
lines changed

7 files changed

+149
-96
lines changed

packages/fiori/cypress/specs/NavigationLayout.cy.tsx

Lines changed: 100 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,67 @@ function Sample() {
1818

1919
<SideNavigationGroup text="Group 1" expanded={true}>
2020
<SideNavigationItem text="Item 1" href="#item1" icon={home}></SideNavigationItem>
21-
<SideNavigationItem text="Item 2" href="#item2"icon={home}></SideNavigationItem>
21+
<SideNavigationItem text="Item 2" href="#item2" icon={home}></SideNavigationItem>
22+
<SideNavigationItem text="Item 3" href="#item3" icon={home}></SideNavigationItem>
23+
</SideNavigationGroup>
24+
25+
<SideNavigationItem
26+
slot="fixedItems"
27+
text="Legal"
28+
href="https://www.sap.com/about/legal/impressum.html"
29+
target="_blank"
30+
icon={home}>
31+
</SideNavigationItem>
32+
</SideNavigation>
33+
34+
<div>
35+
Content
36+
</div>
37+
</NavigationLayout>;
38+
}
39+
40+
function SampleWithCollapsedMode() {
41+
return <NavigationLayout id="nl1" mode="Collapsed">
42+
<ShellBar slot="header" primaryTitle="UI5 Web Components">
43+
<Button icon={menu} slot="startButton" id="startButton"></Button>
44+
</ShellBar>
45+
46+
<SideNavigation id="sn1" slot="sideContent">
47+
<SideNavigationItem text="Home" href="#home" icon={home}></SideNavigationItem>
48+
49+
<SideNavigationGroup text="Group 1" expanded={true}>
50+
<SideNavigationItem text="Item 1" href="#item1" icon={home}></SideNavigationItem>
51+
<SideNavigationItem text="Item 2" href="#item2" icon={home}></SideNavigationItem>
52+
<SideNavigationItem text="Item 3" href="#item3" icon={home}></SideNavigationItem>
53+
</SideNavigationGroup>
54+
55+
<SideNavigationItem
56+
slot="fixedItems"
57+
text="Legal"
58+
href="https://www.sap.com/about/legal/impressum.html"
59+
target="_blank"
60+
icon={home}>
61+
</SideNavigationItem>
62+
</SideNavigation>
63+
64+
<div>
65+
Content
66+
</div>
67+
</NavigationLayout>;
68+
}
69+
70+
function SampleWithExpandedMode() {
71+
return <NavigationLayout id="nl1" mode="Expanded">
72+
<ShellBar slot="header" primaryTitle="UI5 Web Components">
73+
<Button icon={menu} slot="startButton" id="startButton"></Button>
74+
</ShellBar>
75+
76+
<SideNavigation id="sn1" slot="sideContent">
77+
<SideNavigationItem text="Home" href="#home" icon={home}></SideNavigationItem>
78+
79+
<SideNavigationGroup text="Group 1" expanded={true}>
80+
<SideNavigationItem text="Item 1" href="#item1" icon={home}></SideNavigationItem>
81+
<SideNavigationItem text="Item 2" href="#item2" icon={home}></SideNavigationItem>
2282
<SideNavigationItem text="Item 3" href="#item3" icon={home}></SideNavigationItem>
2383
</SideNavigationGroup>
2484

@@ -38,11 +98,10 @@ function Sample() {
3898
}
3999

40100
describe("Rendering and interaction", () => {
41-
beforeEach(() => {
42-
cy.mount(<Sample />);
43-
});
44101

45102
it("tests initial rendering", () => {
103+
cy.mount(<Sample />);
104+
46105
cy.get("[ui5-navigation-layout]")
47106
.shadow()
48107
.find(".ui5-nl-root")
@@ -69,36 +128,46 @@ describe("Rendering and interaction", () => {
69128
.should("exist");
70129
});
71130

72-
// it("tests collapsing", () => {
73-
// cy.get("[ui5-side-navigation]")
74-
// .should("have.prop", "collapsed", false);
131+
it("tests collapsing", () => {
132+
cy.mount(<Sample />);
133+
134+
cy.get("[ui5-side-navigation]")
135+
.should("have.prop", "collapsed", false);
136+
137+
cy.get("[ui5-navigation-layout]")
138+
.invoke("prop", "mode", "Collapsed");
139+
140+
cy.get("[ui5-side-navigation]")
141+
.should("have.prop", "collapsed", true);
75142

76-
// cy.get("[ui5-navigation-layout]")
77-
// .invoke("prop", "mode", "Collapsed");
143+
cy.get("[ui5-navigation-layout]")
144+
.invoke("prop", "mode", "Expanded");
78145

79-
// cy.get("[ui5-side-navigation]")
80-
// .should("have.prop", "collapsed", true);
146+
cy.get("[ui5-side-navigation]")
147+
.should("have.prop", "collapsed", false);
148+
});
81149

82-
// cy.get("[ui5-navigation-layout]")
83-
// .invoke("prop", "mode", "Expanded");
150+
it("tests that initial mode=Collapsed overrides default expand/collapse behavior", () => {
151+
cy.mount(<SampleWithCollapsedMode />);
84152

85-
// cy.get("[ui5-side-navigation]")
86-
// .should("have.prop", "collapsed", false);
87-
// });
153+
cy.get("[ui5-side-navigation]")
154+
.should("have.prop", "collapsed", true);
155+
});
88156
});
89157

90-
describe("Navigation Layout on Phone", () => {
158+
describe("Navigation Layout on Small screens (599px or less)", () => {
91159
beforeEach(() => {
92-
cy.ui5SimulateDevice("phone");
93-
cy.mount(<Sample />);
160+
cy.viewport(500, 1080);
94161
});
95162

96163
it("tests initial rendering", () => {
164+
cy.mount(<Sample />);
165+
97166
cy.get("[ui5-navigation-layout]")
98167
.should("have.prop", "sideCollapsed", true);
99168

100169
cy.get("[ui5-side-navigation]")
101-
.should("have.prop", "collapsed", false);
170+
.should("have.prop", "collapsed", true);
102171

103172
cy.get("[ui5-navigation-layout]")
104173
.shadow()
@@ -107,6 +176,8 @@ describe("Navigation Layout on Phone", () => {
107176
});
108177

109178
it("tests collapsing", () => {
179+
cy.mount(<Sample />);
180+
110181
cy.get("[ui5-navigation-layout]")
111182
.invoke("prop", "mode", "Expanded");
112183

@@ -123,4 +194,13 @@ describe("Navigation Layout on Phone", () => {
123194
.find(".ui5-nl-aside")
124195
.should("not.be.visible");
125196
});
197+
198+
it("tests that initial mode=Expanded overrides default expand/collapse behavior", () => {
199+
cy.mount(<SampleWithExpandedMode />);
200+
201+
cy.get("[ui5-navigation-layout]")
202+
.shadow()
203+
.find(".ui5-nl-aside")
204+
.should("be.visible");
205+
});
126206
});

packages/fiori/src/NavigationLayout.ts

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@ import customElement from "@ui5/webcomponents-base/dist/decorators/customElement
33
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
44
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
55
import jsxRenderer from "@ui5/webcomponents-base/dist/renderer/JsxRenderer.js";
6-
import {
7-
isPhone,
8-
isTablet,
9-
isCombi,
10-
} from "@ui5/webcomponents-base/dist/Device.js";
116
import NavigationLayoutMode from "./types/NavigationLayoutMode.js";
127
import type SideNavigation from "./SideNavigation.js";
138

@@ -31,10 +26,10 @@ import NavigationLayoutCss from "./generated/themes/NavigationLayout.css.js";
3126
*
3227
* ### Responsive Behavior
3328
*
34-
* On desktop and tablet devices, the side navigation is visible
29+
* On larger screens (screen width of 600px or more), the side navigation is visible
3530
* by default and can be expanded or collapsed using the `mode` property.
36-
* On phone devices, the side navigation is hidden by default and can
37-
* be displayed using the `mode` property.
31+
* On small screens (screen width of 599px or less), the side navigation is hidden by
32+
* default and can be displayed using the `mode` property.
3833
*
3934
* ### ES6 Module Import
4035
*
@@ -54,7 +49,7 @@ import NavigationLayoutCss from "./generated/themes/NavigationLayout.css.js";
5449
template: NavigationLayoutTemplate,
5550
})
5651
class NavigationLayout extends UI5Element {
57-
_defaultSideCollapsed = isPhone() || (isTablet() && !isCombi());
52+
_defaultSideCollapsed = window.innerWidth < 600; // Small screens (599px or less)
5853

5954
/**
6055
* Specifies the navigation layout mode.
@@ -76,18 +71,6 @@ class NavigationLayout extends UI5Element {
7671
@property({ type: Boolean })
7772
hasSideNavigation = false;
7873

79-
/**
80-
* @private
81-
*/
82-
@property({ type: Boolean })
83-
isPhone = isPhone();
84-
85-
/**
86-
* @private
87-
*/
88-
@property({ type: Boolean })
89-
isTablet = isTablet() && !isCombi();
90-
9174
/**
9275
* Gets whether the side navigation is collapsed.
9376
* @public
@@ -121,10 +104,6 @@ class NavigationLayout extends UI5Element {
121104
onBeforeRendering() {
122105
this.calcSideCollapsed();
123106

124-
if (isPhone()) {
125-
return;
126-
}
127-
128107
const sideNavigation = this.sideContent[0];
129108
this.hasSideNavigation = !!sideNavigation;
130109

packages/fiori/src/SideNavigation.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
1111
import type I18nBundle from "@ui5/webcomponents-base/dist/i18nBundle.js";
1212
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
1313
import type { ITabbable } from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
14-
import {
15-
isPhone,
16-
isTablet,
17-
isCombi,
18-
} from "@ui5/webcomponents-base/dist/Device.js";
1914

2015
import NavigationMode from "@ui5/webcomponents-base/dist/types/NavigationMode.js";
2116
import type SideNavigationItemBase from "./SideNavigationItemBase.js";
@@ -77,8 +72,8 @@ type PopupSideNavigationItem = SideNavigationItem & { associatedItem: SideNaviga
7772
* The `ui5-side-navigation` component is designed to be used within a `ui5-navigation-layout` component to ensure an optimal user experience.
7873
*
7974
* Using it standalone may not match the intended design and functionality.
80-
* For example, the side navigation may not exhibit the correct behavior on phones and tablets.
81-
* Padding of the `ui5-shellbar` will not match the padding of the side navigation.
75+
* For example, the side navigation may not exhibit the correct behavior on smaller screens.
76+
* Additionally, the padding of the `ui5-shellbar` will not match the padding of the side navigation.
8277
*
8378
* ### Keyboard Handling
8479
*
@@ -128,11 +123,12 @@ class SideNavigation extends UI5Element {
128123
/**
129124
* Defines whether the `ui5-side-navigation` is expanded or collapsed.
130125
*
131-
* **Note:** The collapsed mode is not supported on phones.
126+
* **Note:** On small screens (screen width of 599px or less) the collapsed mode is not supported, and in
127+
* expanded mode the side navigation will take the whole width of the screen.
132128
* The `ui5-side-navigation` component is intended to be used within a `ui5-navigation-layout`
133129
* component to ensure proper responsive behavior. If you choose not to use the
134130
* `ui5-navigation-layout`, you will need to implement the appropriate responsive patterns yourself,
135-
* particularly for phones where the collapsed mode should not be used.
131+
* particularly for smaller screens where the collapsed mode should not be used.
136132
*
137133
* @public
138134
* @default false
@@ -190,23 +186,10 @@ class SideNavigation extends UI5Element {
190186
@property({ type: Object })
191187
_menuPopoverItems: Array<SideNavigationItem> = [];
192188

193-
/**
194-
* Defines if the component is rendered on a mobile device.
195-
* @private
196-
*/
197-
@property({ type: Boolean })
198-
isPhone = isPhone();
199-
200189
_isOverflow = false;
201190
_flexibleItemNavigation: ItemNavigation;
202191
_fixedItemNavigation: ItemNavigation;
203192

204-
/**
205-
* @private
206-
*/
207-
@property({ type: Boolean })
208-
isTouchDevice = false;
209-
210193
@i18n("@ui5/webcomponents-fiori")
211194
static i18nBundle: I18nBundle;
212195

@@ -492,16 +475,17 @@ class SideNavigation extends UI5Element {
492475

493476
onEnterDOM() {
494477
ResizeHandler.register(this, this._handleResizeBound);
495-
496-
this.isTouchDevice = isPhone() || (isTablet() && !isCombi());
497478
}
498479

499480
onExitDOM() {
500481
ResizeHandler.deregister(this, this._handleResizeBound);
501482
}
502483

503484
handleResize() {
504-
this._updateOverflowItems();
485+
// In smaller screen the side navigation hidden when collapsed and there is no overflow items
486+
if (window.innerWidth > 600) {
487+
this._updateOverflowItems();
488+
}
505489
}
506490

507491
_updateOverflowItems() {
@@ -521,6 +505,9 @@ class SideNavigation extends UI5Element {
521505
const overflowItems = this.overflowItems;
522506

523507
let itemsHeight = overflowItems.reduce<number>((sum, itemRef) => {
508+
if (!itemRef) {
509+
return sum;
510+
}
524511
itemRef.classList.remove("ui5-sn-item-hidden");
525512
return sum + itemRef.offsetHeight;
526513
}, 0);
@@ -548,7 +535,7 @@ class SideNavigation extends UI5Element {
548535
}
549536

550537
overflowItems.forEach(item => {
551-
if (item === selectedItem) {
538+
if (!item || item === selectedItem) {
552539
return;
553540
}
554541

@@ -682,7 +669,7 @@ class SideNavigation extends UI5Element {
682669
const result: Array<SideNavigationItem> = [];
683670

684671
this.overflowItems.forEach(item => {
685-
if (isInstanceOfSideNavigationItem(item) && item.classList.contains(overflowClass)) {
672+
if (item && isInstanceOfSideNavigationItem(item) && item.classList.contains(overflowClass)) {
686673
result.push(item);
687674
}
688675
});

0 commit comments

Comments
 (0)