Skip to content

Commit

Permalink
feat(circular-progress): adds circular-progress element
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 520758465
  • Loading branch information
material-web-copybara authored and copybara-github committed Mar 30, 2023
1 parent fdd7e82 commit 3adab6a
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Field | ✅ | ✅ | 🟡
Icon | ✅ | ✅ | ❌
List | ✅ | 🟡 | 🟡
Menu | ✅ | 🟡 | 🟡
Progress indicator (circular) | 🟡 | | ❌
Progress indicator (circular) | | 🟡 | ❌
Progress indicator (linear) | 🟡 | ❌ | ❌
Radio button | ✅ | ✅ | ❌
Ripple | ✅ | ✅ | 🟡
Expand Down
6 changes: 6 additions & 0 deletions circularprogress/_circular-progress.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

@forward './lib/circular-progress' show theme;
31 changes: 31 additions & 0 deletions circularprogress/circular-progress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {customElement} from 'lit/decorators.js';

import {CircularProgress} from './lib/circular-progress.js';
import {styles} from './lib/circular-progress-styles.css.js';

declare global {
interface HTMLElementTagNameMap {
'md-circular-progress': MdCircularProgress;
}
}

/**
* @summary Circular progress indicators display progress by animating along an
* invisible circular track in a clockwise direction. They can be applied
* directly to a surface, such as a button or card.
*
* @description
* Progress indicators inform users about the status of ongoing processes.
* - Determinate indicators display how long a process will take.
* - Indeterminate indicators express an unspecified amount of wait time.
*/
@customElement('md-circular-progress')
export class MdCircularProgress extends CircularProgress {
static override styles = [styles];
}
15 changes: 15 additions & 0 deletions circularprogress/circular-progress_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {createTokenTests} from '../testing/tokens.js';

import {MdCircularProgress} from './circular-progress.js';

describe('<md-circular-progress>', () => {
describe('.styles', () => {
createTokenTests(MdCircularProgress.styles);
});
});
14 changes: 14 additions & 0 deletions circularprogress/harness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {Harness} from '../testing/harness.js';

import {CircularProgress} from './lib/circular-progress.js';

/**
* Test harness for circular-progress.
*/
export class CircularProgressHarness extends Harness<CircularProgress> {}
282 changes: 282 additions & 0 deletions circularprogress/lib/_circular-progress.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// go/keep-sorted start
@use '../../sass/theme';
@use '../../tokens';
// go/keep-sorted end

@mixin theme($tokens) {
$reference: tokens.md-comp-circular-progress-indicator-values();
$tokens: theme.validate-theme($reference, $tokens);
$tokens: theme.create-theme-vars($tokens, 'circular-progress');

@include theme.emit-theme-vars($tokens);
}

@mixin styles() {
$tokens: tokens.md-comp-circular-progress-indicator-values();
$tokens: theme.create-theme-vars($tokens, 'circular-progress');

$container-padding: 4px;

// note, these value come from the m2 version but match current gm3 values.
// Constants:
// ARCSIZE = 270 degrees (amount of circle the arc takes up)
// ARCTIME = 1333ms (time it takes to expand and contract arc)
// ARCSTARTROT = 216 degrees (how much the start location of the arc
// should rotate each time, 216 gives us a
// 5 pointed star shape (it's 360/5 * 3).
// For a 7 pointed star, we might do
// 360/7 * 3 = 154.286)
// ARCTIME
$arc-duration: 1333ms;
// 4 * ARCTIME
$cycle-duration: calc(4 * $arc-duration);
// ARCTIME * 360 / (ARCSTARTROT + (360-ARCSIZE))
$linear-rotate-duration: calc($arc-duration * 360 / 306);

$indeterminate-easing: cubic-bezier(0.4, 0, 0.2, 1);

:host {
@each $token, $value in $tokens {
--_#{$token}: #{$value};
}

display: inline-flex;
vertical-align: middle;
min-block-size: var(--_size);
min-inline-size: var(--_size);
position: relative;
align-items: center;
justify-content: center;

// `contain` and `content-visibility` are performance optimizations
// important here because progress indicators are often used when a cpu
// intensive task is underway so it's especially important to minimize
// their cpu consumption.
contain: strict;
content-visibility: auto;
}

.circular-progress {
flex: 1;
align-self: stretch;
margin: $container-padding;
}

.circular-progress,
.spinner,
.left,
.right,
.circle,
svg,
.track,
.progress {
position: absolute;
inset: 0;
}

svg {
transform: rotate(-90deg);
}

circle {
cx: 50%;
cy: 50%;
r: calc(50% * (1 - var(--_active-indicator-width) / 100));
// match size to indeterminate border width
stroke-width: calc(var(--_active-indicator-width) * 1%);
// note, pathLength is set so this can be normalized
stroke-dasharray: 100;
fill: transparent;
}

.progress {
// note, these value come from the m2 version but match current gm3 values.
transition: stroke-dashoffset 500ms cubic-bezier(0, 0, 0.2, 1);
stroke: var(--_active-indicator-color);
}

.track {
stroke: transparent;
}

.circular-progress.indeterminate {
will-change: transform;
animation: linear infinite linear-rotate;
animation-duration: $linear-rotate-duration;
}

.spinner {
will-change: transform;
animation: infinite both rotate-arc;
animation-duration: $cycle-duration;
animation-timing-function: $indeterminate-easing;
}

.left {
overflow: hidden;
inset: 0 50% 0 0;
}

.right {
overflow: hidden;
inset: 0 0 0 50%;
}

.circle {
box-sizing: border-box;
border-radius: 50%;
// match size to svg stroke width, which is a fraction of the overall
// padding box width.
$_padding-box-width: calc(var(--_size) - 2 * $container-padding);
$_active-indicator-fraction: calc(var(--_active-indicator-width) / 100);
border: solid calc($_active-indicator-fraction * $_padding-box-width);
border-color: var(--_active-indicator-color) var(--_active-indicator-color)
transparent transparent;
will-change: transform;
animation: expand-arc;
animation-iteration-count: infinite;
animation-fill-mode: both;
animation-duration: $arc-duration, $cycle-duration;
animation-timing-function: $indeterminate-easing;
}

.four-color .circle {
animation-name: expand-arc, four-color;
}

.left .circle {
rotate: 135deg;
inset: 0 -100% 0 0;
}
.right .circle {
rotate: 100deg;
inset: 0 0 0 -100%;
animation-delay: calc(-0.5 * $arc-duration), 0ms;
}

@media screen and (forced-colors: active) {
.progress {
stroke: CanvasText;
}

.circle {
border-color: CanvasText CanvasText Canvas Canvas;
}
}

// Indeterminate mode is 3 animations composed together:
// 1. expand-arc: an arc is expanded/contracted between 10deg and 270deg.
// 2. rotate-arc: at the same time, the arc is rotated in increments
// of 270deg.
// 3. linear-rotate: that rotating arc is then linearly rotated to produce
// a spinning expanding/contracting arc.
//
// See original implementation:
// https://github.com/PolymerElements/paper-spinner/blob/master/paper-spinner-styles.js.

// 1. expand-arc
// This is used on 2 divs which each represent half the desired
// 270deg arc with one offset by 50%. This creates an arc which expands from
// 10deg to 270deg.
@keyframes expand-arc {
0% {
transform: rotate(265deg);
}
50% {
transform: rotate(130deg);
}
100% {
transform: rotate(265deg);
}
}

// 2. rotate-arc
// The arc seamlessly travels around the circle indefinitely so it needs to
// end at a full rotation of the circle. This rotates the 270 deg
// (270/360 = 3/4) arc 4x (4 * 3/4 = 3) so it ends at
// (3 * 360 = 1080).
// This is sub-divided into increments of 135deg since the arc is rendered
// with 2 divs acting together.
@keyframes rotate-arc {
12.5% {
transform: rotate(135deg);
}
25% {
transform: rotate(270deg);
}
37.5% {
transform: rotate(405deg);
}
50% {
transform: rotate(540deg);
}
62.5% {
transform: rotate(675deg);
}
75% {
transform: rotate(810deg);
}
87.5% {
transform: rotate(945deg);
}
100% {
transform: rotate(1080deg);
}
}

// 3. linear-rotate
// The traveling expanding arc is linearly rotated to produce the spinner
// effect.
@keyframes linear-rotate {
to {
transform: rotate(360deg);
}
}

// This animates between 4 colors which are each shown for 25% of the time.
// Each color is shown solid for 3/5 of that time (3/5 * 25% = 15%) and
// transitions to the next color for 2/5 of that time (2/5 * 25% = 10%).
@keyframes four-color {
0% {
border-top-color: var(--_four-color-active-indicator-one-color);
border-right-color: var(--_four-color-active-indicator-one-color);
}
15% {
border-top-color: var(--_four-color-active-indicator-one-color);
border-right-color: var(--_four-color-active-indicator-one-color);
}
25% {
border-top-color: var(--_four-color-active-indicator-two-color);
border-right-color: var(--_four-color-active-indicator-two-color);
}
40% {
border-top-color: var(--_four-color-active-indicator-two-color);
border-right-color: var(--_four-color-active-indicator-two-color);
}
50% {
border-top-color: var(--_four-color-active-indicator-three-color);
border-right-color: var(--_four-color-active-indicator-three-color);
}
65% {
border-top-color: var(--_four-color-active-indicator-three-color);
border-right-color: var(--_four-color-active-indicator-three-color);
}
75% {
border-top-color: var(--_four-color-active-indicator-four-color);
border-right-color: var(--_four-color-active-indicator-four-color);
}
90% {
border-top-color: var(--_four-color-active-indicator-four-color);
border-right-color: var(--_four-color-active-indicator-four-color);
}
100% {
border-top-color: var(--_four-color-active-indicator-one-color);
border-right-color: var(--_four-color-active-indicator-one-color);
}
}
}
8 changes: 8 additions & 0 deletions circularprogress/lib/circular-progress-styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Copyright 2023 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

@use './circular-progress';

@include circular-progress.styles;
Loading

0 comments on commit 3adab6a

Please sign in to comment.