Skip to content

Commit

Permalink
fix(button): adds pending button, fixes #3162 (#3163)
Browse files Browse the repository at this point in the history
* feat(button): adds pending button, fixes #3162

* chore(button): import sp-progress-circle whenever required

* chore: updated golden image hash

* chore(button): updated aria-label on changing pending state

* chore(button): removed redudant styles and functions

* chore(button): added tshirt sizing to the progress-circle

* chore(button): removed isPending state

* chore(button): update aria-label on pending toggle

* chore: updated golden image hash

* chore(button): removed ispending and timeout

* chore(button): updated css animation names

* chore(button): update aria-label on removing pending

* chore(button): added docs for pending state

* chore: updated golden image hash

* chore(button): update arialabel on disabled change in pending state

* chore: updated golden image hash

* fix(button): updated aria-label management in button

* fix(button): fixed aria-label management and added more tests

* fix(button): revert back the aria-label management logic in ButtonBase

* chore: updated golden image hash

* fix(button): shifted aria-label management to updated in button

* chore: udpated golden image hash

* chore: updated golden image hash

* chore(button): improved code readability

---------

Co-authored-by: TarunAdobe <ttomar@adobe.com>
  • Loading branch information
benjamind and TarunAdobe committed Jan 5, 2024
1 parent a6253c8 commit 71254ec
Show file tree
Hide file tree
Showing 23 changed files with 949 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Expand Up @@ -10,7 +10,7 @@ executors:
parameters:
current_golden_images_hash:
type: string
default: 69904096a0526a3ef8bbf1b2667d9edb4adc821b
default: e8720ac5d0ee0d076e61309294041334afe2c160
wireit_cache_name:
type: string
default: wireit
Expand Down
24 changes: 20 additions & 4 deletions packages/button/README.md
Expand Up @@ -285,10 +285,11 @@ The `treatment` attribute accepts `fill` and `outline` as values, and defaults t

## States

In addition to the variant, `<sp-button>` elements have a disabled state visual state
which can be applied by adding the attribute `disabled`. All `<sp-button>`
variants support this. In addition to affectng the visual state, the `disabled`
attribute prevents focus and disallows `click` events.
In addition to the variant, `<sp-button>` elements support two different visual states, disabled and pending, which can be applied by adding the attribute `disabled` or `pending` respectively. All `<sp-button>` variants support these states.

### Disabled

While disabled, `<sp-button>` elements will not respond to click events and will appear faded.

```html
<sp-button-group>
Expand All @@ -297,6 +298,21 @@ attribute prevents focus and disallows `click` events.
</sp-button-group>
```

### Pending

While in pending state, `<sp-button>` elements will not respond to click events and will appear faded with an indeterminent `<sp-progress-circle>`.
`<sp-button>` elements label and icon will be hidden while in pending state.

Note: `pending` state of the `<sp-button>` element is applied after 1s delay to avoid flashing the pending state for quick actions.
You can override the delay by adding custom css var `--pending-delay` to your css.

```html
<sp-button-group>
<sp-button variant="primary">Normal</sp-button>
<sp-button variant="primary" pending>Pending</sp-button>
</sp-button-group>
```

## Handling events

Events handlers for clicks and other user actions can be registered on a
Expand Down
75 changes: 75 additions & 0 deletions packages/button/src/Button.ts
Expand Up @@ -12,12 +12,15 @@ governing permissions and limitations under the License.

import {
CSSResultArray,
html,
PropertyValues,
SizedMixin,
TemplateResult,
} from '@spectrum-web-components/base';
import { property } from '@spectrum-web-components/base/src/decorators.js';
import { ButtonBase } from './ButtonBase.js';
import buttonStyles from './button.css.js';
import { when } from '@spectrum-web-components/base/src/directives.js';

export type DeprecatedButtonVariants = 'cta' | 'overBackground';
export type ButtonStatics = 'white' | 'black';
Expand Down Expand Up @@ -51,6 +54,22 @@ export class Button extends SizedMixin(ButtonBase, { noDefaultSize: true }) {
return [...super.styles, buttonStyles];
}

@property({ type: String, attribute: 'pending-label' })
public pendingLabel = 'Pending';

// pending is the property that consumers can use to set the button into a pending state
@property({ type: Boolean, reflect: true, attribute: true })
public pending = false;

private cachedAriaLabel: string | null = null;

public override click(): void {
if (this.pending) {
return;
}
super.click();
}

/**
* The visual variant to apply to this button.
*/
Expand Down Expand Up @@ -140,4 +159,60 @@ export class Button extends SizedMixin(ButtonBase, { noDefaultSize: true }) {
this.setAttribute('variant', this.variant);
}
}

protected override updated(changed: PropertyValues): void {
super.updated(changed);

if (changed.has('pending')) {
if (
this.pending &&
this.pendingLabel !== this.getAttribute('aria-label')
) {
if (!this.disabled) {
this.cachedAriaLabel =
this.getAttribute('aria-label') || '';
this.setAttribute('aria-label', this.pendingLabel);
}
} else if (!this.pending && this.cachedAriaLabel) {
this.setAttribute('aria-label', this.cachedAriaLabel);
} else if (!this.pending && this.cachedAriaLabel === '') {
this.removeAttribute('aria-label');
}
}

if (changed.has('disabled')) {
if (
!this.disabled &&
this.pendingLabel !== this.getAttribute('aria-label')
) {
if (this.pending) {
this.cachedAriaLabel =
this.getAttribute('aria-label') || '';
this.setAttribute('aria-label', this.pendingLabel);
}
} else if (this.disabled && this.cachedAriaLabel) {
this.setAttribute('aria-label', this.cachedAriaLabel);
} else if (this.disabled && this.cachedAriaLabel == '') {
this.removeAttribute('aria-label');
}
}
}

protected override renderButton(): TemplateResult {
return html`
${this.buttonContent}
${when(this.pending, () => {
import(
'@spectrum-web-components/progress-circle/sp-progress-circle.js'
);
return html`
<sp-progress-circle
indeterminate
static="white"
aria-hidden="true"
></sp-progress-circle>
`;
})}
`;
}
}
4 changes: 4 additions & 0 deletions packages/button/src/button-base.css
Expand Up @@ -75,6 +75,7 @@ slot[name='icon']::slotted(img) {
--spectrum-ui-icon-tshirt-size-width: var(
--spectrum-alias-ui-icon-cornertriangle-size-75
);
--mod-progress-circle-size: var(--spectrum-alias-workflow-icon-size-s);
}

:host([size='m']) {
Expand All @@ -90,6 +91,7 @@ slot[name='icon']::slotted(img) {
--spectrum-ui-icon-tshirt-size-width: var(
--spectrum-alias-ui-icon-cornertriangle-size-100
);
--mod-progress-circle-size: var(--spectrum-alias-workflow-icon-size-m);
}

:host([size='l']) {
Expand All @@ -105,6 +107,7 @@ slot[name='icon']::slotted(img) {
--spectrum-ui-icon-tshirt-size-width: var(
--spectrum-alias-ui-icon-cornertriangle-size-200
);
--mod-progress-circle-size: var(--spectrum-alias-workflow-icon-size-l);
}

:host([size='xl']) {
Expand All @@ -120,4 +123,5 @@ slot[name='icon']::slotted(img) {
--spectrum-ui-icon-tshirt-size-width: var(
--spectrum-alias-ui-icon-cornertriangle-size-300
);
--mod-progress-circle-size: var(--spectrum-alias-workflow-icon-size-xl);
}
69 changes: 69 additions & 0 deletions packages/button/src/button.css
Expand Up @@ -21,3 +21,72 @@ governing permissions and limitations under the License.
border-color: highlight;
}
}

@keyframes show-progress-circle {
0% {
visibility: hidden;
}

100% {
visibility: visible;
}
}

@keyframes hide-icons-label {
0% {
visibility: visible;
}

100% {
visibility: hidden;
}
}

@keyframes update-pending-button-styles {
100% {
background-color: var(
--highcontrast-button-background-color-disabled,
var(
--mod-button-background-color-disabled,
var(--spectrum-button-background-color-disabled)
)
);
border-color: var(
--highcontrast-button-border-color-disabled,
var(
--mod-button-border-color-disabled,
var(--spectrum-button-border-color-disabled)
)
);
color: var(
--highcontrast-button-content-color-disabled,
var(
--mod-button-content-color-disabled,
var(--spectrum-button-content-color-disabled)
)
);
}
}

:host([pending]:not([disabled])) {
cursor: default;
pointer-events: none;
animation: update-pending-button-styles 0s var(--pending-delay, 1s) forwards;
}

sp-progress-circle {
display: block;
visibility: hidden;
position: absolute;
left: 50%;
transform: translate(-50%, 0);
}

:host([pending]:not([disabled])) sp-progress-circle {
animation: show-progress-circle 0s var(--pending-delay, 1s) forwards;
}

:host([pending]:not([disabled])) slot[name='icon'],
:host([pending]:not([disabled])) #label {
animation: hide-icons-label 0s var(--pending-delay, 1s) forwards;
}
1 change: 1 addition & 0 deletions packages/button/src/spectrum-button.css
Expand Up @@ -529,6 +529,7 @@ governing permissions and limitations under the License.
)
);
}
:host .is-disabled,
:host([disabled]) {
background-color: var(
--highcontrast-button-background-color-disabled,
Expand Down
7 changes: 6 additions & 1 deletion packages/button/src/spectrum-config.js
Expand Up @@ -39,9 +39,14 @@ const config = {
converter.classToHost(),
converter.classToAttribute('spectrum-Button--quiet'),
converter.classToAttribute('spectrum-Button--emphasized'),
converter.classToAttribute('is-disabled', 'disabled'),
converter.classToAttribute('is-selected', 'selected'),
converter.classToAttribute('is-focused', 'focused'),
/**
* HACK!
* This relies on the fact that spectrum-css is using both `&:disabled` and `&.is-disabled` in the selectors
* for disabled states. We're using the class based selector here to also emit a `pending` selector.
*/
// converter.classToAttribute('is-disabled', 'pending'),
converter.pseudoToAttribute('disabled', 'disabled'),
converter.pseudoToAttribute('active', 'active'),
converter.classToAttribute(
Expand Down
50 changes: 50 additions & 0 deletions packages/button/stories/button-accent-fill-pending.stories.ts
@@ -0,0 +1,50 @@
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
import { TemplateResult } from '@spectrum-web-components/base';
import { Properties, renderButtonSet } from './index.js';
import { args, argTypes } from './index.js';

const variant = 'accent';
const treatment = 'fill';
const pending = true;

export default {
component: 'sp-button',
title: 'Button/Accent/Fill/Pending',
args: {
...args,
variant,
treatment,
pending,
},
argTypes,
};

export const s = (args: Properties): TemplateResult => renderButtonSet(args);
s.args = {
size: 's',
};

export const m = (args: Properties): TemplateResult => renderButtonSet(args);
m.args = {
size: 'm',
};

export const l = (args: Properties): TemplateResult => renderButtonSet(args);
l.args = {
size: 'l',
};

export const XL = (args: Properties): TemplateResult => renderButtonSet(args);
XL.args = {
size: 'xl',
};
50 changes: 50 additions & 0 deletions packages/button/stories/button-accent-outline-pending.stories.ts
@@ -0,0 +1,50 @@
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
import { TemplateResult } from '@spectrum-web-components/base';
import { Properties, renderButtonSet } from './index.js';
import { args, argTypes } from './index.js';

const variant = 'accent';
const treatment = 'outline';
const pending = true;

export default {
component: 'sp-button',
title: 'Button/Accent/Outline/Pending',
args: {
...args,
variant,
treatment,
pending,
},
argTypes,
};

export const s = (args: Properties): TemplateResult => renderButtonSet(args);
s.args = {
size: 's',
};

export const m = (args: Properties): TemplateResult => renderButtonSet(args);
m.args = {
size: 'm',
};

export const l = (args: Properties): TemplateResult => renderButtonSet(args);
l.args = {
size: 'l',
};

export const XL = (args: Properties): TemplateResult => renderButtonSet(args);
XL.args = {
size: 'xl',
};

0 comments on commit 71254ec

Please sign in to comment.