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
23 changes: 23 additions & 0 deletions labs/gb/components/loading/_loading-tokens.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Copyright 2026 Google LLC
// SPDX-License-Identifier: Apache-2.0
//

// Token set: 'md.comp.loading'
// Version: 34.0.21

@mixin root {
// LINT.IfChange
--active-indicator-color: var(--md-sys-color-primary);
--active-indicator-size: 38px;
--container-color: transparent;
--container-height: 48px;
--container-shape: var(--md-sys-shape-corner-full);
--container-width: 48px;
// LINT.ThenChange(loading-element.ts) keep in sync with '@cssprop'
}

@mixin contained {
--active-indicator-color: var(--md-sys-color-on-primary-container);
--container-color: var(--md-sys-color-primary-container);
}
35 changes: 35 additions & 0 deletions labs/gb/components/loading/demo/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import './material-collection.js';
import './index.js';

import {
type KnobTypesToKnobs,
MaterialCollection,
materialInitsToStoryInits,
setUpDemo,
} from './material-collection.js';
import {
boolInput,
Knob,
textInput,
} from './index.js';

import {stories, type StoryKnobs} from './stories.js';

const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
'Loading',
[
new Knob('contained', {ui: boolInput(), defaultValue: false}),
new Knob('closed', {ui: boolInput(), defaultValue: false}),
new Knob('ariaLabel', {ui: textInput(), defaultValue: 'Loading'}),
] as unknown as KnobTypesToKnobs<StoryKnobs>,
);

collection.addStories(...materialInitsToStoryInits(stories));

setUpDemo(collection);
134 changes: 134 additions & 0 deletions labs/gb/components/loading/demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<!--
Copyright 2026 Google LLC
SPDX-License-Identifier: Apache-2.0
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Material 3 Expressive Loading</title>

<!-- Google Material 3 stylesheet -->
<link rel="stylesheet"
href="https://static.corp.google.com/material-web/gbreeze/latest/styles/gm3.css">
<!-- Fonts -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Google+Sans+Flex:opsz,slnt,wdth,wght,GRAD,ROND@6..144,-10..0,25..151,1..1000,0..100,0..100&display=swap">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Google+Symbols:opsz,wght,FILL,GRAD,ROND@20..48,100..700,0..1,-50..200,0..100">

<style>
:root {
--md-icon-font: "Google Symbols";
}

body {
background-color: var(--md-sys-color-surface);
box-sizing: border-box;
color: var(--md-sys-color-on-surface);
display: flex;
flex-direction: column;
font-family: "Google Sans Flex", sans-serif;
gap: 32px;
margin: 0;
min-block-size: 100vh;
padding: 32px;
transition: all;
}

.demo-header {
display: flex;
flex-direction: column;
gap: 8px;
transition: all;
}

.demo-title {
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-headline-medium);
margin: 0;
transition: all;
}

.demo-subtitle {
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-large);
margin: 0;
transition: all;
}

.demo-grid {
display: grid;
gap: 24px;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
inline-size: 100%;
max-inline-size: 960px;
transition: all;
}

.demo-card {
align-items: center;
background-color: var(--md-sys-color-surface-container);
border-radius: var(--md-sys-shape-corner-extra-large);
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 20px;
justify-content: center;
min-block-size: 200px;
padding: 24px;
position: relative;
transition: all;
}

.demo-card-heading {
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-medium);
margin: 0;
text-align: center;
transition: all;
}

.demo-card-desc {
color: var(--md-sys-color-on-surface-variant);
font: var(--md-sys-typescale-body-medium);
margin: 0;
text-align: center;
transition: all;
}
</style>

<script type="module" src="./demo.js"></script>
</head>
<body>
<header class="demo-header">
<h1 class="demo-title">Loading</h1>
<p class="demo-subtitle">
Material 3 Expressive short-duration indeterminate loading component
with 7-shape morphing animation.
</p>
</header>

<main class="demo-grid">
<section class="demo-card">
<h2 class="demo-card-heading">Default (Uncontained)</h2>
<md-gb-loading
aria-label="Loading content"></md-gb-loading>
<p class="demo-card-desc">
Primary color active indicator on transparent background.
</p>
</section>

<section class="demo-card">
<h2 class="demo-card-heading">Contained</h2>
<md-gb-loading
contained
aria-label="Loading content"></md-gb-loading>
<p class="demo-card-desc">
On-primary-container active indicator on primary-container pill.
</p>
</section>
</main>
</body>
</html>
95 changes: 95 additions & 0 deletions labs/gb/components/loading/demo/stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import '@material/web/labs/gb/components/loading/md-gb-loading.js';

import {type MaterialStoryInit} from './material-collection.js';
import {adoptStyles} from '@material/web/labs/gb/styles/adopt-styles.js';
import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.cssresult.js';
import {css, html} from 'lit';

adoptStyles(document, [m3Styles]);

/** Knob types for loading stories. */
export interface StoryKnobs {
contained: boolean;
closed: boolean;
ariaLabel: string;
}

const demoStyles = css`
.demo-row {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 32px;
padding: 24px;
transition: all;
}

.demo-card {
align-items: center;
background-color: var(--md-sys-color-surface-container);
border-radius: var(--md-sys-shape-corner-large);
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 16px;
inline-size: 280px;
justify-content: center;
min-block-size: 160px;
padding: 24px;
transition: all;
}

.demo-card-label {
color: var(--md-sys-color-on-surface);
font: var(--md-sys-typescale-title-small);
margin: 0;
transition: all;
}
`;

const playground: MaterialStoryInit<StoryKnobs> = {
name: 'Playground',
styles: [demoStyles],
render({contained, closed, ariaLabel}) {
return html`
<div class="demo-row">
<md-gb-loading
?contained=${contained}
?closed=${closed}
aria-label=${ariaLabel}></md-gb-loading>
</div>
`;
},
};

const configurations: MaterialStoryInit<StoryKnobs> = {
name: 'Configurations',
styles: [demoStyles],
render() {
return html`
<div class="demo-row">
<div class="demo-card">
<p class="demo-card-label">Default (Uncontained)</p>
<md-gb-loading
aria-label="Default loading"></md-gb-loading>
</div>

<div class="demo-card">
<p class="demo-card-label">Contained</p>
<md-gb-loading
contained
aria-label="Contained loading"></md-gb-loading>
</div>
</div>
`;
},
};

/** All loading stories. */
export const stories = [playground, configurations];
Loading
Loading