diff --git a/CHANGELOG.md b/CHANGELOG.md index 1faa5a8f9..0e38f2788 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Banner component [#1174](https://github.com/IgniteUI/igniteui-webcomponents/issues/1174) ### Fixed - Input, Textarea - passing `undefined` to **value** sets the underlying input value to undefined [#1206](https://github.com/IgniteUI/igniteui-webcomponents/issues/1206) diff --git a/src/components/banner/banner.spec.ts b/src/components/banner/banner.spec.ts new file mode 100644 index 000000000..afeda6774 --- /dev/null +++ b/src/components/banner/banner.spec.ts @@ -0,0 +1,289 @@ +import { + elementUpdated, + expect, + fixture, + html, + nextFrame, +} from '@open-wc/testing'; +import { spy } from 'sinon'; + +import { defineComponents } from '../common/definitions/defineComponents.js'; +import { finishAnimationsFor } from '../common/utils.spec.js'; +import IgcBannerComponent from './banner.js'; + +describe('Banner', () => { + before(() => { + defineComponents(IgcBannerComponent); + }); + + const createDefaultBanner = () => html` + You are currently offline. + `; + + const createSlottedBanner = () => html` + + + Build 123 completed! +
+ OK 1 + View log +
+
+ `; + + let banner: IgcBannerComponent; + + beforeEach(async () => { + banner = await fixture(createDefaultBanner()); + }); + + const DIFF_OPTIONS = { + ignoreTags: ['igc-button'], + ignoreAttributes: ['inert'], + }; + + const BUTTON_DIFF_OPTIONS = ['variant', 'size', 'style']; + + async function clickHideComplete() { + finishAnimationsFor(banner.shadowRoot!); + await elementUpdated(banner); + await nextFrame(); + await nextFrame(); + } + + describe('Initialization Tests', () => { + it('passes the a11y audit', async () => { + await expect(banner).to.be.accessible(); + await expect(banner).shadowDom.to.be.accessible(); + }); + + it('is correctly initialized with its default component state', () => { + expect(banner.open).to.be.false; + expect(banner.dir).to.be.empty; + }); + + it('should render a default action button', () => { + const button = banner.shadowRoot!.querySelector('igc-button'); + + expect(button).not.to.be.null; + expect(button).dom.to.equal(`OK`, { + ignoreAttributes: BUTTON_DIFF_OPTIONS, + }); + }); + + it('is correctly rendered both in shown/hidden states', async () => { + expect(banner.open).to.be.false; + + expect(banner).dom.to.equal( + 'You are currently offline.' + ); + expect(banner).shadowDom.to.equal( + `
+
+
+
+ +
+
+ +
+
+
+ + OK + +
+
+
`, + { + ignoreAttributes: BUTTON_DIFF_OPTIONS, + } + ); + + await banner.show(); + + expect(banner).dom.to.equal( + 'You are currently offline.' + ); + expect(banner).shadowDom.to.equal( + `
+
+
+
+ +
+
+ +
+
+
+ + OK + +
+
+
`, + { + ignoreAttributes: BUTTON_DIFF_OPTIONS, + } + ); + }); + + it('should correctly render slotted content', async () => { + banner = await fixture(createSlottedBanner()); + + const prefix = banner.querySelector('igc-icon'); + const actions = banner.querySelector('div'); + + expect(prefix).not.to.be.null; + expect(actions).not.to.be.null; + + expect(actions?.children[0]).dom.to.equal( + 'OK 1', + { + ignoreAttributes: [...BUTTON_DIFF_OPTIONS, 'type'], + } + ); + + expect(actions?.children[1]).dom.to.equal( + 'View log', + { + ignoreAttributes: [...BUTTON_DIFF_OPTIONS, 'type'], + } + ); + }); + }); + + describe('Methods` Tests', () => { + it('calls `show` and `hide` methods successfully', async () => { + expect(banner.open).to.be.false; + + await banner.show(); + + expect(banner.open).to.be.true; + expect(banner).dom.to.equal( + 'You are currently offline.', + DIFF_OPTIONS + ); + + await banner.hide(); + + expect(banner.open).to.be.false; + expect(banner).dom.to.equal( + 'You are currently offline.', + DIFF_OPTIONS + ); + }); + + it('calls `toggle` method successfully', async () => { + expect(banner.open).to.be.false; + + await banner.toggle(); + + expect(banner.open).to.be.true; + expect(banner).dom.to.equal( + 'You are currently offline.', + DIFF_OPTIONS + ); + + await banner.toggle(); + + expect(banner.open).to.be.false; + expect(banner).dom.to.equal( + 'You are currently offline.', + DIFF_OPTIONS + ); + }); + + it('`show`, `hide`, `toggle` methods return proper values', async () => { + expect(banner.open).to.be.false; + + // hide banner when already hidden + let animation = await banner.hide(); + expect(animation).to.be.false; + + // show banner when hidden + animation = await banner.show(); + expect(animation).to.be.true; + expect(banner.open).to.be.true; + + // show banner when already shown + animation = await banner.show(); + expect(animation).to.be.false; + + // hide banner when shown + animation = await banner.hide(); + expect(animation).to.be.true; + expect(banner.open).to.be.false; + + // hide -> show + animation = await banner.toggle(); + expect(animation).to.be.true; + expect(banner.open).to.be.true; + + // show -> hide + animation = await banner.toggle(); + expect(animation).to.be.true; + expect(banner.open).to.be.false; + }); + }); + + describe('Action Tests', () => { + it('should close the banner when clicking the default button', async () => { + expect(banner.open).to.be.false; + + await banner.show(); + + expect(banner.open).to.be.true; + + const button = banner.shadowRoot!.querySelector('igc-button'); + + button?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + await clickHideComplete(); + + expect(banner.open).to.be.false; + }); + + it('should emit correct event sequence for the default action button', async () => { + const eventSpy = spy(banner, 'emitEvent'); + + expect(banner.open).to.be.false; + + await banner.show(); + + expect(banner.open).to.be.true; + + const button = banner.shadowRoot!.querySelector('igc-button'); + button?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + + expect(eventSpy.callCount).to.equal(1); + expect(eventSpy).calledWith('igcClosing', { cancelable: true }); + + eventSpy.resetHistory(); + await clickHideComplete(); + + expect(eventSpy).calledWith('igcClosed'); + expect(banner.open).to.be.false; + }); + + it('can cancel `igcClosing` event', async () => { + const eventSpy = spy(banner, 'emitEvent'); + const button = banner.shadowRoot!.querySelector('igc-button'); + + banner.addEventListener('igcClosing', (event) => { + event.preventDefault(); + }); + + await banner.show(); + + expect(banner.open).to.be.true; + + button?.dispatchEvent(new MouseEvent('click', { bubbles: true })); + await clickHideComplete(); + + expect(eventSpy).calledWith('igcClosing'); + expect(eventSpy).not.calledWith('igcClosed'); + expect(banner.open).to.be.true; + }); + }); +}); diff --git a/src/components/banner/banner.ts b/src/components/banner/banner.ts new file mode 100644 index 000000000..62c7b6ef7 --- /dev/null +++ b/src/components/banner/banner.ts @@ -0,0 +1,148 @@ +import { LitElement, html } from 'lit'; +import { property } from 'lit/decorators.js'; +import { type Ref, createRef, ref } from 'lit/directives/ref.js'; + +import { addAnimationController } from '../../animations/player.js'; +import { growVerIn, growVerOut } from '../../animations/presets/grow/index.js'; +import { themes } from '../../theming/theming-decorator.js'; +import IgcButtonComponent from '../button/button.js'; +import { registerComponent } from '../common/definitions/register.js'; +import type { Constructor } from '../common/mixins/constructor.js'; +import { EventEmitterMixin } from '../common/mixins/event-emitter.js'; +import { styles } from './themes/banner.base.css.js'; +import { all } from './themes/themes.js'; + +export interface IgcBannerComponentEventMap { + igcClosing: CustomEvent; + igcClosed: CustomEvent; +} + +/** + * The `igc-banner` component displays important and concise message(s) for a user to address, that is specific to a page or feature. + * + * @element igc-banner + * + * @slot - Renders the text content of the banner message. + * @slot prefix - Renders additional content at the start of the message block. + * @slot actions - Renders any action elements. + * + * @fires igcClosing - Emitted before closing the banner - when a user interacts (click) with the default action of the banner. + * @fires igcClosed - Emitted after the banner is closed - when a user interacts (click) with the default action of the banner. + * + * @csspart base - The base wrapper of the banner component. + * @csspart spacer - The inner wrapper that sets the space around the banner. + * @csspart message - The part that holds the text and the illustration. + * @csspart illustration - The part that holds the banner icon/illustration. + * @csspart content - The part that holds the banner text content. + * @csspart actions - The part that holds the banner action buttons. + */ + +@themes(all) +export default class IgcBannerComponent extends EventEmitterMixin< + IgcBannerComponentEventMap, + Constructor +>(LitElement) { + public static readonly tagName = 'igc-banner'; + public static styles = [styles]; + + /* blazorSuppress */ + public static register() { + registerComponent(IgcBannerComponent, IgcButtonComponent); + } + + private _internals: ElementInternals; + private _bannerRef: Ref = createRef(); + private _animationPlayer = addAnimationController(this, this._bannerRef); + + /** + * Determines whether the banner is being shown/hidden. + * @attr + */ + @property({ type: Boolean, reflect: true }) + public open = false; + + constructor() { + super(); + this._internals = this.attachInternals(); + + this._internals.role = 'status'; + this._internals.ariaLive = 'polite'; + } + + /** Shows the banner if not already shown. Returns `true` when the animation has completed. */ + public async show(): Promise { + if (this.open) { + return false; + } + + this.open = true; + return await this.toggleAnimation('open'); + } + + /** Hides the banner if not already hidden. Returns `true` when the animation has completed. */ + public async hide(): Promise { + if (!this.open) { + return false; + } + + await this.toggleAnimation('close'); + this.open = false; + return true; + } + + /** Toggles between shown/hidden state. Returns `true` when the animation has completed. */ + public async toggle(): Promise { + return this.open ? await this.hide() : await this.show(); + } + + private async toggleAnimation(dir: 'open' | 'close') { + const animation = dir === 'open' ? growVerIn : growVerOut; + + const [_, event] = await Promise.all([ + this._animationPlayer.stopAll(), + this._animationPlayer.play(animation()), + ]); + + return event.type === 'finish'; + } + + private async handleClick() { + if (this.emitEvent('igcClosing', { cancelable: true })) { + await this.hide(); + this.emitEvent('igcClosed'); + } + } + + protected override render() { + return html` +
+
+
+
+ +
+
+ +
+
+
+ + OK + +
+
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'igc-banner': IgcBannerComponent; + } +} diff --git a/src/components/banner/themes/banner.base.scss b/src/components/banner/themes/banner.base.scss new file mode 100644 index 000000000..647fb6935 --- /dev/null +++ b/src/components/banner/themes/banner.base.scss @@ -0,0 +1,62 @@ +@use 'styles/utilities' as *; + +:host { + display: block; + min-width: rem(320px); + flex-basis: rem(320px); +} + +[part~='spacer'] { + display: flex; + flex-wrap: wrap; + padding: rem(16px) rem(8px); + gap: rem(8px); + justify-content: flex-end; +} + +[part~='message'] { + min-width: rem(150px); + flex-basis: rem(150px); + flex-grow: 1; +} + +[part~='illustration'] { + justify-content: center; +} + +[part~='illustration'], +[part~='message'] { + display: flex; + align-items: center; +} + +[part~='actions'] { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + align-items: center; + align-self: flex-end; +} + +::slotted([slot='actions']) { + display: flex; + flex-wrap: wrap; + gap: rem(8px); +} + +::slotted([slot='prefix']) { + margin-inline-end: rem(16px); +} + +// OPEN CLOSE BEHAVIOR +[part~='base'] { + overflow: hidden; +} + +:host(:not([open])) [part~='base'] { + height: 0; +} + +:host([open]) [part~='base'] { + height: auto; +} diff --git a/src/components/banner/themes/dark/_themes.scss b/src/components/banner/themes/dark/_themes.scss new file mode 100644 index 000000000..785c2d135 --- /dev/null +++ b/src/components/banner/themes/dark/_themes.scss @@ -0,0 +1,7 @@ +@use 'styles/utilities' as *; +@use 'igniteui-theming/sass/themes/schemas/components/dark/banner' as *; + +$material: digest-schema($dark-material-banner); +$bootstrap: digest-schema($dark-bootstrap-banner); +$fluent: digest-schema($dark-fluent-banner); +$indigo: digest-schema($dark-indigo-banner); diff --git a/src/components/banner/themes/dark/banner.bootstrap.scss b/src/components/banner/themes/dark/banner.bootstrap.scss new file mode 100644 index 000000000..840363c57 --- /dev/null +++ b/src/components/banner/themes/dark/banner.bootstrap.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $bootstrap; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/dark/banner.fluent.scss b/src/components/banner/themes/dark/banner.fluent.scss new file mode 100644 index 000000000..4624a8067 --- /dev/null +++ b/src/components/banner/themes/dark/banner.fluent.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $fluent; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/dark/banner.indigo.scss b/src/components/banner/themes/dark/banner.indigo.scss new file mode 100644 index 000000000..076d232e7 --- /dev/null +++ b/src/components/banner/themes/dark/banner.indigo.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $indigo; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/dark/banner.material.scss b/src/components/banner/themes/dark/banner.material.scss new file mode 100644 index 000000000..a5e2f2109 --- /dev/null +++ b/src/components/banner/themes/dark/banner.material.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $material; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/light/_themes.scss b/src/components/banner/themes/light/_themes.scss new file mode 100644 index 000000000..070321847 --- /dev/null +++ b/src/components/banner/themes/light/_themes.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'igniteui-theming/sass/themes/schemas/components/light/banner' as *; + +$base: digest-schema($light-banner); +$material: digest-schema($material-banner); +$bootstrap: digest-schema($bootstrap-banner); +$fluent: digest-schema($fluent-banner); +$indigo: digest-schema($indigo-banner); diff --git a/src/components/banner/themes/light/banner.bootstrap.scss b/src/components/banner/themes/light/banner.bootstrap.scss new file mode 100644 index 000000000..840363c57 --- /dev/null +++ b/src/components/banner/themes/light/banner.bootstrap.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $bootstrap; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/light/banner.fluent.scss b/src/components/banner/themes/light/banner.fluent.scss new file mode 100644 index 000000000..4624a8067 --- /dev/null +++ b/src/components/banner/themes/light/banner.fluent.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $fluent; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/light/banner.indigo.scss b/src/components/banner/themes/light/banner.indigo.scss new file mode 100644 index 000000000..076d232e7 --- /dev/null +++ b/src/components/banner/themes/light/banner.indigo.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $indigo; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/light/banner.material.scss b/src/components/banner/themes/light/banner.material.scss new file mode 100644 index 000000000..a5e2f2109 --- /dev/null +++ b/src/components/banner/themes/light/banner.material.scss @@ -0,0 +1,8 @@ +@use 'styles/utilities' as *; +@use 'themes' as *; + +$theme: $material; + +:host { + @include css-vars-from-theme($theme, 'ig-banner'); +} diff --git a/src/components/banner/themes/shared/banner.bootstrap.scss b/src/components/banner/themes/shared/banner.bootstrap.scss new file mode 100644 index 000000000..fe7d91362 --- /dev/null +++ b/src/components/banner/themes/shared/banner.bootstrap.scss @@ -0,0 +1,32 @@ +@use 'styles/utilities' as *; +@use '../light/themes' as *; + +$theme: $bootstrap; + +[part~='spacer'] { + background: var-get($theme, 'banner-background'); + box-shadow: inset 0 rem(-1px) 0 0 var-get($theme, 'banner-border-color'); + border-radius: var-get($theme, 'border-radius'); +} + +[part~='message'] { + padding: 0 rem(8px); +} + +::slotted([slot='prefix']) { + --default-size: 3; + --component-size: var(--ig-size, var(--default-size)) !important; + + color: inherit; +} + +[part~='illustration'] { + color: var-get($theme, 'banner-illustration-color'); +} + +[part~='content'] { + @include type-style('body-2'); + + color: var-get($theme, 'banner-message-color'); +} + diff --git a/src/components/banner/themes/shared/banner.fluent.scss b/src/components/banner/themes/shared/banner.fluent.scss new file mode 100644 index 000000000..35b50284d --- /dev/null +++ b/src/components/banner/themes/shared/banner.fluent.scss @@ -0,0 +1,32 @@ +@use 'styles/utilities' as *; +@use '../light/themes' as *; + +$theme: $fluent; + +[part~='spacer'] { + background: var-get($theme, 'banner-background'); + box-shadow: inset 0 rem(-1px) 0 0 var-get($theme, 'banner-border-color'); + border-radius: var-get($theme, 'border-radius'); +} + +[part~='message'] { + padding: 0 rem(8px); +} + +::slotted([slot='prefix']) { + --default-size: 3; + --component-size: var(--ig-size, var(--default-size)) !important; + + color: inherit; +} + +[part~='illustration'] { + color: var-get($theme, 'banner-illustration-color'); +} + +[part~='content'] { + @include type-style('caption'); + + color: var-get($theme, 'banner-message-color'); +} + diff --git a/src/components/banner/themes/shared/banner.indigo.scss b/src/components/banner/themes/shared/banner.indigo.scss new file mode 100644 index 000000000..9f3d4f580 --- /dev/null +++ b/src/components/banner/themes/shared/banner.indigo.scss @@ -0,0 +1,33 @@ +@use 'styles/utilities' as *; +@use '../light/themes' as *; + +$theme: $indigo; + +[part~='spacer'] { + padding: rem(16px); + background: var-get($theme, 'banner-background'); + box-shadow: inset 0 0 0 rem(1px) var-get($theme, 'banner-border-color'); + border-radius: var-get($theme, 'border-radius'); +} + +[part~='message'] { + gap: rem(8px); +} + +::slotted([slot='prefix']) { + --default-size: 2; + --component-size: var(--ig-size, var(--default-size)) !important; + + margin-inline-end: 0; + color: inherit; +} + +[part~='illustration'] { + color: var-get($theme, 'banner-illustration-color'); +} + +[part~='content'] { + @include type-style('body-2'); + + color: var-get($theme, 'banner-message-color'); +} diff --git a/src/components/banner/themes/shared/banner.material.scss b/src/components/banner/themes/shared/banner.material.scss new file mode 100644 index 000000000..9285ea335 --- /dev/null +++ b/src/components/banner/themes/shared/banner.material.scss @@ -0,0 +1,31 @@ +@use 'styles/utilities' as *; +@use '../light/themes' as *; + +$theme: $material; + +[part~='spacer'] { + background: var-get($theme, 'banner-background'); + box-shadow: inset 0 rem(-1px) 0 0 var-get($theme, 'banner-border-color'); + border-radius: var-get($theme, 'border-radius'); +} + +[part~='message'] { + padding: 0 rem(8px); +} + +::slotted([slot='prefix']) { + --default-size: 3; + --component-size: var(--ig-size, var(--default-size)) !important; + + color: inherit; +} + +[part~='illustration'] { + color: var-get($theme, 'banner-illustration-color'); +} + +[part~='content'] { + @include type-style('body-2'); + + color: var-get($theme, 'banner-message-color'); +} diff --git a/src/components/banner/themes/themes.ts b/src/components/banner/themes/themes.ts new file mode 100644 index 000000000..a2c47bc10 --- /dev/null +++ b/src/components/banner/themes/themes.ts @@ -0,0 +1,50 @@ +import { css } from 'lit'; + +import type { Themes } from '../../../theming/types.js'; +// Dark Overrides +import { styles as bootstrapDark } from './dark/banner.bootstrap.css.js'; +import { styles as fluentDark } from './dark/banner.fluent.css.js'; +import { styles as indigoDark } from './dark/banner.indigo.css.js'; +import { styles as materialDark } from './dark/banner.material.css.js'; +// Light Overrides +import { styles as bootstrapLight } from './light/banner.bootstrap.css.js'; +import { styles as fluentLight } from './light/banner.fluent.css.js'; +import { styles as indigoLight } from './light/banner.indigo.css.js'; +import { styles as materialLight } from './light/banner.material.css.js'; +// Shared Styles +import { styles as bootstrap } from './shared/banner.bootstrap.css.js'; +import { styles as fluent } from './shared/banner.fluent.css.js'; +import { styles as indigo } from './shared/banner.indigo.css.js'; +import { styles as material } from './shared/banner.material.css.js'; + +const light = { + bootstrap: css` + ${bootstrap} ${bootstrapLight} + `, + material: css` + ${material} ${materialLight} + `, + fluent: css` + ${fluent} ${fluentLight} + `, + indigo: css` + ${indigo} ${indigoLight} + `, +}; + +const dark = { + bootstrap: css` + ${bootstrap} ${bootstrapDark} + `, + material: css` + ${material} ${materialDark} + `, + fluent: css` + ${fluent} ${fluentDark} + `, + indigo: css` + ${indigo} ${indigoDark} + `, +}; + +export const all: Themes = { light, dark }; diff --git a/src/components/common/definitions/defineAllComponents.ts b/src/components/common/definitions/defineAllComponents.ts index 0284d2e72..b2e876b19 100644 --- a/src/components/common/definitions/defineAllComponents.ts +++ b/src/components/common/definitions/defineAllComponents.ts @@ -1,6 +1,7 @@ import IgcAccordionComponent from '../../accordion/accordion.js'; import IgcAvatarComponent from '../../avatar/avatar.js'; import IgcBadgeComponent from '../../badge/badge.js'; +import IgcBannerComponent from '../../banner/banner.js'; import IgcButtonGroupComponent from '../../button-group/button-group.js'; import IgcToggleButtonComponent from '../../button-group/toggle-button.js'; import IgcButtonComponent from '../../button/button.js'; @@ -65,6 +66,7 @@ const allComponents: IgniteComponent[] = [ IgcAvatarComponent, IgcAccordionComponent, IgcBadgeComponent, + IgcBannerComponent, IgcButtonComponent, IgcIconButtonComponent, IgcToggleButtonComponent, diff --git a/src/index.ts b/src/index.ts index b63fdffa6..b9c83d032 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export { default as IgcAvatarComponent } from './components/avatar/avatar.js'; export { default as IgcAccordionComponent } from './components/accordion/accordion.js'; export { default as IgcBadgeComponent } from './components/badge/badge.js'; +export { default as IgcBannerComponent } from './components/banner/banner.js'; export { default as IgcButtonComponent } from './components/button/button.js'; export { default as IgcButtonGroupComponent } from './components/button-group/button-group.js'; export { default as IgcCalendarComponent } from './components/calendar/calendar.js'; diff --git a/stories/banner.stories.ts b/stories/banner.stories.ts new file mode 100644 index 000000000..54ba6ba35 --- /dev/null +++ b/stories/banner.stories.ts @@ -0,0 +1,98 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import { html } from 'lit'; + +import { + IgcBannerComponent, + IgcButtonComponent, + IgcIconComponent, + IgcNavbarComponent, + defineComponents, + registerIconFromText, +} from '../src/index.js'; + +defineComponents( + IgcBannerComponent, + IgcIconComponent, + IgcButtonComponent, + IgcNavbarComponent +); + +// region default +const metadata: Meta = { + title: 'Banner', + component: 'igc-banner', + parameters: { + docs: { + description: { + component: + 'The `igc-banner` component displays important and concise message(s) for a user to address, that is specific to a page or feature.', + }, + }, + actions: { handles: ['igcClosing', 'igcClosed'] }, + }, + argTypes: { + open: { + type: 'boolean', + description: 'Determines whether the banner is being shown/hidden.', + control: 'boolean', + table: { defaultValue: { summary: false } }, + }, + }, + args: { open: false }, +}; + +export default metadata; + +interface IgcBannerArgs { + /** Determines whether the banner is being shown/hidden. */ + open: boolean; +} +type Story = StoryObj; + +// endregion + +const checkIcon = + ''; + +registerIconFromText('success', checkIcon, 'material'); + +const BasicTemplate = ({ open }: IgcBannerArgs) => { + return html` + + +

Title

+
+ + Toggle Banner + `; +}; + +const SlottedContentTemplate = ({ open }: IgcBannerArgs) => { + return html` + + + Toggle Banner + `; +}; + +export const Basic: Story = BasicTemplate.bind({}); +export const SlottedContent: Story = SlottedContentTemplate.bind({});