Skip to content

Commit 86f0333

Browse files
author
GerganaKremenska
authored
refactor(ui5-card): header slot is added (#3490)
Extract the Card's header part as a separate component with all the related API, called CardHeader and introduce new slot "header" in the Card. BREAKING CHANGE: titleText, subtitleText, status, headerInteractive properties, action and avatar slots, and header-click events are not available anymore. Instead, use the newly created CardHeader component, where all these properties are available ("headerInteractive" becomes "interactive" and "header-click" becomes "click"). In addition, the Card now has a "header" slot, for which you can use the CardHeader.
1 parent e335f52 commit 86f0333

16 files changed

+1209
-929
lines changed

docs/Public Module Imports.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ For API documentation and samples, please check the [UI5 Web Components Playgrou
2525
| Avatar | `ui5-avatar` | `import "@ui5/webcomponents/dist/Avatar.js";` |
2626
| Avatar Group | `ui5-avatar-group` | `import "@ui5/webcomponents/dist/AvatarGroup.js";` |
2727
| Badge | `ui5-badge` | `import "@ui5/webcomponents/dist/Badge.js";` |
28-
| Busy Indicator | `ui5-busy-indicator` | `import "@ui5/webcomponents/dist/BusyIndicator.js";` |
28+
| Busy Indicator | `ui5-busy-indicator` | `import "@ui5/webcomponents/dist/BusyIndicator.js";` |
2929
| Button | `ui5-button` | `import "@ui5/webcomponents/dist/Button.js";` |
3030
| Card | `ui5-card` | `import "@ui5/webcomponents/dist/Card.js";` |
31+
| CardHeader | `ui5-card-header` | `import "@ui5/webcomponents/dist/CardHeader.js";` |
3132
| Carousel | `ui5-carousel` | `import "@ui5/webcomponents/dist/Carousel.js";` |
3233
| Checkbox | `ui5-checkbox` | `import "@ui5/webcomponents/dist/CheckBox.js";` |
3334
| Color Palette | `ui5-color-palette` | `import "@ui5/webcomponents/dist/ColorPalette.js";` |
@@ -57,8 +58,8 @@ For API documentation and samples, please check the [UI5 Web Components Playgrou
5758
| Responsive Popover | `ui5-responsive-popover` | `import "@ui5/webcomponents/dist/ResponsivePopover.js";` |
5859
| Select | `ui5-select` | `import "@ui5/webcomponents/dist/Select.js";` |
5960
| Select Option | `ui5-option` | comes with `ui5-select ` |
60-
| Segmented Button | `ui5-segmented-button` | `import "@ui5/webcomponents/dist/SegmentedButton.js";` |
61-
| Segmented Button Item | `ui5-segmented-button-item`| comes with `ui5-segmented-button ` |
61+
| Segmented Button | `ui5-segmented-button` | `import "@ui5/webcomponents/dist/SegmentedButton.js";` |
62+
| Segmented Button Item | `ui5-segmented-button-item`| comes with `ui5-segmented-button ` |
6263
| Suggestion Item | `ui5-suggestion-item` | comes with `InputSuggestions.js` feature - see below |
6364
| Slider | `ui5-slider` | `import "@ui5/webcomponents/dist/Slider.js";` |
6465
| Step Input | `ui5-step-input` | `import "@ui5/webcomponents/dist/StepInput.js";` |

packages/fiori/test/pages/Timeline.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ <h1 class="header-title">ui5-timeline</h1>
7474
<div class="samples">
7575
<h2>Timeline within Card</h2>
7676
<div class="sample">
77-
<ui5-card
78-
heading="Upcoming Activities"
79-
subtitle="For Today">
77+
<ui5-card>
78+
<ui5-card-header slot="header" title-text="Upcoming Activities" subtitle-text="For Today">
79+
</ui5-card-header>
8080
<ui5-timeline>
8181
<ui5-timeline-item id="test-item" title-text="called" subtitle-text="20.02.2017 11:30" icon="phone" name="Stanislava Baltova" name-clickable></ui5-timeline-item>
8282
<ui5-timeline-item id="test-item" title-text="called" subtitle-text="20.02.2017 11:30" icon="phone" name="Stanislava Baltova"></ui5-timeline-item>

packages/main/bundle.common.js

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Badge from "./dist/Badge.js";
3636
import BusyIndicator from "./dist/BusyIndicator.js";
3737
import Button from "./dist/Button.js";
3838
import Card from "./dist/Card.js";
39+
import CardHeader from "./dist/CardHeader.js";
3940
import Carousel from "./dist/Carousel.js";
4041
import CheckBox from "./dist/CheckBox.js";
4142
import ColorPalette from "./dist/ColorPalette.js";

packages/main/src/Card.hbs

+6-34
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,16 @@
11
<div
2-
class="{{classes.main}}"
2+
class="{{classes}}"
33
dir="{{effectiveDir}}"
44
role="region"
55
aria-label="{{ariaLabelText}}"
6-
aria-labelledby="{{ariaLabelledByCard}}">
6+
aria-labelledby="{{ariaLabelledByCard}}"
7+
>
8+
<!-- header -->
79
{{#if hasHeader}}
8-
<div class="{{classes.header}}"
9-
@click="{{_headerClick}}"
10-
@keydown="{{_headerKeydown}}"
11-
@keyup="{{_headerKeyup}}"
12-
role="{{ariaHeaderRole}}"
13-
aria-labelledby="{{ariaLabelledByHeader}}"
14-
aria-level="{{ariaLevel}}"
15-
aria-roledescription="{{ariaCardHeaderRoleDescription}}"
16-
tabindex="0">
17-
18-
{{#if hasAvatar}}
19-
<div id="{{_id}}-avatar" class="ui5-card-avatar" aria-label="{{ariaCardAvatarLabel}}">
20-
<slot name="avatar"></slot>
21-
</div>
22-
{{/if}}
23-
24-
<div class="ui5-card-header-text">
25-
{{#if titleText}}
26-
<div id="{{_id}}-title" class="ui5-card-title" part="title">{{titleText}}</div>
27-
{{/if}}
28-
29-
{{#if subtitleText}}
30-
<div id="{{_id}}-subtitle" class="ui5-card-subtitle" part="subtitle">{{subtitleText}}</div>
31-
{{/if}}
32-
</div>
33-
34-
{{#if hasAction}}
35-
<slot name="action"></slot>
36-
{{else}}
37-
<span id="{{_id}}-status" part="status" class="ui5-card-status">{{status}}</span>
38-
{{/if}}
10+
<div class="ui5-card-header-root">
11+
<slot name="header"></slot>
3912
</div>
4013
{{/if}}
41-
4214
<div role="group" aria-label="{{ariaCardContentLabel}}">
4315
<slot></slot>
4416
</div>

packages/main/src/Card.js

+20-201
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
22
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
33
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
44
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
5-
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
65
import CardTemplate from "./generated/templates/CardTemplate.lit.js";
76
import Icon from "./Icon.js";
87

98
import {
109
ARIA_ROLEDESCRIPTION_CARD,
11-
AVATAR_TOOLTIP,
1210
ARIA_LABEL_CARD_CONTENT,
13-
ARIA_ROLEDESCRIPTION_CARD_HEADER,
14-
ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER,
1511
} from "./generated/i18n/i18n-defaults.js";
1612

1713
// Styles
@@ -38,82 +34,20 @@ const metadata = {
3834
},
3935

4036
/**
41-
* Defines the visual representation in the header of the card.
42-
* Supports images and icons.
37+
* Defines the header of the component.
4338
* <br><br>
44-
* <b>Note:</b>
45-
* SAP-icons font provides numerous options. To find all the available icons, see the
46-
* <ui5-link target="_blank" href="https://openui5.hana.ondemand.com/test-resources/sap/m/demokit/iconExplorer/webapp/index.html" class="api-table-content-cell-link">Icon Explorer</ui5-link>.
39+
* <b>Note:</b> Use <code>ui5-card-header</code> for the intended design.
4740
* @type {HTMLElement[]}
48-
* @slot
49-
* @public
50-
*/
51-
avatar: {
52-
type: HTMLElement,
53-
},
54-
55-
/**
56-
* Defines an action, displayed in the right most part of the header.
57-
* <br><br>
58-
* <b>Note:</b> If set, the <code>status</code> text will not be displayed,
59-
* you can either have <code>action</code>, or <code>status</code>.
60-
* @type {HTMLElement[]}
61-
* @slot
41+
* @since 1.0.0-rc.15
42+
* @slot content
6243
* @public
63-
* @since 1.0.0-rc.8
6444
*/
65-
action: {
45+
header: {
6646
type: HTMLElement,
6747
},
6848
},
6949
properties: /** @lends sap.ui.webcomponents.main.Card.prototype */ {
7050

71-
/**
72-
* Defines the title displayed in the component header.
73-
* @type {string}
74-
* @defaultvalue ""
75-
* @public
76-
* @since 1.0.0-rc.15
77-
*/
78-
titleText: {
79-
type: String,
80-
},
81-
82-
/**
83-
* Defines the subtitle displayed in the component header.
84-
* @type {string}
85-
* @defaultvalue ""
86-
* @public
87-
* @since 1.0.0-rc5
88-
*/
89-
subtitleText: {
90-
type: String,
91-
},
92-
93-
/**
94-
* Defines the status displayed in the component header.
95-
* <br><br>
96-
* <b>Note:</b> If the <code>action</code> slot is set, the <code>status</code> will not be displayed,
97-
* you can either have <code>action</code>, or <code>status</code>.
98-
* @type {string}
99-
* @defaultvalue ""
100-
* @public
101-
*/
102-
status: {
103-
type: String,
104-
},
105-
106-
/**
107-
* Defines if the component header would be interactive,
108-
* e.g gets hover effect, gets focused and <code>headerPress</code> event is fired, when it is pressed.
109-
* @type {boolean}
110-
* @defaultvalue false
111-
* @public
112-
*/
113-
headerInteractive: {
114-
type: Boolean,
115-
},
116-
11751
/**
11852
* Sets the accessible aria name of the component.
11953
*
@@ -138,25 +72,8 @@ const metadata = {
13872
type: String,
13973
defaultValue: "",
14074
},
141-
142-
_headerActive: {
143-
type: Boolean,
144-
noAttribute: true,
145-
},
146-
},
147-
events: /** @lends sap.ui.webcomponents.main.Card.prototype */ {
148-
149-
/**
150-
* Fired when the component header is activated
151-
* by mouse/tap or by using the Enter or Space key.
152-
* <br><br>
153-
* <b>Note:</b> The event would be fired only if the <code>headerInteractive</code> property is set to true.
154-
* @event sap.ui.webcomponents.main.Card#header-click
155-
* @public
156-
* @since 0.10.0
157-
*/
158-
"header-click": {},
15975
},
76+
events: /** @lends sap.ui.webcomponents.main.Card.prototype */ {},
16077
};
16178

16279
/**
@@ -166,26 +83,19 @@ const metadata = {
16683
* The <code>ui5-card</code> is a component that represents information in the form of a
16784
* tile with separate header and content areas.
16885
* The content area of a <code>ui5-card</code> can be arbitrary HTML content.
169-
* The header can be used through several properties, such as: <code>titleText</code>, <code>subtitleText</code>, <code>status</code>
170-
* and two slots: <code>avatar</code> and <code>action</code>.
86+
* The header can be used through slot <code>header</code>. For which there is a <code>ui5-card-header</code> component to achieve the card look and fill.
17187
*
172-
* <h3>Keyboard handling</h3>
173-
* In case you enable <code>headerInteractive</code> property, you can press the <code>ui5-card</code> header by Space and Enter keys.
88+
* Note: We recommend the usage of <code>ui5-card-header</code> for the header slot, so advantage can be taken for keyboard handling, styling and accessibility.
17489
*
17590
* <h3>CSS Shadow Parts</h3>
17691
*
17792
* <ui5-link target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part">CSS Shadow Parts</ui5-link> allow developers to style elements inside the Shadow DOM.
178-
* <br>
179-
* The <code>ui5-card</code> exposes the following CSS Shadow Parts:
180-
* <ul>
181-
* <li>title - Used to style the title of the card</li>
182-
* <li>subtitle - Used to style the subtitle of the card</li>
183-
* <li>status - Used to style the status of the card</li>
184-
* </ul>
18593
*
18694
* <h3>ES6 Module Import</h3>
18795
*
18896
* <code>import "@ui5/webcomponents/dist/Card";</code>
97+
* <br>
98+
* <code>import "@ui5/webcomponents/dist/CardHeader.js";</code> (for <code>ui5-card-header</code>)
18999
*
190100
* @constructor
191101
* @author SAP SE
@@ -219,36 +129,13 @@ class Card extends UI5Element {
219129

220130
get classes() {
221131
return {
222-
main: {
223-
"ui5-card-root": true,
224-
"ui5-card--nocontent": !this.content.length,
225-
},
226-
header: {
227-
"ui5-card-header": true,
228-
"ui5-card-header--interactive": this.headerInteractive,
229-
"ui5-card-header--active": this.headerInteractive && this._headerActive,
230-
},
132+
"ui5-card-root": true,
133+
"ui5-card--nocontent": !this.content.length,
231134
};
232135
}
233136

234-
get icon() {
235-
return !!this.avatar && this.avatar.startsWith("sap-icon://");
236-
}
237-
238-
get image() {
239-
return !!this.avatar && !this.icon;
240-
}
241-
242-
get ariaHeaderRole() {
243-
return this.headerInteractive ? "button" : "heading";
244-
}
245-
246-
get ariaLevel() {
247-
return this.headerInteractive ? undefined : "3";
248-
}
249-
250137
get hasHeader() {
251-
return !!(this.titleText || this.subtitleText || this.status || this.hasAction || this.avatar);
138+
return !!this.header.length;
252139
}
253140

254141
get ariaLabelText() {
@@ -259,46 +146,18 @@ class Card extends UI5Element {
259146
return this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_CARD);
260147
}
261148

262-
get ariaCardHeaderRoleDescription() {
263-
return this.headerInteractive ? this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER) : this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_CARD_HEADER);
264-
}
265-
266-
get ariaCardAvatarLabel() {
267-
return this.i18nBundle.getText(AVATAR_TOOLTIP);
268-
}
269-
270149
get ariaCardContentLabel() {
271150
return this.i18nBundle.getText(ARIA_LABEL_CARD_CONTENT);
272151
}
273152

274-
get ariaLabelledByHeader() {
275-
const labels = [];
276-
277-
if (this.subtitleText) {
278-
labels.push(`${this._id}-subtitle`);
279-
}
280-
281-
if (this.status) {
282-
labels.push(`${this._id}-status`);
283-
}
284-
285-
if (this.hasAvatar) {
286-
labels.push(`${this._id}-avatar`);
287-
}
288-
289-
return labels.length !== 0 ? labels.join(" ") : undefined;
290-
}
291-
292153
get ariaLabelledByCard() {
293-
return this.titleText ? `${this._id}-title ${this._id}-desc` : `${this._id}-desc`;
294-
}
295-
296-
get hasAvatar() {
297-
return !!this.avatar.length;
298-
}
299-
300-
get hasAction() {
301-
return !!this.action.length;
154+
let labels;
155+
if (this.hasHeader) {
156+
labels = this.header[0].hasAttribute("title-text") ? `${this._id}--header-title ${this._id}-desc` : `${this._id}-desc`;
157+
} else {
158+
labels = `${this._id}-desc`;
159+
}
160+
return labels;
302161
}
303162

304163
static get dependencies() {
@@ -308,46 +167,6 @@ class Card extends UI5Element {
308167
static async onDefine() {
309168
await fetchI18nBundle("@ui5/webcomponents");
310169
}
311-
312-
_headerClick() {
313-
if (this.headerInteractive) {
314-
this.fireEvent("header-click");
315-
}
316-
}
317-
318-
_headerKeydown(event) {
319-
if (!this.headerInteractive) {
320-
return;
321-
}
322-
323-
const enter = isEnter(event);
324-
const space = isSpace(event);
325-
326-
this._headerActive = enter || space;
327-
328-
if (enter) {
329-
this.fireEvent("header-click");
330-
return;
331-
}
332-
333-
if (space) {
334-
event.preventDefault();
335-
}
336-
}
337-
338-
_headerKeyup(event) {
339-
if (!this.headerInteractive) {
340-
return;
341-
}
342-
343-
const space = isSpace(event);
344-
345-
this._headerActive = false;
346-
347-
if (space) {
348-
this.fireEvent("header-click");
349-
}
350-
}
351170
}
352171

353172
Card.define();

0 commit comments

Comments
 (0)