diff --git a/app/assets/javascripts/discourse/components/d-button.js b/app/assets/javascripts/discourse/components/d-button.js index 12104321278..4df816a3474 100644 --- a/app/assets/javascripts/discourse/components/d-button.js +++ b/app/assets/javascripts/discourse/components/d-button.js @@ -1,4 +1,5 @@ import { notEmpty, empty, equal } from "@ember/object/computed"; +import { computed } from "@ember/object"; import Component from "@ember/component"; import discourseComputed from "discourse-common/utils/decorators"; import DiscourseURL from "discourse/lib/url"; @@ -11,17 +12,36 @@ export default Component.extend({ type: "button", + isLoading: computed({ + set(key, value) { + this.set("forceDisabled", value); + return value; + } + }), + tagName: "button", - classNameBindings: ["btnLink::btn", "btnLink", "noText", "btnType"], + classNameBindings: [ + "isLoading:is-loading", + "btnLink::btn", + "btnLink", + "noText", + "btnType" + ], attributeBindings: [ "form", - "disabled", + "isDisabled:disabled", "translatedTitle:title", "translatedLabel:aria-label", "tabindex", "type" ], + isDisabled: computed("disabled", "forceDisabled", function() { + return this.forceDisabled || this.disabled; + }), + + forceDisabled: false, + btnIcon: notEmpty("icon"), btnLink: equal("display", "link"), diff --git a/app/assets/javascripts/discourse/templates/components/d-button.hbs b/app/assets/javascripts/discourse/templates/components/d-button.hbs index aeb5f6b6f65..58105b2cd20 100644 --- a/app/assets/javascripts/discourse/templates/components/d-button.hbs +++ b/app/assets/javascripts/discourse/templates/components/d-button.hbs @@ -1,5 +1,13 @@ {{#if icon}} - {{d-icon icon}} + {{#if isLoading}} + {{d-icon "spinner" class="loading-icon"}} + {{else}} + {{d-icon icon}} + {{/if}} +{{else}} + {{#if isLoading}} + {{d-icon "spinner" class="loading-icon"}} + {{/if}} {{/if}} {{#if translatedLabel}} diff --git a/app/assets/stylesheets/common/components/buttons.scss b/app/assets/stylesheets/common/components/buttons.scss index 1f7ea765550..643975ec508 100644 --- a/app/assets/stylesheets/common/components/buttons.scss +++ b/app/assets/stylesheets/common/components/buttons.scss @@ -67,6 +67,31 @@ } cursor: not-allowed; } + + .loading-container { + display: none; + margin: 0 6.75px 0 0; + } + + &.is-loading { + &.btn-text { + .d-button-label { + font-size: $font-down-2; + } + + &.btn-small { + .loading-icon { + font-size: $font-down-1; + margin-right: 0.2em; + } + } + } + + .loading-icon { + -webkit-animation: rotate-forever 1s infinite linear, fadein 1s; + animation: rotate-forever 1s infinite linear, fadein 1s; + } + } } .btn.hidden { diff --git a/lib/svg_sprite/svg_sprite.rb b/lib/svg_sprite/svg_sprite.rb index a1f1c12822e..f2c4bcdbb72 100644 --- a/lib/svg_sprite/svg_sprite.rb +++ b/lib/svg_sprite/svg_sprite.rb @@ -198,7 +198,8 @@ module SvgSprite "user-shield", "user-times", "users", - "wrench" + "wrench", + "spinner" ]) FA_ICON_MAP = { 'far fa-' => 'far-', 'fab fa-' => 'fab-', 'fas fa-' => '', 'fa-' => '' } diff --git a/test/javascripts/components/d-button-test.js b/test/javascripts/components/d-button-test.js index cf86842b98f..521fe7bb18a 100644 --- a/test/javascripts/components/d-button-test.js +++ b/test/javascripts/components/d-button-test.js @@ -54,3 +54,49 @@ componentTest("link-styled button", { ); } }); + +componentTest("isLoading button", { + template: "{{d-button isLoading=isLoading}}", + + beforeEach() { + this.set("isLoading", true); + }, + + test(assert) { + assert.ok( + find("button.is-loading .spinner").length, + "it has a spinner showing" + ); + assert.ok( + find("button[disabled]").length, + "while loading the button is disabled" + ); + + this.set("isLoading", false); + + assert.notOk( + find("button .spinner").length, + "it doesn't have a spinner showing" + ); + assert.ok( + find("button:not([disabled])").length, + "while not loading the button is enabled" + ); + } +}); + +componentTest("disabled button", { + template: "{{d-button disabled=disabled}}", + + beforeEach() { + this.set("disabled", true); + }, + + test(assert) { + assert.ok(find("button[disabled]").length, "the button is disabled"); + + this.set("disabled", false); + + assert.ok(find("button:not([disabled])").length, "the button is enabled"); + } +});