DEV: adds a loading property to d-button (#9072)

DEV: adds a loading property to d-button (#9072)

Usage:

{{d-button icon="times" label="foo.bar" isLoading=true}}

Note that a button loading without an icon will shrink text size to prevent button to jump in size.

A button while loading is disabled.

diff --git a/app/assets/javascripts/discourse/components/d-button.js b/app/assets/javascripts/discourse/components/d-button.js
index 1210432..4df816a 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 aeb5f6b..58105b2 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 1f7ea76..643975e 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 a1f1c12..f2c4bcd 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 cf86842..521fe7b 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");
+  }
+});

GitHub sha: 5b6cdd6f

This commit appears in #9072 which was approved by eviltrout, eviltrout, and awesomerobot. It was merged by jjaffeux.