Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(ui5-card): header slot is added #3490

Merged
merged 13 commits into from
Jul 28, 2021
1 change: 1 addition & 0 deletions packages/main/bundle.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import Badge from "./dist/Badge.js";
import BusyIndicator from "./dist/BusyIndicator.js";
import Button from "./dist/Button.js";
import Card from "./dist/Card.js";
import CardHeader from "./dist/CardHeader.js";
import Carousel from "./dist/Carousel.js";
import CheckBox from "./dist/CheckBox.js";
import ColorPalette from "./dist/ColorPalette.js";
Expand Down
36 changes: 3 additions & 33 deletions packages/main/src/Card.hbs
Original file line number Diff line number Diff line change
@@ -1,42 +1,12 @@
<div
class="{{classes.main}}"
class="{{classes}}"
dir="{{effectiveDir}}"
role="region"
aria-label="{{ariaLabelText}}"
aria-labelledby="{{ariaLabelledByCard}}">
{{#if hasHeader}}
GerganaKremenska marked this conversation as resolved.
Show resolved Hide resolved
<div class="{{classes.header}}"
@click="{{_headerClick}}"
@keydown="{{_headerKeydown}}"
@keyup="{{_headerKeyup}}"
role="{{ariaHeaderRole}}"
aria-labelledby="{{ariaLabelledByHeader}}"
aria-level="{{ariaLevel}}"
aria-roledescription="{{ariaCardHeaderRoleDescription}}"
tabindex="0">

{{#if hasAvatar}}
<div id="{{_id}}-avatar" class="ui5-card-avatar" aria-label="{{ariaCardAvatarLabel}}">
<slot name="avatar"></slot>
</div>
{{/if}}

<div class="ui5-card-header-text">
{{#if titleText}}
<div id="{{_id}}-title" class="ui5-card-title" part="title">{{titleText}}</div>
{{/if}}

{{#if subtitleText}}
<div id="{{_id}}-subtitle" class="ui5-card-subtitle" part="subtitle">{{subtitleText}}</div>
{{/if}}
</div>

{{#if hasAction}}
<slot name="action"></slot>
{{else}}
<span id="{{_id}}-status" part="status" class="ui5-card-status">{{status}}</span>
{{/if}}
</div>
<!-- header -->
<slot name="header"></slot>
{{/if}}

<div role="group" aria-label="{{ariaCardContentLabel}}">
Expand Down
220 changes: 19 additions & 201 deletions packages/main/src/Card.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/AriaLabelHelper.js";
import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
import CardTemplate from "./generated/templates/CardTemplate.lit.js";
import Icon from "./Icon.js";

import {
ARIA_ROLEDESCRIPTION_CARD,
AVATAR_TOOLTIP,
ARIA_LABEL_CARD_CONTENT,
ARIA_ROLEDESCRIPTION_CARD_HEADER,
ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER,
} from "./generated/i18n/i18n-defaults.js";

// Styles
Expand All @@ -38,82 +34,19 @@ const metadata = {
},

/**
* Defines the visual representation in the header of the card.
* Supports images and icons.
* Defines the header of the component.
GerganaKremenska marked this conversation as resolved.
Show resolved Hide resolved
* <br><br>
* <b>Note:</b>
* SAP-icons font provides numerous options. To find all the available icons, see the
* <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>.
* <b>Note:</b> Use <code>ui5-card-header</code> for the intended design.
* @type {HTMLElement[]}
* @slot
* @public
*/
avatar: {
type: HTMLElement,
},

/**
* Defines an action, displayed in the right most part of the header.
* <br><br>
* <b>Note:</b> If set, the <code>status</code> text will not be displayed,
* you can either have <code>action</code>, or <code>status</code>.
* @type {HTMLElement[]}
* @slot
* @slot content
* @public
* @since 1.0.0-rc.8
*/
action: {
header: {
type: HTMLElement,
},
},
properties: /** @lends sap.ui.webcomponents.main.Card.prototype */ {

/**
* Defines the title displayed in the component header.
* @type {string}
* @defaultvalue ""
* @public
* @since 1.0.0-rc.15
*/
titleText: {
type: String,
},

/**
* Defines the subtitle displayed in the component header.
* @type {string}
* @defaultvalue ""
* @public
* @since 1.0.0-rc5
*/
subtitleText: {
type: String,
},

/**
* Defines the status displayed in the component header.
* <br><br>
* <b>Note:</b> If the <code>action</code> slot is set, the <code>status</code> will not be displayed,
* you can either have <code>action</code>, or <code>status</code>.
* @type {string}
* @defaultvalue ""
* @public
*/
status: {
type: String,
},

/**
* Defines if the component header would be interactive,
* e.g gets hover effect, gets focused and <code>headerPress</code> event is fired, when it is pressed.
* @type {boolean}
* @defaultvalue false
* @public
*/
headerInteractive: {
type: Boolean,
},

/**
* Defines the aria-label attribute for the component
*
Expand All @@ -138,25 +71,8 @@ const metadata = {
type: String,
defaultValue: "",
},

_headerActive: {
type: Boolean,
noAttribute: true,
},
},
events: /** @lends sap.ui.webcomponents.main.Card.prototype */ {

/**
* Fired when the component header is activated
* by mouse/tap or by using the Enter or Space key.
* <br><br>
* <b>Note:</b> The event would be fired only if the <code>headerInteractive</code> property is set to true.
* @event sap.ui.webcomponents.main.Card#header-click
* @public
* @since 0.10.0
*/
"header-click": {},
},
events: /** @lends sap.ui.webcomponents.main.Card.prototype */ {},
};

/**
Expand All @@ -166,26 +82,19 @@ const metadata = {
* The <code>ui5-card</code> is a component that represents information in the form of a
* tile with separate header and content areas.
* The content area of a <code>ui5-card</code> can be arbitrary HTML content.
* The header can be used through several properties, such as: <code>titleText</code>, <code>subtitleText</code>, <code>status</code>
* and two slots: <code>avatar</code> and <code>action</code>.
* 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.
*
* <h3>Keyboard handling</h3>
* In case you enable <code>headerInteractive</code> property, you can press the <code>ui5-card</code> header by Space and Enter keys.
* 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.
*
* <h3>CSS Shadow Parts</h3>
*
* <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.
* <br>
* The <code>ui5-card</code> exposes the following CSS Shadow Parts:
* <ul>
* <li>title - Used to style the title of the card</li>
* <li>subtitle - Used to style the subtitle of the card</li>
* <li>status - Used to style the status of the card</li>
* </ul>
*
* <h3>ES6 Module Import</h3>
*
* <code>import "@ui5/webcomponents/dist/Card";</code>
* <br>
* <code>import "@ui5/webcomponents/dist/CardHeader.js";</code> (for <code>ui5-card-header</code>)
*
* @constructor
* @author SAP SE
GerganaKremenska marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -219,36 +128,13 @@ class Card extends UI5Element {

get classes() {
return {
main: {
"ui5-card-root": true,
"ui5-card--nocontent": !this.content.length,
},
header: {
"ui5-card-header": true,
"ui5-card-header--interactive": this.headerInteractive,
"ui5-card-header--active": this.headerInteractive && this._headerActive,
},
"ui5-card-root": true,
"ui5-card--nocontent": !this.content.length,
};
}

get icon() {
return !!this.avatar && this.avatar.startsWith("sap-icon://");
}

get image() {
return !!this.avatar && !this.icon;
}

get ariaHeaderRole() {
return this.headerInteractive ? "button" : "heading";
}

get ariaLevel() {
return this.headerInteractive ? undefined : "3";
}

get hasHeader() {
return !!(this.titleText || this.subtitleText || this.status || this.hasAction || this.avatar);
return !!this.header.length;
}

get ariaLabelText() {
Expand All @@ -259,46 +145,18 @@ class Card extends UI5Element {
return this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_CARD);
}

get ariaCardHeaderRoleDescription() {
return this.headerInteractive ? this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_INTERACTIVE_CARD_HEADER) : this.i18nBundle.getText(ARIA_ROLEDESCRIPTION_CARD_HEADER);
}

get ariaCardAvatarLabel() {
return this.i18nBundle.getText(AVATAR_TOOLTIP);
}

get ariaCardContentLabel() {
return this.i18nBundle.getText(ARIA_LABEL_CARD_CONTENT);
}

get ariaLabelledByHeader() {
const labels = [];

if (this.subtitleText) {
labels.push(`${this._id}-subtitle`);
}

if (this.status) {
labels.push(`${this._id}-status`);
}

if (this.hasAvatar) {
labels.push(`${this._id}-avatar`);
}

return labels.length !== 0 ? labels.join(" ") : undefined;
}

get ariaLabelledByCard() {
return this.titleText ? `${this._id}-title ${this._id}-desc` : `${this._id}-desc`;
}

get hasAvatar() {
return !!this.avatar.length;
}

get hasAction() {
return !!this.action.length;
let labels;
if (this.hasHeader) {
labels = this.header[0].hasAttribute("title-text") ? `${this._id}--header-title ${this._id}-desc` : `${this._id}-desc`;
} else {
labels = `${this._id}-desc`;
}
return labels;
}

static get dependencies() {
Expand All @@ -308,46 +166,6 @@ class Card extends UI5Element {
static async onDefine() {
await fetchI18nBundle("@ui5/webcomponents");
}

_headerClick() {
if (this.headerInteractive) {
this.fireEvent("header-click");
}
}

_headerKeydown(event) {
if (!this.headerInteractive) {
return;
}

const enter = isEnter(event);
const space = isSpace(event);

this._headerActive = enter || space;

if (enter) {
this.fireEvent("header-click");
return;
}

if (space) {
event.preventDefault();
}
}

_headerKeyup(event) {
if (!this.headerInteractive) {
return;
}

const space = isSpace(event);

this._headerActive = false;

if (space) {
this.fireEvent("header-click");
}
}
}

Card.define();
Expand Down
32 changes: 32 additions & 0 deletions packages/main/src/CardHeader.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class="{{classes}}"
@click="{{_headerClick}}"
@keydown="{{_headerKeydown}}"
@keyup="{{_headerKeyup}}"
role="{{ariaHeaderRole}}"
aria-labelledby="{{ariaLabelledByHeader}}"
aria-level="{{ariaLevel}}"
aria-roledescription="{{ariaCardHeaderRoleDescription}}"
tabindex="0"
id="{{_id}}--header">
GerganaKremenska marked this conversation as resolved.
Show resolved Hide resolved
{{#if hasAvatar}}
<div id="{{_id}}-avatar" class="ui5-card-avatar" aria-label="{{ariaCardAvatarLabel}}">
<slot name="avatar"></slot>
</div>
{{/if}}

<div class="ui5-card-header-text">
{{#if titleText}}
<div id="{{_id}}-title" class="ui5-card-title" part="title">{{titleText}}</div>
{{/if}}

{{#if subtitleText}}
<div id="{{_id}}-subtitle" class="ui5-card-subtitle" part="subtitle">{{subtitleText}}</div>
{{/if}}
</div>

{{#if hasAction}}
<slot name="action"></slot>
{{else}}
<span id="{{_id}}-status" part="status" class="ui5-card-status">{{status}}</span>
{{/if}}
</div>
Loading