← All proposals

Spinner: agentic color variant

Extend the existing PDS v2 Spinner (not a new component).

Extends a PDS componentOpen in Storybook

proposals/Spinner/spinner.css108 lines · View on GitHub
.pds-spinner {
	--spinner-color: var(--pds-color-foreground-default);

	align-items: center;
	display: flex;
	flex-direction: column;
	text-align: center;
}

.pds-spinner--inline {
	align-self: center;
	display: inline-flex;
}

.pds-spinner__icon {
	height: var(--spinner-size);
	overflow: visible;
	width: var(--spinner-size);
}

.pds-spinner__track {
	fill: none;
	stroke: var(--spinner-color);
	stroke-opacity: 0.3;
	stroke-width: 2.5;
}

.pds-spinner__rotator {
	animation: spinner-rotate 1s linear infinite;
	transform-origin: 12px 12px;
}

.pds-spinner__arc {
	fill: none;
	stroke: var(--spinner-color);
	/* Circumference of the r=10.5 circle (2 * pi * 10.5 = 65.97) is larger
	   than the dash so only one arc segment ever shows. */
	stroke-dasharray: 18.7 66;
	stroke-linecap: round;
	stroke-width: 2.5;
}

/* Agentic: --pds-gradient-ai-active is a CSS gradient, which an SVG stroke
   cannot use. Instead the gradient is the background of a box that is masked
   to the same ring / arc shapes (alpha only, so the mask colour is
   irrelevant). The arc box rotates; its gradient layer counter-rotates so
   the gradient stays fixed while the arc spins, like the ring in the
   default spinner. */
.pds-spinner--agentic {
	--spinner-arc-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='10.5' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-dasharray='18.7 66'/%3E%3C/svg%3E");
	--spinner-ring-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='10.5' fill='none' stroke='black' stroke-width='2.5'/%3E%3C/svg%3E");
}

.pds-spinner--agentic .pds-spinner__icon {
	display: block;
	position: relative;
}

.pds-spinner__gradient-track,
.pds-spinner__gradient-rotator,
.pds-spinner__gradient-arc {
	inset: 0;
	position: absolute;
}

.pds-spinner__gradient-track {
	background: var(--pds-gradient-ai-active);
	mask: var(--spinner-ring-mask) center / 100% 100% no-repeat;
	opacity: 0.3;
}

.pds-spinner__gradient-rotator {
	animation: spinner-rotate 1s linear infinite;
}

.pds-spinner__gradient-arc {
	mask: var(--spinner-arc-mask) center / 100% 100% no-repeat;
}

.pds-spinner__gradient-arc::before {
	animation: spinner-rotate 1s linear infinite reverse;
	background: var(--pds-gradient-ai-active);
	content: '';
	inset: 0;
	position: absolute;
}

@keyframes spinner-rotate {
	to {
		transform: rotate(360deg);
	}
}

.pds-spinner__label {
	font-family: var(--pds-typography-ff-default);
	font-weight: var(--pds-typography-fw-medium);
	line-height: var(--pds-typography-lh-m);
	max-width: calc(var(--spinner-size) * 6);
}

@media (prefers-reduced-motion: reduce) {
	.pds-spinner__rotator,
	.pds-spinner__gradient-rotator,
	.pds-spinner__gradient-arc::before {
		animation: none;
	}
}