diff --git a/packages/main/cypress/specs/Button.cy.tsx b/packages/main/cypress/specs/Button.cy.tsx
index 17b46a6994eb..53fa03ba40a1 100644
--- a/packages/main/cypress/specs/Button.cy.tsx
+++ b/packages/main/cypress/specs/Button.cy.tsx
@@ -205,6 +205,29 @@ describe("Button general interaction", () => {
.should("not.called");
});
+ it("tests clicking on button with busy indicator", () => {
+ cy.mount();
+
+ cy.get("[ui5-button]")
+ .as("button");
+
+ cy.get("@button")
+ .then(button => {
+ button.get(0).addEventListener("click", cy.stub().as("clicked"));
+ });
+
+ cy.get("@button")
+ .shadow()
+ .find("[ui5-busy-indicator]")
+ .should("be.visible");
+
+ cy.get("@button")
+ .realClick();
+
+ cy.get("@clicked")
+ .should("not.called");
+ });
+
it("tests button with text icon role", () => {
cy.mount();
@@ -453,6 +476,18 @@ describe("Accessibility", () => {
.should("have.attr", "aria-controls", "registration-dialog");
});
+ it("aria-busy is properly applied on the button with busy indicator", () => {
+ cy.mount();
+
+ cy.get("[ui5-button]")
+ .as("button");
+
+ cy.get("@button")
+ .shadow()
+ .find("button")
+ .should("have.attr", "aria-busy", "true");
+ });
+
it("setting accessible-description is applied to button tag", () => {
cy.mount();
diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts
index ef9f6f123057..5b26bf80232e 100644
--- a/packages/main/src/Button.ts
+++ b/packages/main/src/Button.ts
@@ -313,7 +313,28 @@ class Button extends UI5Element implements IButton {
nonInteractive = false;
/**
- * The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio.
+ * Defines whether the button shows a loading indicator.
+ *
+ * **Note:** If set to `true`, a busy indicator component will be displayed on the related button.
+ * @default false
+ * @public
+ * @since 2.13.0
+ */
+ @property({ type: Boolean })
+ loading = false;
+
+ /**
+ * Specifies the delay in milliseconds before the loading indicator appears within the associated button.
+ * @default 1000
+ * @public
+ * @since 2.13.0
+ */
+ @property({ type: Number })
+ loadingDelay = 1000;
+
+ /**
+ * The button's current title is determined by either the `tooltip` property or the icon's tooltip, with the `tooltip`
+ * property taking precedence if both are set.
* @private
*/
@property({ noAttribute: true })
@@ -448,6 +469,11 @@ class Button extends UI5Element implements IButton {
return;
}
+ if (this.loading) {
+ e.preventDefault();
+ return;
+ }
+
const {
altKey,
ctrlKey,
@@ -491,7 +517,7 @@ class Button extends UI5Element implements IButton {
}
_ontouchend(e: TouchEvent) {
- if (this.disabled) {
+ if (this.disabled || this.loading) {
e.preventDefault();
e.stopPropagation();
}
@@ -540,7 +566,7 @@ class Button extends UI5Element implements IButton {
_setActiveState(active: boolean) {
const eventPrevented = !this.fireDecoratorEvent("active-state-change");
- if (eventPrevented) {
+ if (eventPrevented || this.loading) {
return;
}
diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx
index 2c101fcadd04..8fe9cd834028 100644
--- a/packages/main/src/ButtonTemplate.tsx
+++ b/packages/main/src/ButtonTemplate.tsx
@@ -1,5 +1,6 @@
import type Button from "./Button.js";
import Icon from "./Icon.js";
+import BusyIndicator from "./BusyIndicator.js";
export default function ButtonTemplate(this: Button, injectedProps?: {
ariaPressed?: boolean,
@@ -15,7 +16,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
"ui5-button-root": true,
"ui5-button-badge-placement-end": this.badge[0]?.design === "InlineText",
"ui5-button-badge-placement-end-top": this.badge[0]?.design === "OverlayText",
- "ui5-button-badge-dot": this.badge[0]?.design === "AttentionDot",
+ "ui5-button-badge-dot": this.badge[0]?.design === "AttentionDot"
}}
disabled={this.disabled}
data-sap-focus-ref
@@ -37,6 +38,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
aria-haspopup={this._hasPopup}
aria-label={this.ariaLabelText}
aria-description={this.ariaDescriptionText}
+ aria-busy={this.loading ? "true" : undefined}
title={this.buttonTitle}
part="button"
role={this.effectiveAccRole}
@@ -69,5 +71,15 @@ export default function ButtonTemplate(this: Button, injectedProps?: {
+
+
+
diff --git a/packages/website/docs/_components_pages/main/Button/Button.mdx b/packages/website/docs/_components_pages/main/Button/Button.mdx
index d3ebf9ef4350..a98e89f21c47 100644
--- a/packages/website/docs/_components_pages/main/Button/Button.mdx
+++ b/packages/website/docs/_components_pages/main/Button/Button.mdx
@@ -10,6 +10,7 @@ import EndIcon from "../../../_samples/main/Button/EndIcon/EndIcon.md";
import CustomStyling from "../../../_samples/main/Button/CustomStyling/CustomStyling.md";
import MenuButton from "../../../_samples/main/Button/MenuButton/MenuButton.md";
import ButtonBadge from "../../../_samples/main/Button/ButtonBadge/ButtonBadge.md";
+import Loading from "../../../_samples/main/Button/Loading/Loading.md"
<%COMPONENT_OVERVIEW%>
@@ -49,4 +50,7 @@ Achieve Menu Button functionality.
### Button with badge
Add a badge to the button.
-