Skip to content

Commit

Permalink
feat(input): add loading state (#422)
Browse files Browse the repository at this point in the history
Co-authored-by: Murat Çorlu <[email protected]>
Co-authored-by: Aykut Saraç <[email protected]>
  • Loading branch information
3 people authored Mar 9, 2023
1 parent ee3a830 commit 15b83e8
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 12 deletions.
34 changes: 28 additions & 6 deletions src/components/button/bl-button.css
Original file line number Diff line number Diff line change
@@ -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%;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
27 changes: 27 additions & 0 deletions src/components/button/bl-button.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
},
Expand Down Expand Up @@ -57,7 +63,9 @@ export const SingleButtonTemplate = (args) => html`<bl-button
size=${ifDefined(args.size)}
icon="${ifDefined(args.icon)}"
label="${ifDefined(args.label)}"
loading-label="${ifDefined(args.loadingLabel)}"
?disabled=${args.disabled}
?loading=${args.loading}
style=${ifDefined(args.styles ? styleMap(args.styles) : undefined)}
>${unsafeHTML(args.content)}</bl-button>`

Expand All @@ -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

<bl-badge icon="document">ADR</bl-badge>
Expand Down Expand Up @@ -189,6 +204,18 @@ If button has a limited width and a long text that can not fit in a single line,
</Story>
</Canvas>

## 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.

<Canvas columns={1}>
<Story name="Loading State Button">
{LoadingStateTemplate.bind({})}
</Story>
</Canvas>

## Disabled Buttons

We have 2 types of disabled buttons: Disable version of Primary and Secondary buttons is the same.
Expand Down
25 changes: 25 additions & 0 deletions src/components/button/bl-button.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeOfBlButton>(
html`<bl-button loading>Test</bl-button>`
);
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 () => {
Expand All @@ -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<typeOfBlButton>(
html`<bl-button loading-label="Loading..." loading>Login</bl-button>`
);

expect(el.shadowRoot?.querySelector('.label')).to.have.text('Loading...');
});
});
describe('Link button', () => {
it('renders element with anchor tag', async () => {
Expand Down
27 changes: 21 additions & 6 deletions src/components/button/bl-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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`<slot></slot>`;
const isAnchor = !!this.href;
const icon = this.icon ? html`<bl-icon name=${this.icon}></bl-icon>` : '';
const slots = html`<slot name="icon">${icon}</slot> <span class="label"><slot></slot></span>`;
const loadingIcon = this.loading ? html`<bl-icon class="loading-icon" name="loading"></bl-icon>` : '';
const slots = html`<slot name="icon">${icon}</slot> <span class="label">${label}</span>`;
const caret = this.dropdown ? this.caretTemplate() : '';
const classes = classMap({
'button': true,
Expand All @@ -165,21 +180,21 @@ export default class BlButton extends LitElement {
return isAnchor
? html`<a
class=${classes}
aria-disabled="${ifDefined(this.disabled)}"
aria-disabled="${ifDefined(isDisabled)}"
aria-label="${ifDefined(this.label)}"
href=${ifDefined(this.href)}
target=${ifDefined(this.target)}
role="button"
>${slots}
>${loadingIcon} ${slots}
</a>`
: html`<button
class=${classes}
aria-disabled="${ifDefined(this.disabled)}"
aria-disabled="${ifDefined(isDisabled)}"
aria-label="${ifDefined(this.label)}"
?disabled=${this.disabled}
?disabled=${isDisabled}
@click="${this._handleClick}"
>
${slots} ${caret}
${loadingIcon} ${slots} ${caret}
</button>`;
}
}
Expand Down

0 comments on commit 15b83e8

Please sign in to comment.