From 729489ceed83412604317620c08be8c5f62e0f87 Mon Sep 17 00:00:00 2001 From: Daniel Fiala Date: Mon, 29 Sep 2025 07:58:47 +0200 Subject: [PATCH] feat(top-app-bar): Move topappbar component --- all.ts | 4 + docs/components/images/topappbar/theming.png | Bin 0 -> 5679 bytes docs/components/top-app-bar.md | 385 ++++++++++++++++++ tokens/_index.scss | 4 + tokens/_md-comp-top-app-bar-large.scss | 23 ++ tokens/_md-comp-top-app-bar-medium.scss | 24 ++ .../_md-comp-top-app-bar-small-centered.scss | 23 ++ tokens/_md-comp-top-app-bar-small.scss | 23 ++ topappbar/center-aligned-top-app-bar.ts | 21 + topappbar/demo/demo.ts | 25 ++ topappbar/demo/project.json | 9 + topappbar/demo/stories.ts | 91 +++++ .../internal/_center-aligned-top-app-bar.scss | 15 + topappbar/internal/_large-top-app-bar.scss | 22 + topappbar/internal/_medium-top-app-bar.scss | 22 + topappbar/internal/_shared.scss | 74 ++++ topappbar/internal/_small-top-app-bar.scss | 10 + .../center-aligned-top-app-bar-styles.scss | 3 + topappbar/internal/expanded-top-app-bar.ts | 49 +++ .../internal/large-top-app-bar-styles.scss | 3 + .../internal/medium-top-app-bar-styles.scss | 3 + topappbar/internal/shared-styles.scss | 3 + .../internal/small-top-app-bar-styles.scss | 3 + topappbar/internal/top-app-bar.ts | 49 +++ topappbar/large-top-app-bar.ts | 18 + topappbar/medium-top-app-bar.ts | 18 + topappbar/small-top-app-bar.ts | 18 + 27 files changed, 942 insertions(+) create mode 100644 docs/components/images/topappbar/theming.png create mode 100644 docs/components/top-app-bar.md create mode 100644 tokens/_md-comp-top-app-bar-large.scss create mode 100644 tokens/_md-comp-top-app-bar-medium.scss create mode 100644 tokens/_md-comp-top-app-bar-small-centered.scss create mode 100644 tokens/_md-comp-top-app-bar-small.scss create mode 100644 topappbar/center-aligned-top-app-bar.ts create mode 100644 topappbar/demo/demo.ts create mode 100644 topappbar/demo/project.json create mode 100644 topappbar/demo/stories.ts create mode 100644 topappbar/internal/_center-aligned-top-app-bar.scss create mode 100644 topappbar/internal/_large-top-app-bar.scss create mode 100644 topappbar/internal/_medium-top-app-bar.scss create mode 100644 topappbar/internal/_shared.scss create mode 100644 topappbar/internal/_small-top-app-bar.scss create mode 100644 topappbar/internal/center-aligned-top-app-bar-styles.scss create mode 100644 topappbar/internal/expanded-top-app-bar.ts create mode 100644 topappbar/internal/large-top-app-bar-styles.scss create mode 100644 topappbar/internal/medium-top-app-bar-styles.scss create mode 100644 topappbar/internal/shared-styles.scss create mode 100644 topappbar/internal/small-top-app-bar-styles.scss create mode 100644 topappbar/internal/top-app-bar.ts create mode 100644 topappbar/large-top-app-bar.ts create mode 100644 topappbar/medium-top-app-bar.ts create mode 100644 topappbar/small-top-app-bar.ts diff --git a/all.ts b/all.ts index 617e41970f..0a7e15a6a6 100644 --- a/all.ts +++ b/all.ts @@ -104,5 +104,9 @@ export * from './tabs/secondary-tab.js'; export * from './tabs/tabs.js'; export * from './textfield/filled-text-field.js'; export * from './textfield/outlined-text-field.js'; +export * from './topappbar/center-aligned-top-app-bar.js'; +export * from './topappbar/large-top-app-bar.js'; +export * from './topappbar/medium-top-app-bar.js'; +export * from './topappbar/small-top-app-bar.js'; // go/keep-sorted end // LINT.ThenChange(:imports) diff --git a/docs/components/images/topappbar/theming.png b/docs/components/images/topappbar/theming.png new file mode 100644 index 0000000000000000000000000000000000000000..990921f04d68e28670f580ce0e320517aeec750a GIT binary patch literal 5679 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 d)a9cSS!ekc&s^%Gh-m + + + + +# Top App Bar + + + + + + + + + + +**This documentation is fully rendered on the +[Material Web Additions catalog](https://material-web-additions.maicol07.it/components/top-app-bar/).** + + + + +[Top app bars](https://m3.material.io/components/top-app-bar) display navigation, actions, and text at the top of a screen. + + + + + + + +* [Design article](https://m3.material.io/components/top-app-bar) +* [API Documentation](#api) +* [Source code](https://github.com/maicol07/material-web-additions/tree/main/top-app-bar) + + + + + + + + +## Types + + + +![Top app bar types](https://firebasestorage.googleapis.com/v0/b/design-spec/o/projects%2Fgoogle-material-3%2Fimages%2Flz77955k-1.png?alt=media&token=7bc97a43-321a-4d2c-a232-9a5e5ae1463b "Center-aligned, small, medium and large top app bar types.") + + + + + + + + +1. [Center-aligned](#center-aligned) +2. [Small](#small) +3. [Medium](#medium) +4. [Large](#large) + +## Usage + +### Center-aligned +A center-aligned top app bar is a top app bar with the title centered. + +```html + + + menu + +
Title Large
+ + account_circle + +
+``` + +### Small +A small top app bar is a top app bar with a title and up to 3 trailing icons. + +```html + + + arrow_back + +
Title Large
+
+ + attach_file + + + event + + + more_vert + +
+
+``` + +### Medium +A medium top app bar is a top app bar with a title and up to 3 trailing icons. + +```html + + + arrow_back + +
Headline Small
+
+ + attach_file + + + event + + + more_vert + +
+
+``` + +### Large +A large top app bar is a top app bar with an headline and up to 3 trailing icons. + +```html + + + arrow_back + +
Headline Small
+
+ + attach_file + + + event + + + more_vert + +
+
+``` + +## Theming + +Top app bars supports [Material theming](https://material-web.dev/theming/material-theming/) and can be customized +in terms of color, typography, and shape. + +### Tokens +| Token | Default value | +|------------------------------------------|------------------------------| +| `--md-$type-top-app-bar-container-color` | `--md-sys-color-surface` | +| `--md-$type-top-app-bar-container-shape` | `--md-sys-shape-corner-none` | + +* [All tokens](https://github.com/maicol07/material-web-additions/blob/main/tokens/_md-comp-top-app-bar-small.scss) + + +### Example + + + +![Image of a small top app bar with a different theme applied](images/top-app-bar/theming.png "Small top app bar theming example.") + + + + + + + + +```html + + + + + arrow_back + +
Headline Small
+
+ + attach_file + + + event + + + more_vert + +
+
+``` + + + +## API + + +### MdCenterAlignedTopAppBar <md-center-aligned-top-app-bar> + +#### Properties + + + +Property | Attribute | Type | Default | Description +--- | --- | --- | --- | --- +`sticky` | `sticky` | `boolean` | `false` | Whether the top app bar is sticky. + + + +### MdLargeTopAppBar <md-large-top-app-bar> + +#### Properties + + + +Property | Attribute | Type | Default | Description +--- | --- | --- | --- | --- +`sticky` | `sticky` | `boolean` | `false` | Whether the top app bar is sticky. + + + +### MdMediumTopAppBar <md-medium-top-app-bar> + +#### Properties + + + +Property | Attribute | Type | Default | Description +--- | --- | --- | --- | --- +`sticky` | `sticky` | `boolean` | `false` | Whether the top app bar is sticky. + + + +### MdSmallTopAppBar <md-small-top-app-bar> + +#### Properties + + + +Property | Attribute | Type | Default | Description +--- | --- | --- | --- | --- +`sticky` | `sticky` | `boolean` | `false` | Whether the top app bar is sticky. + + + + diff --git a/tokens/_index.scss b/tokens/_index.scss index 27b16732d7..32d2b4c904 100644 --- a/tokens/_index.scss +++ b/tokens/_index.scss @@ -55,6 +55,10 @@ @forward './md-comp-switch' as md-comp-switch-*; @forward './md-comp-test-table' as md-comp-test-table-*; @forward './md-comp-text-button' as md-comp-text-button-*; +@forward './md-comp-top-app-bar-large' as md-comp-top-app-bar-large-*; +@forward './md-comp-top-app-bar-medium' as md-comp-top-app-bar-medium-*; +@forward './md-comp-top-app-bar-small' as md-comp-top-app-bar-small-*; +@forward './md-comp-top-app-bar-small-centered' as md-comp-top-app-bar-small-centered-*; @forward './md-ref-palette' as md-ref-palette-*; @forward './md-ref-typeface' as md-ref-typeface-*; @forward './md-sys-color' as md-sys-color-*; diff --git a/tokens/_md-comp-top-app-bar-large.scss b/tokens/_md-comp-top-app-bar-large.scss new file mode 100644 index 0000000000..d16e397346 --- /dev/null +++ b/tokens/_md-comp-top-app-bar-large.scss @@ -0,0 +1,23 @@ +// +// Copyright 2023 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +// go/keep-sorted start +@use './md-sys-color'; +@use './md-sys-elevation'; +@use './md-sys-shape'; +@use './md-sys-typescale'; +@use './v0_192/md-comp-top-app-bar-large'; +// go/keep-sorted end + +$_default: ( + 'md-sys-color': md-sys-color.values-light(), + 'md-sys-elevation': md-sys-elevation.values(), + 'md-sys-shape': md-sys-shape.values(), + 'md-sys-typescale': md-sys-typescale.values(), +); + +@function values($deps: $_default, $exclude-hardcoded-values: false) { + @return md-comp-top-app-bar-large.values($deps, $exclude-hardcoded-values); +} diff --git a/tokens/_md-comp-top-app-bar-medium.scss b/tokens/_md-comp-top-app-bar-medium.scss new file mode 100644 index 0000000000..6b84722516 --- /dev/null +++ b/tokens/_md-comp-top-app-bar-medium.scss @@ -0,0 +1,24 @@ +// +// Copyright 2023 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +// go/keep-sorted start +@use './md-sys-color'; +@use './md-sys-elevation'; +@use './md-sys-shape'; +@use './md-sys-typescale'; +@use './v0_192/md-comp-top-app-bar-medium'; +// go/keep-sorted end + + +$_default: ( + 'md-sys-color': md-sys-color.values-light(), + 'md-sys-elevation': md-sys-elevation.values(), + 'md-sys-shape': md-sys-shape.values(), + 'md-sys-typescale': md-sys-typescale.values(), +); + +@function values($deps: $_default, $exclude-hardcoded-values: false) { + @return md-comp-top-app-bar-medium.values($deps, $exclude-hardcoded-values); +} diff --git a/tokens/_md-comp-top-app-bar-small-centered.scss b/tokens/_md-comp-top-app-bar-small-centered.scss new file mode 100644 index 0000000000..059b9e4ec8 --- /dev/null +++ b/tokens/_md-comp-top-app-bar-small-centered.scss @@ -0,0 +1,23 @@ +// +// Copyright 2023 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +// go/keep-sorted start +@use './md-sys-color'; +@use './md-sys-elevation'; +@use './md-sys-shape'; +@use './md-sys-typescale'; +@use './v0_192/md-comp-top-app-bar-small-centered'; +// go/keep-sorted end + +$_default: ( + 'md-sys-color': md-sys-color.values-light(), + 'md-sys-elevation': md-sys-elevation.values(), + 'md-sys-shape': md-sys-shape.values(), + 'md-sys-typescale': md-sys-typescale.values(), +); + +@function values($deps: $_default, $exclude-hardcoded-values: false) { + @return md-comp-top-app-bar-small-centered.values($deps, $exclude-hardcoded-values); +} diff --git a/tokens/_md-comp-top-app-bar-small.scss b/tokens/_md-comp-top-app-bar-small.scss new file mode 100644 index 0000000000..774dcf53e5 --- /dev/null +++ b/tokens/_md-comp-top-app-bar-small.scss @@ -0,0 +1,23 @@ +// +// Copyright 2023 Google LLC +// SPDX-License-Identifier: Apache-2.0 +// + +// go/keep-sorted start +@use './md-sys-color'; +@use './md-sys-elevation'; +@use './md-sys-shape'; +@use './md-sys-typescale'; +@use './v0_192/md-comp-top-app-bar-small'; +// go/keep-sorted end + +$_default: ( + 'md-sys-color': md-sys-color.values-light(), + 'md-sys-elevation': md-sys-elevation.values(), + 'md-sys-shape': md-sys-shape.values(), + 'md-sys-typescale': md-sys-typescale.values(), +); + +@function values($deps: $_default, $exclude-hardcoded-values: false) { + @return md-comp-top-app-bar-small.values($deps, $exclude-hardcoded-values); +} diff --git a/topappbar/center-aligned-top-app-bar.ts b/topappbar/center-aligned-top-app-bar.ts new file mode 100644 index 0000000000..bfbc4942bd --- /dev/null +++ b/topappbar/center-aligned-top-app-bar.ts @@ -0,0 +1,21 @@ +import {TopAppBar} from './internal/top-app-bar.js'; + +import {CSSResultOrNative} from 'lit'; +import {customElement} from 'lit/decorators.js'; + +import {styles as sharedStyles} from './internal/shared-styles.js'; +import {styles} from './internal/center-aligned-top-app-bar-styles.js'; + +declare global { + interface HTMLElementTagNameMap { + 'md-center-aligned-top-app-bar': MdCenterAlignedTopAppBar; + } +} + +/** + * @deprecated Due to new M3 expressive design update + */ +@customElement('md-center-aligned-top-app-bar') +export class MdCenterAlignedTopAppBar extends TopAppBar { + static override styles: CSSResultOrNative[] = [sharedStyles, styles]; +} diff --git a/topappbar/demo/demo.ts b/topappbar/demo/demo.ts new file mode 100644 index 0000000000..edc6dd81de --- /dev/null +++ b/topappbar/demo/demo.ts @@ -0,0 +1,25 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import '~catalog/stories/index.js'; +import '~catalog/stories/material-collection.js'; + +import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo} from '~catalog/stories/material-collection.js'; +import {boolInput, Knob} from '~catalog/stories/index.js'; + +import {stories, StoryKnobs} from './stories.js'; + +const collection = + new MaterialCollection>('Top app bar', [ + new Knob('sticky', { + ui: boolInput(), + defaultValue: false + }) + ]); + +collection.addStories(...materialInitsToStoryInits(stories)); + +setUpDemo(collection, {fonts: 'roboto', icons: 'material-symbols'}); diff --git a/topappbar/demo/project.json b/topappbar/demo/project.json new file mode 100644 index 0000000000..92f3e79587 --- /dev/null +++ b/topappbar/demo/project.json @@ -0,0 +1,9 @@ +{ + "extends": "/assets/stories/base.json", + "files": { + "demo.ts": { + "hidden": true + }, + "stories.ts": {} + } +} diff --git a/topappbar/demo/stories.ts b/topappbar/demo/stories.ts new file mode 100644 index 0000000000..7ab88cbbca --- /dev/null +++ b/topappbar/demo/stories.ts @@ -0,0 +1,91 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import '@material/web/icon/icon.js'; +import '@material/web/top-app-bar/center-aligned-top-app-bar.js'; +import '@material/web/top-app-bar/small-top-app-bar.js'; +import '@material/web/top-app-bar/medium-top-app-bar.js'; +import '@material/web/top-app-bar/large-top-app-bar.js'; + +import {MaterialStoryInit} from '~catalog/stories/material-collection.js'; +import {html} from 'lit'; + +/** Knob types for button stories. */ +export interface StoryKnobs { + /** Whether the top app bar is open. */ + sticky: boolean; +} + + +const centerAlignedTopAppBar: MaterialStoryInit = { + name: 'Center Aligned Top App Bar', + render({sticky}) { + return html` + + + menu + + Title + + search + + + `; + } +}; +const smallTopAppBar: MaterialStoryInit = { + name: 'Small Top App Bar', + render({sticky}) { + return html` + + + menu + + Title + + search + + + `; + } +}; + +const mediumTopAppBar: MaterialStoryInit = { + name: 'Medium Top App Bar', + render({sticky}) { + return html` + + + menu + + Title + + search + + + `; + } +}; + +const largeTopAppBar: MaterialStoryInit = { + name: 'Large Top App Bar', + render({sticky}) { + return html` + + + menu + + Title + + search + + + `; + } +}; + +/** Button stories. */ +export const stories = [centerAlignedTopAppBar, smallTopAppBar, mediumTopAppBar, largeTopAppBar]; diff --git a/topappbar/internal/_center-aligned-top-app-bar.scss b/topappbar/internal/_center-aligned-top-app-bar.scss new file mode 100644 index 0000000000..d354cca1e2 --- /dev/null +++ b/topappbar/internal/_center-aligned-top-app-bar.scss @@ -0,0 +1,15 @@ +@use "../../tokens"; + +@mixin styles() { + :host { + $tokens: tokens.md-comp-top-app-bar-small-centered-values(); + @each $token, $value in $tokens { + --_#{$token}: var(--md-center-aligned-top-app-bar-#{$token}, #{$value}); + } + + slot:not([name])::slotted(*) { + display: flex; + justify-content: center; + } + } +} diff --git a/topappbar/internal/_large-top-app-bar.scss b/topappbar/internal/_large-top-app-bar.scss new file mode 100644 index 0000000000..2aa6522b23 --- /dev/null +++ b/topappbar/internal/_large-top-app-bar.scss @@ -0,0 +1,22 @@ +@use "../../tokens"; + +@mixin styles() { + :host { + $tokens: tokens.md-comp-top-app-bar-large-values(); + @each $token, $value in $tokens { + --_#{$token}: var(--md-large-top-app-bar-#{$token}, #{$value}); + } + + flex-wrap: wrap; + + slot[name="start"] { + flex-basis: 15%; + } + + slot[name="end"] { + flex-basis: 75%; + display: flex; + justify-content: flex-end; + } + } +} diff --git a/topappbar/internal/_medium-top-app-bar.scss b/topappbar/internal/_medium-top-app-bar.scss new file mode 100644 index 0000000000..67978f2a61 --- /dev/null +++ b/topappbar/internal/_medium-top-app-bar.scss @@ -0,0 +1,22 @@ +@use "../../tokens"; + +@mixin styles() { + :host { + $tokens: tokens.md-comp-top-app-bar-medium-values(); + @each $token, $value in $tokens { + --_#{$token}: var(--md-medium-top-app-bar-#{$token}, #{$value}); + } + + flex-wrap: wrap; + + slot[name="start"] { + flex-basis: 15%; + } + + slot[name="end"] { + flex-basis: 75%; + display: flex; + justify-content: flex-end; + } + } +} diff --git a/topappbar/internal/_shared.scss b/topappbar/internal/_shared.scss new file mode 100644 index 0000000000..a61c51b630 --- /dev/null +++ b/topappbar/internal/_shared.scss @@ -0,0 +1,74 @@ +@use 'sass:map'; +@use "../../elevation/elevation"; + +@mixin styles() { + :host { + background-color: var(--_container-color); + border-radius: var(--_container-shape); + height: var(--_container-height); + + width: 100%; + display: flex; + align-items: center; + position: relative; // For elevation + padding-inline: 16px; + gap: 24px; + + @include elevation.theme( + ( + level: var(--_container-elevation), + shadow-color: var(--_container-shadow-color) + ) + ); + + slot[name="start"] { + &::slotted(md-icon-button) { + --md-icon-button-icon-color: var(--_leading-icon-color); + --md-icon-button-icon-size: var(--_leading-icon-size); + } + &::slotted(md-icon) { + --md-icon-size: var(--_leading-icon-size); + color: var(--_leading-icon-color); + } + } + + slot[name="end"] { + &::slotted(md-icon-button) { + --md-icon-button-icon-color: var(--_trailing-icon-color); + --md-icon-button-icon-size: var(--_trailing-icon-size); + } + &::slotted(md-icon) { + --md-icon-size: var(--_trailing-icon-size); + color: var(--_trailing-icon-color); + } + } + + // Headline + slot:not([name]) { + color: var(--_headline-color); + font: var(--_headline-font); + line-height: var(--_headline-line-height); + font-size: var(--_headline-size); + font-weight: var(--_headline-weight); + + &::slotted(*) { + flex: 1; + } + } + } + + :host([sticky]) { + position: sticky; + transition-duration: 250ms; + transition-timing-function: ease-in-out; + } + + :host([sticky].is-scrolling) { + background-color: var(--_on-scroll-container-color); + @include elevation.theme( + ( + level: var(--_on-scroll-container-elevation) + ) + ) + } +} diff --git a/topappbar/internal/_small-top-app-bar.scss b/topappbar/internal/_small-top-app-bar.scss new file mode 100644 index 0000000000..6b631e3658 --- /dev/null +++ b/topappbar/internal/_small-top-app-bar.scss @@ -0,0 +1,10 @@ +@use "../../tokens"; + +@mixin styles() { + :host { + $tokens: tokens.md-comp-top-app-bar-small-values(); + @each $token, $value in $tokens { + --_#{$token}: var(--md-small-top-app-bar-#{$token}, #{$value}); + } + } +} diff --git a/topappbar/internal/center-aligned-top-app-bar-styles.scss b/topappbar/internal/center-aligned-top-app-bar-styles.scss new file mode 100644 index 0000000000..ec765623ba --- /dev/null +++ b/topappbar/internal/center-aligned-top-app-bar-styles.scss @@ -0,0 +1,3 @@ +@use './center-aligned-top-app-bar'; + +@include center-aligned-top-app-bar.styles(); diff --git a/topappbar/internal/expanded-top-app-bar.ts b/topappbar/internal/expanded-top-app-bar.ts new file mode 100644 index 0000000000..659b9b3347 --- /dev/null +++ b/topappbar/internal/expanded-top-app-bar.ts @@ -0,0 +1,49 @@ +import '../../elevation/elevation.js'; +import {html, LitElement} from 'lit'; +import {property} from 'lit/decorators/property.js'; + +/** + * ExpandedTopAppBar component. + */ +export abstract class ExpandedTopAppBar extends LitElement { + /** Whether the top app bar is sticky. */ + @property({type: Boolean, reflect: true}) sticky = false; + + protected override render() { + return html` + ${this.renderLeadingContent()} + ${this.renderTrailingContent()} + ${this.renderHeadline()} + + `; + } + + protected renderLeadingContent() { + return html``; + } + + protected renderHeadline() { + return html``; + } + + protected renderTrailingContent() { + return html``; + } + + override connectedCallback() { + super.connectedCallback(); + const observer = new IntersectionObserver( + ([e]) => this.onStuckChanged(e), + {threshold: [1]} + ); + + observer.observe(this) + } + + protected onStuckChanged(e: IntersectionObserverEntry) { + const isStuck = e.intersectionRatio < 1; + if (this.sticky) { + e.target.classList.toggle('is-scrolling', isStuck); + } + } +} diff --git a/topappbar/internal/large-top-app-bar-styles.scss b/topappbar/internal/large-top-app-bar-styles.scss new file mode 100644 index 0000000000..e203de5c38 --- /dev/null +++ b/topappbar/internal/large-top-app-bar-styles.scss @@ -0,0 +1,3 @@ +@use './large-top-app-bar'; + +@include large-top-app-bar.styles(); diff --git a/topappbar/internal/medium-top-app-bar-styles.scss b/topappbar/internal/medium-top-app-bar-styles.scss new file mode 100644 index 0000000000..a455663abc --- /dev/null +++ b/topappbar/internal/medium-top-app-bar-styles.scss @@ -0,0 +1,3 @@ +@use './medium-top-app-bar'; + +@include medium-top-app-bar.styles(); diff --git a/topappbar/internal/shared-styles.scss b/topappbar/internal/shared-styles.scss new file mode 100644 index 0000000000..db50a912bb --- /dev/null +++ b/topappbar/internal/shared-styles.scss @@ -0,0 +1,3 @@ +@use './shared'; + +@include shared.styles(); diff --git a/topappbar/internal/small-top-app-bar-styles.scss b/topappbar/internal/small-top-app-bar-styles.scss new file mode 100644 index 0000000000..a1e070ad30 --- /dev/null +++ b/topappbar/internal/small-top-app-bar-styles.scss @@ -0,0 +1,3 @@ +@use './small-top-app-bar'; + +@include small-top-app-bar.styles(); diff --git a/topappbar/internal/top-app-bar.ts b/topappbar/internal/top-app-bar.ts new file mode 100644 index 0000000000..ec9a4a13a5 --- /dev/null +++ b/topappbar/internal/top-app-bar.ts @@ -0,0 +1,49 @@ +import '../../elevation/elevation.js'; +import {html, LitElement} from 'lit'; +import {property} from 'lit/decorators/property.js'; + +/** + * TopAppBar component. + */ +export abstract class TopAppBar extends LitElement { + /** Whether the top app bar is sticky. */ + @property({type: Boolean, reflect: true}) sticky = false; + + protected override render() { + return html` + ${this.renderLeadingContent()} + ${this.renderHeadline()} + ${this.renderTrailingContent()} + + `; + } + + protected renderLeadingContent() { + return html``; + } + + protected renderHeadline() { + return html``; + } + + protected renderTrailingContent() { + return html``; + } + + override connectedCallback() { + super.connectedCallback(); + const observer = new IntersectionObserver( + ([e]) => this.onStuckChanged(e), + {threshold: [1]} + ); + + observer.observe(this) + } + + protected onStuckChanged(e: IntersectionObserverEntry) { + const isStuck = e.intersectionRatio < 1; + if (this.sticky) { + e.target.classList.toggle('is-scrolling', isStuck); + } + } +} diff --git a/topappbar/large-top-app-bar.ts b/topappbar/large-top-app-bar.ts new file mode 100644 index 0000000000..9750d1dcbd --- /dev/null +++ b/topappbar/large-top-app-bar.ts @@ -0,0 +1,18 @@ +import {ExpandedTopAppBar} from './internal/expanded-top-app-bar.js'; + +import {CSSResultOrNative} from 'lit'; +import {customElement} from 'lit/decorators.js'; + +import {styles as sharedStyles} from './internal/shared-styles.js'; +import {styles} from './internal/large-top-app-bar-styles.js'; + +declare global { + interface HTMLElementTagNameMap { + 'md-large-top-app-bar': MdLargeTopAppBar; + } +} + +@customElement('md-large-top-app-bar') +export class MdLargeTopAppBar extends ExpandedTopAppBar { + static override styles: CSSResultOrNative[] = [sharedStyles, styles]; +} diff --git a/topappbar/medium-top-app-bar.ts b/topappbar/medium-top-app-bar.ts new file mode 100644 index 0000000000..b58e5dd701 --- /dev/null +++ b/topappbar/medium-top-app-bar.ts @@ -0,0 +1,18 @@ +import {ExpandedTopAppBar} from './internal/expanded-top-app-bar.js'; + +import {CSSResultOrNative} from 'lit'; +import {customElement} from 'lit/decorators.js'; + +import {styles as sharedStyles} from './internal/shared-styles.js'; +import {styles} from './internal/medium-top-app-bar-styles.js'; + +declare global { + interface HTMLElementTagNameMap { + 'md-medium-top-app-bar': MdMediumTopAppBar; + } +} + +@customElement('md-medium-top-app-bar') +export class MdMediumTopAppBar extends ExpandedTopAppBar { + static override styles: CSSResultOrNative[] = [sharedStyles, styles]; +} diff --git a/topappbar/small-top-app-bar.ts b/topappbar/small-top-app-bar.ts new file mode 100644 index 0000000000..a75883072b --- /dev/null +++ b/topappbar/small-top-app-bar.ts @@ -0,0 +1,18 @@ +import {TopAppBar} from './internal/top-app-bar.js'; + +import {CSSResultOrNative} from 'lit'; +import {customElement} from 'lit/decorators.js'; + +import {styles as sharedStyles} from './internal/shared-styles.js'; +import {styles} from './internal/small-top-app-bar-styles.js'; + +declare global { + interface HTMLElementTagNameMap { + 'md-small-top-app-bar': MdSmallTopAppBar; + } +} + +@customElement('md-small-top-app-bar') +export class MdSmallTopAppBar extends TopAppBar { + static override styles: CSSResultOrNative[] = [sharedStyles, styles]; +}