mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
ec157dc469
Summary: Adds initial implementation of dark mode. Preferences for dark mode are available on the account settings page. Dark mode is currently a beta feature as there are still some small bugs to squash and a few remaining UI elements to style. Test Plan: Browser tests. Reviewers: jarek Reviewed By: jarek Subscribers: paulfitz, jarek Differential Revision: https://phab.getgrist.com/D3587
866 lines
42 KiB
TypeScript
866 lines
42 KiB
TypeScript
/**
|
|
* CSS Variables. To use in your web application, add `cssRootVars` to the class list for your app's
|
|
* root node, typically `<body>`.
|
|
*
|
|
* The fonts used attempt to default to system fonts as described here:
|
|
* https://css-tricks.com/snippets/css/system-font-stack/
|
|
*
|
|
*/
|
|
import {urlState} from 'app/client/models/gristUrlState';
|
|
import {getTheme, ProductFlavor} from 'app/client/ui/CustomThemes';
|
|
import {Theme, ThemeAppearance} from 'app/common/ThemePrefs';
|
|
import {dom, makeTestId, Observable, styled, TestId} from 'grainjs';
|
|
import debounce = require('lodash/debounce');
|
|
import values = require('lodash/values');
|
|
|
|
const VAR_PREFIX = 'grist';
|
|
|
|
class CustomProp {
|
|
constructor(public name: string, public value?: string, public fallback?: string | CustomProp) {
|
|
|
|
}
|
|
|
|
public decl(): string | undefined {
|
|
if (this.value === undefined) { return undefined; }
|
|
|
|
return `--${VAR_PREFIX}-${this.name}: ${this.value};`;
|
|
}
|
|
|
|
public toString(): string {
|
|
let value = `--${VAR_PREFIX}-${this.name}`;
|
|
if (this.fallback) {
|
|
value += `, ${this.fallback}`;
|
|
}
|
|
return `var(${value})`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Theme-agnostic color properties.
|
|
*
|
|
* These are appropriate for UI elements whose color should not change based on the active
|
|
* theme. Generally, you should instead use the properties defined in `theme`, which will change
|
|
* based on the active theme.
|
|
*/
|
|
export const colors = {
|
|
lightGrey: new CustomProp('color-light-grey', '#F7F7F7'),
|
|
mediumGrey: new CustomProp('color-medium-grey', 'rgba(217,217,217,0.6)'),
|
|
mediumGreyOpaque: new CustomProp('color-medium-grey-opaque', '#E8E8E8'),
|
|
darkGrey: new CustomProp('color-dark-grey', '#D9D9D9'),
|
|
|
|
light: new CustomProp('color-light', '#FFFFFF'),
|
|
dark: new CustomProp('color-dark', '#262633'),
|
|
darkBg: new CustomProp('color-dark-bg', '#262633'),
|
|
slate: new CustomProp('color-slate', '#929299'),
|
|
|
|
lightGreen: new CustomProp('color-light-green', '#16B378'),
|
|
darkGreen: new CustomProp('color-dark-green', '#009058'),
|
|
darkerGreen: new CustomProp('color-darker-green', '#007548'),
|
|
lighterGreen: new CustomProp('color-lighter-green', '#b1ffe2'),
|
|
|
|
lighterBlue: new CustomProp('color-lighter-blue', '#87b2f9'),
|
|
lightBlue: new CustomProp('color-light-blue', '#3B82F6'),
|
|
orange: new CustomProp('color-orange', '#F9AE41'),
|
|
|
|
cursor: new CustomProp('color-cursor', '#16B378'),
|
|
selection: new CustomProp('color-selection', 'rgba(22,179,120,0.15)'),
|
|
selectionOpaque: new CustomProp('color-selection-opaque', '#DCF4EB'),
|
|
selectionDarkerOpaque: new CustomProp('color-selection-darker-opaque', '#d6eee5'),
|
|
|
|
inactiveCursor: new CustomProp('color-inactive-cursor', '#A2E1C9'),
|
|
|
|
hover: new CustomProp('color-hover', '#bfbfbf'),
|
|
error: new CustomProp('color-error', '#D0021B'),
|
|
warning: new CustomProp('color-warning', '#F9AE41'),
|
|
warningBg: new CustomProp('color-warning-bg', '#dd962c'),
|
|
backdrop: new CustomProp('color-backdrop', 'rgba(38,38,51,0.9)')
|
|
|
|
};
|
|
|
|
export const vars = {
|
|
/* Fonts */
|
|
fontFamily: new CustomProp('font-family', `-apple-system,BlinkMacSystemFont,Segoe UI,
|
|
Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol`),
|
|
|
|
// This is more monospace and looks better for data that should often align (e.g. to have 00000
|
|
// take similar space to 11111). This is the main font for user data.
|
|
fontFamilyData: new CustomProp('font-family-data',
|
|
`Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol`),
|
|
|
|
/* Font sizes */
|
|
xxsmallFontSize: new CustomProp('xx-font-size', '8px'),
|
|
xsmallFontSize: new CustomProp('x-small-font-size', '10px'),
|
|
smallFontSize: new CustomProp('small-font-size', '11px'),
|
|
mediumFontSize: new CustomProp('medium-font-size', '13px'),
|
|
introFontSize: new CustomProp('intro-font-size', '14px'), // feels friendlier
|
|
largeFontSize: new CustomProp('large-font-size', '16px'),
|
|
xlargeFontSize: new CustomProp('x-large-font-size', '18px'),
|
|
xxlargeFontSize: new CustomProp('xx-large-font-size', '20px'),
|
|
xxxlargeFontSize: new CustomProp('xxx-large-font-size', '22px'),
|
|
|
|
/* Controls size and space */
|
|
controlFontSize: new CustomProp('control-font-size', '12px'),
|
|
smallControlFontSize: new CustomProp('small-control-font-size', '10px'),
|
|
bigControlFontSize: new CustomProp('big-control-font-size', '13px'),
|
|
headerControlFontSize: new CustomProp('header-control-font-size', '22px'),
|
|
bigControlTextWeight: new CustomProp('big-text-weight', '500'),
|
|
headerControlTextWeight: new CustomProp('header-text-weight', '600'),
|
|
|
|
/* Labels */
|
|
labelTextSize: new CustomProp('label-text-size', 'medium'),
|
|
labelTextBg: new CustomProp('label-text-bg', '#FFFFFF'),
|
|
labelActiveBg: new CustomProp('label-active-bg', '#F0F0F0'),
|
|
|
|
controlMargin: new CustomProp('normal-margin', '2px'),
|
|
controlPadding: new CustomProp('normal-padding', '3px 5px'),
|
|
tightPadding: new CustomProp('tight-padding', '1px 2px'),
|
|
loosePadding: new CustomProp('loose-padding', '5px 15px'),
|
|
|
|
/* Control colors and borders */
|
|
primaryBg: new CustomProp('primary-fg', '#16B378'),
|
|
primaryBgHover: new CustomProp('primary-fg-hover', '#009058'),
|
|
primaryFg: new CustomProp('primary-bg', '#ffffff'),
|
|
|
|
controlBg: new CustomProp('control-bg', '#ffffff'),
|
|
controlFg: new CustomProp('control-fg', '#16B378'),
|
|
controlFgHover: new CustomProp('primary-fg-hover', '#009058'),
|
|
|
|
controlBorder: new CustomProp('control-border', '1px solid #11B683'),
|
|
controlBorderRadius: new CustomProp('border-radius', '4px'),
|
|
|
|
logoBg: new CustomProp('logo-bg', '#040404'),
|
|
logoSize: new CustomProp('logo-size', '22px 22px'),
|
|
toastBg: new CustomProp('toast-bg', '#040404'),
|
|
};
|
|
|
|
/**
|
|
* Theme-related color properties.
|
|
*
|
|
* Unlike `colors`, these properties don't define any values as they aren't known ahead of time.
|
|
* Instead, when the application loads, CSS variables mapped to these properties are attached to
|
|
* the document based on the user's theme preferences.
|
|
*
|
|
* In the case that CSS variables aren't attached to the document, their fallback values will be
|
|
* used. This ensures that styles are still applied even when there's trouble fetching preferences,
|
|
* and also serves as a method of maintaining backwards compatibility with custom CSS rules that
|
|
* use legacy variable names (prefixed with `grist-color-`).
|
|
*/
|
|
export const theme = {
|
|
/* Text */
|
|
text: new CustomProp('theme-text', undefined, colors.dark),
|
|
lightText: new CustomProp('theme-text-light', undefined, colors.slate),
|
|
darkText: new CustomProp('theme-text-dark', undefined, 'black'),
|
|
errorText: new CustomProp('theme-text-error', undefined, colors.error),
|
|
dangerText: new CustomProp('theme-text-danger', undefined, '#FFA500'),
|
|
disabledText: new CustomProp('theme-text-disabled', undefined, colors.slate),
|
|
|
|
/* Page */
|
|
pageBg: new CustomProp('theme-page-bg', undefined, colors.lightGrey),
|
|
pageBackdrop: new CustomProp('theme-page-backdrop', undefined, 'grey'),
|
|
|
|
/* Page Panels */
|
|
mainPanelBg: new CustomProp('theme-page-panels-main-panel-bg', undefined, 'white'),
|
|
leftPanelBg: new CustomProp('theme-page-panels-left-panel-bg', undefined, colors.lightGrey),
|
|
rightPanelBg: new CustomProp('theme-page-panels-right-panel-bg', undefined, colors.lightGrey),
|
|
topHeaderBg: new CustomProp('theme-page-panels-top-header-bg', undefined, 'white'),
|
|
bottomFooterBg: new CustomProp('theme-page-panels-bottom-footer-bg', undefined, 'white'),
|
|
pagePanelsBorder: new CustomProp('theme-page-panels-border', undefined, colors.mediumGrey),
|
|
pagePanelsBorderResizing: new CustomProp('theme-page-panels-border-resizing', undefined,
|
|
colors.lightGreen),
|
|
sidePanelOpenerFg: new CustomProp('theme-page-panels-side-panel-opener-fg', undefined,
|
|
colors.slate),
|
|
sidePanelOpenerActiveFg: new CustomProp('theme-page-panels-side-panel-opener-active-fg',
|
|
undefined, 'white'),
|
|
sidePanelOpenerActiveBg: new CustomProp('theme-page-panels-side-panel-opener-active-bg',
|
|
undefined, colors.lightGreen),
|
|
|
|
/* Add New */
|
|
addNewCircleFg: new CustomProp('theme-add-new-circle-fg', undefined, colors.light),
|
|
addNewCircleBg: new CustomProp('theme-add-new-circle-bg', undefined, colors.darkGreen),
|
|
addNewCircleHoverBg: new CustomProp('theme-add-new-circle-hover-bg', undefined,
|
|
colors.darkerGreen),
|
|
addNewCircleSmallFg: new CustomProp('theme-add-new-circle-small-fg', undefined, colors.light),
|
|
addNewCircleSmallBg: new CustomProp('theme-add-new-circle-small-bg', undefined,
|
|
colors.lightGreen),
|
|
addNewCircleSmallHoverBg: new CustomProp('theme-add-new-circle-small-hover-bg', undefined,
|
|
colors.darkGreen),
|
|
|
|
/* Top Bar */
|
|
topBarButtonPrimaryFg: new CustomProp('theme-top-bar-button-primary-fg', undefined,
|
|
colors.lightGreen),
|
|
topBarButtonSecondaryFg: new CustomProp('theme-top-bar-button-secondary-fg', undefined,
|
|
colors.slate),
|
|
topBarButtonDisabledFg: new CustomProp('theme-top-bar-button-disabled-fg', undefined,
|
|
colors.darkGrey),
|
|
topBarButtonErrorFg: new CustomProp('theme-top-bar-button-error-fg', undefined, colors.error),
|
|
|
|
/* Notifications */
|
|
notificationsPanelHeaderBg: new CustomProp('theme-notifications-panel-header-bg', undefined,
|
|
colors.lightGrey),
|
|
notificationsPanelBodyBg: new CustomProp('theme-notifications-panel-body-bg', undefined,
|
|
'white'),
|
|
notificationsPanelBorder: new CustomProp('theme-notifications-panel-border', undefined,
|
|
colors.darkGrey),
|
|
|
|
/* Toasts */
|
|
toastText: new CustomProp('theme-toast-text', undefined, colors.light),
|
|
toastLightText: new CustomProp('theme-toast-text-light', undefined, colors.slate),
|
|
toastBg: new CustomProp('theme-toast-bg', undefined, vars.toastBg),
|
|
toastErrorIcon: new CustomProp('theme-toast-error-icon', undefined, colors.error),
|
|
toastErrorBg: new CustomProp('theme-toast-error-bg', undefined, colors.error),
|
|
toastSuccessIcon: new CustomProp('theme-toast-success-icon', undefined, colors.darkGreen),
|
|
toastSuccessBg: new CustomProp('theme-toast-success-bg', undefined, colors.darkGreen),
|
|
toastWarningIcon: new CustomProp('theme-toast-warning-icon', undefined, colors.warning),
|
|
toastWarningBg: new CustomProp('theme-toast-warning-bg', undefined, colors.warningBg),
|
|
toastInfoIcon: new CustomProp('theme-toast-info-icon', undefined, colors.lightBlue),
|
|
toastInfoBg: new CustomProp('theme-toast-info-bg', undefined, colors.lightBlue),
|
|
toastControlFg: new CustomProp('theme-toast-control-fg', undefined, colors.lightGreen),
|
|
toastInfoControlFg: new CustomProp('theme-toast-control-info-fg', undefined, colors.lighterBlue),
|
|
|
|
/* Tooltips */
|
|
tooltipFg: new CustomProp('theme-tooltip-fg', undefined, 'white'),
|
|
tooltipBg: new CustomProp('theme-tooltip-bg', undefined, 'rgba(0, 0, 0, 0.75)'),
|
|
tooltipIcon: new CustomProp('theme-tooltip-icon', undefined, colors.slate),
|
|
tooltipCloseButtonFg: new CustomProp('theme-tooltip-close-button-fg', undefined, 'white'),
|
|
tooltipCloseButtonHoverFg: new CustomProp('theme-tooltip-close-button-hover-fg', undefined,
|
|
'black'),
|
|
tooltipCloseButtonHoverBg: new CustomProp('theme-tooltip-close-button-hover-bg', undefined,
|
|
'white'),
|
|
|
|
/* Modals */
|
|
modalBg: new CustomProp('theme-modal-bg', undefined, 'white'),
|
|
modalBackdrop: new CustomProp('theme-modal-backdrop', undefined, colors.backdrop),
|
|
modalBorder: new CustomProp('theme-modal-border', undefined, colors.mediumGreyOpaque),
|
|
modalBorderDark: new CustomProp('theme-modal-border-dark', undefined, colors.darkGrey),
|
|
modalBorderHover: new CustomProp('theme-modal-border-hover', undefined, colors.slate),
|
|
modalInnerShadow: new CustomProp('theme-modal-shadow-inner', undefined,
|
|
'rgba(31, 37, 50, 0.31)'),
|
|
modalOuterShadow: new CustomProp('theme-modal-shadow-outer', undefined,
|
|
'rgba(76, 86, 103, 0.24)'),
|
|
modalCloseButtonFg: new CustomProp('theme-modal-close-button-fg', undefined, colors.slate),
|
|
modalBackdropCloseButtonFg: new CustomProp('theme-modal-backdrop-close-button-fg', undefined,
|
|
vars.primaryBg),
|
|
modalBackdropCloseButtonHoverFg: new CustomProp('theme-modal-backdrop-close-button-hover-fg',
|
|
undefined, colors.lighterGreen),
|
|
|
|
/* Popups */
|
|
popupBg: new CustomProp('theme-popup-bg', undefined, 'white'),
|
|
popupInnerShadow: new CustomProp('theme-popup-shadow-inner', undefined,
|
|
'rgba(31, 37, 50, 0.31)'),
|
|
popupOuterShadow: new CustomProp('theme-popup-shadow-outer', undefined,
|
|
'rgba(76, 86, 103, 0.24)'),
|
|
popupCloseButtonFg: new CustomProp('theme-popup-close-button-fg', undefined, colors.slate),
|
|
|
|
/* Progress Bars */
|
|
progressBarFg: new CustomProp('theme-progress-bar-fg', undefined, colors.lightGreen),
|
|
progressBarErrorFg: new CustomProp('theme-progress-bar-error-fg', undefined, colors.error),
|
|
progressBarBg: new CustomProp('theme-progress-bar-bg', undefined, colors.darkGrey),
|
|
|
|
/* Links */
|
|
link: new CustomProp('theme-link', undefined, colors.lightGreen),
|
|
linkHover: new CustomProp('theme-link-hover', undefined, colors.lightGreen),
|
|
|
|
/* Hover */
|
|
hover: new CustomProp('theme-hover', undefined, colors.mediumGrey),
|
|
lightHover: new CustomProp('theme-hover-light', undefined, colors.lightGrey),
|
|
|
|
/* Cell Editor */
|
|
cellEditorFg: new CustomProp('theme-cell-editor-fg', undefined, colors.dark),
|
|
cellEditorBg: new CustomProp('theme-cell-editor-bg', undefined, colors.light),
|
|
|
|
/* Cursor */
|
|
cursor: new CustomProp('theme-cursor', undefined, colors.cursor),
|
|
cursorInactive: new CustomProp('theme-cursor-inactive', undefined, colors.inactiveCursor),
|
|
cursorReadonly: new CustomProp('theme-cursor-readonly', undefined, colors.slate),
|
|
|
|
/* Tables */
|
|
tableHeaderFg: new CustomProp('theme-table-header-fg', undefined, 'unset'),
|
|
tableHeaderSelectedFg: new CustomProp('theme-table-header-selected-fg', undefined, 'unset'),
|
|
tableHeaderBg: new CustomProp('theme-table-header-bg', undefined, colors.lightGrey),
|
|
tableHeaderSelectedBg: new CustomProp('theme-table-header-selected-bg', undefined,
|
|
colors.mediumGreyOpaque),
|
|
tableHeaderBorder: new CustomProp('theme-table-header-border', undefined, 'lightgray'),
|
|
tableHeaderBorderDark: new CustomProp('theme-table-header-border-dark', undefined,
|
|
colors.darkGrey),
|
|
tableBodyBg: new CustomProp('theme-table-body-bg', undefined, 'unset'),
|
|
tableBodyBorder: new CustomProp('theme-table-body-border', undefined, colors.darkGrey),
|
|
tableAddNewBg: new CustomProp('theme-table-add-new-bg', undefined, 'inherit'),
|
|
tableScrollShadow: new CustomProp('theme-table-scroll-shadow', undefined, '#444444'),
|
|
tableFrozenColumnsBorder: new CustomProp('theme-table-frozen-columns-border', undefined,
|
|
'#999999'),
|
|
tableDragDropIndicator: new CustomProp('theme-table-drag-drop-indicator', undefined, 'gray'),
|
|
tableDragDropShadow: new CustomProp('theme-table-drag-drop-shadow', undefined, '#F0F0F0'),
|
|
|
|
/* Cards */
|
|
cardCompactWidgetBg: new CustomProp('theme-card-compact-widget-bg', undefined,
|
|
colors.mediumGrey),
|
|
cardCompactRecordBg: new CustomProp('theme-card-compact-record-bg', undefined, 'white'),
|
|
cardBlocksBg: new CustomProp('theme-card-blocks-bg', undefined, colors.mediumGrey),
|
|
cardFormLabel: new CustomProp('theme-card-form-label', undefined, colors.slate),
|
|
cardCompactLabel: new CustomProp('theme-card-compact-label', undefined, colors.slate),
|
|
cardBlocksLabel: new CustomProp('theme-card-blocks-label', undefined, colors.slate),
|
|
cardFormBorder: new CustomProp('theme-card-form-border', undefined, 'lightgrey'),
|
|
cardCompactBorder: new CustomProp('theme-card-compact-border', undefined, colors.darkGrey),
|
|
cardEditingLayoutBg: new CustomProp('theme-card-editing-layout-bg', undefined,
|
|
'rgba(192, 192, 192, 0.2)'),
|
|
cardEditingLayoutBorder: new CustomProp('theme-card-editing-layout-border', undefined,
|
|
colors.darkGrey),
|
|
|
|
/* Card Lists */
|
|
cardListFormBorder: new CustomProp('theme-card-list-form-border', undefined, colors.darkGrey),
|
|
cardListBlocksBorder: new CustomProp('theme-card-list-blocks-border', undefined,
|
|
colors.darkGrey),
|
|
|
|
/* Selection */
|
|
selection: new CustomProp('theme-selection', undefined, colors.selection),
|
|
selectionOpaqueFg: new CustomProp('theme-selection-opaque-fg', undefined, 'unset'),
|
|
selectionOpaqueBg: new CustomProp('theme-selection-opaque-bg', undefined,
|
|
colors.selectionOpaque),
|
|
selectionOpaqueDarkBg: new CustomProp('theme-selection-opaque-dark-bg', undefined,
|
|
colors.selectionDarkerOpaque),
|
|
|
|
/* Widgets */
|
|
widgetBorder: new CustomProp('theme-widget-border', undefined, colors.darkGrey),
|
|
widgetActiveBorder: new CustomProp('theme-widget-active-border', undefined, colors.lightGreen),
|
|
widgetInactiveStripesLight: new CustomProp('theme-widget-inactive-stripes-light', undefined,
|
|
colors.lightGrey),
|
|
widgetInactiveStripesDark: new CustomProp('theme-widget-inactive-stripes-dark', undefined,
|
|
colors.mediumGreyOpaque),
|
|
|
|
/* Pinned Docs */
|
|
pinnedDocFooterBg: new CustomProp('theme-pinned-doc-footer-bg', undefined, colors.light),
|
|
pinnedDocBorder: new CustomProp('theme-pinned-doc-border', undefined, colors.mediumGrey),
|
|
pinnedDocBorderHover: new CustomProp('theme-pinned-doc-border-hover', undefined, colors.slate),
|
|
pinnedDocEditorBg: new CustomProp('theme-pinned-doc-editor-bg', undefined, colors.mediumGrey),
|
|
|
|
/* Raw Data */
|
|
rawDataTableBorder: new CustomProp('theme-raw-data-table-border', undefined, colors.mediumGrey),
|
|
rawDataTableBorderHover: new CustomProp('theme-raw-data-table-border-hover',
|
|
undefined, colors.slate),
|
|
|
|
/* Controls */
|
|
controlFg: new CustomProp('theme-control-fg', undefined, vars.controlFg),
|
|
controlPrimaryFg: new CustomProp('theme-control-primary-fg', undefined, vars.primaryFg),
|
|
controlPrimaryBg: new CustomProp('theme-control-primary-bg', undefined, vars.primaryBg),
|
|
controlSecondaryFg: new CustomProp('theme-control-secondary-fg', undefined, colors.slate),
|
|
controlHoverFg: new CustomProp('theme-control-hover-fg', undefined, vars.controlFgHover),
|
|
controlPrimaryHoverBg: new CustomProp('theme-control-primary-hover-bg', undefined,
|
|
vars.primaryBgHover),
|
|
controlSecondaryHoverFg: new CustomProp('theme-control-secondary-hover-fg', undefined,
|
|
colors.dark),
|
|
controlSecondaryHoverBg: new CustomProp('theme-control-secondary-hover-bg', undefined,
|
|
colors.darkGrey),
|
|
controlDisabledFg: new CustomProp('theme-control-disabled-fg', undefined, colors.light),
|
|
controlDisabledBg: new CustomProp('theme-control-disabled-bg', undefined, colors.slate),
|
|
controlPrimaryDisabled: new CustomProp('theme-control-primary-disabled', undefined,
|
|
colors.inactiveCursor),
|
|
controlBorder: new CustomProp('theme-control-border', undefined, '#11B683'),
|
|
|
|
/* Checkboxes */
|
|
checkboxBg: new CustomProp('theme-checkbox-bg', undefined, colors.light),
|
|
checkboxDisabledBg: new CustomProp('theme-checkbox-disabled-bg', undefined, colors.darkGrey),
|
|
checkboxBorder: new CustomProp('theme-checkbox-border', undefined, colors.darkGrey),
|
|
checkboxBorderHover: new CustomProp('theme-checkbox-border-hover', undefined, colors.hover),
|
|
|
|
/* Move Docs */
|
|
moveDocsSelectedFg: new CustomProp('theme-move-docs-selected-fg', undefined, 'white'),
|
|
moveDocsSelectedBg: new CustomProp('theme-move-docs-selected-bg', undefined, colors.lightGreen),
|
|
moveDocsDisabledFg: new CustomProp('theme-move-docs-disabled-bg', undefined, colors.darkGrey),
|
|
|
|
/* Filter Bar */
|
|
filterBarButtonSavedFg: new CustomProp('theme-filter-bar-button-saved-fg', undefined,
|
|
colors.light),
|
|
filterBarButtonSavedBg: new CustomProp('theme-filter-bar-button-saved-bg', undefined,
|
|
colors.slate),
|
|
filterBarButtonSavedHoverBg: new CustomProp('theme-filter-bar-button-saved-hover-bg', undefined,
|
|
colors.darkGrey),
|
|
|
|
/* Icon Buttons */
|
|
iconButtonFg: new CustomProp('theme-icon-button-fg', undefined, colors.light),
|
|
iconButtonPrimaryBg: new CustomProp('theme-icon-button-primary-bg', undefined,
|
|
colors.lightGreen),
|
|
iconButtonPrimaryHoverBg: new CustomProp('theme-icon-button-primary-hover-bg',
|
|
undefined, colors.darkGreen),
|
|
iconButtonSecondaryBg: new CustomProp('theme-icon-button-secondary-bg', undefined,
|
|
colors.darkGrey),
|
|
iconButtonSecondaryHoverBg: new CustomProp('theme-icon-button-secondary-hover-bg',
|
|
undefined, colors.slate),
|
|
|
|
/* Left Panel */
|
|
pageHoverBg: new CustomProp('theme-left-panel-page-hover-bg', undefined, colors.mediumGrey),
|
|
activePageFg: new CustomProp('theme-left-panel-active-page-fg', undefined, 'white'),
|
|
activePageBg: new CustomProp('theme-left-panel-active-page-bg', undefined, colors.darkBg),
|
|
disabledPageFg: new CustomProp('theme-left-panel-disabled-page-fg', undefined, colors.darkGrey),
|
|
pageOptionsFg: new CustomProp('theme-left-panel-page-options-bg', undefined, colors.slate),
|
|
pageOptionsHoverFg: new CustomProp('theme-left-panel-page-options-hover-fg', undefined, 'white'),
|
|
pageOptionsHoverBg: new CustomProp('theme-left-panel-page-options-hover-bg', undefined,
|
|
colors.darkGrey),
|
|
pageOptionsSelectedHoverBg: new CustomProp('theme-left-panel-page-options-selected-hover-bg',
|
|
undefined, colors.slate),
|
|
pageInitialsFg: new CustomProp('theme-left-panel-page-initials-fg', undefined, 'white'),
|
|
pageInitialsBg: new CustomProp('theme-left-panel-page-initials-bg', undefined, colors.slate),
|
|
|
|
/* Right Panel */
|
|
rightPanelTabFg: new CustomProp('theme-right-panel-tab-fg', undefined, colors.dark),
|
|
rightPanelTabBg: new CustomProp('theme-right-panel-tab-bg', undefined, colors.lightGrey),
|
|
rightPanelTabIcon: new CustomProp('theme-right-panel-tab-icon', undefined, colors.slate),
|
|
rightPanelTabIconHover: new CustomProp('theme-right-panel-tab-icon-hover', undefined,
|
|
colors.lightGreen),
|
|
rightPanelTabHoverBg: new CustomProp('theme-right-panel-tab-hover-bg', undefined,
|
|
colors.mediumGrey),
|
|
rightPanelTabSelectedFg: new CustomProp('theme-right-panel-tab-selected-fg', undefined,
|
|
colors.light),
|
|
rightPanelTabSelectedBg: new CustomProp('theme-right-panel-tab-selected-bg', undefined,
|
|
colors.lightGreen),
|
|
rightPanelTabCloseButtonHoverBg: new CustomProp('theme-right-panel-tab-close-button-hover-bg',
|
|
undefined, colors.darkGreen),
|
|
rightPanelSubtabFg: new CustomProp('theme-right-panel-subtab-fg', undefined, colors.lightGreen),
|
|
rightPanelSubtabSelectedFg: new CustomProp('theme-right-panel-subtab-selected-fg', undefined,
|
|
colors.dark),
|
|
rightPanelSubtabSelectedUnderline: new CustomProp('theme-right-panel-subtab-selected-underline',
|
|
undefined, colors.lightGreen),
|
|
rightPanelSubtabHoverFg: new CustomProp('theme-right-panel-subtab-hover-fg', undefined,
|
|
colors.darkGreen),
|
|
rightPanelSubtabHoverUnderline: new CustomProp('theme-right-panel-subtab-hover-underline',
|
|
undefined, colors.lightGreen),
|
|
rightPanelDisabledOverlay: new CustomProp('theme-right-panel-disabled-overlay', undefined,
|
|
'white'),
|
|
rightPanelToggleButtonEnabledFg: new CustomProp('theme-right-panel-toggle-button-enabled-fg',
|
|
undefined, colors.light),
|
|
rightPanelToggleButtonEnabledBg: new CustomProp('theme-right-panel-toggle-button-enabled-bg',
|
|
undefined, colors.dark),
|
|
rightPanelToggleButtonEnabledHoverFg: new CustomProp(
|
|
'theme-right-panel-toggle-button-enabled-hover-fg', undefined, colors.darkGrey),
|
|
rightPanelToggleButtonDisabledFg: new CustomProp('theme-right-panel-toggle-button-disabled-fg',
|
|
undefined, colors.light),
|
|
rightPanelToggleButtonDisabledBg: new CustomProp('theme-right-panel-toggle-button-disabled-bg',
|
|
undefined, colors.mediumGreyOpaque),
|
|
rightPanelFieldSettingsBg: new CustomProp('theme-right-panel-field-settings-bg',
|
|
undefined, colors.mediumGreyOpaque),
|
|
rightPanelFieldSettingsButtonBg: new CustomProp('theme-right-panel-field-settings-button-bg',
|
|
undefined, 'lightgrey'),
|
|
|
|
/* Document History */
|
|
documentHistorySnapshotFg: new CustomProp('theme-document-history-snapshot-fg', undefined,
|
|
colors.dark),
|
|
documentHistorySnapshotSelectedFg: new CustomProp('theme-document-history-snapshot-selected-fg',
|
|
undefined, colors.light),
|
|
documentHistorySnapshotBg: new CustomProp('theme-document-history-snapshot-bg', undefined,
|
|
'white'),
|
|
documentHistorySnapshotSelectedBg: new CustomProp('theme-document-history-snapshot-selected-bg',
|
|
undefined, colors.dark),
|
|
documentHistorySnapshotBorder: new CustomProp('theme-document-history-snapshot-border',
|
|
undefined, colors.mediumGrey),
|
|
documentHistoryActivityText: new CustomProp('theme-document-history-activity-text', undefined,
|
|
'unset'),
|
|
documentHistoryActivityLightText: new CustomProp('theme-document-history-activity-text-light',
|
|
undefined, '#333333'),
|
|
|
|
/* Accents */
|
|
accentIcon: new CustomProp('theme-accent-icon', undefined, colors.lightGreen),
|
|
accentBorder: new CustomProp('theme-accent-border', undefined, colors.lightGreen),
|
|
accentText: new CustomProp('theme-accent-text', undefined, colors.lightGreen),
|
|
|
|
/* Inputs */
|
|
inputFg: new CustomProp('theme-input-fg', undefined, 'black'),
|
|
inputBg: new CustomProp('theme-input-bg', undefined, 'white'),
|
|
inputDisabledFg: new CustomProp('theme-input-disabled-fg', undefined, colors.slate),
|
|
inputDisabledBg: new CustomProp('theme-input-disabled-bg', undefined, colors.lightGrey),
|
|
inputPlaceholderFg: new CustomProp('theme-input-placeholder-fg', undefined, '#757575'),
|
|
inputBorder: new CustomProp('theme-input-border', undefined, colors.darkGrey),
|
|
inputValid: new CustomProp('theme-input-valid', undefined, colors.lightGreen),
|
|
inputInvalid: new CustomProp('theme-input-invalid', undefined, colors.error),
|
|
inputFocus: new CustomProp('theme-input-focus', undefined, '#5E9ED6'),
|
|
inputReadonlyBg: new CustomProp('theme-input-readonly-bg', undefined, colors.lightGrey),
|
|
inputReadonlyBorder: new CustomProp('theme-input-readonly-border', undefined, colors.mediumGreyOpaque),
|
|
|
|
/* Choice Entry */
|
|
choiceEntryBg: new CustomProp('theme-choice-entry-bg', undefined, 'white'),
|
|
choiceEntryBorder: new CustomProp('theme-choice-entry-border', undefined, colors.darkGrey),
|
|
choiceEntryBorderHover: new CustomProp('theme-choice-entry-border-hover', undefined,
|
|
colors.hover),
|
|
|
|
/* Select Buttons */
|
|
selectButtonFg: new CustomProp('theme-select-button-fg', undefined, colors.dark),
|
|
selectButtonPlaceholderFg: new CustomProp('theme-select-button-placeholder-fg', undefined,
|
|
colors.slate),
|
|
selectButtonDisabledFg: new CustomProp('theme-select-button-disabled-fg', undefined, 'grey'),
|
|
selectButtonBg: new CustomProp('theme-select-button-bg', undefined, 'white'),
|
|
selectButtonBorder: new CustomProp('theme-select-button-border', undefined, colors.darkGrey),
|
|
selectButtonBorderInvalid: new CustomProp('theme-select-button-border-invalid', undefined,
|
|
colors.error),
|
|
|
|
/* Menus */
|
|
menuText: new CustomProp('theme-menu-text', undefined, colors.slate),
|
|
menuLightText: new CustomProp('theme-menu-light-text', undefined, colors.slate),
|
|
menuBg: new CustomProp('theme-menu-bg', undefined, 'white'),
|
|
menuSubheaderFg: new CustomProp('theme-menu-subheader-fg', undefined, 'unset'),
|
|
menuBorder: new CustomProp('theme-menu-border', undefined, colors.mediumGreyOpaque),
|
|
menuShadow: new CustomProp('theme-menu-shadow', undefined, 'rgba(38, 38, 51, 0.6)'),
|
|
|
|
/* Menu Items */
|
|
menuItemFg: new CustomProp('theme-menu-item-fg', undefined, 'unset'),
|
|
menuItemSelectedFg: new CustomProp('theme-menu-item-selected-fg', undefined, colors.light),
|
|
menuItemSelectedBg: new CustomProp('theme-menu-item-selected-bg', undefined, vars.primaryBg),
|
|
menuItemDisabledFg: new CustomProp('theme-menu-item-disabled-fg', undefined, '#D9D9D9'),
|
|
menuItemIconFg: new CustomProp('theme-menu-item-icon-fg', undefined, colors.slate),
|
|
menuItemIconSelectedFg: new CustomProp('theme-menu-item-icon-selected-fg', undefined, 'white'),
|
|
menuItemLinkFg: new CustomProp('theme-menu-item-link-fg', undefined, colors.lightGreen),
|
|
menuItemLinkSelectedFg: new CustomProp('theme-menu-item-link-selected-fg', undefined,
|
|
colors.darkGreen),
|
|
menuItemLinkselectedBg: new CustomProp('theme-menu-item-link-selected-bg', undefined,
|
|
colors.mediumGreyOpaque),
|
|
|
|
/* Autocomplete */
|
|
autocompleteMatchText: new CustomProp('theme-autocomplete-match-text', undefined,
|
|
colors.lightGreen),
|
|
autocompleteSelectedMatchText: new CustomProp('theme-autocomplete-selected-match-text',
|
|
undefined, colors.lighterGreen),
|
|
autocompleteChoiceSelectedBg: new CustomProp('theme-autocomplete-item-selected-bg', undefined,
|
|
colors.mediumGreyOpaque),
|
|
|
|
/* Search */
|
|
searchBorder: new CustomProp('theme-search-border', undefined, 'grey'),
|
|
searchPrevNextButtonFg: new CustomProp('theme-search-prev-next-button-fg', undefined,
|
|
colors.slate),
|
|
searchPrevNextButtonBg: new CustomProp('theme-search-prev-next-button-bg', undefined,
|
|
colors.mediumGrey),
|
|
|
|
/* Loaders */
|
|
loaderFg: new CustomProp('theme-loader-fg', undefined, colors.lightGreen),
|
|
loaderBg: new CustomProp('theme-loader-bg', undefined, colors.darkGrey),
|
|
|
|
/* Site Switcher */
|
|
siteSwitcherActiveFg: new CustomProp('theme-site-switcher-active-fg', undefined, colors.light),
|
|
siteSwitcherActiveBg: new CustomProp('theme-site-switcher-active-bg', undefined, colors.dark),
|
|
|
|
/* Doc Menu */
|
|
docMenuDocOptionsFg: new CustomProp('theme-doc-menu-doc-options-fg', undefined, colors.darkGrey),
|
|
docMenuDocOptionsHoverFg: new CustomProp('theme-doc-menu-doc-options-hover-fg', undefined,
|
|
colors.slate),
|
|
docMenuDocOptionsHoverBg: new CustomProp('theme-doc-menu-doc-options-hover-bg', undefined,
|
|
colors.darkGrey),
|
|
|
|
/* Shortcut Keys */
|
|
shortcutKeyFg: new CustomProp('theme-shortcut-key-fg', undefined, 'black'),
|
|
shortcutKeyPrimaryFg: new CustomProp('theme-shortcut-key-primary-fg', undefined,
|
|
colors.darkGreen),
|
|
shortcutKeySecondaryFg: new CustomProp('theme-shortcut-key-secondary-fg', undefined,
|
|
colors.slate),
|
|
shortcutKeyBg: new CustomProp('theme-shortcut-key-bg', undefined, 'white'),
|
|
shortcutKeyBorder: new CustomProp('theme-shortcut-key-border', undefined, colors.slate),
|
|
|
|
/* Breadcrumbs */
|
|
breadcrumbsTagFg: new CustomProp('theme-breadcrumbs-tag-fg', undefined, 'white'),
|
|
breadcrumbsTagBg: new CustomProp('theme-breadcrumbs-tag-bg', undefined, colors.slate),
|
|
breadcrumbsTagAlertBg: new CustomProp('theme-breadcrumbs-tag-alert-fg', undefined, colors.error),
|
|
|
|
/* Page Widget Picker */
|
|
widgetPickerPrimaryBg: new CustomProp('theme-widget-picker-primary-bg', undefined, 'white'),
|
|
widgetPickerSecondaryBg: new CustomProp('theme-widget-picker-secondary-bg', undefined,
|
|
colors.lightGrey),
|
|
widgetPickerItemFg: new CustomProp('theme-widget-picker-item-fg', undefined, colors.lightGrey),
|
|
widgetPickerItemSelectedBg: new CustomProp('theme-widget-picker-item-selected-bg', undefined,
|
|
colors.lightGrey),
|
|
widgetPickerItemDisabledBg: new CustomProp('theme-widget-picker-item-disabled-bg', undefined,
|
|
colors.lightGrey),
|
|
widgetPickerIcon: new CustomProp('theme-widget-picker-icon', undefined, colors.slate),
|
|
widgetPickerPrimaryIcon: new CustomProp('theme-widget-picker-primary-icon', undefined,
|
|
colors.lightGreen),
|
|
widgetPickerSummaryIcon: new CustomProp('theme-widget-picker-summary-icon', undefined,
|
|
colors.darkGreen),
|
|
widgetPickerBorder: new CustomProp('theme-widget-picker-border', undefined, colors.mediumGrey),
|
|
widgetPickerShadow: new CustomProp('theme-widget-picker-shadow', undefined,
|
|
'rgba(38,38,51,0.20)'),
|
|
|
|
/* Code View */
|
|
codeViewText: new CustomProp('theme-code-view-text', undefined, '#444'),
|
|
codeViewKeyword: new CustomProp('theme-code-view-keyword', undefined, '#444'),
|
|
codeViewComment: new CustomProp('theme-code-view-comment', undefined, '#888888'),
|
|
codeViewMeta: new CustomProp('theme-code-view-meta', undefined, '#1F7199'),
|
|
codeViewTitle: new CustomProp('theme-code-view-title', undefined, '#880000'),
|
|
codeViewParams: new CustomProp('theme-code-view-params', undefined, '#444'),
|
|
codeViewString: new CustomProp('theme-code-view-string', undefined, '#880000'),
|
|
codeViewNumber: new CustomProp('theme-code-view-number', undefined, '#880000'),
|
|
|
|
/* Importer */
|
|
importerTableInfoBorder: new CustomProp('theme-importer-table-info-border', undefined, colors.darkGrey),
|
|
importerPreviewBorder: new CustomProp('theme-importer-preview-border', undefined,
|
|
colors.darkGrey),
|
|
importerSkippedTableOverlay: new CustomProp('theme-importer-skipped-table-overlay', undefined,
|
|
colors.mediumGrey),
|
|
importerMatchIcon: new CustomProp('theme-importer-match-icon', undefined, colors.darkGrey),
|
|
|
|
/* Menu Toggles */
|
|
menuToggleFg: new CustomProp('theme-menu-toggle-fg', undefined, colors.slate),
|
|
menuToggleHoverFg: new CustomProp('theme-menu-toggle-hover-fg', undefined, colors.darkGreen),
|
|
menuToggleActiveFg: new CustomProp('theme-menu-toggle-active-fg', undefined, colors.darkerGreen),
|
|
menuToggleBg: new CustomProp('theme-menu-toggle-bg', undefined, 'white'),
|
|
menuToggleBorder: new CustomProp('theme-menu-toggle-border', undefined, colors.slate),
|
|
|
|
/* Button Groups */
|
|
buttonGroupFg: new CustomProp('theme-button-group-fg', undefined, colors.dark),
|
|
buttonGroupLightFg: new CustomProp('theme-button-group-light-fg', undefined, colors.slate),
|
|
buttonGroupBg: new CustomProp('theme-button-group-bg', undefined, 'unset'),
|
|
buttonGroupIcon: new CustomProp('theme-button-group-icon', undefined, colors.slate),
|
|
buttonGroupBorder: new CustomProp('theme-button-group-border', undefined, colors.darkGrey),
|
|
buttonGroupBorderHover: new CustomProp('theme-button-group-border-hover', undefined,
|
|
colors.hover),
|
|
buttonGroupSelectedFg: new CustomProp('theme-button-group-selected-fg', undefined, colors.light),
|
|
buttonGroupLightSelectedFg: new CustomProp('theme-button-group-light-selected-fg', undefined,
|
|
colors.lightGreen),
|
|
buttonGroupSelectedBg: new CustomProp('theme-button-group-selected-bg', undefined, colors.dark),
|
|
buttonGroupSelectedBorder: new CustomProp('theme-button-group-selected-border', undefined,
|
|
colors.dark),
|
|
|
|
/* Access Rules */
|
|
accessRulesTableHeaderFg: new CustomProp('theme-access-rules-table-header-fg', undefined,
|
|
colors.dark),
|
|
accessRulesTableHeaderBg: new CustomProp('theme-access-rules-table-header-bg', undefined,
|
|
colors.mediumGrey),
|
|
accessRulesTableBodyFg: new CustomProp('theme-access-rules-table-body-fg', undefined,
|
|
colors.dark),
|
|
accessRulesTableBorder: new CustomProp('theme-access-rules-table-border', undefined,
|
|
colors.slate),
|
|
|
|
/* Cells */
|
|
cellFg: new CustomProp('theme-cell-fg', undefined, 'unset'),
|
|
cellBg: new CustomProp('theme-cell-bg', undefined, '#FFFFFF00'),
|
|
cellZebraBg: new CustomProp('theme-cell-zebra-bg', undefined, '#F8F8F8'),
|
|
|
|
/* Formula Editor */
|
|
formulaEditorBg: new CustomProp('theme-formula-editor-bg', undefined, 'white'),
|
|
|
|
/* Charts */
|
|
chartFg: new CustomProp('theme-chart-fg', undefined, '#444'),
|
|
chartBg: new CustomProp('theme-chart-bg', undefined, '#fff'),
|
|
chartLegendBg: new CustomProp('theme-chart-legend-bg', undefined, '#FFFFFF80'),
|
|
chartXAxis: new CustomProp('theme-chart-x-axis', undefined, '#444'),
|
|
chartYAxis: new CustomProp('theme-chart-y-axis', undefined, '#444'),
|
|
};
|
|
|
|
const cssColors = values(colors).map(v => v.decl()).join('\n');
|
|
const cssVars = values(vars).map(v => v.decl()).join('\n');
|
|
const cssFontParams = `
|
|
font-family: ${vars.fontFamily};
|
|
font-size: ${vars.mediumFontSize};
|
|
-moz-osx-font-smoothing: grayscale;
|
|
-webkit-font-smoothing: antialiased;
|
|
`;
|
|
|
|
// We set box-sizing globally to match bootstrap's setting of border-box, since we are integrating
|
|
// into an app which already has it set, and it's impossible to make things look consistently with
|
|
// AND without it. This duplicates bootstrap's setting.
|
|
const cssBorderBox = `
|
|
*, *:before, *:after {
|
|
-webkit-box-sizing: border-box;
|
|
-moz-box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
}
|
|
`;
|
|
|
|
// These styles duplicate bootstrap's global settings, which we rely on even on pages that don't
|
|
// have bootstrap.
|
|
const cssInputFonts = `
|
|
button, input, select, textarea {
|
|
font-family: inherit;
|
|
font-size: inherit;
|
|
line-height: inherit;
|
|
}
|
|
`;
|
|
|
|
// Font style classes used by style selector.
|
|
const cssFontStyles = `
|
|
.font-italic {
|
|
font-style: italic;
|
|
}
|
|
.font-bold {
|
|
font-weight: 800;
|
|
}
|
|
.font-underline {
|
|
text-decoration: underline;
|
|
}
|
|
.font-strikethrough {
|
|
text-decoration: line-through;
|
|
}
|
|
.font-strikethrough.font-underline {
|
|
text-decoration: line-through underline;
|
|
}
|
|
`;
|
|
|
|
const cssVarsOnly = styled('div', cssColors + cssVars);
|
|
const cssBodyVars = styled('div', cssFontParams + cssColors + cssVars + cssBorderBox + cssInputFonts + cssFontStyles);
|
|
|
|
const cssBody = styled('body', `
|
|
margin: 0;
|
|
height: 100%;
|
|
`);
|
|
|
|
const cssRoot = styled('html', `
|
|
height: 100%;
|
|
overflow: hidden;
|
|
`);
|
|
|
|
export const cssRootVars = cssBodyVars.className;
|
|
|
|
// Also make a globally available testId, with a simple "test-" prefix (i.e. in tests, query css
|
|
// class ".test-{name}". Ideally, we'd use noTestId() instead in production.
|
|
export const testId: TestId = makeTestId('test-');
|
|
|
|
// Min width for normal screen layout (in px). Note: <768px is bootstrap's definition of small
|
|
// screen (covers phones, including landscape, but not tablets).
|
|
const largeScreenWidth = 992;
|
|
const mediumScreenWidth = 768;
|
|
const smallScreenWidth = 576; // Anything below this is extra-small (e.g. portrait phones).
|
|
|
|
// Fractional width for max-query follows https://getbootstrap.com/docs/4.0/layout/overview/#responsive-breakpoints
|
|
export const mediaMedium = `(max-width: ${largeScreenWidth - 0.02}px)`;
|
|
export const mediaSmall = `(max-width: ${mediumScreenWidth - 0.02}px)`;
|
|
export const mediaNotSmall = `(min-width: ${mediumScreenWidth}px)`;
|
|
export const mediaXSmall = `(max-width: ${smallScreenWidth - 0.02}px)`;
|
|
|
|
export const mediaDeviceNotSmall = `(min-device-width: ${mediumScreenWidth}px)`;
|
|
|
|
export function isNarrowScreen() {
|
|
return window.innerWidth < mediumScreenWidth;
|
|
}
|
|
|
|
let _isNarrowScreenObs: Observable<boolean>|undefined;
|
|
|
|
// Returns a singleton observable for whether the screen is a small one.
|
|
export function isNarrowScreenObs(): Observable<boolean> {
|
|
if (!_isNarrowScreenObs) {
|
|
const obs = Observable.create<boolean>(null, isNarrowScreen());
|
|
window.addEventListener('resize', () => obs.set(isNarrowScreen()));
|
|
_isNarrowScreenObs = obs;
|
|
}
|
|
return _isNarrowScreenObs;
|
|
}
|
|
|
|
export const cssHideForNarrowScreen = styled('div', `
|
|
@media ${mediaSmall} {
|
|
& {
|
|
display: none !important;
|
|
}
|
|
}
|
|
`);
|
|
|
|
let _isScreenResizingObs: Observable<boolean>|undefined;
|
|
|
|
// Returns a singleton observable for whether user is currently resizing the window. (listen to
|
|
// `resize` events and uses a timer of 1000ms).
|
|
export function isScreenResizing(): Observable<boolean> {
|
|
if (!_isScreenResizingObs) {
|
|
const obs = Observable.create<boolean>(null, false);
|
|
const ping = debounce(() => obs.set(false), 1000);
|
|
window.addEventListener('resize', () => { obs.set(true); ping(); });
|
|
_isScreenResizingObs = obs;
|
|
}
|
|
return _isScreenResizingObs;
|
|
}
|
|
|
|
let _prefersDarkModeObs: Observable<boolean>|undefined;
|
|
|
|
/**
|
|
* Returns a singleton observable for whether the user agent prefers dark mode.
|
|
*/
|
|
export function prefersDarkModeObs(): Observable<boolean> {
|
|
if (!_prefersDarkModeObs) {
|
|
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const obs = Observable.create<boolean>(null, query.matches);
|
|
query.addEventListener('change', event => obs.set(event.matches));
|
|
_prefersDarkModeObs = obs;
|
|
}
|
|
return _prefersDarkModeObs;
|
|
}
|
|
|
|
/**
|
|
* Attaches the global css properties to the document's root to make them available in the page.
|
|
*/
|
|
export function attachCssRootVars(productFlavor: ProductFlavor, varsOnly: boolean = false) {
|
|
dom.update(document.documentElement, varsOnly ? dom.cls(cssVarsOnly.className) : dom.cls(cssRootVars));
|
|
document.documentElement.classList.add(cssRoot.className);
|
|
document.body.classList.add(cssBody.className);
|
|
const customTheme = getTheme(productFlavor);
|
|
if (customTheme.bodyClassName) {
|
|
document.body.classList.add(customTheme.bodyClassName);
|
|
}
|
|
const interfaceStyle = urlState().state.get().params?.style || 'full';
|
|
document.body.classList.add(`interface-${interfaceStyle}`);
|
|
}
|
|
|
|
/**
|
|
* Attaches theme-related css properties to the theme style element.
|
|
*/
|
|
export function attachCssThemeVars({appearance, colors: themeColors}: Theme) {
|
|
// Prepare the custom properties needed for applying the theme.
|
|
const properties = Object.entries(themeColors)
|
|
.map(([name, value]) => `--grist-theme-${name}: ${value};`);
|
|
|
|
// Include properties for styling the scrollbar.
|
|
properties.push(...getCssScrollbarProperties(appearance));
|
|
|
|
// Include properties for picking an appropriate background image.
|
|
properties.push(...getCssThemeBackgroundProperties(appearance));
|
|
|
|
// Apply the properties to the theme style element.
|
|
getOrCreateStyleElement('grist-theme').textContent = `:root {
|
|
${properties.join('\n')}
|
|
}`;
|
|
|
|
// Make the browser aware of the color scheme.
|
|
document.documentElement.style.setProperty(`color-scheme`, appearance);
|
|
|
|
// Cache the appearance in local storage; this is currently used to apply a suitable
|
|
// background image that's shown while the application is loading.
|
|
localStorage.setItem('appearance', appearance);
|
|
}
|
|
|
|
/**
|
|
* Gets scrollbar-related css properties that are appropriate for the given `appearance`.
|
|
*
|
|
* Note: Browser support for customizing scrollbars is still a mixed bag; the bulk of customization
|
|
* is non-standard and unsupported by Firefox. If support matures, we could expose some of these in
|
|
* custom themes, but for now we'll just go with reasonable presets.
|
|
*/
|
|
function getCssScrollbarProperties(appearance: ThemeAppearance) {
|
|
return [
|
|
'--scroll-bar-fg: ' +
|
|
(appearance === 'dark' ? '#6B6B6B;' : '#A8A8A8;'),
|
|
'--scroll-bar-hover-fg: ' +
|
|
(appearance === 'dark' ? '#7B7B7B;' : '#8F8F8F;'),
|
|
'--scroll-bar-active-fg: ' +
|
|
(appearance === 'dark' ? '#8B8B8B;' : '#7C7C7C;'),
|
|
'--scroll-bar-bg: ' +
|
|
(appearance === 'dark' ? '#2B2B2B;' : '#F0F0F0;'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Gets background-related css properties that are appropriate for the given `appearance`.
|
|
*
|
|
* Currently, this sets a property for showing a background image that's visible while a page
|
|
* is loading.
|
|
*/
|
|
function getCssThemeBackgroundProperties(appearance: ThemeAppearance) {
|
|
const value = appearance === 'dark'
|
|
? 'url("img/prismpattern.png")'
|
|
: 'url("img/gplaypattern.png")';
|
|
return [`--grist-theme-bg: ${value};`];
|
|
}
|
|
|
|
/**
|
|
* Gets or creates a style element in the head of the document with the given `id`.
|
|
*
|
|
* Useful for grouping CSS values such as theme custom properties without needing to
|
|
* pollute the document with in-line styles.
|
|
*/
|
|
function getOrCreateStyleElement(id: string) {
|
|
let style = document.head.querySelector(id);
|
|
if (style) { return style; }
|
|
style = document.createElement('style');
|
|
style.setAttribute('id', id);
|
|
document.head.append(style);
|
|
return style;
|
|
}
|