import {colors, isNarrowScreenObs} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {Disposable, dom, DomArg, DomElementArg, makeTestId, Observable, styled} from 'grainjs'; const testId = makeTestId('test-banner-'); export interface BannerOptions { /** * Content to display in the banner. */ content: DomArg; /** * The banner style. * * Warning banners have a yellow background. Error banners have a red * background. */ style: 'warning' | 'error' | 'info'; /** * Optional variant of `content` to display when screen width becomes narrow. */ contentSmall?: DomArg; /** * Whether a button to close the banner should be shown. * * If true, `onClose` should also be specified; it will be called when the close * button is clicked. * * Defaults to false. */ showCloseButton?: boolean; /** * Whether a button to collapse/expand the banner should be shown on narrow screens. * * Defaults to false. */ showExpandButton?: boolean; /** * If provided, applies the css class to the banner container. */ bannerCssClass?: string; /** * Function that is called when the banner close button is clicked. * * Should be used to handle disposal of the Banner. */ onClose?(): void; } /** * A customizable banner for displaying at the top of a page. */ export class Banner extends Disposable { private readonly _isExpanded = Observable.create(this, true); constructor(private _options: BannerOptions) { super(); } public buildDom() { return cssBanner({class: this._options.bannerCssClass || ''}, cssBanner.cls(`-${this._options.style}`), this._buildContent(), this._buildButtons(), testId('element') ); } private _buildContent() { const {content, contentSmall} = this._options; return dom.domComputed(use => { if (contentSmall === undefined) { return [content]; } const isExpanded = use(this._isExpanded); const isNarrowScreen = use(isNarrowScreenObs()); return [isNarrowScreen && !isExpanded ? contentSmall : content]; }); } private _buildButtons() { return cssButtons( this._options.showExpandButton ? this._buildExpandButton() : null, this._options.showCloseButton ? this._buildCloseButton() : null, ); } private _buildCloseButton() { return cssButton('CrossBig', dom.on('click', () => this._options.onClose?.()), testId('close'), ); } private _buildExpandButton() { return dom.maybe(isNarrowScreenObs(), () => { return cssExpandButton('Dropdown', cssExpandButton.cls('-expanded', this._isExpanded), dom.on('click', () => this._isExpanded.set(!this._isExpanded.get())), ); }); } } export function buildBannerMessage(...domArgs: DomElementArg[]) { return cssBannerMessage( cssIcon('Idea'), cssLightlyBoldedText(domArgs), ); } const cssBanner = styled('div', ` display: flex; padding: 10px; gap: 16px; color: white; &-info { color: black; background: #FFFACD; } &-warning { background: #E6A117; } &-error { background: ${colors.error}; } `); export const cssBannerLink = styled('span', ` cursor: pointer; color: unset; text-decoration: underline; &:hover, &:focus { color: unset; } `); const cssButtons = styled('div', ` display: flex; gap: 16px; flex-shrink: 0; margin-left: auto; `); const cssButton = styled(icon, ` width: 16px; height: 16px; cursor: pointer; background-color: white; `); const cssExpandButton = styled(cssButton, ` &-expanded { -webkit-mask-image: var(--icon-DropdownUp) !important; } `); const cssLightlyBoldedText = styled('div', ` font-weight: 500; `); const cssIconAndText = styled('div', ` display: flex; gap: 16px; `); const cssBannerMessage = styled(cssIconAndText, ` flex-grow: 1; justify-content: center; `); const cssIcon = styled(icon, ` flex-shrink: 0; width: 16px; height: 16px; background-color: white; `);