summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Eastwood <contact@ericeastwood.com>2017-10-13 17:01:21 -0500
committerEric Eastwood <contact@ericeastwood.com>2017-10-23 05:24:42 +0300
commit69be3ce86f1fc8c2273870a92b597399cedf2480 (patch)
treef8b8b22ac855e28f35ab3d5b091900793b4a3d2f
parentb3f749036ea919de3982c81b157ab2d790ecb4c5 (diff)
downloadgitlab-ce-add-shared-vue-loading-button.tar.gz
Encompasses designs from - https://gitlab.com/gitlab-org/gitlab-ce/issues/23206#note_43355199 - https://gitlab.com/gitlab-org/gitlab-ce/issues/29985#note_30417407
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue71
-rw-r--r--app/assets/stylesheets/framework.scss1
-rw-r--r--app/assets/stylesheets/framework/buttons.scss5
-rw-r--r--app/assets/stylesheets/framework/vue_transitions.scss9
-rw-r--r--app/assets/stylesheets/pages/repo.scss5
-rw-r--r--changelogs/unreleased/add-shared-vue-loading-button.yml5
-rw-r--r--spec/javascripts/vue_shared/components/loading_button_spec.js93
7 files changed, 184 insertions, 5 deletions
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
new file mode 100644
index 00000000000..6670b554faf
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -0,0 +1,71 @@
+<script>
+
+/* This is a re-usable vue component for rendering a button
+ that will probably be sending off ajax requests and need
+ to show the loading status by setting the `loading` option.
+ This can also be used for initial page load when you don't
+ know the action of the button yet by setting
+ `loading: true, label: undefined`.
+
+ Sample configuration:
+
+ <loading-button
+ :loading="true"
+ :label="Hello"
+ @click="..."
+ />
+
+*/
+
+import loadingIcon from './loading_icon.vue';
+
+export default {
+ props: {
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ label: {
+ type: String,
+ required: false,
+ },
+ },
+ components: {
+ loadingIcon,
+ },
+ methods: {
+ onClick(e) {
+ this.$emit('click', e);
+ },
+ },
+};
+</script>
+
+<template>
+ <button
+ class="btn btn-align-content"
+ @click="onClick"
+ type="button"
+ :disabled="loading"
+ >
+ <transition name="fade">
+ <loading-icon
+ v-if="loading"
+ :inline="true"
+ class="js-loading-button-icon"
+ :class="{
+ 'append-right-5': label
+ }"
+ />
+ </transition>
+ <transition name="fade">
+ <span
+ v-if="label"
+ class="js-loading-button-label"
+ >
+ {{ label }}
+ </span>
+ </transition>
+ </button>
+</template>
diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss
index aa61ddc6a2c..a0bb9e61436 100644
--- a/app/assets/stylesheets/framework.scss
+++ b/app/assets/stylesheets/framework.scss
@@ -5,6 +5,7 @@
@import "framework/layout";
@import "framework/animations";
+@import "framework/vue_transitions";
@import "framework/avatar";
@import "framework/asciidoctor";
@import "framework/banner";
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index b131e2d57ee..00a0e9cef67 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -292,6 +292,11 @@
}
}
+.btn-align-content {
+ display: flex;
+ align-items: center;
+}
+
.btn-group {
&.btn-grouped {
@include btn-with-margin;
diff --git a/app/assets/stylesheets/framework/vue_transitions.scss b/app/assets/stylesheets/framework/vue_transitions.scss
new file mode 100644
index 00000000000..e07a177e153
--- /dev/null
+++ b/app/assets/stylesheets/framework/vue_transitions.scss
@@ -0,0 +1,9 @@
+.fade-enter-active,
+.fade-leave-active {
+ transition: opacity $sidebar-transition-duration $general-hover-transition-curve;
+}
+
+.fade-enter,
+.fade-leave-to {
+ opacity: 0;
+}
diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss
index ea37ccf5e3d..97ca01f0f54 100644
--- a/app/assets/stylesheets/pages/repo.scss
+++ b/app/assets/stylesheets/pages/repo.scss
@@ -1,8 +1,3 @@
-.fade-enter-active,
-.fade-leave-active {
- transition: opacity $sidebar-transition-duration;
-}
-
.monaco-loader {
position: absolute;
top: 0;
diff --git a/changelogs/unreleased/add-shared-vue-loading-button.yml b/changelogs/unreleased/add-shared-vue-loading-button.yml
new file mode 100644
index 00000000000..a8904acc4e7
--- /dev/null
+++ b/changelogs/unreleased/add-shared-vue-loading-button.yml
@@ -0,0 +1,5 @@
+---
+title: Add loading button for new UX paradigm
+merge_request: 14883
+author:
+type: added
diff --git a/spec/javascripts/vue_shared/components/loading_button_spec.js b/spec/javascripts/vue_shared/components/loading_button_spec.js
new file mode 100644
index 00000000000..97c8a08fcdd
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/loading_button_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import loadingButton from '~/vue_shared/components/loading_button.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+const LABEL = 'Hello';
+
+describe('LoadingButton', function () {
+ let vm;
+ let LoadingButton;
+
+ beforeEach(() => {
+ LoadingButton = Vue.extend(loadingButton);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('loading spinner', () => {
+ it('shown when loading', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: true,
+ });
+
+ expect(vm.$el.querySelector('.js-loading-button-icon')).toBeDefined();
+ });
+ });
+
+ describe('disabled state', () => {
+ it('disabled when loading', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: true,
+ });
+
+ expect(vm.$el.disabled).toEqual(true);
+ });
+
+ it('not disabled when normal', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: false,
+ });
+
+ expect(vm.$el.disabled).toEqual(false);
+ });
+ });
+
+ describe('label', () => {
+ it('shown when normal', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: false,
+ label: LABEL,
+ });
+ const label = vm.$el.querySelector('.js-loading-button-label');
+
+ expect(label.textContent.trim()).toEqual(LABEL);
+ });
+
+ it('shown when loading', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: true,
+ label: LABEL,
+ });
+ const label = vm.$el.querySelector('.js-loading-button-label');
+
+ expect(label.textContent.trim()).toEqual(LABEL);
+ });
+ });
+
+ describe('click callback prop', () => {
+ it('calls given callback when normal', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: false,
+ });
+ spyOn(vm, '$emit');
+
+ vm.$el.click();
+
+ expect(vm.$emit).toHaveBeenCalledWith('click', jasmine.any(Object));
+ });
+
+ it('does not call given callback when disabled because of loading', () => {
+ vm = mountComponent(LoadingButton, {
+ loading: true,
+ indeterminate: true,
+ });
+ spyOn(vm, '$emit');
+
+ vm.$el.click();
+
+ expect(vm.$emit).not.toHaveBeenCalled();
+ });
+ });
+});