diff --git a/src/components/button/bl-button.css b/src/components/button/bl-button.css
index db884f08..d4ebb619 100644
--- a/src/components/button/bl-button.css
+++ b/src/components/button/bl-button.css
@@ -1,3 +1,8 @@
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(359deg); }
+}
+
:host {
display: var(--bl-button-display, inline-block);
max-width: 100%;
@@ -23,7 +28,7 @@
justify-content: var(--bl-button-justify, center);
align-items: center;
box-sizing: border-box;
- width:100%;
+ width: 100%;
height: var(--height);
border: solid 1px var(--border-color);
border-radius: 6px;
@@ -76,15 +81,24 @@
.button:focus-visible::after {
border: 2px solid var(--main-color);
border-radius: var(--bl-border-radius-l);
- content: "";
+ content: '';
position: absolute;
inset: -4px;
}
+.loading-icon {
+ animation: spin 1s linear infinite;
+ font-size: var(--icon-size);
+}
+
:host ::slotted(bl-icon) {
font-size: var(--icon-size);
}
+:host([loading]) ::slotted(bl-icon) {
+ display: none;
+}
+
:host .has-icon:not(.has-content) {
--padding-horizontal: var(--padding-vertical);
--margin-icon: 0;
@@ -120,7 +134,15 @@
cursor: not-allowed;
}
-:host([disabled]) .button {
+:host([loading]) {
+ cursor: wait;
+}
+
+:host([loading]) bl-icon:not(.loading-icon) {
+ display: none;
+}
+
+:host .button[aria-disabled='true'] {
--main-color: var(--bl-color-tertiary);
--main-hover-color: var(--bl-color-tertiary);
--content-color: var(--bl-color-content-passive);
@@ -130,16 +152,16 @@
text-decoration: none;
}
-:host([variant='tertiary'][disabled]) .button {
+:host([variant='tertiary']) .button[aria-disabled='true'] {
--main-color: transparent;
}
-:host([variant='secondary']:hover:not([disabled])) .button {
+:host([variant='secondary']:hover) .button[aria-disabled='false'] {
--content-color: var(--bl-color-content-primary-contrast);
--bg-color: var(--main-hover-color);
}
-:host([variant='tertiary']:hover:not([disabled])) .button {
+:host([variant='tertiary']:hover) .button[aria-disabled='false'] {
--content-color: var(--main-hover-color);
--bg-color: var(--text-hover-color);
}
diff --git a/src/components/button/bl-button.stories.mdx b/src/components/button/bl-button.stories.mdx
index 36952c59..23925d56 100644
--- a/src/components/button/bl-button.stories.mdx
+++ b/src/components/button/bl-button.stories.mdx
@@ -23,6 +23,12 @@ import { Meta, Canvas, ArgsTable, Story, Preview, Source } from '@storybook/addo
default: 'default',
control: { type: 'select' }
},
+ loadingLabel: {
+ control: 'text'
+ },
+ loading: {
+ control: 'boolean'
+ },
disabled: {
control: 'boolean'
},
@@ -57,7 +63,9 @@ export const SingleButtonTemplate = (args) => html`${unsafeHTML(args.content)}`
@@ -77,6 +85,13 @@ ${SingleButtonTemplate({size: 'large', ...args})}
${SingleButtonTemplate({size: 'medium', ...args})}
${SingleButtonTemplate({size: 'small', ...args})}`
+export const LoadingStateTemplate = (args) => html`
+${SingleButtonTemplate({ size: 'large', loading: true, icon: "info", ...args})}
+${SingleButtonTemplate({ size: 'large', loading: true, loadingLabel: 'Custom Loading Label...', content: 'Login', icon: 'account', ...args})}
+${SingleButtonTemplate({ loading: true, disabled: true, content: 'Disabled' })}
+${SingleButtonTemplate({ size: 'small', loading: true, content: 'Create' })}
+`
+
# Button
ADR
@@ -189,6 +204,18 @@ If button has a limited width and a long text that can not fit in a single line,
+## Loading Buttons
+
+Button can be set in loading state. In this state button becomes disabled with a loading indicator. You can set this state by setting `loading` attribute. Additionally, button icons are overridden by the spinner during the loading state.
+
+A custom loading text can be also set with `loading-label` attribute. It's suggested to use `loading-label` to inform the user about the process.
+
+
+
## Disabled Buttons
We have 2 types of disabled buttons: Disable version of Primary and Secondary buttons is the same.
diff --git a/src/components/button/bl-button.test.ts b/src/components/button/bl-button.test.ts
index 1b0746f1..2aee640a 100644
--- a/src/components/button/bl-button.test.ts
+++ b/src/components/button/bl-button.test.ts
@@ -86,6 +86,23 @@ describe('bl-button', () => {
expect(el.getAttribute('target')).to.eq('_self');
});
+
+ it('is disabled button during loading state', async () => {
+ const el = await fixture(
+ html`Test`
+ );
+ expect(el.shadowRoot?.querySelector('.loading-icon')).to.exist;
+ expect(el).to.have.attribute('loading');
+ expect(el.shadowRoot?.querySelector('button')).to.have.attribute('disabled');
+
+ el.removeAttribute('loading');
+ await elementUpdated(el);
+
+ expect(el.shadowRoot?.querySelector('.loading-icon')).not.to.exist;
+ expect(el).not.have.attribute('loading');
+ expect(el.shadowRoot?.querySelector('button')).not.have.attribute('disabled');
+
+ });
});
describe('Slot', () => {
it('renders default slot with element', async () => {
@@ -94,6 +111,14 @@ describe('bl-button', () => {
);
expect(el.shadowRoot?.querySelector('button')).to.exist;
});
+
+ it('renders loading label when set and loading', async () => {
+ const el = await fixture(
+ html`Login`
+ );
+
+ expect(el.shadowRoot?.querySelector('.label')).to.have.text('Loading...');
+ });
});
describe('Link button', () => {
it('renders element with anchor tag', async () => {
diff --git a/src/components/button/bl-button.ts b/src/components/button/bl-button.ts
index 51e5c3cf..e1e5a27f 100644
--- a/src/components/button/bl-button.ts
+++ b/src/components/button/bl-button.ts
@@ -50,6 +50,18 @@ export default class BlButton extends LitElement {
@property({ type: String })
label: string;
+ /**
+ * Sets the button label for loading status.
+ */
+ @property({ type: String, attribute: 'loading-label' })
+ loadingLabel: string;
+
+ /**
+ * Sets loading state of button
+ */
+ @property({ type: Boolean, reflect: true })
+ loading = false;
+
/**
* Sets button as disabled
*/
@@ -151,9 +163,12 @@ export default class BlButton extends LitElement {
}
render(): TemplateResult {
+ const isDisabled = this.loading || this.disabled;
+ const label = (this.loading && this.loadingLabel) ? this.loadingLabel : html``;
const isAnchor = !!this.href;
const icon = this.icon ? html`` : '';
- const slots = html`${icon} `;
+ const loadingIcon = this.loading ? html`` : '';
+ const slots = html`${icon} ${label}`;
const caret = this.dropdown ? this.caretTemplate() : '';
const classes = classMap({
'button': true,
@@ -165,21 +180,21 @@ export default class BlButton extends LitElement {
return isAnchor
? html`${slots}
+ >${loadingIcon} ${slots}
`
: html``;
}
}