gristlabs_grist-core/app/client/ui2018/cssVars.ts

866 lines
42 KiB
TypeScript
Raw Normal View History

/**
2022-02-19 09:46:49 +00:00
* 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';
(core) Fix prevent auto-expansion when user is resizing browser window Summary: Diff fixes an annoying issue when the left panel would sometimes auto-expand when the user is resizing the window. Alghouth it is quite easy to reproduce the issue still would happen a bit randomly. Here are what was done to fix it: 1) The most annoying manifestation of the issue happens with doc menu page. Indeed the panel is not meant to be collapsing hence triggering overlapping expansion messes UI a great deal. This was fix by asserting that the panel was collapsible before triggering expansion `if (left.hideOpener) { return; }`. 2) To prevent issue to happen also with doc page, we first test whether the user is actually dragging using `ev1.buttons !== 0` and also we've added a `isScreeResizingObs` observable. Although this seems to have fixed the problem for me, other developers still reports occurence of the issue on there machine but at a lesser frequence. It is unknown what this solution does not fully work, still situation seems acceptable now (most annoying part was 1st item). Diff also brings another small improvement when using Grist in a split screen setup when Grist is on the right. Moving cursor back and forth between the two windows would frequently leave the left panel inadvertandly expanded. Diff added a fix to allow panel to collapse when cursor leave window. Test Plan: Tested manually as it is hard to test when selenium. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3562
2022-09-01 08:07:15 +00:00
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;
}
}
`);
(core) Fix prevent auto-expansion when user is resizing browser window Summary: Diff fixes an annoying issue when the left panel would sometimes auto-expand when the user is resizing the window. Alghouth it is quite easy to reproduce the issue still would happen a bit randomly. Here are what was done to fix it: 1) The most annoying manifestation of the issue happens with doc menu page. Indeed the panel is not meant to be collapsing hence triggering overlapping expansion messes UI a great deal. This was fix by asserting that the panel was collapsible before triggering expansion `if (left.hideOpener) { return; }`. 2) To prevent issue to happen also with doc page, we first test whether the user is actually dragging using `ev1.buttons !== 0` and also we've added a `isScreeResizingObs` observable. Although this seems to have fixed the problem for me, other developers still reports occurence of the issue on there machine but at a lesser frequence. It is unknown what this solution does not fully work, still situation seems acceptable now (most annoying part was 1st item). Diff also brings another small improvement when using Grist in a split screen setup when Grist is on the right. Moving cursor back and forth between the two windows would frequently leave the left panel inadvertandly expanded. Diff added a fix to allow panel to collapse when cursor leave window. Test Plan: Tested manually as it is hard to test when selenium. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3562
2022-09-01 08:07:15 +00:00
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;
}