Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

### Enhancements

- `Button`, `IconButton`: Use the shared `Spinner` for loading indicators ([#84321](https://github.com/WordPress/gutenberg/pull/84321)).

### Bug Fixes

- `Menu`, `Popover`: Keep popups open when the user interacts with an iframe inside the popup. ([#83870](https://github.com/WordPress/gutenberg/pull/83870))
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { speak } from '@wordpress/a11y';
import { forwardRef, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { type ButtonProps } from './types';
import { Spinner } from '../spinner';
import styles from './style.module.css';
import resetStyles from '../utils/css/resets.module.css';
import focusStyles from '../utils/css/focus.module.scss';
Expand Down Expand Up @@ -59,6 +60,9 @@ export const Button = forwardRef< HTMLButtonElement, ButtonProps >(
{ ...props }
>
{ children }
{ loading && variant !== 'unstyled' && (
<Spinner className={ styles.spinner } />
) }
Comment on lines +63 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom render compositions can now leave a loading button blank. Could we preserve the spinner when the renderer supplies its own children or wraps props.children, and cover those cases?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explored the possibility for a bit, but I couldn't find a scenario where a consumer would need to do anything like that through a render. I even found it hard to think of a situation where they would mistakenly end up with a children composition like that.

Given that there's a complexity cost to support the spinner path in that kind of composition (and it still won't be guaranteed to work in all possible cases), I would wait until we hear about it or encounter it in reality. I would probably even wait to document the "don't do children in render" point, because it's extra information that most consumers may never need.

And if we do start encountering this case, I think the better path might be to go back to the pseudo-element approach. Or would you prefer that we just keep the pseudo-element approach right now and discard the PR?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No strong opinions. The pseudo-element approach would avoid the issue altogether, if you're ok with it, maybe it's the better choice here? THe only downside would be style duplication with Spinner, but maybe it's ok?

Anyway, don't consider it blocking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think as long as the render children thing stays a non-issue, we'd prefer the Spinner approach. Open to revisiting!

</_Button>
);
}
Expand Down
71 changes: 20 additions & 51 deletions packages/ui/src/button/style.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,41 +109,6 @@
color: GrayText;
}
}

/* Loading spinner — not animated and hidden unless in loading state */
&::before {
content: "";
position: absolute;
top: 50%;
/* stylelint-disable-next-line logical-css/require-logical-properties -- Physical centering technique used with transform: translate(-50%, -50%) */
left: 50%;
transform: translate(-50%, -50%);

display: block;
box-sizing: border-box;
height: var(--wp-ui-button-font-size);
aspect-ratio: 1;

border: var(--wpds-border-width-focus) solid;
border-block-start-color: var(--wp-ui-button-foreground-color);
border-inline-end-color: var(--wp-ui-button-foreground-color);
border-block-end-color: transparent;
border-inline-start-color: transparent;
border-radius: 50%;

pointer-events: none;
opacity: 0;

@media not ( prefers-reduced-motion ) {
transition: opacity 0.1s ease-out;
}

@media ( forced-colors: active ) {
border-color: ButtonText;
border-block-end-style: none;
border-inline-start-style: none;
}
}
}

.is-small {
Expand Down Expand Up @@ -245,29 +210,33 @@
color: ButtonFace;
}

* {
> :not(.spinner) {
opacity: 0;
}
}
}

&::before {
transition-delay: 0.05s;
opacity: 1;
@layer compositions {
.spinner {
position: absolute;
inset: 0;
margin: auto;
width: var(--wp-ui-button-font-size);
height: var(--wp-ui-button-font-size);
color: var(--wp-ui-button-foreground-color);
pointer-events: none;

@media not ( prefers-reduced-motion ) {
animation: loading-animation 1s linear infinite;
@media ( prefers-reduced-motion ) {
> * {
animation: none;
}
}
}

}

@keyframes loading-animation {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}

100% {
transform: translate(-50%, -50%) rotate(360deg);
@media ( forced-colors: active ) {
> * {
stroke: ButtonText;
}
}
}
}
}
Loading