Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions elements/pf-v6-spinner/demo/basic.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Basic
description: A basic spinner indicates that an action is in progress.
---
<section>
<pf-v6-spinner accessible-label="Loading contents">Loading...</pf-v6-spinner>
</section>

<script type="module">
import '@patternfly/elements/pf-v6-spinner/pf-v6-spinner.js';
</script>

<style>
section {
padding: 1em;
}
</style>
17 changes: 17 additions & 0 deletions elements/pf-v6-spinner/demo/custom-size.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Custom size
description: A spinner can have a custom diameter using the `diameter` attribute.
---
<section>
<pf-v6-spinner diameter="80px" accessible-label="Custom size spinner">Loading...</pf-v6-spinner>
</section>

<script type="module">
import '@patternfly/elements/pf-v6-spinner/pf-v6-spinner.js';
</script>

<style>
section {
padding: 1em;
}
</style>
35 changes: 35 additions & 0 deletions elements/pf-v6-spinner/demo/inline-size.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: Inline size
description: An inline spinner inherits its font size, so its size will match the content around it.
---
<section>
<h1>
Heading
<pf-v6-spinner inline accessible-label="Spinner in a heading">Loading...</pf-v6-spinner>
</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed hendrerit nisi in cursus maximus.</p>
<h2>
Second level
<pf-v6-spinner inline accessible-label="Spinner in a subheading">Loading...</pf-v6-spinner>
</h2>
<p>
Curabitur accumsan turpis pharetra blandit. Quisque condimentum maximus mi,
<pf-v6-spinner inline accessible-label="Spinner in a paragraph">Loading...</pf-v6-spinner>
sit amet commodo arcu rutrum id. Proin pretium urna vel cursus venenatis.
Suspendisse potenti.
</p>
<small>
Sometimes you need small text
<pf-v6-spinner inline accessible-label="Spinner in small text">Loading...</pf-v6-spinner>
</small>
</section>

<script type="module">
import '@patternfly/elements/pf-v6-spinner/pf-v6-spinner.js';
</script>

<style>
section {
padding: 1em;
}
</style>
24 changes: 24 additions & 0 deletions elements/pf-v6-spinner/demo/size-variations.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: Size variations
description: Spinners can be sized using the `size` attribute with values `xs`, `sm`, `md`, `lg`, and `xl`.
---
<section>
<pf-v6-spinner size="xs" accessible-label="Extra small spinner">Loading...</pf-v6-spinner>
<pf-v6-spinner size="sm" accessible-label="Small spinner">Loading...</pf-v6-spinner>
<pf-v6-spinner size="md" accessible-label="Medium spinner">Loading...</pf-v6-spinner>
<pf-v6-spinner size="lg" accessible-label="Large spinner">Loading...</pf-v6-spinner>
<pf-v6-spinner size="xl" accessible-label="Extra large spinner">Loading...</pf-v6-spinner>
</section>

<script type="module">
import '@patternfly/elements/pf-v6-spinner/pf-v6-spinner.js';
</script>

<style>
section {
display: flex;
align-items: center;
gap: 1em;
padding: 1em;
}
</style>
112 changes: 112 additions & 0 deletions elements/pf-v6-spinner/pf-v6-spinner.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
:host {
display: inline-block;
width: min-content;
min-height: 0;
aspect-ratio: 1 / 1;
}

[hidden] {
display: none !important;
}

svg {
overflow: hidden;
width: var(--pf-v6-c-spinner--Width,
var(--pf-v6-c-spinner--diameter,
var(--pf-t--global--icon--size--2xl, 3.5rem)));
height: var(--pf-v6-c-spinner--Height,
var(--pf-v6-c-spinner--diameter,
var(--pf-t--global--icon--size--2xl, 3.5rem)));
animation:
rotate
calc(var(--pf-v6-c-spinner--AnimationDuration, 1.4s) * 2)
var(--pf-v6-c-spinner--AnimationTimingFunction, linear) infinite;
}

circle {
width: 100%;
height: 100%;
transform-origin: 50% 50%;
stroke-linecap: round;
stroke-dasharray: 283;
stroke-dashoffset: 280;
stroke: var(--pf-v6-c-spinner--Color,
var(--pf-t--global--icon--color--brand--default, #0066cc));
stroke-width: var(--pf-v6-c-spinner--StrokeWidth, 10);
animation:
dash
var(--pf-v6-c-spinner--AnimationDuration, 1.4s)
var(--pf-v6-c-spinner__path--AnimationTimingFunction, ease-in-out) infinite;
}

:host([size="xs"]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-xs--diameter,
var(--pf-t--global--icon--size--sm, 0.75rem));

& circle {
stroke-width: var(--pf-v6-c-spinner--StrokeWidth, 15);
}
}

:host([size="sm"]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-sm--diameter,
var(--pf-t--global--icon--size--md, 0.875rem));
}

:host([size="md"]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-md--diameter,
var(--pf-t--global--icon--size--lg, 1rem));
}

:host([size="lg"]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-lg--diameter,
var(--pf-t--global--icon--size--xl, 1.5rem));
}

:host([size="xl"]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-xl--diameter,
var(--pf-t--global--icon--size--2xl, 3.5rem));
}

:host([inline]) svg {
--pf-v6-c-spinner--diameter: var(--pf-v6-c-spinner--m-inline--diameter, 1em);
}

@keyframes rotate {
0% {
rotate: 0deg;
}

100% {
rotate: 360deg;
}
}

@keyframes dash {
0% {
stroke-dashoffset: 280;
rotate: 0deg;
}

15% {
stroke-width: calc(var(--pf-v6-c-spinner__path--StrokeWidth,
var(--pf-v6-c-spinner--StrokeWidth, 10)) - 4);
}

40% {
stroke-dasharray: 220;
stroke-dashoffset: 150;
}

100% {
stroke-dashoffset: 280;
rotate: 720deg;
}
}

@media (prefers-reduced-motion: reduce) {
svg,
circle {
animation-duration: 0s;
}
}
73 changes: 73 additions & 0 deletions elements/pf-v6-spinner/pf-v6-spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { LitElement, html, type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { property } from 'lit/decorators/property.js';

import { InternalsController } from '@patternfly/pfe-core/controllers/internals-controller.js';

import styles from './pf-v6-spinner.css';

export type SpinnerSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';

/**
* A **spinner** is an animated visual that indicates when a quick action is
* in progress. For actions that may take a long time, use a progress bar instead.
* @summary Indicates that an action is in progress.
* @cssprop {<length>} [--pf-v6-c-spinner--diameter] - Custom diameter of the spinner
* @cssprop {<color>} [--pf-v6-c-spinner--Color] - Color of the spinner stroke
* @cssprop {<time>} [--pf-v6-c-spinner--AnimationDuration=1.4s] - Duration of one animation cycle
* @cssprop {<number>} [--pf-v6-c-spinner--StrokeWidth=10] - Width of the spinner stroke
*/
@customElement('pf-v6-spinner')
export class PfV6Spinner extends LitElement {
static readonly styles: CSSStyleSheet[] = [styles];

/** Preset sizes for the spinner */
@property({ reflect: true }) size?: SpinnerSize;

/**
* When true, the spinner inherits its font size from the surrounding text,
* allowing it to display inline with content.
*/
@property({ type: Boolean, reflect: true }) inline = false;

/** Custom diameter of spinner set as CSS variable */
@property({ reflect: true }) diameter?: string;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should be an exclusively css thing, it also overlaps with size

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ok, will remove this. It was actually prompted whether the agent should do that. I left it as is to stay closer to the React API. That said, would you go so far as to say we are setting a repeatable precedent that, if the prop sets a value in React (80px), or whatever it may be, we would only handle this as a CSS var? Something we could encode into the skill?


/** Accessible label describing what is loading */
@property({ attribute: 'accessible-label' }) accessibleLabel?: string;

#internals = InternalsController.of(this, {
role: 'progressbar',
ariaValueText: 'Loading...',
ariaLabel: 'Loading...',
});

override willUpdate(changed: Map<PropertyKey, unknown>): void {
if (changed.has('accessibleLabel')) {
const label = this.accessibleLabel ?? 'Loading...';
this.#internals.ariaLabel = label;
this.#internals.ariaValueText = label;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this should be a separate public property

cc @adamjohnson

}
if (changed.has('diameter')) {
if (this.diameter) {
this.style.setProperty('--pf-v6-c-spinner--diameter', this.diameter);
} else {
this.style.removeProperty('--pf-v6-c-spinner--diameter');
}
}
}

override render(): TemplateResult {
return html`
<svg viewBox="0 0 100 100" aria-hidden="true">
<circle cx="50" cy="50" r="45" fill="none" />
</svg>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'pf-v6-spinner': PfV6Spinner;
}
}
Loading
Loading