Skip to content

Commit 4506f0c

Browse files
feat: implement Material 3 typography tokens and classes (#120)
* feat: implement Material Design 3 Expressive Loading Indicator * feat: Add wavy shape attribute for linear and circular progress indicators * fix: Address PR review feedback for loading indicator component * fix: Prevent vertical clipping of wavy linear progress indicators * fix: Reduce linear wavy progress background track height to 4px and center it * feat: Add stop indicator dot and rounded track corners to linear progress * refactor: Move progress and loading indicator components to indicators directory and update README * refactor: Rename loading-indicator component to md-loading and file to loading.js * 2.12.0 * refactor: replace wavy boolean attribute with shape='flat|wavy' on md-progress * feat: implement Material 3 typography tokens and classes * Update indicators/loading.js Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix: address PR feedback on md-loading and md-progress indicators --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent b9d6514 commit 4506f0c

4 files changed

Lines changed: 423 additions & 114 deletions

File tree

indicators/loading.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ class M3Animator {
227227
const rawDt = Math.min((ts - this.lastTs) / 1e3, 0.1)
228228
const dt = rawDt * this.speed
229229
this.lastTs = ts
230-
if (dt <= 0) return
230+
if (isNaN(dt) || !isFinite(dt) || dt <= 0) return
231231
this.elapsed += dt * 1e3
232232
const cycle = Math.floor(this.elapsed / DURATION_PER_SHAPE_MS)
233233
if (cycle > this.prevCycle) {
@@ -294,7 +294,9 @@ function setupCanvas(canvas, cssSize) {
294294
canvas.style.width = `${cssSize}px`
295295
canvas.style.height = `${cssSize}px`
296296
const ctx = canvas.getContext("2d")
297-
ctx.scale(dpr, dpr)
297+
if (ctx) {
298+
ctx.scale(dpr, dpr)
299+
}
298300
return ctx
299301
}
300302

@@ -357,6 +359,7 @@ export class Loading extends LitElement {
357359
}
358360

359361
startAnimation() {
362+
if (this.paused) return
360363
if (this.rafId) return
361364
const canvas = this.renderRoot?.querySelector('#canvas')
362365
if (!canvas) return
@@ -420,31 +423,40 @@ export class Loading extends LitElement {
420423
if (changedProperties.has('color') || changedProperties.has('containerColor') || changedProperties.has('contained')) {
421424
this.updateResolvedColors()
422425
}
426+
if (changedProperties.has('paused')) {
427+
if (this.paused) {
428+
this.stopAnimation()
429+
} else {
430+
this.startAnimation()
431+
}
432+
}
423433
}
424434

425-
_resolveColor(colorString) {
435+
_resolveColor(colorString, depth = 0) {
436+
if (depth > 5) return '#6750a4'
426437
if (!colorString) return '#6750a4'
427438
const trimmed = colorString.trim()
439+
const windowExists = typeof window !== 'undefined'
428440
if (trimmed === 'currentColor') {
429-
return window.getComputedStyle(this).color || '#6750a4'
441+
return (windowExists ? window.getComputedStyle(this).color : '') || '#6750a4'
430442
}
431443
if (trimmed.startsWith('var(') && trimmed.endsWith(')')) {
432444
const content = trimmed.substring(4, trimmed.length - 1).trim()
433445
const commaIdx = content.indexOf(',')
434446
if (commaIdx === -1) {
435447
const varName = content.trim()
436-
const val = window.getComputedStyle(this).getPropertyValue(varName).trim()
437-
return val ? this._resolveColor(val) : '#6750a4'
448+
const val = windowExists ? window.getComputedStyle(this).getPropertyValue(varName).trim() : ''
449+
return val ? this._resolveColor(val, depth + 1) : '#6750a4'
438450
} else {
439451
const varName = content.substring(0, commaIdx).trim()
440452
const fallback = content.substring(commaIdx + 1).trim()
441-
const val = window.getComputedStyle(this).getPropertyValue(varName).trim()
442-
return val ? this._resolveColor(val) : this._resolveColor(fallback)
453+
const val = windowExists ? window.getComputedStyle(this).getPropertyValue(varName).trim() : ''
454+
return val ? this._resolveColor(val, depth + 1) : this._resolveColor(fallback, depth + 1)
443455
}
444456
}
445457
if (trimmed.startsWith('--')) {
446-
const val = window.getComputedStyle(this).getPropertyValue(trimmed).trim()
447-
return val ? this._resolveColor(val) : '#6750a4'
458+
const val = windowExists ? window.getComputedStyle(this).getPropertyValue(trimmed).trim() : ''
459+
return val ? this._resolveColor(val, depth + 1) : '#6750a4'
448460
}
449461
return trimmed
450462
}

indicators/progress.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export class Progress extends LitElement {
4141
*/
4242
this.buffer = 0
4343
this.shape = 'flat'
44+
this.wavyCirclePath = null
4445
}
4546

4647
firstUpdated() {
@@ -49,6 +50,7 @@ export class Progress extends LitElement {
4950

5051
updated(changedProperties) {
5152
if (changedProperties.has('shape') || changedProperties.has('type')) {
53+
this.wavyCirclePath = null
5254
this.updateWavyAnimationState()
5355
}
5456
}
@@ -73,7 +75,10 @@ export class Progress extends LitElement {
7375
const loop = (ts) => {
7476
if (!this.wavyAnimationActive) return
7577
this.wavyPhase += 0.08
76-
const path = this.renderRoot.querySelector('.active-track.wavy-circle')
78+
if (!this.wavyCirclePath) {
79+
this.wavyCirclePath = this.renderRoot.querySelector('.active-track.wavy-circle')
80+
}
81+
const path = this.wavyCirclePath
7782
if (path) {
7883
const isIndeterminateWavy = this.indeterminate && this.shape === 'wavy'
7984
const progress = isIndeterminateWavy ? 0.25 : (this.value / this.max)
@@ -696,6 +701,10 @@ export class Progress extends LitElement {
696701
customElements.define('md-progress', Progress)
697702

698703
function getWavyCirclePath(cx, cy, r, amplitude, frequency, phase, progress = 1.0) {
704+
if (isNaN(progress) || !isFinite(progress)) {
705+
progress = 0
706+
}
707+
progress = Math.max(0, Math.min(1, progress))
699708
const points = []
700709
const steps = Math.max(10, Math.round(180 * progress))
701710
for (let i = 0; i <= steps; i++) {

typography/md-typescale-styles.js

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,2 @@
1-
import { css } from 'lit'
2-
export const styles = css`
3-
@layer {
4-
.md-typescale-display-small,
5-
.md-typescale-display-small-prominent {
6-
font: var(--md-sys-typescale-display-small-weight, var(--md-ref-typeface-weight-regular, 400))
7-
var(--md-sys-typescale-display-small-size, 2.25rem) / var(--md-sys-typescale-display-small-line-height, 2.75rem)
8-
var(--md-sys-typescale-display-small-font, var(--md-ref-typeface-brand, Roboto));
9-
}
10-
.md-typescale-display-medium,
11-
.md-typescale-display-medium-prominent {
12-
font: var(--md-sys-typescale-display-medium-weight, var(--md-ref-typeface-weight-regular, 400))
13-
var(--md-sys-typescale-display-medium-size, 2.8125rem) /
14-
var(--md-sys-typescale-display-medium-line-height, 3.25rem)
15-
var(--md-sys-typescale-display-medium-font, var(--md-ref-typeface-brand, Roboto));
16-
}
17-
.md-typescale-display-large,
18-
.md-typescale-display-large-prominent {
19-
font: var(--md-sys-typescale-display-large-weight, var(--md-ref-typeface-weight-regular, 400))
20-
var(--md-sys-typescale-display-large-size, 3.5625rem) / var(--md-sys-typescale-display-large-line-height, 4rem)
21-
var(--md-sys-typescale-display-large-font, var(--md-ref-typeface-brand, Roboto));
22-
}
23-
.md-typescale-headline-small,
24-
.md-typescale-headline-small-prominent {
25-
font: var(--md-sys-typescale-headline-small-weight, var(--md-ref-typeface-weight-regular, 400))
26-
var(--md-sys-typescale-headline-small-size, 1.5rem) / var(--md-sys-typescale-headline-small-line-height, 2rem)
27-
var(--md-sys-typescale-headline-small-font, var(--md-ref-typeface-brand, Roboto));
28-
}
29-
.md-typescale-headline-medium,
30-
.md-typescale-headline-medium-prominent {
31-
font: var(--md-sys-typescale-headline-medium-weight, var(--md-ref-typeface-weight-regular, 400))
32-
var(--md-sys-typescale-headline-medium-size, 1.75rem) /
33-
var(--md-sys-typescale-headline-medium-line-height, 2.25rem)
34-
var(--md-sys-typescale-headline-medium-font, var(--md-ref-typeface-brand, Roboto));
35-
}
36-
.md-typescale-headline-large,
37-
.md-typescale-headline-large-prominent {
38-
font: var(--md-sys-typescale-headline-large-weight, var(--md-ref-typeface-weight-regular, 400))
39-
var(--md-sys-typescale-headline-large-size, 2rem) / var(--md-sys-typescale-headline-large-line-height, 2.5rem)
40-
var(--md-sys-typescale-headline-large-font, var(--md-ref-typeface-brand, Roboto));
41-
}
42-
.md-typescale-title-small,
43-
.md-typescale-title-small-prominent {
44-
font: var(--md-sys-typescale-title-small-weight, var(--md-ref-typeface-weight-medium, 500))
45-
var(--md-sys-typescale-title-small-size, 0.875rem) / var(--md-sys-typescale-title-small-line-height, 1.25rem)
46-
var(--md-sys-typescale-title-small-font, var(--md-ref-typeface-plain, Roboto));
47-
}
48-
.md-typescale-title-medium,
49-
.md-typescale-title-medium-prominent {
50-
font: var(--md-sys-typescale-title-medium-weight, var(--md-ref-typeface-weight-medium, 500))
51-
var(--md-sys-typescale-title-medium-size, 1rem) / var(--md-sys-typescale-title-medium-line-height, 1.5rem)
52-
var(--md-sys-typescale-title-medium-font, var(--md-ref-typeface-plain, Roboto));
53-
}
54-
.md-typescale-title-large,
55-
.md-typescale-title-large-prominent {
56-
font: var(--md-sys-typescale-title-large-weight, var(--md-ref-typeface-weight-regular, 400))
57-
var(--md-sys-typescale-title-large-size, 1.375rem) / var(--md-sys-typescale-title-large-line-height, 1.75rem)
58-
var(--md-sys-typescale-title-large-font, var(--md-ref-typeface-brand, Roboto));
59-
}
60-
.md-typescale-body-small,
61-
.md-typescale-body-small-prominent {
62-
font: var(--md-sys-typescale-body-small-weight, var(--md-ref-typeface-weight-regular, 400))
63-
var(--md-sys-typescale-body-small-size, 0.75rem) / var(--md-sys-typescale-body-small-line-height, 1rem)
64-
var(--md-sys-typescale-body-small-font, var(--md-ref-typeface-plain, Roboto));
65-
}
66-
.md-typescale-body-medium,
67-
.md-typescale-body-medium-prominent {
68-
font: var(--md-sys-typescale-body-medium-weight, var(--md-ref-typeface-weight-regular, 400))
69-
var(--md-sys-typescale-body-medium-size, 0.875rem) / var(--md-sys-typescale-body-medium-line-height, 1.25rem)
70-
var(--md-sys-typescale-body-medium-font, var(--md-ref-typeface-plain, Roboto));
71-
}
72-
.md-typescale-body-large,
73-
.md-typescale-body-large-prominent {
74-
font: var(--md-sys-typescale-body-large-weight, var(--md-ref-typeface-weight-regular, 400))
75-
var(--md-sys-typescale-body-large-size, 1rem) / var(--md-sys-typescale-body-large-line-height, 1.5rem)
76-
var(--md-sys-typescale-body-large-font, var(--md-ref-typeface-plain, Roboto));
77-
}
78-
.md-typescale-label-small,
79-
.md-typescale-label-small-prominent {
80-
font: var(--md-sys-typescale-label-small-weight, var(--md-ref-typeface-weight-medium, 500))
81-
var(--md-sys-typescale-label-small-size, 0.6875rem) / var(--md-sys-typescale-label-small-line-height, 1rem)
82-
var(--md-sys-typescale-label-small-font, var(--md-ref-typeface-plain, Roboto));
83-
}
84-
.md-typescale-label-medium,
85-
.md-typescale-label-medium-prominent {
86-
font: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500))
87-
var(--md-sys-typescale-label-medium-size, 0.75rem) / var(--md-sys-typescale-label-medium-line-height, 1rem)
88-
var(--md-sys-typescale-label-medium-font, var(--md-ref-typeface-plain, Roboto));
89-
}
90-
.md-typescale-label-medium-prominent {
91-
font-weight: var(--md-sys-typescale-label-medium-weight-prominent, var(--md-ref-typeface-weight-bold, 700));
92-
}
93-
.md-typescale-label-large,
94-
.md-typescale-label-large-prominent {
95-
font: var(--md-sys-typescale-label-large-weight, var(--md-ref-typeface-weight-medium, 500))
96-
var(--md-sys-typescale-label-large-size, 0.875rem) / var(--md-sys-typescale-label-large-line-height, 1.25rem)
97-
var(--md-sys-typescale-label-large-font, var(--md-ref-typeface-plain, Roboto));
98-
}
99-
.md-typescale-label-large-prominent {
100-
font-weight: var(--md-sys-typescale-label-large-weight-prominent, var(--md-ref-typeface-weight-bold, 700));
101-
}
102-
}
103-
`
1+
export { styles } from './md-typescale.js'
2+

0 commit comments

Comments
 (0)