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

// Auto-generated by cascade_tokens
// Add `// EDIT:` comments for manual adjustments.
//
// Token sets: ['md.comp.snackbar']
// Version: 40.0.0
// --prefix=md.comp.snackbar
// --exclude="state-layer,container.shadow-color"

@mixin root {
// LINT.IfChange
// md.comp.snackbar.action.label-text.color
--action-label-text-color: var(--md-sys-color-inverse-primary);
// ┌ md.comp.snackbar.action.label-text.font
// ├ md.comp.snackbar.action.label-text.line-height
// ├ md.comp.snackbar.action.label-text.size
// ├ md.comp.snackbar.action.label-text.weight
--action-label-text: var(--md-sys-typescale-label-lg);
// ┌┄ md.comp.snackbar.action.label-text
--action-label-text-axes: var(--md-sys-typescale-label-lg-axes);
// md.comp.snackbar.action.label-text.tracking
--action-label-text-tracking: var(--md-sys-typescale-label-lg-tracking);
// md.comp.snackbar.container.color
--container-color: var(--md-sys-color-inverse-surface);
// md.comp.snackbar.container.elevation
--container-elevation: var(--md-sys-elevation-shadow-3);
// md.comp.snackbar.container.shape
--container-shape: var(--md-sys-shape-corner-xs);
// md.comp.snackbar.icon.color
--icon-color: var(--md-sys-color-inverse-on-surface);
// md.comp.snackbar.icon.size
--icon-size: 24px;
// ┌ md.comp.snackbar.supporting-text.font
// ├ md.comp.snackbar.supporting-text.line-height
// ├ md.comp.snackbar.supporting-text.size
// ├ md.comp.snackbar.supporting-text.weight
--supporting-text: var(--md-sys-typescale-body-md);
// ┌┄ md.comp.snackbar.supporting-text
--supporting-text-axes: var(--md-sys-typescale-body-md-axes);
// md.comp.snackbar.supporting-text.color
--supporting-text-color: var(--md-sys-color-inverse-on-surface);
// md.comp.snackbar.supporting-text.tracking
--supporting-text-tracking: var(--md-sys-typescale-body-md-tracking);
// EDIT: No upstream Spec token.
--min-width: 344px;
// EDIT: No upstream Spec token.
--max-width: 672px;
// EDIT: No upstream Spec token.
--min-height: 48px;
// EDIT: No upstream Spec token.
--bottom-offset: 16px;
// EDIT: No upstream Spec token.
--position: absolute;
// EDIT: No upstream Spec token.
--z-index: 1000;
// LINT.ThenChange(snackbar-element.ts) keep in sync with `@cssprop` jsdoc tags
}

// Inherits every `root` token from the parent. `md-gb-snackbar` includes
// `root` on its host and this on the `.snackbar` inside its shadow root, so
// token values set on the host element still apply.
@mixin inherit {
--action-label-text-color: inherit;
--action-label-text: inherit;
--action-label-text-axes: inherit;
--action-label-text-tracking: inherit;
--container-color: inherit;
--container-elevation: inherit;
--container-shape: inherit;
--icon-color: inherit;
--icon-size: inherit;
--supporting-text: inherit;
--supporting-text-axes: inherit;
--supporting-text-color: inherit;
--supporting-text-tracking: inherit;
--min-width: inherit;
--max-width: inherit;
--min-height: inherit;
--bottom-offset: inherit;
--position: inherit;
--z-index: inherit;
}
56 changes: 56 additions & 0 deletions labs/gb/components/snackbar/demo/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* @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,
selectDropdown,
textInput,
} from './index.js';

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

const collection = new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>(
'Snackbar',
[
new Knob('timeoutMs', {
ui: selectDropdown({
options: [
{value: '-1', label: '-1 (Indeterminate)'},
{value: '5000', label: '5000 ms'},
{value: '3000', label: '3000 ms'},
{value: '10000', label: '10000 ms'},
],
}),
defaultValue: '-1',
}),
new Knob('message', {
ui: textInput(),
defaultValue: 'Your message has been sent.',
}),
new Knob('action', {
ui: textInput(),
defaultValue: 'Undo',
}),
new Knob('hasCloseButton', {
ui: boolInput(),
defaultValue: true,
}),
],
);

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

setUpDemo(collection, {fonts: 'roboto', icons: 'material-symbols'});
212 changes: 212 additions & 0 deletions labs/gb/components/snackbar/demo/stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

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

import {type MaterialStoryInit} from './material-collection.js';
import {type SnackbarElement} from '@material/web/labs/gb/components/snackbar/snackbar-element.js';
import {adoptStyles} from '@material/web/labs/gb/styles/adopt-styles.js';
import {css, html, nothing, type TemplateResult} from 'lit';
import {createRef, ref} from 'lit/directives/ref.js';

import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.css' with {type: 'css'}; // github-only
// import {styles as m3Styles} from '@material/web/labs/gb/styles/m3.cssresult.js'; // google3-only

/** Knob types for snackbar stories. */
export interface StoryKnobs {
timeoutMs: string;
message: string;
action: string;
hasCloseButton: boolean;
}

adoptStyles(document, [m3Styles]);

const styles = css`
.demo-container {
display: flex;
flex-direction: column;
gap: 32px;
padding: 24px;
box-sizing: border-box;
}
.demo-section {
display: flex;
flex-direction: column;
gap: 16px;
h2 {
font: var(--md-sys-typescale-headline-sm);
letter-spacing: var(--md-sys-typescale-headline-sm-tracking);
margin: 0;
color: var(--md-sys-color-on-surface);
}
}
.static-grid {
display: grid;
grid-template-columns: repeat(2, max-content);
gap: 24px;
}
/*
* Each cell is a container the snackbar anchors to: it sits --bottom-offset
* (16px) above the cell's bottom edge and, below the 600px breakpoint, 16px
* in from each side. The fixed height fits the tallest example (a wrapped
* action row) with clear space above, and lines up every snackbar's bottom
* edge across the grid.
*/
.cell-stage {
width: 376px;
block-size: 176px;
outline: 1px dashed var(--md-sys-color-outline-variant);
border: none;
border-radius: 8px;
background-color: var(--md-sys-color-surface-container-low);
box-sizing: border-box;
position: relative;
}
.interactive-stage {
resize: horizontal;
overflow: hidden;
position: relative;
width: 100%;
min-width: 360px;
min-height: 240px;
border: 2px dashed var(--md-sys-color-outline-variant);
border-radius: 16px;
background-color: var(--md-sys-color-surface-container-low);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 24px;
}
.demo-controls {
display: flex;
gap: 12px;
margin-block-end: 16px;
}
.demo-hint {
font: var(--md-sys-typescale-body-md);
letter-spacing: var(--md-sys-typescale-body-md-tracking);
color: var(--md-sys-color-on-surface-variant);
text-align: center;
margin: 0;
}
`;

interface StaticExample {
/**
* A string is passed as `label-text`; a template is slotted, so two-line
* labels can break where Figma breaks them.
*/
label: string | TemplateResult;
action?: string;
close?: boolean;
}

// Figma 44:43717 "Examples", row-major: without close on the left, with close
// on the right.
const staticExamples: StaticExample[] = [
{label: 'Single-line snackbar'},
{label: 'Single-line snackbar with close affordance', close: true},
{label: 'Single-line snackbar with action', action: 'Action'},
{label: 'Single-line snackbar with action', action: 'Action', close: true},
{label: html`Two-line snackbar<br />without action`},
{label: html`Two-line snackbar with<br />close affordance`, close: true},
{label: html`Two-line snackbar<br />with action`, action: 'Action'},
{
label: html`Two-line snackbar with longer<br />action and close affordance`,
action: 'Action',
close: true,
},
{
label: html`Two-line snackbar with<br />longer action`,
action: 'Longer Action',
},
{
label: html`Two-line snackbar with longer<br />action and close affordance`,
action: 'Longer Action',
close: true,
},
];

const playground: MaterialStoryInit<StoryKnobs> = {
name: 'Playground',
styles,
render(knobs) {
const snackbarRef = createRef<SnackbarElement>();

return html`
<div class="demo-container">
<section class="demo-section">
<h2>Interactive</h2>
<div class="demo-controls">
<md-gb-button
color="filled"
@click=${() => snackbarRef.value?.show()}>
Show Snackbar
</md-gb-button>
<md-gb-button
color="outlined"
@click=${() => snackbarRef.value?.close()}>
Close Snackbar
</md-gb-button>
</div>
<div class="interactive-stage">
<p class="demo-hint">
Drag the corner to resize: below a 600px container width the
snackbar uses the compact layout. Use Show/Close to replay it.
</p>
<md-gb-snackbar
${ref(snackbarRef)}
open
.labelText=${knobs.message}
.timeoutMs=${Number(knobs.timeoutMs)}
.actionLabel=${knobs.action}
?has-close-button=${knobs.hasCloseButton}>
</md-gb-snackbar>
</div>
</section>
<section class="demo-section">
<h2>Static examples</h2>
<div class="static-grid">
${staticExamples.map(
({label, action = '', close = false}) => html`
<div class="cell-stage">
<md-gb-snackbar
open
timeout-ms="-1"
?has-close-button=${close}
label-text=${typeof label === 'string' ? label : ''}
action-label=${action}
@close=${(e: Event) => {
e.preventDefault();
}}
>${typeof label === 'string'
? nothing
: label}</md-gb-snackbar
>
</div>
`,
)}
</div>
</section>
</div>
`;
},
};

/** Snackbar demo stories. */
export const stories = [playground];
Loading
Loading