mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Add behavioral and coaching call popups
Summary: Adds a new category of popups that are shown dynamically when certain parts of the UI are first rendered, and a free coaching call popup that's shown to users on their site home page. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3706
This commit is contained in:
@@ -228,6 +228,8 @@ export const theme = {
|
||||
'black'),
|
||||
tooltipCloseButtonHoverBg: new CustomProp('theme-tooltip-close-button-hover-bg', undefined,
|
||||
'white'),
|
||||
tooltipPopupHeaderFg: new CustomProp('theme-tooltip-popup-header-fg', undefined, colors.light),
|
||||
tooltipPopupHeaderBg: new CustomProp('theme-tooltip-popup-header-bg', undefined, colors.lightGreen),
|
||||
|
||||
/* Modals */
|
||||
modalBg: new CustomProp('theme-modal-bg', undefined, 'white'),
|
||||
|
||||
@@ -98,6 +98,11 @@ export const menuCssClass = cssMenuElem.className;
|
||||
// Add grist-floating-menu class to support existing browser tests
|
||||
const defaults = { menuCssClass: menuCssClass + ' grist-floating-menu' };
|
||||
|
||||
export interface SelectOptions<T> extends weasel.ISelectUserOptions {
|
||||
/** Additional DOM element args to pass to each select option. */
|
||||
renderOptionArgs?: (option: IOptionFull<T | null>) => DomElementArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a select dropdown widget. The observable `obs` reflects the value of the selected
|
||||
* option, and `optionArray` is an array (regular or observable) of option values and labels.
|
||||
@@ -121,15 +126,20 @@ const defaults = { menuCssClass: menuCssClass + ' grist-floating-menu' };
|
||||
* ]);
|
||||
* select(employee, allEmployees, {defLabel: "Select employee:"});
|
||||
*
|
||||
* const name = observable("alice");
|
||||
* const names = ["alice", "bob", "carol"];
|
||||
* select(name, names, {renderOptionArgs: (op) => console.log(`Rendered option ${op.value}`)});
|
||||
*
|
||||
* Note that this select element is not compatible with browser address autofill for usage in
|
||||
* forms, and that formSelect should be used for this purpose.
|
||||
*/
|
||||
export function select<T>(obs: Observable<T>, optionArray: MaybeObsArray<IOption<T>>,
|
||||
options: weasel.ISelectUserOptions = {}) {
|
||||
options: SelectOptions<T> = {}) {
|
||||
const {renderOptionArgs, ...weaselOptions} = options;
|
||||
const _menu = cssSelectMenuElem(testId('select-menu'));
|
||||
const _btn = cssSelectBtn(testId('select-open'));
|
||||
|
||||
const {menuCssClass: menuClass, ...otherOptions} = options;
|
||||
const {menuCssClass: menuClass, ...otherOptions} = weaselOptions;
|
||||
const selectOptions = {
|
||||
buttonArrow: cssInlineCollapseIcon('Collapse'),
|
||||
menuCssClass: _menu.className + ' ' + (menuClass || ''),
|
||||
@@ -141,6 +151,7 @@ export function select<T>(obs: Observable<T>, optionArray: MaybeObsArray<IOption
|
||||
cssOptionRow(
|
||||
op.icon ? cssOptionRowIcon(op.icon) : null,
|
||||
cssOptionLabel(op.label),
|
||||
renderOptionArgs ? renderOptionArgs(op) : null,
|
||||
testId('select-row')
|
||||
)
|
||||
) as HTMLElement; // TODO: should be changed in weasel
|
||||
|
||||
@@ -7,7 +7,7 @@ import {bigBasicButton, bigPrimaryButton, cssButton} from 'app/client/ui2018/but
|
||||
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {loadingSpinner} from 'app/client/ui2018/loaders';
|
||||
import {waitGrainObs} from 'app/common/gutil';
|
||||
import {IOpenController, IPopupDomCreator, IPopupOptions, PopupControl, popupOpen} from 'popweasel';
|
||||
import {IOpenController, IPopupOptions, PopupControl, popupOpen} from 'popweasel';
|
||||
import {Computed, Disposable, dom, DomContents, DomElementArg, input, keyframes,
|
||||
MultiHolder, Observable, styled} from 'grainjs';
|
||||
import {cssMenuElem} from 'app/client/ui2018/menus';
|
||||
@@ -483,7 +483,7 @@ export function cssModalWidth(style: ModalWidth) {
|
||||
*/
|
||||
export function modalTooltip(
|
||||
reference: Element,
|
||||
domCreator: IPopupDomCreator,
|
||||
domCreator: (ctl: IOpenController) => DomElementArg,
|
||||
options: IPopupOptions = {}
|
||||
): PopupControl {
|
||||
return popupOpen(reference, (ctl: IOpenController) => {
|
||||
@@ -496,7 +496,7 @@ export function modalTooltip(
|
||||
|
||||
/* CSS styled components */
|
||||
|
||||
const cssModalTooltip = styled(cssMenuElem, `
|
||||
export const cssModalTooltip = styled(cssMenuElem, `
|
||||
padding: 16px 24px;
|
||||
background: ${theme.modalBg};
|
||||
border-radius: 3px;
|
||||
@@ -562,6 +562,19 @@ export const cssModalButtons = styled('div', `
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssModalCloseButton = styled('div', `
|
||||
align-self: flex-end;
|
||||
margin: -8px;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
--icon-color: ${theme.modalCloseButtonFg};
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.hover};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssFadeIn = keyframes(`
|
||||
from {background-color: transparent}
|
||||
`);
|
||||
|
||||
118
app/client/ui2018/popups.ts
Normal file
118
app/client/ui2018/popups.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import {cssButton} from 'app/client/ui2018/buttons';
|
||||
import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {Disposable, dom, DomElementArg, styled} from 'grainjs';
|
||||
|
||||
interface IPopupController extends Disposable {
|
||||
/** Close the popup. */
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A controller for an open popup.
|
||||
*
|
||||
* Callers are responsible for providing a suitable close callback (`_doClose`).
|
||||
* Typically, this callback should remove the popup from the DOM and run any of
|
||||
* its disposers.
|
||||
*
|
||||
* Used by popup DOM creator functions to close popups on certain interactions,
|
||||
* like clicking a dismiss button from the body of the popup.
|
||||
*/
|
||||
class PopupController extends Disposable implements IPopupController {
|
||||
constructor(
|
||||
private _doClose: () => void,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._doClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple card popup that's shown in the bottom-right corner of the screen.
|
||||
*
|
||||
* Disposed whenever the `trigger` element is disposed.
|
||||
*/
|
||||
export function cardPopup(
|
||||
triggerElement: Element,
|
||||
createFn: (ctl: PopupController) => DomElementArg,
|
||||
): void {
|
||||
// Closes this popup, removing it from the DOM.
|
||||
const closePopup = () => {
|
||||
document.body.removeChild(popupDom);
|
||||
// Ensure we run the disposers for the DOM contained in the popup.
|
||||
dom.domDispose(popupDom);
|
||||
};
|
||||
|
||||
const popupDom = cssPopupCard(
|
||||
dom.create((owner) => {
|
||||
// Create a controller for this popup. We'll pass it into `createFn` so that
|
||||
// the body of the popup can close this popup, if needed.
|
||||
const ctl = PopupController.create(owner, closePopup);
|
||||
return dom('div',
|
||||
createFn(ctl),
|
||||
testId('popup-card-content'),
|
||||
);
|
||||
}),
|
||||
testId('popup-card'),
|
||||
);
|
||||
|
||||
// Show the popup by appending it to the DOM.
|
||||
document.body.appendChild(popupDom);
|
||||
|
||||
// If the trigger element is disposed, close this popup.
|
||||
dom.onDisposeElem(triggerElement, closePopup);
|
||||
}
|
||||
|
||||
const cssPopupCard = styled('div', `
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
margin-left: 16px;
|
||||
max-width: 428px;
|
||||
padding: 32px;
|
||||
background-color: ${theme.popupBg};
|
||||
box-shadow: 0 2px 18px 0 ${theme.popupInnerShadow}, 0 0 1px 0 ${theme.popupOuterShadow};
|
||||
outline: none;
|
||||
|
||||
@media ${mediaSmall} {
|
||||
& {
|
||||
padding: 24px;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssPopupTitle = styled('div', `
|
||||
font-size: ${vars.xxxlargeFontSize};
|
||||
font-weight: ${vars.headerControlTextWeight};
|
||||
color: ${theme.text};
|
||||
margin: 0 0 16px 0;
|
||||
line-height: 32px;
|
||||
overflow-wrap: break-word;
|
||||
`);
|
||||
|
||||
export const cssPopupBody = styled('div', `
|
||||
color: ${theme.text};
|
||||
`);
|
||||
|
||||
export const cssPopupButtons = styled('div', `
|
||||
margin: 24px 0 0 0;
|
||||
|
||||
& > button,
|
||||
& > .${cssButton.className} {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
`);
|
||||
|
||||
export const cssPopupCloseButton = styled('div', `
|
||||
align-self: flex-end;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
--icon-color: ${theme.popupCloseButtonFg};
|
||||
|
||||
&:hover {
|
||||
background-color: ${theme.hover};
|
||||
}
|
||||
`);
|
||||
Reference in New Issue
Block a user