Skip to content

Commit c1df9af

Browse files
authored
Add is-loading and aria-disabled to Button (#1879)
1 parent 4cf40c6 commit c1df9af

File tree

7 files changed

+96
-3
lines changed

7 files changed

+96
-3
lines changed

.changeset/cuddly-birds-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudfour/patterns': minor
3+
---
4+
5+
Add `is-loading` state class to Buttons

.changeset/rare-wasps-fly.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cloudfour/patterns': minor
3+
---
4+
5+
Add support for `aria-disabled` attribute to Buttons

src/base/_animation.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@keyframes rotate-clockwise {
2+
to {
3+
transform: rotate(360deg);
4+
}
5+
}

src/base/_index.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
@use './themes';
2+
@use './animation';
23
@use './defaults';
34
@use './typography';

src/components/button/button.stories.mdx

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,30 @@ The `content_start`/`content_end` blocks override the
144144
</Story>
145145
</Canvas>
146146

147-
## Icon with slash
147+
## States
148148

149-
To add a slash across the icon(s), use the `is-slashed` state modifier CSS class.
149+
Buttons with a `disabled` attribute or with `aria-disabled` set to `'true'` will appear less prominent. ([Which should you use?](https://css-tricks.com/making-disabled-buttons-more-inclusive/))
150+
151+
Buttons with the `is-loading` class will display a spinner. This informs the user visually that the button is waiting for some sort of response.
152+
153+
<Canvas>
154+
<Story name="Disabled" args={{ label: 'Disabled', disabled: true }}>
155+
{(args) => buttonStory(args)}
156+
</Story>
157+
<Story
158+
name="ARIA Disabled"
159+
args={{ label: 'ARIA Disabled', aria_disabled: 'true' }}
160+
>
161+
{(args) => buttonStory(args)}
162+
</Story>
163+
<Story name="Loading" args={{ label: 'Loading', class: 'is-loading' }}>
164+
{(args) => buttonStory(args)}
165+
</Story>
166+
</Canvas>
167+
168+
### Icon with slash
169+
170+
To add a slash across the icon(s), use the `is-slashed` state class.
150171

151172
If no `content_start_icon`/`content_end_icon` or `content_start`/`content_end` block content is passed in, the `'bell'` icon will be used by default as the `content_start_icon` value.
152173

@@ -189,6 +210,7 @@ You can override the `content_start_icon` value to a different icon.
189210

190211
## Template Properties
191212

213+
- `aria_disabled` (string, `'true'`/`'false'`): Sets the button's `aria-disabled` attribute.
192214
- `aria_expanded` (string, `'true'`/`'false'`): Sets the button's `aria-expanded` attribute.
193215
- `class` (string): Append a class to the root element.
194216
- `content_start_icon` (string): The name of the [icon](/docs/design-icons--page) to render in the `content_start` block.

src/components/button/button.twig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515

1616
<{{ tag_name }}
1717
class="c-button{% if class %} {{ class }}{% endif %}"
18+
{% if aria_disabled %}
19+
aria-disabled="{{ aria_disabled }}"
20+
{% endif %}
1821
{% if aria_expanded %}
1922
aria-expanded="{{ aria_expanded }}"
2023
{% endif %}

src/mixins/_button.scss

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,54 @@
9393
transform: scale(scale.$effect-shrink);
9494
}
9595

96-
&:disabled {
96+
&:disabled,
97+
&[aria-disabled='true'] {
9798
cursor: not-allowed;
9899
filter: grayscale(60%);
99100
opacity: 0.85;
100101
transform: none;
101102
}
103+
104+
/**
105+
* Loading state
106+
*/
107+
108+
&.is-loading {
109+
/**
110+
* Display a spinner. Static if the user prefers reduced motion, animated
111+
* otherwise.
112+
*/
113+
114+
&::after {
115+
animation: transition.$glacial rotate-clockwise linear infinite;
116+
block-size: size.$icon-medium;
117+
border: size.$edge-control solid currentColor;
118+
border-block-start-color: transparent;
119+
border-radius: size.$border-radius-full;
120+
content: '';
121+
inline-size: size.$icon-medium;
122+
inset: 0;
123+
margin: auto;
124+
position: absolute;
125+
126+
@media (prefers-reduced-motion: reduce) {
127+
animation: none;
128+
border-block-start-color: currentColor;
129+
border-style: dashed;
130+
}
131+
}
132+
133+
/**
134+
* Hide child elements (content, extra). We use this technique because it is
135+
* performant and will not negate any keyboard events.
136+
*
137+
* @link https: //stackoverflow.com/a/34529598
138+
*/
139+
140+
> * {
141+
opacity: 0;
142+
}
143+
}
102144
}
103145

104146
/**
@@ -121,6 +163,16 @@
121163
color: var(--theme-color-text-button-tertiary);
122164
}
123165

166+
/// Rules for content element, used to contain button label.
167+
@mixin content {
168+
/// We use opacity because it will not paint but it also won't remove the
169+
/// element's keyboard events (if any).
170+
/// @link https: //stackoverflow.com/a/34529598
171+
.is-loading & {
172+
opacity: 0;
173+
}
174+
}
175+
124176
/// Rules for extra element. Used to contain icons or other visual affordances.
125177
@mixin extra {
126178
display: flex;

0 commit comments

Comments
 (0)