Skip to content

Commit 19392ff

Browse files
feat(ui5-tabcontainer): support responsive paddings (#2775)
Fixes: #2539
1 parent 9558bba commit 19392ff

File tree

6 files changed

+244
-51
lines changed

6 files changed

+244
-51
lines changed

packages/base/src/MediaRange.js

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
const querySets = {};
2+
3+
/**
4+
* Initializes a screen width media query range set.
5+
*
6+
* This initialization step makes the range set ready to be used for one of the other functions in namespace <code>MediaRange</code>.
7+
*
8+
* A range set can be defined as shown in the following example:
9+
* <pre>
10+
* MediaRange.initRangeSet("MyRangeSet", [200, 400], ["Small", "Medium", "Large"]);
11+
* </pre>
12+
* This example defines the following named ranges:
13+
* <ul>
14+
* <li><code>"Small"</code>: For screens smaller than 200 pixels.</li>
15+
* <li><code>"Medium"</code>: For screens greater than or equal to 200 pixels and smaller than 400 pixels.</li>
16+
* <li><code>"Large"</code>: For screens greater than or equal to 400 pixels.</li>
17+
* </ul>
18+
*
19+
* @param {string} name The name of the range set to be initialized.
20+
* The name must be a valid id and consist only of letters and numeric digits.
21+
*
22+
* @param {int[]} [borders] The range borders
23+
*
24+
* @param {string[]} [names] The names of the ranges. The names must be a valid id and consist only of letters and digits.
25+
*
26+
* @name MediaRange.initRangeSet
27+
* @function
28+
* @public
29+
*/
30+
const _initRangeSet = (name, borders, names) => {
31+
querySets[name] = {
32+
borders,
33+
names,
34+
};
35+
};
36+
37+
/**
38+
* Returns information about the current active range of the range set with the given name.
39+
*
40+
* If the optional parameter <code>width</code> is given, the active range will be determined for that width,
41+
* otherwise it is determined for the current window size.
42+
*
43+
* @param {string} name The name of the range set. The range set must be initialized beforehand ({@link MediaRange.initRangeSet})
44+
* @param {int} [width] An optional width, based on which the range should be determined;
45+
* If <code>width</code> is not provided, the window size will be used.
46+
* @returns {string} The name of the current active interval of the range set.
47+
*
48+
* @name MediaRange.getCurrentRange
49+
* @function
50+
* @public
51+
*/
52+
const _getCurrentRange = (name, width = window.innerWidth) => {
53+
const querySet = querySets[name];
54+
let i = 0;
55+
56+
if (!querySet) {
57+
return null;
58+
}
59+
60+
for (; i < querySet.borders.length; i++) {
61+
if (width < querySet.borders[i]) {
62+
return querySet.names[i];
63+
}
64+
}
65+
66+
return querySet.names[i];
67+
};
68+
69+
/**
70+
* Enumeration containing the names and settings of predefined screen width media query range sets.
71+
*
72+
* @namespace
73+
* @name MediaRange.RANGESETS
74+
* @public
75+
*/
76+
const RANGESETS = {
77+
/**
78+
* A 4-step range set (S-M-L-XL).
79+
*
80+
* The ranges of this set are:
81+
* <ul>
82+
* <li><code>"S"</code>: For screens smaller than 600 pixels.</li>
83+
* <li><code>"M"</code>: For screens greater than or equal to 600 pixels and smaller than 1024 pixels.</li>
84+
* <li><code>"L"</code>: For screens greater than or equal to 1024 pixels and smaller than 1440 pixels.</li>
85+
* <li><code>"XL"</code>: For screens greater than or equal to 1440 pixels.</li>
86+
* </ul>
87+
*
88+
* @name MediaRange.RANGESETS.RANGE_4STEPS
89+
* @public
90+
*/
91+
RANGE_4STEPS: "4Step",
92+
};
93+
94+
/**
95+
* API for screen width changes.
96+
*
97+
* @namespace
98+
* @name MediaRange
99+
*/
100+
101+
const MediaRange = {
102+
RANGESETS,
103+
initRangeSet: _initRangeSet,
104+
getCurrentRange: _getCurrentRange,
105+
};
106+
107+
108+
MediaRange.initRangeSet(MediaRange.RANGESETS.RANGE_4STEPS, [600, 1024, 1440], ["S", "M", "L", "XL"]);
109+
110+
export default MediaRange;

packages/main/src/TabContainer.hbs

+36-25
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,41 @@
77
{{/if}}
88

99
<div class="{{classes.header}}" id="{{_id}}-header">
10-
<ui5-icon @click="{{_onHeaderBackArrowClick}}" class="{{classes.headerBackArrow}}" name="slim-arrow-left" tabindex="-1" accessible-name="{{previousIconACCName}}" show-tooltip></ui5-icon>
11-
12-
<!-- tab items -->
13-
<div class="{{classes.headerScrollContainer}}" id="{{_id}}-headerScrollContainer">
14-
<ul
15-
role="tablist"
16-
class="{{classes.headerList}}"
17-
@click="{{_onHeaderClick}}"
18-
@keydown="{{_onHeaderKeyDown}}"
19-
@keyup="{{_onHeaderKeyUp}}"
20-
>
21-
{{#each items}}
22-
{{#unless this.isSeparator}}
23-
{{this.stripPresentation}}
24-
{{/unless}}
25-
{{#if this.isSeparator}}
26-
<li id="{{this._id}}" role="separator" class="{{../classes.separator}}" style="list-style-type: none;"></li>
27-
{{/if}}
28-
{{/each}}
29-
</ul>
10+
<div class="{{classes.headerInnerContainer}}">
11+
<div class="{{classes.headerBackArrow}}">
12+
<ui5-button @click="{{_onHeaderBackArrowClick}}"
13+
icon="slim-arrow-left"
14+
design="Transparent"
15+
tabindex="-1"
16+
title="{{previousIconACCName}}"></ui5-button>
17+
</div>
18+
<!-- tab items -->
19+
<div class="{{classes.headerScrollContainer}}" id="{{_id}}-headerScrollContainer">
20+
<ul
21+
role="tablist"
22+
class="{{classes.headerList}}"
23+
@click="{{_onHeaderClick}}"
24+
@keydown="{{_onHeaderKeyDown}}"
25+
@keyup="{{_onHeaderKeyUp}}"
26+
>
27+
{{#each items}}
28+
{{#unless this.isSeparator}}
29+
{{this.stripPresentation}}
30+
{{/unless}}
31+
{{#if this.isSeparator}}
32+
<li id="{{this._id}}" role="separator" class="{{../classes.separator}}" style="list-style-type: none;"></li>
33+
{{/if}}
34+
{{/each}}
35+
</ul>
36+
</div>
37+
<div class="{{classes.headerForwardArrow}}">
38+
<ui5-button @click="{{_onHeaderForwardArrowClick}}"
39+
icon="slim-arrow-right"
40+
design="Transparent"
41+
tabindex="-1"
42+
title="{{nextIconACCName}}"></ui5-button>
43+
</div>
3044
</div>
31-
32-
<ui5-icon @click="{{_onHeaderForwardArrowClick}}" class="{{classes.headerForwardArrow}}" name="slim-arrow-right" tabindex="-1" accessible-name="{{nextIconACCName}}" show-tooltip></ui5-icon>
33-
3445
<!-- overflow button -->
3546
{{#if shouldShowOverflow}}
3647
<div
@@ -43,9 +54,9 @@
4354
<ui5-button
4455
icon="{{overflowMenuIcon}}"
4556
design="Transparent"
57+
tabindex="-1"
4658
title="{{overflowMenuTitle}}"
47-
aria-haspopup="true"
48-
></ui5-button>
59+
aria-haspopup="true"></ui5-button>
4960
{{/if}}
5061
</div>
5162
{{/if}}

packages/main/src/TabContainer.js

+23-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import AnimationMode from "@ui5/webcomponents-base/dist/types/AnimationMode.js";
88
import { getAnimationMode } from "@ui5/webcomponents-base/dist/config/AnimationMode.js";
99
import ItemNavigation from "@ui5/webcomponents-base/dist/delegate/ItemNavigation.js";
1010
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
11+
import MediaRange from "@ui5/webcomponents-base/dist/MediaRange.js";
1112
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
1213
import "@ui5/webcomponents-icons/dist/slim-arrow-up.js";
1314
import "@ui5/webcomponents-icons/dist/slim-arrow-down.js";
@@ -152,6 +153,16 @@ const metadata = {
152153
defaultValue: TabLayout.Standard,
153154
},
154155

156+
/**
157+
* Defines the current media query size.
158+
*
159+
* @type {string}
160+
* @private
161+
*/
162+
mediaRange: {
163+
type: String,
164+
},
165+
155166
_selectedTab: {
156167
type: Object,
157168
},
@@ -275,7 +286,7 @@ class TabContainer extends UI5Element {
275286
constructor() {
276287
super();
277288

278-
this._handleHeaderResize = this._handleHeaderResize.bind(this);
289+
this._handleResize = this._handleResize.bind(this);
279290

280291
// Init ScrollEnablement
281292
this._scrollEnablement = new ScrollEnablement(this);
@@ -313,11 +324,11 @@ class TabContainer extends UI5Element {
313324
}
314325

315326
onEnterDOM() {
316-
ResizeHandler.register(this._getHeader(), this._handleHeaderResize);
327+
ResizeHandler.register(this._getHeader(), this._handleResize);
317328
}
318329

319330
onExitDOM() {
320-
ResizeHandler.deregister(this._getHeader(), this._handleHeaderResize);
331+
ResizeHandler.deregister(this._getHeader(), this._handleResize);
321332
}
322333

323334
_onHeaderClick(event) {
@@ -471,8 +482,9 @@ class TabContainer extends UI5Element {
471482
.then(_ => this._updateScrolling());
472483
}
473484

474-
_handleHeaderResize() {
485+
_handleResize() {
475486
this._updateScrolling();
487+
this._updateMediaRange();
476488
}
477489

478490
async _closeRespPopover() {
@@ -492,6 +504,10 @@ class TabContainer extends UI5Element {
492504
}
493505
}
494506

507+
_updateMediaRange() {
508+
this.mediaRange = MediaRange.getCurrentRange(MediaRange.RANGESETS.RANGE_4STEPS, this.getDomRef().offsetWidth);
509+
}
510+
495511
_getHeader() {
496512
return this.shadowRoot.querySelector(`#${this._id}-header`);
497513
}
@@ -523,6 +539,9 @@ class TabContainer extends UI5Element {
523539
"ui5-tc__header": true,
524540
"ui5-tc__header--scrollable": this._scrollable,
525541
},
542+
headerInnerContainer: {
543+
"ui5-tc__headerInnerContainer": true,
544+
},
526545
headerScrollContainer: {
527546
"ui-tc__headerScrollContainer": true,
528547
},

packages/main/src/themes/TabContainer.css

+45-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
}
1717

1818
.ui5-tc__header {
19+
position: relative;
20+
padding: 0 1rem;
1921
display: flex;
2022
align-items: center;
2123
height: var(--_ui5_tc_header_height);
@@ -24,8 +26,14 @@
2426
box-sizing: border-box;
2527
}
2628

29+
.ui5-tc__headerInnerContainer {
30+
position: relative;
31+
flex: 1 1 100%;
32+
min-width: 0; /* fixes chrome flex issue */
33+
}
34+
2735
:host([tabs-placement="Bottom"]) .ui5-tc__header {
28-
border-top: var(--_ui5_tc_header_border_bottom);
36+
border-top: var(--_ui5_tc_header_border_bottom);
2937
}
3038

3139
.ui5-tc-root.ui5-tc--textOnly .ui5-tc__header {
@@ -35,7 +43,7 @@
3543
.ui-tc__headerScrollContainer {
3644
box-sizing: border-box;
3745
overflow: hidden;
38-
flex: 1;
46+
flex: 1 1 100%;
3947
}
4048

4149
.ui5-tc__headerList {
@@ -56,12 +64,26 @@
5664
}
5765

5866
.ui5-tc__headerArrow {
59-
cursor: pointer;
67+
position: absolute;
68+
top: 0;
69+
bottom: 1px;
70+
z-index: 1;
71+
display: flex;
72+
align-items: center;
6073
color: var(--sapContent_IconColor);
74+
background-color: var(--sapObjectHeader_Background);
6175
padding: 0 0.25rem;
6276
visibility: hidden;
6377
}
6478

79+
.ui5-tc__headerArrowLeft {
80+
left: 0;
81+
}
82+
83+
.ui5-tc__headerArrowRight {
84+
right: 0;
85+
}
86+
6587
.ui5-tc__headerArrow:hover,
6688
.ui5-tc__headerArrow:active {
6789
color: var(--sapHighlightColor);
@@ -84,7 +106,7 @@
84106
position: relative;
85107
display: flex;
86108
height: calc(100% - var(--_ui5_tc_header_height)); /* the header height (tabs with icons and text) */
87-
padding: 1rem;
109+
padding: 1rem 2rem;
88110
background-color: var(--sapGroup_ContentBackground);
89111
border-bottom: var(--_ui5_tc_content_border_bottom);
90112
box-sizing: border-box;
@@ -103,7 +125,7 @@
103125
}
104126

105127
.ui5-tc__contentItem {
106-
max-height: 100%;
128+
max-height: 100%;
107129
display: flex;
108130
flex-grow: 1;
109131
overflow: auto;
@@ -113,6 +135,24 @@
113135
display: none;
114136
}
115137

138+
/*** Responsive paddings ***/
139+
140+
:host([media-range="S"]) .ui5-tc__header {
141+
padding: 0;
142+
}
143+
144+
:host([media-range="S"]) .ui5-tc__content {
145+
padding: 1rem;
146+
}
147+
148+
:host([media-range="XL"]) .ui5-tc__header {
149+
padding: 0 2rem;
150+
}
151+
152+
:host([media-range="XL"]) .ui5-tc__content {
153+
padding: 1rem 3rem;
154+
}
155+
116156
/*** RTL ***/
117157
[dir=rtl] .ui-tc__overflowButton {
118158
margin-right: auto;

0 commit comments

Comments
 (0)