Skip to content

Commit acd6b6e

Browse files
authored
feat(ui5-button): Add BusyIndicator
Add busy state functionality to Button component Integrate BusyIndicator with appropriate sizing (S for icon-only, M for regular buttons) Add busy indicator styling and positioning- Update button template to conditionally render BusyIndicator Add test coverage for busy state scenarios
1 parent 9b9ce3a commit acd6b6e

File tree

9 files changed

+173
-7
lines changed

9 files changed

+173
-7
lines changed

packages/main/cypress/specs/Button.cy.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,29 @@ describe("Button general interaction", () => {
205205
.should("not.called");
206206
});
207207

208+
it("tests clicking on button with busy indicator", () => {
209+
cy.mount(<Button loading></Button>);
210+
211+
cy.get("[ui5-button]")
212+
.as("button");
213+
214+
cy.get("@button")
215+
.then(button => {
216+
button.get(0).addEventListener("click", cy.stub().as("clicked"));
217+
});
218+
219+
cy.get("@button")
220+
.shadow()
221+
.find("[ui5-busy-indicator]")
222+
.should("be.visible");
223+
224+
cy.get("@button")
225+
.realClick();
226+
227+
cy.get("@clicked")
228+
.should("not.called");
229+
});
230+
208231
it("tests button with text icon role", () => {
209232
cy.mount(<Button design="Attention" icon="message-warning">Warning</Button>);
210233

@@ -453,6 +476,18 @@ describe("Accessibility", () => {
453476
.should("have.attr", "aria-controls", "registration-dialog");
454477
});
455478

479+
it("aria-busy is properly applied on the button with busy indicator", () => {
480+
cy.mount(<Button loading></Button>);
481+
482+
cy.get("[ui5-button]")
483+
.as("button");
484+
485+
cy.get("@button")
486+
.shadow()
487+
.find("button")
488+
.should("have.attr", "aria-busy", "true");
489+
});
490+
456491
it("setting accessible-description is applied to button tag", () => {
457492
cy.mount(<Button accessibleDescription="A long description."></Button>);
458493

packages/main/src/Button.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,28 @@ class Button extends UI5Element implements IButton {
313313
nonInteractive = false;
314314

315315
/**
316-
* The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio.
316+
* Defines whether the button shows a loading indicator.
317+
*
318+
* **Note:** If set to `true`, a busy indicator component will be displayed on the related button.
319+
* @default false
320+
* @public
321+
* @since 2.13.0
322+
*/
323+
@property({ type: Boolean })
324+
loading = false;
325+
326+
/**
327+
* Specifies the delay in milliseconds before the loading indicator appears within the associated button.
328+
* @default 1000
329+
* @public
330+
* @since 2.13.0
331+
*/
332+
@property({ type: Number })
333+
loadingDelay = 1000;
334+
335+
/**
336+
* The button's current title is determined by either the `tooltip` property or the icon's tooltip, with the `tooltip`
337+
* property taking precedence if both are set.
317338
* @private
318339
*/
319340
@property({ noAttribute: true })
@@ -448,6 +469,11 @@ class Button extends UI5Element implements IButton {
448469
return;
449470
}
450471

472+
if (this.loading) {
473+
e.preventDefault();
474+
return;
475+
}
476+
451477
const {
452478
altKey,
453479
ctrlKey,
@@ -491,7 +517,7 @@ class Button extends UI5Element implements IButton {
491517
}
492518

493519
_ontouchend(e: TouchEvent) {
494-
if (this.disabled) {
520+
if (this.disabled || this.loading) {
495521
e.preventDefault();
496522
e.stopPropagation();
497523
}
@@ -540,7 +566,7 @@ class Button extends UI5Element implements IButton {
540566
_setActiveState(active: boolean) {
541567
const eventPrevented = !this.fireDecoratorEvent("active-state-change");
542568

543-
if (eventPrevented) {
569+
if (eventPrevented || this.loading) {
544570
return;
545571
}
546572

packages/main/src/ButtonTemplate.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type Button from "./Button.js";
22
import Icon from "./Icon.js";
3+
import BusyIndicator from "./BusyIndicator.js";
4+
import BusyIndicatorSize from "./types/BusyIndicatorSize.js";
35

46
export default function ButtonTemplate(this: Button, injectedProps?: {
57
ariaPressed?: boolean,
@@ -15,7 +17,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
1517
"ui5-button-root": true,
1618
"ui5-button-badge-placement-end": this.badge[0]?.design === "InlineText",
1719
"ui5-button-badge-placement-end-top": this.badge[0]?.design === "OverlayText",
18-
"ui5-button-badge-dot": this.badge[0]?.design === "AttentionDot",
20+
"ui5-button-badge-dot": this.badge[0]?.design === "AttentionDot"
1921
}}
2022
disabled={this.disabled}
2123
data-sap-focus-ref
@@ -37,6 +39,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
3739
aria-haspopup={this._hasPopup}
3840
aria-label={this.ariaLabelText}
3941
aria-description={this.ariaDescriptionText}
42+
aria-busy={this.loading ? "true" : undefined}
4043
title={this.buttonTitle}
4144
part="button"
4245
role={this.effectiveAccRole}
@@ -69,5 +72,15 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
6972
<slot name="badge"/>
7073
}
7174
</button>
75+
{this.loading &&
76+
<BusyIndicator
77+
id={`${this._id}-button-busy-indicator`}
78+
class="ui5-button-busy-indicator"
79+
size={this.iconOnly ? BusyIndicatorSize.S : BusyIndicatorSize.M}
80+
active={true}
81+
delay={this.loadingDelay}
82+
inert={this.loading}
83+
/>
84+
}
7285
</>);
7386
}

packages/main/src/themes/Button.css

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
pointer-events: none;
109109
}
110110

111-
:host([desktop]:not([active])) .ui5-button-root:focus-within:after,
111+
:host([desktop]:not([loading])) .ui5-button-root:focus-within:after,
112112
:host(:not([active])) .ui5-button-root:focus-visible:after,
113113
:host([desktop][active][design="Emphasized"]) .ui5-button-root:focus-within:after,
114114
:host([active][design="Emphasized"]) .ui5-button-root:focus-visible:after,
@@ -337,4 +337,41 @@ bdi {
337337
:host(:state(has-overlay-badge)) {
338338
overflow: visible;
339339
margin-inline-end: 0.3125rem;
340-
}
340+
}
341+
342+
:host([loading]){
343+
position: relative;
344+
pointer-events: unset;
345+
}
346+
347+
:host([loading]) .ui5-button-root {
348+
opacity: var(--sapContent_DisabledOpacity);
349+
}
350+
351+
:host([loading][design="Emphasized"]) {
352+
background-color: inherit;
353+
border: inherit;
354+
}
355+
356+
:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover),
357+
:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) {
358+
background-color: inherit;
359+
border: inherit;
360+
}
361+
362+
:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover) .ui5-button-root,
363+
:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) .ui5-button-root {
364+
background-color: var(--sapButton_Emphasized_Hover_Background);
365+
}
366+
367+
:host([loading][design="Emphasized"]) .ui5-button-root {
368+
background-color: var(--sapButton_Emphasized_Background);
369+
border-color: var(--sapButton_Emphasized_BorderColor);
370+
}
371+
372+
.ui5-button-busy-indicator {
373+
position: absolute;
374+
height: 100%;
375+
width: 100%;
376+
top:0;
377+
}

packages/main/test/pages/Button.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,17 @@
265265
<ui5-button icon="arrow-down" tooltip="Go down"></ui5-button>
266266
<ui5-button icon="arrow-down" disabled tooltip="Go down"></ui5-button>
267267

268+
<br />
269+
<br />
270+
<ui5-title>Buttons with busy indicator</ui5-title>
271+
<br />
272+
<ui5-button tooltip="Button with busy indicator" icon="accept" loading="true"></ui5-button>
273+
<ui5-button tooltip="Button with busy indicator" loading="true">Busy Indicator</ui5-button>
274+
<ui5-button design="Transparent" tooltip="Button with busy indicator" loading="true">Busy Indicator</ui5-button>
275+
<ui5-button tooltip="Button with busy indicator" icon="home" design="Emphasized" loading="true">Busy Indicator</ui5-button>
276+
<ui5-button design="Positive" tooltip="Button with busy indicator" loading="true">Busy Indicator</ui5-button>
277+
<ui5-button design="Negative" tooltip="Button with busy indicator" loading="true">Busy Indicator</ui5-button>
278+
<ui5-button design="Attention" tooltip="Button with busy indicator" loading="true">Busy Indicator</ui5-button>
268279

269280
<br />
270281
<br />

packages/website/docs/_components_pages/main/Button/Button.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import EndIcon from "../../../_samples/main/Button/EndIcon/EndIcon.md";
1010
import CustomStyling from "../../../_samples/main/Button/CustomStyling/CustomStyling.md";
1111
import MenuButton from "../../../_samples/main/Button/MenuButton/MenuButton.md";
1212
import ButtonBadge from "../../../_samples/main/Button/ButtonBadge/ButtonBadge.md";
13+
import Loading from "../../../_samples/main/Button/Loading/Loading.md"
1314

1415
<%COMPONENT_OVERVIEW%>
1516

@@ -49,4 +50,7 @@ Achieve Menu Button functionality.
4950
### Button with badge
5051
Add a badge to the button.
5152

52-
<ButtonBadge />
53+
<ButtonBadge />
54+
55+
### Button with Loading
56+
<Loading />
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import html from '!!raw-loader!./sample.html';
2+
import js from '!!raw-loader!./main.js';
3+
4+
<Editor html={html} js={js} />
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import "@ui5/webcomponents/dist/Button.js";
2+
3+
import "@ui5/webcomponents-icons/dist/edit.js";
4+
import "@ui5/webcomponents-icons/dist/employee.js";
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<!-- playground-fold -->
2+
<!DOCTYPE html>
3+
<html lang="en">
4+
5+
<head>
6+
<meta charset="UTF-8">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>Sample</title>
9+
</head>
10+
11+
<body style="background-color: var(--sapBackgroundColor)">
12+
<!-- playground-fold-end -->
13+
<div>
14+
<br/>
15+
<ui5-button icon="sap-icon://edit" tooltip="Accept terms & conditions" loading></ui5-button>
16+
<ui5-button loading>Loading</ui5-button>
17+
<ui5-button icon="sap-icon://employee" loading>Loading</ui5-button>
18+
<ui5-button design="Transparent" loading>Loading</ui5-button>
19+
<ui5-button icon="sap-icon://employee" design="Transparent" loading>Loading</ui5-button>
20+
<ui5-button design="Emphasized" loading>Loading</ui5-button>
21+
<ui5-button icon="sap-icon://employee" design="Emphasized" loading>Loading</ui5-button>
22+
<ui5-button design="Positive" loading>Loading</ui5-button>
23+
<ui5-button icon="sap-icon://employee" design="Positive" loading>Loading</ui5-button>
24+
<ui5-button design="Negative" loading>Loading</ui5-button>
25+
<ui5-button icon="sap-icon://employee" design="Negative" loading>Loading</ui5-button>
26+
</div>
27+
<!-- playground-fold -->
28+
<script type="module" src="main.js"></script>
29+
</body>
30+
31+
</html>
32+
<!-- playground-fold-end -->

0 commit comments

Comments
 (0)