mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
166 lines
3.6 KiB
TypeScript
166 lines
3.6 KiB
TypeScript
|
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';
|
||
|
|
||
|
/**
|
||
|
* 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;
|
||
|
|
||
|
/**
|
||
|
* 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(
|
||
|
cssBanner.cls(`-${this._options.style}`),
|
||
|
this._buildContent(),
|
||
|
this._buildButtons(),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
|
||
|
&-warning {
|
||
|
background: #E6A117;
|
||
|
}
|
||
|
|
||
|
&-error {
|
||
|
background: ${colors.error};
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
`);
|
||
|
|
||
|
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;
|
||
|
`);
|