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:
@@ -733,8 +733,17 @@ BaseView.prototype.getLastDataRowIndex = function() {
|
||||
* Creates and opens ColumnFilterMenu for a given field/column, and returns its PopupControl.
|
||||
*/
|
||||
BaseView.prototype.createFilterMenu = function(openCtl, filterInfo, options) {
|
||||
return createFilterMenu(openCtl, this._sectionFilter, filterInfo, this._mainRowSource,
|
||||
this.tableModel.tableData, options);
|
||||
const {showAllFiltersButton, onClose} = options;
|
||||
return createFilterMenu({
|
||||
openCtl,
|
||||
sectionFilter: this._sectionFilter,
|
||||
filterInfo,
|
||||
rowSource: this._mainRowSource,
|
||||
tableData: this.tableModel.tableData,
|
||||
gristDoc: this.gristDoc,
|
||||
showAllFiltersButton,
|
||||
onClose,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
114
app/client/components/BehavioralPrompts.ts
Normal file
114
app/client/components/BehavioralPrompts.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {showBehavioralPrompt} from 'app/client/components/modals';
|
||||
import {AppModel} from 'app/client/models/AppModel';
|
||||
import {GristBehavioralPrompts} from 'app/client/ui/GristTooltips';
|
||||
import {isNarrowScreen} from 'app/client/ui2018/cssVars';
|
||||
import {BehavioralPrompt} from 'app/common/Prefs';
|
||||
import {Computed, Disposable, dom} from 'grainjs';
|
||||
import {IPopupOptions} from 'popweasel';
|
||||
|
||||
export interface AttachOptions {
|
||||
/** Defaults to false. */
|
||||
hideArrow?: boolean;
|
||||
popupOptions?: IPopupOptions;
|
||||
onDispose?(): void;
|
||||
}
|
||||
|
||||
interface QueuedTip {
|
||||
prompt: BehavioralPrompt;
|
||||
refElement: Element;
|
||||
options: AttachOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages tips that are shown the first time a user performs some action.
|
||||
*
|
||||
* Tips are shown in the order that they are attached.
|
||||
*/
|
||||
export class BehavioralPrompts extends Disposable {
|
||||
private _prefs = this._appModel.behavioralPrompts;
|
||||
private _dismissedTips: Computed<Set<BehavioralPrompt>> = Computed.create(this, use => {
|
||||
const {dismissedTips} = use(this._prefs);
|
||||
return new Set(dismissedTips.filter(BehavioralPrompt.guard));
|
||||
});
|
||||
private _queuedTips: QueuedTip[] = [];
|
||||
|
||||
constructor(private _appModel: AppModel) {
|
||||
super();
|
||||
}
|
||||
|
||||
public showTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions = {}) {
|
||||
this._queueTip(refElement, prompt, options);
|
||||
}
|
||||
|
||||
public attachTip(prompt: BehavioralPrompt, options: AttachOptions = {}) {
|
||||
return (element: Element) => {
|
||||
this._queueTip(element, prompt, options);
|
||||
};
|
||||
}
|
||||
|
||||
public hasSeenTip(prompt: BehavioralPrompt) {
|
||||
return this._dismissedTips.get().has(prompt);
|
||||
}
|
||||
|
||||
private _queueTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions) {
|
||||
if (isNarrowScreen() || this._prefs.get().dontShowTips || this.hasSeenTip(prompt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._queuedTips.push({prompt, refElement, options});
|
||||
if (this._queuedTips.length > 1) {
|
||||
// If we're already showing a tip, wait for that one to be dismissed, which will
|
||||
// cause the next one in the queue to be shown.
|
||||
return;
|
||||
}
|
||||
|
||||
this._showTip(refElement, prompt, options);
|
||||
}
|
||||
|
||||
private _showTip(refElement: Element, prompt: BehavioralPrompt, options: AttachOptions) {
|
||||
const close = () => {
|
||||
if (!ctl.isDisposed()) {
|
||||
ctl.close();
|
||||
}
|
||||
};
|
||||
|
||||
const {hideArrow = false, onDispose, popupOptions} = options;
|
||||
const {title, content} = GristBehavioralPrompts[prompt];
|
||||
const ctl = showBehavioralPrompt(refElement, title, content(), {
|
||||
onClose: (dontShowTips) => {
|
||||
if (dontShowTips) { this._dontShowTips(); }
|
||||
this._markAsSeen(prompt);
|
||||
},
|
||||
hideArrow,
|
||||
popupOptions,
|
||||
});
|
||||
|
||||
ctl.onDispose(() => {
|
||||
onDispose?.();
|
||||
this._showNextQueuedTip();
|
||||
});
|
||||
dom.onElem(refElement, 'click', () => close());
|
||||
dom.onDisposeElem(refElement, () => close());
|
||||
}
|
||||
|
||||
private _showNextQueuedTip() {
|
||||
this._queuedTips.shift();
|
||||
if (this._queuedTips.length !== 0) {
|
||||
const [nextTip] = this._queuedTips;
|
||||
const {refElement, prompt, options} = nextTip;
|
||||
this._showTip(refElement, prompt, options);
|
||||
}
|
||||
}
|
||||
|
||||
private _markAsSeen(prompt: BehavioralPrompt) {
|
||||
const {dismissedTips} = this._prefs.get();
|
||||
const newDismissedTips = new Set(dismissedTips);
|
||||
newDismissedTips.add(prompt);
|
||||
this._prefs.set({...this._prefs.get(), dismissedTips: [...newDismissedTips]});
|
||||
}
|
||||
|
||||
private _dontShowTips() {
|
||||
this._prefs.set({...this._prefs.get(), dontShowTips: true});
|
||||
this._queuedTips = [];
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export class DataTables extends Disposable {
|
||||
cssTableList(
|
||||
/*************** List section **********/
|
||||
testId('list'),
|
||||
docListHeader(t('RawDataTables')),
|
||||
cssHeader(t('RawDataTables')),
|
||||
cssList(
|
||||
dom.forEach(this._tables, tableRec =>
|
||||
cssItem(
|
||||
@@ -185,6 +185,10 @@ const container = styled('div', `
|
||||
position: relative;
|
||||
`);
|
||||
|
||||
const cssHeader = styled(docListHeader, `
|
||||
display: inline-block;
|
||||
`);
|
||||
|
||||
const cssList = styled('div', `
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import {AccessRules} from 'app/client/aclui/AccessRules';
|
||||
import {ActionLog} from 'app/client/components/ActionLog';
|
||||
import BaseView from 'app/client/components/BaseView';
|
||||
import {BehavioralPrompts} from 'app/client/components/BehavioralPrompts';
|
||||
import {isNumericLike, isNumericOnly} from 'app/client/components/ChartView';
|
||||
import {CodeEditorPanel} from 'app/client/components/CodeEditorPanel';
|
||||
import * as commands from 'app/client/components/commands';
|
||||
@@ -46,6 +47,7 @@ import {isTourActive} from "app/client/ui/OnBoardingPopups";
|
||||
import {IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
|
||||
import {linkFromId, selectBy} from 'app/client/ui/selectBy';
|
||||
import {startWelcomeTour} from 'app/client/ui/welcomeTour';
|
||||
import {IWidgetType} from 'app/client/ui/widgetTypes';
|
||||
import {isNarrowScreen, mediaSmall, testId} from 'app/client/ui2018/cssVars';
|
||||
import {IconName} from 'app/client/ui2018/IconList';
|
||||
import {invokePrompt} from 'app/client/ui2018/modals';
|
||||
@@ -164,6 +166,8 @@ export class GristDoc extends DisposableWithEvents {
|
||||
// If the doc has a docTour. Used also to enable the UI button to restart the tour.
|
||||
public readonly hasDocTour: Computed<boolean>;
|
||||
|
||||
public readonly behavioralPrompts = BehavioralPrompts.create(this, this.docPageModel.appModel);
|
||||
|
||||
private _actionLog: ActionLog;
|
||||
private _undoStack: UndoStack;
|
||||
private _lastOwnActionGroup: ActionGroupWithCursorPos|null = null;
|
||||
@@ -601,6 +605,8 @@ export class GristDoc extends DisposableWithEvents {
|
||||
|
||||
// The newly-added section should be given focus.
|
||||
this.viewModel.activeSectionId(res.sectionRef);
|
||||
|
||||
this._maybeShowEditCardLayoutTip(val.type).catch(reportError);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -641,6 +647,8 @@ export class GristDoc extends DisposableWithEvents {
|
||||
await this.openDocPage(result.viewRef);
|
||||
// The newly-added section should be given focus.
|
||||
this.viewModel.activeSectionId(result.sectionRef);
|
||||
|
||||
this._maybeShowEditCardLayoutTip(val.type).catch(reportError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,6 +1095,32 @@ export class GristDoc extends DisposableWithEvents {
|
||||
}
|
||||
}
|
||||
|
||||
private async _maybeShowEditCardLayoutTip(selectedWidgetType: IWidgetType) {
|
||||
if (
|
||||
// Don't show the tip if a non-card widget was selected.
|
||||
!['single', 'detail'].includes(selectedWidgetType) ||
|
||||
// Or if we've already seen it.
|
||||
this.behavioralPrompts.hasSeenTip('editCardLayout')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the right panel to the widget subtab.
|
||||
commands.allCommands.viewTabOpen.run();
|
||||
|
||||
// Wait for the right panel to finish animation if it was collapsed before.
|
||||
await commands.allCommands.rightPanelOpen.run();
|
||||
|
||||
const editLayoutButton = document.querySelector('.behavioral-prompt-edit-card-layout');
|
||||
if (!editLayoutButton) { throw new Error('GristDoc failed to find edit card layout button'); }
|
||||
|
||||
this.behavioralPrompts.showTip(editLayoutButton, 'editCardLayout', {
|
||||
popupOptions: {
|
||||
placement: 'left-start',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async _promptForName() {
|
||||
return await invokePrompt("Table name", "Create", '', "Default table name");
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export class RawDataPage extends Disposable {
|
||||
|
||||
public buildDom() {
|
||||
return cssContainer(
|
||||
dom('div', this._gristDoc.behavioralPrompts.attachTip('rawDataPage', {hideArrow: true})),
|
||||
dom('div',
|
||||
dom.create(DataTables, this._gristDoc),
|
||||
dom.create(DocumentUsage, this._gristDoc.docPageModel),
|
||||
|
||||
@@ -30,12 +30,10 @@
|
||||
|
||||
.g_record_delete_field {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background-color: #404040;
|
||||
border: 1px solid #404040;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
border-radius: 1rem;
|
||||
color: white;
|
||||
color: var(--grist-theme-control-secondary-fg, #404040);
|
||||
cursor: pointer;
|
||||
|
||||
display: none;
|
||||
|
||||
@@ -125,7 +125,7 @@ RecordLayoutEditor.prototype.buildFinishButtons = function() {
|
||||
|
||||
RecordLayoutEditor.prototype.buildLeafDom = function() {
|
||||
return dom('div.layout_grabbable.g_record_layout_editing',
|
||||
dom('div.g_record_delete_field.glyphicon.glyphicon-remove',
|
||||
dom('div.g_record_delete_field.glyphicon.glyphicon-eye-close',
|
||||
dom.on('mousedown', (ev) => ev.stopPropagation()),
|
||||
dom.on('click', (ev, elem) => {
|
||||
ev.preventDefault();
|
||||
|
||||
@@ -177,7 +177,8 @@ ViewConfigTab.prototype._buildLayoutDom = function() {
|
||||
dom.autoDispose(layoutEditorObs),
|
||||
dom.on('click', () => commands.allCommands.editLayout.run()),
|
||||
grainjsDom.hide(layoutEditorObs),
|
||||
testId('detail-edit-layout')
|
||||
grainjsDom.cls('behavioral-prompt-edit-card-layout'),
|
||||
testId('detail-edit-layout'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {reportError} from 'app/client/models/errors';
|
||||
import {filterBar} from 'app/client/ui/FilterBar';
|
||||
import {viewSectionMenu} from 'app/client/ui/ViewSectionMenu';
|
||||
import {buildWidgetTitle} from 'app/client/ui/WidgetTitle';
|
||||
import {isNarrowScreenObs, colors, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {colors, isNarrowScreenObs, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||
import {mod} from 'app/common/gutil';
|
||||
@@ -323,7 +323,7 @@ export function buildViewSectionDom(options: {
|
||||
dom.create(viewSectionMenu, gristDoc, vs)
|
||||
)
|
||||
)),
|
||||
dom.create(filterBar, vs),
|
||||
dom.create(filterBar, gristDoc, vs),
|
||||
dom.maybe<BaseView|null>(vs.viewInstance, (viewInstance) => [
|
||||
dom('div.view_data_pane_container.flexvbox',
|
||||
cssResizing.cls('', isResizing),
|
||||
|
||||
@@ -116,6 +116,11 @@ exports.groups = [{
|
||||
keys: [],
|
||||
desc: 'Shortcut to open the left panel',
|
||||
},
|
||||
{
|
||||
name: 'rightPanelOpen',
|
||||
keys: [],
|
||||
desc: 'Shortcut to open the right panel',
|
||||
},
|
||||
{
|
||||
name: 'videoTourToolsOpen',
|
||||
keys: [],
|
||||
|
||||
@@ -2,11 +2,14 @@ import * as commands from 'app/client/components/commands';
|
||||
import {GristDoc} from 'app/client/components/GristDoc';
|
||||
import {FocusLayer} from 'app/client/lib/FocusLayer';
|
||||
import {reportSuccess} from 'app/client/models/errors';
|
||||
import {basicButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {basicButton, bigPrimaryButton, primaryButton} from 'app/client/ui2018/buttons';
|
||||
import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox';
|
||||
import {testId, theme} from 'app/client/ui2018/cssVars';
|
||||
import {modalTooltip} from 'app/client/ui2018/modals';
|
||||
import {dom, DomContents, observable, styled} from 'grainjs';
|
||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||
import {icon} from 'app/client/ui2018/icons';
|
||||
import {cssModalTooltip, modalTooltip} from 'app/client/ui2018/modals';
|
||||
import {dom, DomContents, keyframes, observable, styled, svg} from 'grainjs';
|
||||
import {IPopupOptions} from 'popweasel';
|
||||
import merge = require('lodash/merge');
|
||||
|
||||
/**
|
||||
* This is a file for all custom and pre-configured popups, modals, toasts and tooltips, used
|
||||
@@ -131,6 +134,163 @@ export function reportUndo(
|
||||
}
|
||||
}
|
||||
|
||||
export interface ShowBehavioralPromptOptions {
|
||||
onClose: (dontShowTips: boolean) => void;
|
||||
/** Defaults to false. */
|
||||
hideArrow?: boolean;
|
||||
popupOptions?: IPopupOptions;
|
||||
}
|
||||
|
||||
export function showBehavioralPrompt(
|
||||
refElement: Element,
|
||||
title: string,
|
||||
content: DomContents,
|
||||
options: ShowBehavioralPromptOptions
|
||||
) {
|
||||
const {onClose, hideArrow, popupOptions} = options;
|
||||
const arrow = hideArrow ? null : buildArrow();
|
||||
const dontShowTips = observable(false);
|
||||
const tooltip = modalTooltip(refElement,
|
||||
(ctl) => [
|
||||
cssBehavioralPromptModal.cls(''),
|
||||
arrow,
|
||||
cssBehavioralPromptContainer(
|
||||
dom.autoDispose(dontShowTips),
|
||||
testId('behavioral-prompt'),
|
||||
elem => { FocusLayer.create(ctl, {defaultFocusElem: elem, pauseMousetrap: true}); },
|
||||
dom.onKeyDown({
|
||||
Escape: () => ctl.close(),
|
||||
Enter: () => { onClose(dontShowTips.get()); ctl.close(); },
|
||||
}),
|
||||
cssBehavioralPromptHeader(
|
||||
cssHeaderIconAndText(
|
||||
icon('Idea'),
|
||||
cssHeaderText('TIP'),
|
||||
),
|
||||
),
|
||||
cssBehavioralPromptBody(
|
||||
cssBehavioralPromptTitle(title, testId('behavioral-prompt-title')),
|
||||
content,
|
||||
cssButtons(
|
||||
dom.style('margin-top', '12px'),
|
||||
dom.style('justify-content', 'space-between'),
|
||||
dom.style('align-items', 'center'),
|
||||
dom('div',
|
||||
cssSkipTipsCheckbox(dontShowTips,
|
||||
cssSkipTipsCheckboxLabel("Don't show tips"),
|
||||
testId('behavioral-prompt-dont-show-tips')
|
||||
),
|
||||
),
|
||||
cssDismissPromptButton('Got it', testId('behavioral-prompt-dismiss'),
|
||||
dom.on('click', () => { onClose(dontShowTips.get()); ctl.close(); })
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
merge(popupOptions, {
|
||||
modifiers: {
|
||||
...(arrow ? {arrow: {element: arrow}}: {}),
|
||||
offset: {
|
||||
offset: '0,12',
|
||||
},
|
||||
}
|
||||
})
|
||||
);
|
||||
dom.onDisposeElem(refElement, () => {
|
||||
if (!tooltip.isDisposed()) {
|
||||
tooltip.close();
|
||||
}
|
||||
});
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
function buildArrow() {
|
||||
return cssArrowContainer(
|
||||
svg('svg',
|
||||
{style: 'width: 13px; height: 18px;'},
|
||||
svg('path', {'d': 'M 0 0 h 13 v 18 Z'}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function sideSelectorChunk(side: 'top'|'bottom'|'left'|'right') {
|
||||
return `.${cssModalTooltip.className}[x-placement^=${side}]`;
|
||||
}
|
||||
|
||||
function fadeInFromSide(side: 'top'|'bottom'|'left'|'right') {
|
||||
let startPosition: string;
|
||||
switch(side) {
|
||||
case 'top': {
|
||||
startPosition = '0px -25px';
|
||||
break;
|
||||
}
|
||||
case 'bottom': {
|
||||
startPosition = '0px 25px';
|
||||
break;
|
||||
}
|
||||
case'left': {
|
||||
startPosition = '-25px 0px';
|
||||
break;
|
||||
}
|
||||
case 'right': {
|
||||
startPosition = '25px 0px';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return keyframes(`
|
||||
from {translate: ${startPosition}; opacity: 0;}
|
||||
to {translate: 0px 0px; opacity: 1;}
|
||||
`);
|
||||
}
|
||||
|
||||
const HEADER_HEIGHT_PX = 30;
|
||||
|
||||
const cssArrowContainer = styled('div', `
|
||||
position: absolute;
|
||||
|
||||
& path {
|
||||
stroke: ${theme.popupBg};
|
||||
stroke-width: 2px;
|
||||
fill: ${theme.popupBg};
|
||||
}
|
||||
|
||||
${sideSelectorChunk('bottom')} > & path {
|
||||
stroke: ${theme.controlPrimaryBg};
|
||||
fill: ${theme.controlPrimaryBg};
|
||||
}
|
||||
|
||||
${sideSelectorChunk('top')} > & {
|
||||
bottom: -17px;
|
||||
}
|
||||
|
||||
${sideSelectorChunk('bottom')} > & {
|
||||
top: -14px;
|
||||
}
|
||||
|
||||
${sideSelectorChunk('right')} > & {
|
||||
left: -12px;
|
||||
margin: ${HEADER_HEIGHT_PX}px 0px ${HEADER_HEIGHT_PX}px 0px;
|
||||
}
|
||||
|
||||
${sideSelectorChunk('left')} > & {
|
||||
right: -12px;
|
||||
margin: ${HEADER_HEIGHT_PX}px 0px ${HEADER_HEIGHT_PX}px 0px;
|
||||
}
|
||||
|
||||
${sideSelectorChunk('top')} svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
${sideSelectorChunk('bottom')} svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
${sideSelectorChunk('left')} svg {
|
||||
transform: scalex(-1);
|
||||
}
|
||||
`);
|
||||
|
||||
const cssTheme = styled('div', `
|
||||
color: ${theme.text};
|
||||
`);
|
||||
@@ -147,3 +307,86 @@ const cssContainer = styled(cssTheme, `
|
||||
const cssWideContainer = styled(cssTheme, `
|
||||
max-width: 340px;
|
||||
`);
|
||||
|
||||
const cssFadeInFromTop = fadeInFromSide('top');
|
||||
|
||||
const cssFadeInFromBottom = fadeInFromSide('bottom');
|
||||
|
||||
const cssFadeInFromLeft = fadeInFromSide('left');
|
||||
|
||||
const cssFadeInFromRight = fadeInFromSide('right');
|
||||
|
||||
const cssBehavioralPromptModal = styled('div', `
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
width: 400px;
|
||||
border-radius: 4px;
|
||||
|
||||
animation-duration: 0.4s;
|
||||
position: relative;
|
||||
|
||||
&[x-placement^=top] {
|
||||
animation-name: ${cssFadeInFromTop};
|
||||
}
|
||||
|
||||
&[x-placement^=bottom] {
|
||||
animation-name: ${cssFadeInFromBottom};
|
||||
}
|
||||
|
||||
&[x-placement^=left] {
|
||||
animation-name: ${cssFadeInFromLeft};
|
||||
}
|
||||
|
||||
&[x-placement^=right] {
|
||||
animation-name: ${cssFadeInFromRight};
|
||||
}
|
||||
`);
|
||||
|
||||
const cssBehavioralPromptContainer = styled(cssTheme, `
|
||||
line-height: 18px;
|
||||
`);
|
||||
|
||||
const cssBehavioralPromptHeader = styled('div', `
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: ${theme.controlPrimaryBg};
|
||||
color: ${theme.controlPrimaryFg};
|
||||
--icon-color: ${theme.controlPrimaryFg};
|
||||
border-radius: 4px 4px 0px 0px;
|
||||
line-height: ${HEADER_HEIGHT_PX}px;
|
||||
`);
|
||||
|
||||
const cssBehavioralPromptBody = styled('div', `
|
||||
padding: 16px;
|
||||
`);
|
||||
|
||||
const cssHeaderIconAndText = styled('div', `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 8px;
|
||||
`);
|
||||
|
||||
const cssHeaderText = styled('div', `
|
||||
font-weight: 600;
|
||||
`);
|
||||
|
||||
const cssDismissPromptButton = styled(bigPrimaryButton, `
|
||||
margin-right: 8px;
|
||||
`);
|
||||
|
||||
const cssBehavioralPromptTitle = styled('div', `
|
||||
font-size: ${vars.xxxlargeFontSize};
|
||||
font-weight: ${vars.headerControlTextWeight};
|
||||
color: ${theme.text};
|
||||
margin: 0 0 16px 0;
|
||||
line-height: 32px;
|
||||
`);
|
||||
|
||||
const cssSkipTipsCheckbox = styled(labeledSquareCheckbox, `
|
||||
line-height: normal;
|
||||
`);
|
||||
|
||||
|
||||
const cssSkipTipsCheckboxLabel = styled('span', `
|
||||
color: ${theme.lightText};
|
||||
`);
|
||||
|
||||
Reference in New Issue
Block a user