From 0e0d4d1ee854aac306b88cba5f52d5ff1bc8e95b Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Fri, 11 Jul 2025 16:58:17 +0300 Subject: [PATCH 01/16] feat: add busy indicator in ui5-button --- packages/main/src/Button.ts | 20 +++++ packages/main/src/ButtonTemplate.tsx | 121 ++++++++++++++------------- packages/main/src/themes/Button.css | 12 +++ packages/main/test/pages/Button.html | 8 ++ 4 files changed, 104 insertions(+), 57 deletions(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index ef9f6f123057..b17b03cd9f43 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -312,6 +312,26 @@ class Button extends UI5Element implements IButton { @property({ type: Boolean }) nonInteractive = false; + /** + * Defines the delay in milliseconds, after which the loading indicator will be displayed. + * + * **Note:** If set to `true` a busy indicator component will be displayed into the related button. + * @default false + * @public + * @since 1.13.0 + */ + @property({ type: Boolean }) + loading = false; + + /** + * Defines the delay in milliseconds, after which the loading indicator will be displayed inside the related button. + * @default 1000 + * @public + * @since 1.13.0 + */ + @property({ type: Number }) + loadingDelay = 1000; + /** * The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio. * @private diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index 2c101fcadd04..e391e00f8ae8 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, @@ -9,65 +10,71 @@ export default function ButtonTemplate(this: Button, injectedProps?: { ariaValueText?: string, }) { return (<> - + {this.shouldRenderBadge && + + } + + ); } diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index de02125f2e2a..013cc343286e 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -337,4 +337,16 @@ bdi { :host(:state(has-overlay-badge)) { overflow: visible; margin-inline-end: 0.3125rem; +} + +:host([loading]){ + pointer-events: unset; + cursor: default; + border-color: rgba(247, 247, 247, 0.4); +} + +.ui5-button-busy-indicator { + background-color: rgba(247, 247, 247, 0.4); + height: 100%; + width: 100%; } \ No newline at end of file diff --git a/packages/main/test/pages/Button.html b/packages/main/test/pages/Button.html index 1da137f840e1..5fa15353a187 100644 --- a/packages/main/test/pages/Button.html +++ b/packages/main/test/pages/Button.html @@ -265,6 +265,14 @@ +
+
+ Button with busy indicator +
+ + Busy Indicator + Busy Indicator + Busy Indicator

From 3a262805fa7b5f21079b53a98233cae1cd3fd808 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 14 Jul 2025 11:16:15 +0300 Subject: [PATCH 02/16] feat: add opacity background to buttom when loading --- packages/main/src/themes/Button.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index 013cc343286e..b8c159e78962 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -341,12 +341,11 @@ bdi { :host([loading]){ pointer-events: unset; - cursor: default; - border-color: rgba(247, 247, 247, 0.4); + border-color: rgba(255, 255, 255, 0.4); } .ui5-button-busy-indicator { - background-color: rgba(247, 247, 247, 0.4); + background-color: rgba(255, 255, 255, 0.4); height: 100%; width: 100%; } \ No newline at end of file From f14d01385306edeb0e215f76e026dc89da0dd492 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 14 Jul 2025 16:43:41 +0300 Subject: [PATCH 03/16] feat: stype opacity on busy indicator in button --- packages/main/cypress/specs/Button.cy.tsx | 23 +++++ packages/main/src/Button.ts | 26 +++-- packages/main/src/ButtonTemplate.tsx | 112 +++++++++++----------- packages/main/src/themes/Button.css | 81 +++++++++++----- packages/main/test/pages/Button.html | 3 + 5 files changed, 148 insertions(+), 97 deletions(-) diff --git a/packages/main/cypress/specs/Button.cy.tsx b/packages/main/cypress/specs/Button.cy.tsx index 17b46a6994eb..eb7d2f09296a 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(); diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index b17b03cd9f43..b78ebe5f4480 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -318,20 +318,11 @@ class Button extends UI5Element implements IButton { * **Note:** If set to `true` a busy indicator component will be displayed into the related button. * @default false * @public - * @since 1.13.0 + * @since 2.13.0 */ @property({ type: Boolean }) loading = false; - /** - * Defines the delay in milliseconds, after which the loading indicator will be displayed inside the related button. - * @default 1000 - * @public - * @since 1.13.0 - */ - @property({ type: Number }) - loadingDelay = 1000; - /** * The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio. * @private @@ -468,6 +459,11 @@ class Button extends UI5Element implements IButton { return; } + if (this.loading) { + e.preventDefault(); + return; + } + const { altKey, ctrlKey, @@ -502,7 +498,7 @@ class Button extends UI5Element implements IButton { } _onmousedown() { - if (this.nonInteractive) { + if (this.nonInteractive || this.loading) { return; } @@ -511,7 +507,7 @@ class Button extends UI5Element implements IButton { } _ontouchend(e: TouchEvent) { - if (this.disabled) { + if (this.disabled || this.loading) { e.preventDefault(); e.stopPropagation(); } @@ -526,7 +522,7 @@ class Button extends UI5Element implements IButton { } _onkeydown(e: KeyboardEvent) { - this._cancelAction = isShift(e) || isEscape(e); + this._cancelAction = isShift(e) || isEscape(e) || this.loading; if (isSpace(e) || isEnter(e)) { this._setActiveState(true); @@ -548,7 +544,7 @@ class Button extends UI5Element implements IButton { } _onfocusout() { - if (this.nonInteractive) { + if (this.nonInteractive || this.loading) { return; } @@ -560,7 +556,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 e391e00f8ae8..dc00e27ab06e 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -10,71 +10,71 @@ export default function ButtonTemplate(this: Button, injectedProps?: { ariaValueText?: string, }) { return (<> - - - + } + + {this.loading && + + } ); } diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index b8c159e78962..e4e8defc67e5 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -52,8 +52,8 @@ user-select: none; } -:host(:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host(:not([hidden]):not([disabled]).ui5_hovered) { +:host(:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host(:not([hidden]):not([loading]):not([disabled]).ui5_hovered) { background: var(--sapButton_Hover_Background); border: 1px solid var(--sapButton_Hover_BorderColor); color: var(--sapButton_Hover_TextColor); @@ -108,12 +108,12 @@ pointer-events: none; } -:host([desktop]:not([active])) .ui5-button-root:focus-within:after, -:host(:not([active])) .ui5-button-root:focus-visible:after, -:host([desktop][active][design="Emphasized"]) .ui5-button-root:focus-within:after, -:host([active][design="Emphasized"]) .ui5-button-root:focus-visible:after, -:host([desktop][active]) .ui5-button-root:focus-within:before, -:host([active]) .ui5-button-root:focus-visible:before { +:host([desktop]:not([active]):not([loading])) .ui5-button-root:focus-within:after, +:host(:not([active]):not([loading])) .ui5-button-root:focus-visible:after, +:host([desktop][active][design="Emphasized"]:not([loading])) .ui5-button-root:focus-within:after, +:host([active][design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:after, +:host([desktop][active]:not([loading])) .ui5-button-root:focus-within:before, +:host([active]:not([loading])) .ui5-button-root:focus-visible:before { content: ""; position: absolute; box-sizing: border-box; @@ -125,18 +125,18 @@ border-radius: var(--_ui5_button_focused_border_radius); } -:host([desktop][active]) .ui5-button-root:focus-within:before, -:host([active]) .ui5-button-root:focus-visible:before { +:host([desktop][active]:not([loading])) .ui5-button-root:focus-within:before, +:host([active]:not([loading])) .ui5-button-root:focus-visible:before { border-color: var(--_ui5_button_pressed_focused_border_color); } -:host([design="Emphasized"][desktop]) .ui5-button-root:focus-within:after, -:host([design="Emphasized"]) .ui5-button-root:focus-visible:after { +:host([design="Emphasized"][desktop]:not([loading])) .ui5-button-root:focus-within:after, +:host([design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:after { border-color: var(--_ui5_button_emphasized_focused_border_color); } -:host([design="Emphasized"][desktop]) .ui5-button-root:focus-within:before, -:host([design="Emphasized"]) .ui5-button-root:focus-visible:before { +:host([design="Emphasized"][desktop]:not([loading])) .ui5-button-root:focus-within:before, +:host([design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:before { content: ""; position: absolute; box-sizing: border-box; @@ -173,8 +173,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Positive"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Positive"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Positive"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Positive"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Accept_Hover_Background); border-color: var(--sapButton_Accept_Hover_BorderColor); color: var(--sapButton_Accept_Hover_TextColor); @@ -193,8 +193,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Negative"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Negative"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Negative"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Negative"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Reject_Hover_Background); border-color: var(--sapButton_Reject_Hover_BorderColor); color: var(--sapButton_Reject_Hover_TextColor); @@ -213,8 +213,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Attention"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Attention"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Attention"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Attention"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Attention_Hover_Background); border-color: var(--sapButton_Attention_Hover_BorderColor); color: var(--sapButton_Attention_Hover_TextColor); @@ -235,8 +235,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Emphasized"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Emphasized"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Emphasized"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Emphasized"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Emphasized_Hover_Background); border-color: var(--sapButton_Emphasized_Hover_BorderColor); border-width: var(--_ui5_button_emphasized_border_width); @@ -268,8 +268,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Transparent"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Transparent"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Transparent"]:not([loading]):not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Transparent"]:not([loading]):not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Lite_Hover_Background); border-color: var(--sapButton_Lite_Hover_BorderColor); color: var(--sapButton_Lite_Hover_TextColor); @@ -340,12 +340,41 @@ bdi { } :host([loading]){ + position: relative; pointer-events: unset; - border-color: rgba(255, 255, 255, 0.4); + border: inherit; +} + +:host([loading]) .ui5-button-root { + opacity: var(--sapContent_DisabledOpacity); + border: var(--sapButton_BorderWidth) solid var(--sapButton_BorderColor); + border-radius: var(--_ui5_button_border_radius); +} + +:host([loading][design="Emphasized"]) { + background-color: inherit; + border: inherit; } +:host([loading][design="Emphasized"]) .ui5-button-root { + background-color: var(--sapButton_Emphasized_Background); + border-color: var(--sapButton_Emphasized_BorderColor); +} + +:host([loading][design="Positive"]) .ui5-button-root { + border-color: var(--sapButton_Accept_BorderColor); +} + +:host([loading][design="Negative"]) .ui5-button-root { + border-color: var(--sapButton_Reject_BorderColor); +} + +:host([loading][design="Attention"]) .ui5-button-root { + border-color: var(--sapButton_Attention_BorderColor); +} .ui5-button-busy-indicator { - background-color: rgba(255, 255, 255, 0.4); + position: absolute; height: 100%; width: 100%; + top:0; } \ No newline at end of file diff --git a/packages/main/test/pages/Button.html b/packages/main/test/pages/Button.html index 5fa15353a187..4c0fbae78137 100644 --- a/packages/main/test/pages/Button.html +++ b/packages/main/test/pages/Button.html @@ -273,6 +273,9 @@ Busy Indicator Busy Indicator Busy Indicator + Busy Indicator + Busy Indicator + Busy Indicator

From 1962be25fa473373940ff3e783c1427706b3ee75 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 14 Jul 2025 16:49:21 +0300 Subject: [PATCH 04/16] refactor: remove tabs --- packages/main/src/ButtonTemplate.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index dc00e27ab06e..5260efb81e25 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -43,12 +43,12 @@ export default function ButtonTemplate(this: Button, injectedProps?: { role={this.effectiveAccRole} > { this.icon && - + } From 7b6b898abb584ffbd24dbf84cc5ce5216bc566d0 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 14 Jul 2025 16:51:09 +0300 Subject: [PATCH 05/16] refactor: remove tabs --- packages/main/src/ButtonTemplate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index 5260efb81e25..b28a6e1f349e 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -67,7 +67,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: { } {this.shouldRenderBadge && - + } {this.loading && From ae4bc15deb7acf9bafdf20ec15a4e87174ff23d8 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 14 Jul 2025 16:52:33 +0300 Subject: [PATCH 06/16] refactor: update text --- packages/main/test/pages/Button.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/test/pages/Button.html b/packages/main/test/pages/Button.html index 4c0fbae78137..e1446f3cd714 100644 --- a/packages/main/test/pages/Button.html +++ b/packages/main/test/pages/Button.html @@ -267,7 +267,7 @@

- Button with busy indicator + Buttons with busy indicator
Busy Indicator From bdf1e56ffa3b61a1048aabe03c865924647f3de6 Mon Sep 17 00:00:00 2001 From: GDamyanov Date: Wed, 16 Jul 2025 09:37:56 +0300 Subject: [PATCH 07/16] Update packages/main/src/Button.ts Co-authored-by: Stoyan <88034608+hinzzx@users.noreply.github.com> --- packages/main/src/Button.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index b78ebe5f4480..97902024fe59 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -313,7 +313,7 @@ class Button extends UI5Element implements IButton { nonInteractive = false; /** - * Defines the delay in milliseconds, after which the loading indicator will be displayed. +Defines whether the button shows a loading indicator. * * **Note:** If set to `true` a busy indicator component will be displayed into the related button. * @default false From 6526dcadf8c24f50fd8c47fbd44dcb57ce903b8a Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 16 Jul 2025 09:43:00 +0300 Subject: [PATCH 08/16] fix: remove redundant tooltip --- packages/main/test/pages/Button.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/test/pages/Button.html b/packages/main/test/pages/Button.html index e1446f3cd714..e33b12c4c453 100644 --- a/packages/main/test/pages/Button.html +++ b/packages/main/test/pages/Button.html @@ -269,7 +269,7 @@
Buttons with busy indicator
- + Busy Indicator Busy Indicator Busy Indicator From b3867277a9816145575eb6abb25a9b5bf05d99e3 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 16 Jul 2025 09:47:48 +0300 Subject: [PATCH 09/16] feat: add loadingDelay property --- packages/main/src/Button.ts | 9 +++++++++ packages/main/src/ButtonTemplate.tsx | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index 97902024fe59..810430c64604 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -323,6 +323,15 @@ Defines whether the button shows a loading indicator. @property({ type: Boolean }) loading = false; + /** + * Defines the delay in milliseconds, after which the loading indicator will be displayed inside the related button. + * @default 1000 + * @public + * @since 2.13.0 + */ + @property({ type: Number }) + loadingDelay = 1000; + /** * The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio. * @private diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index b28a6e1f349e..50fe4bd3689d 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -74,7 +74,8 @@ export default function ButtonTemplate(this: Button, injectedProps?: { + active={true} + delay={this.loadingDelay}/> } ); } From 12583475a530cc6a1cb18acc9dbaff00419fcd75 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Thu, 17 Jul 2025 10:20:50 +0300 Subject: [PATCH 10/16] refactor: update jsdoc --- packages/main/src/Button.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index 810430c64604..7e4a42bac2b1 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -313,7 +313,7 @@ class Button extends UI5Element implements IButton { nonInteractive = false; /** -Defines whether the button shows a loading indicator. + * Defines whether the button shows a loading indicator. * * **Note:** If set to `true` a busy indicator component will be displayed into the related button. * @default false From af1109db90c7f9bf3826edf3a6412f9dedff0925 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 21 Jul 2025 16:02:52 +0300 Subject: [PATCH 11/16] feat: add sample in playground --- .../_components_pages/main/Button/Button.mdx | 6 +++- .../_samples/main/Button/Loading/Loading.md | 4 +++ .../docs/_samples/main/Button/Loading/main.js | 4 +++ .../_samples/main/Button/Loading/sample.html | 32 +++++++++++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/website/docs/_samples/main/Button/Loading/Loading.md create mode 100644 packages/website/docs/_samples/main/Button/Loading/main.js create mode 100644 packages/website/docs/_samples/main/Button/Loading/sample.html 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. - \ No newline at end of file + + +### Button with Loading + \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Button/Loading/Loading.md b/packages/website/docs/_samples/main/Button/Loading/Loading.md new file mode 100644 index 000000000000..66ccdc236d97 --- /dev/null +++ b/packages/website/docs/_samples/main/Button/Loading/Loading.md @@ -0,0 +1,4 @@ +import html from '!!raw-loader!./sample.html'; +import js from '!!raw-loader!./main.js'; + + \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Button/Loading/main.js b/packages/website/docs/_samples/main/Button/Loading/main.js new file mode 100644 index 000000000000..1e5482b9eca0 --- /dev/null +++ b/packages/website/docs/_samples/main/Button/Loading/main.js @@ -0,0 +1,4 @@ +import "@ui5/webcomponents/dist/Button.js"; + +import "@ui5/webcomponents-icons/dist/edit.js"; +import "@ui5/webcomponents-icons/dist/employee.js"; \ No newline at end of file diff --git a/packages/website/docs/_samples/main/Button/Loading/sample.html b/packages/website/docs/_samples/main/Button/Loading/sample.html new file mode 100644 index 000000000000..a276e988ec23 --- /dev/null +++ b/packages/website/docs/_samples/main/Button/Loading/sample.html @@ -0,0 +1,32 @@ + + + + + + + + Sample + + + + +
+
+ + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading +
+ + + + + + \ No newline at end of file From cc36fbb23fa530eba44eb6930fc0906b12c106f0 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Mon, 21 Jul 2025 21:06:06 +0300 Subject: [PATCH 12/16] fix: update jsdocs --- packages/main/src/Button.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index 7e4a42bac2b1..372782766d38 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -315,7 +315,7 @@ class Button extends UI5Element implements IButton { /** * Defines whether the button shows a loading indicator. * - * **Note:** If set to `true` a busy indicator component will be displayed into the related button. + * **Note:** If set to `true`, a busy indicator component will be displayed on the related button. * @default false * @public * @since 2.13.0 @@ -324,7 +324,7 @@ class Button extends UI5Element implements IButton { loading = false; /** - * Defines the delay in milliseconds, after which the loading indicator will be displayed inside the related button. + * Specifies the delay in milliseconds before the loading indicator appears within the associated button. * @default 1000 * @public * @since 2.13.0 @@ -333,7 +333,8 @@ class Button extends UI5Element implements IButton { loadingDelay = 1000; /** - * The current title of the button, either the tooltip property or the icons tooltip. The tooltip property with higher prio. + * 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 }) From bcec2e303f9f855ccfa68d3e79f7a8af7a042fb7 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Thu, 24 Jul 2025 13:37:59 +0300 Subject: [PATCH 13/16] fix: move to the next element by clicking tab --- packages/main/src/Button.ts | 8 ++--- packages/main/src/ButtonTemplate.tsx | 8 +++-- packages/main/src/themes/Button.css | 53 +++++++++++++++------------- 3 files changed, 39 insertions(+), 30 deletions(-) diff --git a/packages/main/src/Button.ts b/packages/main/src/Button.ts index 372782766d38..de083390d8fd 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -508,7 +508,7 @@ class Button extends UI5Element implements IButton { } _onmousedown() { - if (this.nonInteractive || this.loading) { + if (this.nonInteractive) { return; } @@ -532,7 +532,7 @@ class Button extends UI5Element implements IButton { } _onkeydown(e: KeyboardEvent) { - this._cancelAction = isShift(e) || isEscape(e) || this.loading; + this._cancelAction = isShift(e) || isEscape(e); if (isSpace(e) || isEnter(e)) { this._setActiveState(true); @@ -554,7 +554,7 @@ class Button extends UI5Element implements IButton { } _onfocusout() { - if (this.nonInteractive || this.loading) { + if (this.nonInteractive) { return; } @@ -607,7 +607,7 @@ class Button extends UI5Element implements IButton { } get effectiveAccRole(): AriaRole { - return toLowercaseEnumValue(this.accessibleRole); + return this.loading ? "progressbar" : toLowercaseEnumValue(this.accessibleRole); } get tabIndexValue() { diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index 50fe4bd3689d..8fe9cd834028 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -38,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} @@ -71,11 +72,14 @@ export default function ButtonTemplate(this: Button, injectedProps?: { } {this.loading && - + delay={this.loadingDelay} + inert={this.loading} + /> } ); } diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index 310b9f6a63fb..890e887fea8d 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -52,8 +52,8 @@ user-select: none; } -:host(:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host(:not([hidden]):not([loading]):not([disabled]).ui5_hovered) { +:host(:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host(:not([hidden]):not([disabled]).ui5_hovered) { background: var(--sapButton_Hover_Background); border: 1px solid var(--sapButton_Hover_BorderColor); color: var(--sapButton_Hover_TextColor); @@ -108,12 +108,12 @@ pointer-events: none; } -:host([desktop]:not([active]):not([loading])) .ui5-button-root:focus-within:after, -:host(:not([active]):not([loading])) .ui5-button-root:focus-visible:after, -:host([desktop][active][design="Emphasized"]:not([loading])) .ui5-button-root:focus-within:after, -:host([active][design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:after, -:host([desktop][active]:not([loading])) .ui5-button-root:focus-within:before, -:host([active]:not([loading])) .ui5-button-root:focus-visible:before { +:host([desktop]:not([loading])) .ui5-button-root:focus-within:after, +:host(:not([active])) .ui5-button-root:focus-visible:after, +:host([desktop][active][design="Emphasized"]) .ui5-button-root:focus-within:after, +:host([active][design="Emphasized"]) .ui5-button-root:focus-visible:after, +:host([desktop][active]) .ui5-button-root:focus-within:before, +:host([active]) .ui5-button-root:focus-visible:before { content: ""; position: absolute; box-sizing: border-box; @@ -125,18 +125,18 @@ border-radius: var(--_ui5_button_focused_border_radius); } -:host([desktop][active]:not([loading])) .ui5-button-root:focus-within:before, -:host([active]:not([loading])) .ui5-button-root:focus-visible:before { +:host([desktop][active]) .ui5-button-root:focus-within:before, +:host([active]) .ui5-button-root:focus-visible:before { border-color: var(--_ui5_button_pressed_focused_border_color); } -:host([design="Emphasized"][desktop]:not([loading])) .ui5-button-root:focus-within:after, -:host([design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:after { +:host([design="Emphasized"][desktop]) .ui5-button-root:focus-within:after, +:host([design="Emphasized"]) .ui5-button-root:focus-visible:after { border-color: var(--_ui5_button_emphasized_focused_border_color); } -:host([design="Emphasized"][desktop]:not([loading])) .ui5-button-root:focus-within:before, -:host([design="Emphasized"]:not([loading])) .ui5-button-root:focus-visible:before { +:host([design="Emphasized"][desktop]) .ui5-button-root:focus-within:before, +:host([design="Emphasized"]) .ui5-button-root:focus-visible:before { content: ""; position: absolute; box-sizing: border-box; @@ -173,8 +173,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Positive"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Positive"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Positive"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Positive"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Accept_Hover_Background); border-color: var(--sapButton_Accept_Hover_BorderColor); color: var(--sapButton_Accept_Hover_TextColor); @@ -193,8 +193,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Negative"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Negative"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Negative"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Negative"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Reject_Hover_Background); border-color: var(--sapButton_Reject_Hover_BorderColor); color: var(--sapButton_Reject_Hover_TextColor); @@ -213,8 +213,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Attention"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Attention"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Attention"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Attention"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Attention_Hover_Background); border-color: var(--sapButton_Attention_Hover_BorderColor); color: var(--sapButton_Attention_Hover_TextColor); @@ -235,8 +235,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Emphasized"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Emphasized"]:not([active]):not([loading]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Emphasized"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Emphasized"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Emphasized_Hover_Background); border-color: var(--sapButton_Emphasized_Hover_BorderColor); border-width: var(--_ui5_button_emphasized_border_width); @@ -268,8 +268,8 @@ bdi { } /*The ui5_hovered class is set by FileUploader to indicate hover state of the control*/ -:host([design="Transparent"]:not([loading]):not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), -:host([design="Transparent"]:not([loading]):not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { +:host([design="Transparent"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Transparent"]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: var(--sapButton_Lite_Hover_Background); border-color: var(--sapButton_Lite_Hover_BorderColor); color: var(--sapButton_Lite_Hover_TextColor); @@ -356,6 +356,11 @@ bdi { border: inherit; } +:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), +:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { + background-color: inherit; +} + :host([loading][design="Emphasized"]) .ui5-button-root { background-color: var(--sapButton_Emphasized_Background); border-color: var(--sapButton_Emphasized_BorderColor); From 927a05cf45cfdddf482e77280004f2388f298af0 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Thu, 24 Jul 2025 15:40:26 +0300 Subject: [PATCH 14/16] fix: border issues, tab click fix and add test --- packages/main/cypress/specs/Button.cy.tsx | 12 ++++++++++ packages/main/src/Button.ts | 2 +- packages/main/src/themes/Button.css | 27 ++++++++++------------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/main/cypress/specs/Button.cy.tsx b/packages/main/cypress/specs/Button.cy.tsx index eb7d2f09296a..53fa03ba40a1 100644 --- a/packages/main/cypress/specs/Button.cy.tsx +++ b/packages/main/cypress/specs/Button.cy.tsx @@ -476,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 de083390d8fd..5b26bf80232e 100644 --- a/packages/main/src/Button.ts +++ b/packages/main/src/Button.ts @@ -607,7 +607,7 @@ class Button extends UI5Element implements IButton { } get effectiveAccRole(): AriaRole { - return this.loading ? "progressbar" : toLowercaseEnumValue(this.accessibleRole); + return toLowercaseEnumValue(this.accessibleRole); } get tabIndexValue() { diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index 890e887fea8d..fa5a49719e65 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -342,13 +342,10 @@ bdi { :host([loading]){ position: relative; pointer-events: unset; - border: inherit; } :host([loading]) .ui5-button-root { opacity: var(--sapContent_DisabledOpacity); - border: var(--sapButton_BorderWidth) solid var(--sapButton_BorderColor); - border-radius: var(--_ui5_button_border_radius); } :host([loading][design="Emphasized"]) { @@ -359,6 +356,12 @@ bdi { :host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover), :host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) { background-color: inherit; + border: inherit; +} + +:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]):hover) .ui5-button-root, +:host([design="Emphasized"][loading]:not([active]):not([non-interactive]):not([_is-touch]):not([disabled]).ui5_hovered) .ui5-button-root { + background-color: var(--sapButton_Emphasized_Hover_Background); } :host([loading][design="Emphasized"]) .ui5-button-root { @@ -366,20 +369,14 @@ bdi { border-color: var(--sapButton_Emphasized_BorderColor); } -:host([loading][design="Positive"]) .ui5-button-root { - border-color: var(--sapButton_Accept_BorderColor); -} - -:host([loading][design="Negative"]) .ui5-button-root { - border-color: var(--sapButton_Reject_BorderColor); -} - -:host([loading][design="Attention"]) .ui5-button-root { - border-color: var(--sapButton_Attention_BorderColor); -} .ui5-button-busy-indicator { position: absolute; height: 100%; width: 100%; top:0; -} \ No newline at end of file +} + + + + + From 3440544f249abdd5860008310157636e5b00be2d Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Thu, 24 Jul 2025 15:43:13 +0300 Subject: [PATCH 15/16] refactor: remove empty lines --- packages/main/src/themes/Button.css | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/main/src/themes/Button.css b/packages/main/src/themes/Button.css index fa5a49719e65..511decc726b3 100644 --- a/packages/main/src/themes/Button.css +++ b/packages/main/src/themes/Button.css @@ -375,8 +375,3 @@ bdi { width: 100%; top:0; } - - - - - From 717f4804f6ffa5dba6fe179eb84fcefacc238f63 Mon Sep 17 00:00:00 2001 From: Georgi Damyanov Date: Wed, 30 Jul 2025 15:59:10 +0300 Subject: [PATCH 16/16] fix: use enum for size instead of strings --- packages/main/src/ButtonTemplate.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/main/src/ButtonTemplate.tsx b/packages/main/src/ButtonTemplate.tsx index 8fe9cd834028..26d5cdccf6ba 100644 --- a/packages/main/src/ButtonTemplate.tsx +++ b/packages/main/src/ButtonTemplate.tsx @@ -1,6 +1,7 @@ import type Button from "./Button.js"; import Icon from "./Icon.js"; import BusyIndicator from "./BusyIndicator.js"; +import BusyIndicatorSize from "./types/BusyIndicatorSize.js"; export default function ButtonTemplate(this: Button, injectedProps?: { ariaPressed?: boolean, @@ -75,7 +76,7 @@ export default function ButtonTemplate(this: Button, injectedProps?: {