From 7518dbbc4c41f1c17928c801b73a77641f5d9d4a Mon Sep 17 00:00:00 2001 From: Material Web Team Date: Mon, 10 Aug 2026 14:35:37 -0700 Subject: [PATCH] feat(labs): add toolbar web component suite PiperOrigin-RevId: 962372904 --- .../components/toolbar/_toolbar-tokens.scss | 70 +++++ labs/gb/components/toolbar/demo/demo.ts | 25 ++ labs/gb/components/toolbar/demo/stories.ts | 114 ++++++++ labs/gb/components/toolbar/md-gb-toolbar.ts | 41 +++ .../toolbar/toolbar-button-element.ts | 150 ++++++++++ labs/gb/components/toolbar/toolbar-element.ts | 109 ++++++++ .../toolbar/toolbar-separator-element.ts | 42 +++ labs/gb/components/toolbar/toolbar.scss | 256 ++++++++++++++++++ labs/gb/components/toolbar/toolbar.ts | 85 ++++++ 9 files changed, 892 insertions(+) create mode 100644 labs/gb/components/toolbar/_toolbar-tokens.scss create mode 100644 labs/gb/components/toolbar/demo/demo.ts create mode 100644 labs/gb/components/toolbar/demo/stories.ts create mode 100644 labs/gb/components/toolbar/md-gb-toolbar.ts create mode 100644 labs/gb/components/toolbar/toolbar-button-element.ts create mode 100644 labs/gb/components/toolbar/toolbar-element.ts create mode 100644 labs/gb/components/toolbar/toolbar-separator-element.ts create mode 100644 labs/gb/components/toolbar/toolbar.scss create mode 100644 labs/gb/components/toolbar/toolbar.ts diff --git a/labs/gb/components/toolbar/_toolbar-tokens.scss b/labs/gb/components/toolbar/_toolbar-tokens.scss new file mode 100644 index 0000000000..5589d9c32d --- /dev/null +++ b/labs/gb/components/toolbar/_toolbar-tokens.scss @@ -0,0 +1,70 @@ +// +// Copyright 2026 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +@mixin root { + // LINT.IfChange + --button-container-color: var(--md-sys-color-surface-container); + --container-color: var(--md-sys-color-surface-container); + --container-elevation: var(--md-sys-elevation-shadow-3); + --container-leading-space: var(--md-sys-space-100); + --container-shape: var(--md-sys-shape-corner-full); + --container-spacing: var(--md-sys-space-50); + --container-trailing-space: var(--md-sys-space-100); + --docked-container-elevation: var(--md-sys-elevation-shadow-0); + --docked-container-height: 64px; + --docked-container-leading-space: var(--md-sys-space-200); + --docked-container-max-spacing: 32px; + --docked-container-min-spacing: 4px; + --docked-container-shape: var(--md-sys-shape-corner-none); + --docked-container-trailing-space: var(--md-sys-space-200); + --horizontal-container-height: 64px; + --icon-color: var(--md-sys-color-on-surface-variant); + --label-text-color: var(--md-sys-color-on-surface-variant); + --min-button-size: 40px; + --motion-duration: var(--md-sys-motion-duration-short4); + --motion-easing: var(--md-sys-motion-easing-emphasized); + --selected-button-container-color: var(--md-sys-color-secondary-container); + --selected-icon-color: var(--md-sys-color-on-secondary-container); + --selected-label-text-color: var(--md-sys-color-on-secondary-container); + --separator-color: var(--md-sys-color-outline-variant); + --separator-height: 24px; + --separator-margin: 4px; + --separator-width: 1px; + --vertical-container-width: 64px; + // LINT.ThenChange(toolbar-element.ts) keep in sync with `@cssprop` jsdoc tags +} + +@mixin standard { + --button-container-color: var(--md-sys-color-surface-container); + --container-color: var(--md-sys-color-surface-container); + --icon-color: var(--md-sys-color-on-surface-variant); + --label-text-color: var(--md-sys-color-on-surface-variant); + --selected-button-container-color: var(--md-sys-color-secondary-container); + --selected-icon-color: var(--md-sys-color-on-secondary-container); + --selected-label-text-color: var(--md-sys-color-on-secondary-container); +} + +@mixin vibrant { + --button-container-color: var(--md-sys-color-primary-container); + --container-color: var(--md-sys-color-primary-container); + --icon-color: var(--md-sys-color-on-primary-container); + --label-text-color: var(--md-sys-color-on-primary-container); + --selected-button-container-color: var(--md-sys-color-surface-container); + --selected-icon-color: var(--md-sys-color-on-surface); + --selected-label-text-color: var(--md-sys-color-on-surface); +} + +@mixin docked { + --container-elevation: var(--docked-container-elevation); + --container-leading-space: var(--docked-container-leading-space); + --container-shape: var(--docked-container-shape); + --container-spacing: clamp( + var(--docked-container-min-spacing), + 4vw, + var(--docked-container-max-spacing) + ); + --container-trailing-space: var(--docked-container-trailing-space); + --horizontal-container-height: var(--docked-container-height); +} diff --git a/labs/gb/components/toolbar/demo/demo.ts b/labs/gb/components/toolbar/demo/demo.ts new file mode 100644 index 0000000000..bb39f6e7b5 --- /dev/null +++ b/labs/gb/components/toolbar/demo/demo.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import './material-collection.js'; +import './index.js'; + +import { + KnobTypesToKnobs, + MaterialCollection, + materialInitsToStoryInits, + setUpDemo, +} from './material-collection.js'; + +import {stories, StoryKnobs} from './stories.js'; + +const collection = new MaterialCollection>( + 'Toolbar', +); + +collection.addStories(...materialInitsToStoryInits(stories)); + +setUpDemo(collection); diff --git a/labs/gb/components/toolbar/demo/stories.ts b/labs/gb/components/toolbar/demo/stories.ts new file mode 100644 index 0000000000..6f8a34e708 --- /dev/null +++ b/labs/gb/components/toolbar/demo/stories.ts @@ -0,0 +1,114 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import '@material/web/labs/gb/components/toolbar/md-gb-toolbar.js'; +import '@material/web/labs/gb/styles/icon/md-gb-icon.js'; + +import {MaterialStoryInit} from './material-collection.js'; +import { + type ToolbarColor, + type ToolbarOrientation, +} from '@material/web/labs/gb/components/toolbar/toolbar.js'; +import {adoptStyles} from '@material/web/labs/gb/styles/adopt-styles.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 +import {css, html, nothing} from 'lit'; + +/** Knob types for toolbar stories. */ +export interface StoryKnobs { + color?: ToolbarColor; + orientation?: ToolbarOrientation; + docked: boolean; +} + +adoptStyles(document, [ + m3Styles, + css` + :root { + --md-icon-font: 'Material Symbols Outlined'; + } + `, +]); + +const playground: MaterialStoryInit = { + name: 'Playground', + render(knobs) { + return html` + + + + + + + + + `; + }, +}; + +const vibrant: MaterialStoryInit = { + name: 'Vibrant', + render(knobs) { + return html` + + + + + + + `; + }, +}; + +const docked: MaterialStoryInit = { + name: 'Docked', + render(knobs) { + return html` + + + + + + + `; + }, +}; + +/** Toolbar stories. */ +export const stories = [playground, vibrant, docked]; diff --git a/labs/gb/components/toolbar/md-gb-toolbar.ts b/labs/gb/components/toolbar/md-gb-toolbar.ts new file mode 100644 index 0000000000..c511ae70a0 --- /dev/null +++ b/labs/gb/components/toolbar/md-gb-toolbar.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + ToolbarButtonElement, + ToolbarIconButtonElement, +} from './toolbar-button-element.js'; +import {ToolbarElement} from './toolbar-element.js'; +import {ToolbarSeparatorElement} from './toolbar-separator-element.js'; + +declare global { + interface HTMLElementTagNameMap { + /** A Material Design gBreeze toolbar container custom element. */ + 'md-gb-toolbar': ToolbarElement; + /** A Material Design gBreeze toolbar button custom element. */ + 'md-gb-toolbar-button': ToolbarButtonElement; + /** A Material Design gBreeze toolbar icon button custom element. */ + 'md-gb-toolbar-icon-button': ToolbarIconButtonElement; + /** A Material Design gBreeze toolbar separator custom element. */ + 'md-gb-toolbar-separator': ToolbarSeparatorElement; + } +} + +customElements.define('md-gb-toolbar', ToolbarElement); +customElements.define('md-gb-toolbar-button', ToolbarButtonElement); +customElements.define('md-gb-toolbar-icon-button', ToolbarIconButtonElement); +customElements.define('md-gb-toolbar-separator', ToolbarSeparatorElement); + +export { + ToolbarElement as MdGbToolbar, + ToolbarButtonElement as MdGbToolbarButton, + ToolbarIconButtonElement as MdGbToolbarIconButton, + ToolbarSeparatorElement as MdGbToolbarSeparator, + ToolbarElement as Toolbar, + ToolbarButtonElement as ToolbarButton, + ToolbarIconButtonElement as ToolbarIconButton, + ToolbarSeparatorElement as ToolbarSeparator, +}; diff --git a/labs/gb/components/toolbar/toolbar-button-element.ts b/labs/gb/components/toolbar/toolbar-button-element.ts new file mode 100644 index 0000000000..dee3d9145a --- /dev/null +++ b/labs/gb/components/toolbar/toolbar-button-element.ts @@ -0,0 +1,150 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + css, + CSSResultOrNative, + html, + LitElement, + nothing, +} from 'lit'; +import {property} from 'lit/decorators.js'; + +import focusRingStyles from '../focus/focus-ring.css' with {type: 'css'}; // github-only +// import focusRingStyles from '../focus/focus-ring.cssresult.js'; // google3-only +import rippleStyles from '../ripple/ripple.css' with {type: 'css'}; // github-only +// import rippleStyles from '../ripple/ripple.cssresult.js'; // google3-only +import toolbarStyles from './toolbar.css' with {type: 'css'}; // github-only +// import toolbarStyles from './toolbar.cssresult.js'; // google3-only + +/** + * A Material Design gBreeze toolbar button custom element (`md-gb-toolbar-button`). + * + * @slot - Used to display a text label. + * @slot icon - Used to display a custom icon element. + * @fires {Event} change - Fired when a toggle button's selection state changes. --bubbles + * @fires {InputEvent} input - Fired when a toggle button's selection state changes. --bubbles --composed + * @csspart btn - The button's root container. + */ +export class ToolbarButtonElement extends LitElement { + /** @nocollapse */ + static override shadowRootOptions: ShadowRootInit = { + mode: 'open', + delegatesFocus: true, + }; + + /** @nocollapse */ + static override styles: CSSResultOrNative[] = [ + focusRingStyles, + rippleStyles, + toolbarStyles, + css` + :host { + box-sizing: border-box; + display: inline-flex; + position: relative; + vertical-align: middle; + } + `, + ]; + + /** Whether the toolbar button is selected or active. */ + @property({type: Boolean, reflect: true}) selected = false; + + /** Alias for `selected` property. */ + get active(): boolean { + return this.selected; + } + set active(value: boolean) { + this.selected = value; + } + + /** Whether the button behaves as a toggle switch. */ + @property({type: Boolean}) toggle = false; + + /** Whether the button is disabled. */ + @property({type: Boolean, reflect: true}) disabled = false; + + /** Optional Material Symbol icon string identifier. */ + @property({type: String}) icon = ''; + + /** Optional text label for the button. */ + @property({type: String}) label = ''; + + /** Optional accessible aria-label. */ + @property({attribute: 'aria-label'}) override ariaLabel = ''; + + protected handleClick(event: MouseEvent) { + if (this.disabled) { + event.preventDefault(); + event.stopImmediatePropagation(); + return; + } + + if (this.toggle) { + this.selected = !this.selected; + this.dispatchEvent(new Event('change', {bubbles: true})); + this.dispatchEvent( + new InputEvent('input', {bubbles: true, composed: true}), + ); + } + } + + protected override render() { + const isIconButton = + this.tagName.toLowerCase() === 'md-gb-toolbar-icon-button'; + const hasLabel = !isIconButton; + const accessibleLabel = + this.ariaLabel || this.label || this.icon || undefined; + + return html` + + `; + } +} + +/** + * A Material Design gBreeze toolbar icon button (`md-gb-toolbar-icon-button`). + */ +export class ToolbarIconButtonElement extends ToolbarButtonElement { + protected override render() { + const accessibleLabel = + this.ariaLabel || this.icon || undefined; + + return html` + + `; + } +} diff --git a/labs/gb/components/toolbar/toolbar-element.ts b/labs/gb/components/toolbar/toolbar-element.ts new file mode 100644 index 0000000000..aaa00d3dca --- /dev/null +++ b/labs/gb/components/toolbar/toolbar-element.ts @@ -0,0 +1,109 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + css, + CSSResultOrNative, + html, + LitElement, +} from 'lit'; +import {property} from 'lit/decorators.js'; + +import toolbarStyles from './toolbar.css' with {type: 'css'}; // github-only +// import toolbarStyles from './toolbar.cssresult.js'; // google3-only + +import { + type ToolbarColor, + type ToolbarOrientation, + toolbar, +} from './toolbar.js'; + +/** + * A Material Design gBreeze toolbar component. + * + * @slot - Used to display toolbar action items (buttons, icons, separators). + * @csspart toolbar - The toolbar's root container. + * @cssprop --button-container-color + * @cssprop --container-color + * @cssprop --container-elevation + * @cssprop --container-leading-space + * @cssprop --container-shape + * @cssprop --container-spacing + * @cssprop --container-trailing-space + * @cssprop --docked-container-elevation + * @cssprop --docked-container-height + * @cssprop --docked-container-leading-space + * @cssprop --docked-container-max-spacing + * @cssprop --docked-container-min-spacing + * @cssprop --docked-container-shape + * @cssprop --docked-container-trailing-space + * @cssprop --horizontal-container-height + * @cssprop --icon-color + * @cssprop --label-text-color + * @cssprop --min-button-size + * @cssprop --motion-duration + * @cssprop --motion-easing + * @cssprop --selected-button-container-color + * @cssprop --selected-icon-color + * @cssprop --selected-label-text-color + * @cssprop --separator-color + * @cssprop --separator-height + * @cssprop --separator-margin + * @cssprop --separator-width + * @cssprop --vertical-container-width + */ +export class ToolbarElement extends LitElement { + /** @nocollapse */ + static override styles: CSSResultOrNative[] = [ + toolbarStyles, + css` + :host { + display: inline-flex; + } + :host([docked]) { + display: flex; + width: 100%; + } + :host([docked]) [part='toolbar'] { + width: 100%; + } + `, + ]; + + protected readonly internals: ElementInternals = this.attachInternals(); + + constructor() { + super(); + this.internals.role = 'toolbar'; + } + + /** The color scheme of the toolbar. */ + @property({reflect: true}) color: ToolbarColor = 'standard'; + + /** The layout orientation. */ + @property({reflect: true}) orientation: ToolbarOrientation = 'horizontal'; + + /** Whether the toolbar is docked across full width. */ + @property({type: Boolean, reflect: true}) docked = false; + + protected override willUpdate() { + this.internals.ariaOrientation = this.orientation; + } + + protected override render() { + return html` +
+ +
+ `; + } +} diff --git a/labs/gb/components/toolbar/toolbar-separator-element.ts b/labs/gb/components/toolbar/toolbar-separator-element.ts new file mode 100644 index 0000000000..6cc7d05c86 --- /dev/null +++ b/labs/gb/components/toolbar/toolbar-separator-element.ts @@ -0,0 +1,42 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + css, + CSSResultOrNative, + html, + LitElement, +} from 'lit'; + +import toolbarStyles from './toolbar.css' with {type: 'css'}; // github-only +// import toolbarStyles from './toolbar.cssresult.js'; // google3-only + +/** + * A Material Design gBreeze toolbar separator custom element (`md-gb-toolbar-separator`). + * + * @csspart separator - The separator element. + */ +export class ToolbarSeparatorElement extends LitElement { + /** @nocollapse */ + static override styles: CSSResultOrNative[] = [ + toolbarStyles, + css` + :host { + align-self: center; + display: inline-flex; + } + `, + ]; + + protected override render() { + return html` + + `; + } +} diff --git a/labs/gb/components/toolbar/toolbar.scss b/labs/gb/components/toolbar/toolbar.scss new file mode 100644 index 0000000000..64998e6ac7 --- /dev/null +++ b/labs/gb/components/toolbar/toolbar.scss @@ -0,0 +1,256 @@ +/*! + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +// go/keep-sorted start by_regex='(.+) prefix_order=sass: +@use 'toolbar-tokens'; +// go/keep-sorted end + +@layer md.sys, md.comp.icon-btn; + +@layer md.comp.toolbar { + :host { + @include toolbar-tokens.root; + + display: inline-flex; + } + + :host([color='standard']) { + @include toolbar-tokens.standard; + } + + :host([color='vibrant']) { + @include toolbar-tokens.vibrant; + } + + :host([docked]) { + @include toolbar-tokens.docked; + + display: flex; + width: 100%; + } + + :host([docked]) [part='toolbar'], + :host([docked]) .toolbar { + gap: var(--container-spacing); + height: var(--docked-container-height); + justify-content: center; + padding-inline: var(--docked-container-leading-space) + var(--docked-container-trailing-space); + width: 100%; + } + + :host([orientation='vertical']) [part='toolbar'], + :host([orientation='vertical']) .toolbar { + flex-direction: column; + height: fit-content; + padding-block: var(--container-leading-space) + var(--container-trailing-space); + padding-inline: var(--container-leading-space); + width: var(--vertical-container-width); + } + + :host([orientation='vertical']) .toolbar-separator { + height: var(--separator-width); + margin-block: var(--separator-margin); + margin-inline: 0; + width: var(--separator-height); + } + + md-gb-toolbar { + @include toolbar-tokens.root; + + &[color='standard'] { + @include toolbar-tokens.standard; + } + + &[color='vibrant'] { + @include toolbar-tokens.vibrant; + } + + &[docked] { + @include toolbar-tokens.docked; + + display: flex; + width: 100%; + } + } + + .toolbar { + @include toolbar-tokens.root; + + &.toolbar-standard { + @include toolbar-tokens.standard; + } + + &.toolbar-vibrant { + @include toolbar-tokens.vibrant; + } + + &.toolbar-docked { + @include toolbar-tokens.docked; + } + + align-items: center; + background-color: var(--container-color); + border: none; + border-radius: var(--container-shape); + box-shadow: var(--container-elevation); + box-sizing: border-box; + display: flex; + gap: var(--container-spacing); + position: relative; + transition: all var(--motion-duration) var(--motion-easing); + user-select: none; + + &.toolbar-horizontal:not(.toolbar-docked) { + flex-direction: row; + height: var(--horizontal-container-height); + padding-block: var(--container-leading-space); + padding-inline: var(--container-leading-space) + var(--container-trailing-space); + width: fit-content; + } + + &.toolbar-vertical { + flex-direction: column; + height: fit-content; + padding-block: var(--container-leading-space) + var(--container-trailing-space); + padding-inline: var(--container-leading-space); + width: var(--vertical-container-width); + } + + &.toolbar-docked { + gap: var(--container-spacing); + height: var(--docked-container-height); + justify-content: center; + padding-inline: var(--docked-container-leading-space) + var(--docked-container-trailing-space); + width: 100%; + } + + .toolbar-btn { + align-items: center; + appearance: none; + background-color: var(--button-container-color); + border: 0; + border-radius: var(--md-sys-shape-corner-full); + box-sizing: border-box; + color: var(--label-text-color); + cursor: pointer; + display: inline-flex; + font: var(--md-sys-typescale-label-lg); + font-variation-settings: var(--md-sys-typescale-label-lg-axes); + height: var(--min-button-size); + justify-content: center; + letter-spacing: var(--md-sys-typescale-label-lg-tracking); + min-height: var(--min-button-size); + outline: none; + position: relative; + transition: all var(--motion-duration) var(--motion-easing); + user-select: none; + vertical-align: middle; + + &.icon-only { + flex-shrink: 0; + min-width: var(--min-button-size); + padding: 0; + width: var(--min-button-size); + } + + &.has-label { + flex-shrink: 0; + gap: 8px; + min-width: fit-content; + padding-inline: 16px; + white-space: nowrap; + width: auto; + } + + &:disabled { + cursor: default; + opacity: 0.38; + } + + .toolbar-btn-icon, + .md-icon, + ::slotted(md-icon), + ::slotted(.md-icon) { + align-items: center; + color: var(--icon-color); + display: inline-flex; + font-size: 24px; + height: 24px; + justify-content: center; + width: 24px; + } + + .toolbar-btn-label { + align-items: center; + display: inline-flex; + } + + &.selected, + &[aria-pressed='true'] { + background-color: var(--selected-button-container-color); + color: var(--selected-label-text-color); + font-variation-settings: 'FILL' 1; + + .toolbar-btn-icon, + .md-icon, + ::slotted(md-icon), + ::slotted(.md-icon) { + color: var(--selected-icon-color); + --md-icon-color: var(--selected-icon-color); + --md-icon-fill: 1; + font-variation-settings: 'FILL' 1; + } + } + } + + .toolbar-icon-btn { + --icon-btn-container-height: var(--min-button-size); + --icon-btn-container-shape: var(--md-sys-shape-corner-full); + --icon-btn-container-width: var(--min-button-size); + --icon-btn-disabled-icon-color: var(--md-sys-color-on-surface); + --icon-btn-focus-icon-color: var(--icon-color); + --icon-btn-hover-icon-color: var(--icon-color); + --icon-btn-icon-color: var(--icon-color); + --icon-btn-icon-size: 24px; + --icon-btn-pressed-icon-color: var(--icon-color); + --icon-btn-selected-container-color: var( + --selected-button-container-color + ); + --icon-btn-toggle-focus-icon-color: var(--icon-color); + --icon-btn-toggle-hover-icon-color: var(--icon-color); + --icon-btn-toggle-icon-color: var(--icon-color); + --icon-btn-toggle-pressed-icon-color: var(--icon-color); + --icon-btn-toggle-selected-focus-icon-color: var(--selected-icon-color); + --icon-btn-toggle-selected-hover-icon-color: var(--selected-icon-color); + --icon-btn-toggle-selected-icon-color: var(--selected-icon-color); + --icon-btn-toggle-selected-pressed-icon-color: var(--selected-icon-color); + --icon-btn-toggle-unselected-focus-icon-color: var(--icon-color); + --icon-btn-toggle-unselected-hover-icon-color: var(--icon-color); + --icon-btn-toggle-unselected-icon-color: var(--icon-color); + --icon-btn-toggle-unselected-pressed-icon-color: var(--icon-color); + --icon-btn-unselected-container-color: var(--button-container-color); + color: var(--icon-color); + } + + .toolbar-separator { + background-color: var(--separator-color); + height: var(--separator-height); + margin-inline: var(--separator-margin); + width: var(--separator-width); + } + + &.toolbar-vertical .toolbar-separator { + height: var(--separator-width); + margin-block: var(--separator-margin); + margin-inline: 0; + width: var(--separator-height); + } + } +} diff --git a/labs/gb/components/toolbar/toolbar.ts b/labs/gb/components/toolbar/toolbar.ts new file mode 100644 index 0000000000..b5fb6b3b68 --- /dev/null +++ b/labs/gb/components/toolbar/toolbar.ts @@ -0,0 +1,85 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {type ClassInfo} from 'lit/directives/class-map.js'; +import {createClassMapDirective} from '../shared/directives.js'; + +/** Toolbar color configuration types. */ +export type ToolbarColor = 'standard' | 'vibrant'; + +/** Toolbar color configurations. */ +export const TOOLBAR_COLORS = { + standard: 'standard', + vibrant: 'vibrant', +} as const; + +/** Toolbar orientation configuration types. */ +export type ToolbarOrientation = 'horizontal' | 'vertical'; + +/** Toolbar orientation configurations. */ +export const TOOLBAR_ORIENTATIONS = { + horizontal: 'horizontal', + vertical: 'vertical', +} as const; + +/** Toolbar classes constants. */ +export const TOOLBAR_CLASSES = { + toolbar: 'toolbar', + toolbarStandard: 'toolbar-standard', + toolbarVibrant: 'toolbar-vibrant', + toolbarHorizontal: 'toolbar-horizontal', + toolbarVertical: 'toolbar-vertical', + toolbarDocked: 'toolbar-docked', + separator: 'toolbar-separator', +} as const; + +/** The state provided to the `toolbarClasses()` function. */ +export interface ToolbarClassesState { + /** The color scheme of the toolbar. */ + color?: ToolbarColor; + /** The layout orientation of the toolbar. */ + orientation?: ToolbarOrientation; + /** Whether the toolbar is docked across the container width. */ + docked?: boolean; +} + +/** + * Returns toolbar classes to apply based on the given state. + * + * @param state The state of the toolbar. + * @return An object of class names and truthy values if they apply. + */ +export function toolbarClasses({ + color = TOOLBAR_COLORS.standard, + orientation = TOOLBAR_ORIENTATIONS.horizontal, + docked = false, +}: ToolbarClassesState = {}): ClassInfo { + return { + [TOOLBAR_CLASSES.toolbar]: true, + [TOOLBAR_CLASSES.toolbarStandard]: + color === TOOLBAR_COLORS.standard || !color, + [TOOLBAR_CLASSES.toolbarVibrant]: color === TOOLBAR_COLORS.vibrant, + [TOOLBAR_CLASSES.toolbarHorizontal]: + orientation === TOOLBAR_ORIENTATIONS.horizontal || !orientation, + [TOOLBAR_CLASSES.toolbarVertical]: + orientation === TOOLBAR_ORIENTATIONS.vertical, + [TOOLBAR_CLASSES.toolbarDocked]: docked, + }; +} + +/** + * Lit directive for element class styling. + * + * @example + * ```ts + * html`
+ * + *
`; + * ``` + */ +export const toolbar = createClassMapDirective({ + getClasses: toolbarClasses, +});