From ec157dc4692104602cc5981d335fc8b23725de43 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Mon, 5 Sep 2022 18:51:57 -0700 Subject: [PATCH] (core) Add dark mode to user preferences 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 --- app/client/aclui/ACLUsers.ts | 8 +- app/client/aclui/AccessRules.ts | 36 +- app/client/app.css | 14 +- app/client/components/AceEditor.js | 19 +- app/client/components/ActionLog.css | 12 +- app/client/components/ChartView.ts | 91 ++-- app/client/components/CodeEditorPanel.css | 29 + app/client/components/DataTables.ts | 23 +- app/client/components/DetailView.css | 34 +- app/client/components/DocumentUsage.ts | 16 +- app/client/components/Drafts.ts | 6 +- app/client/components/FormulaTransform.ts | 5 +- app/client/components/GridView.css | 67 +-- app/client/components/Importer.ts | 40 +- app/client/components/ParseOptions.ts | 10 +- app/client/components/Preferences.css | 1 + app/client/components/RawDataPage.ts | 10 +- app/client/components/RecordLayout.css | 10 +- app/client/components/TypeTransform.ts | 5 +- app/client/components/ViewConfigTab.js | 32 +- app/client/components/ViewLayout.css | 15 +- app/client/components/ViewLayout.ts | 14 +- app/client/components/viewCommon.css | 67 +-- app/client/lib/ACSelect.ts | 28 +- app/client/lib/TokenField.ts | 4 +- app/client/lib/Validator.ts | 4 +- app/client/lib/koForm.css | 7 +- app/client/models/AppModel.ts | 49 ++ app/client/models/UserPrefs.ts | 36 +- app/client/ui/AccountPage.ts | 156 ++---- app/client/ui/AccountPageCss.ts | 108 ++++ app/client/ui/AccountWidget.ts | 12 +- app/client/ui/AddNewButton.ts | 19 +- app/client/ui/ApiKey.ts | 11 +- app/client/ui/App.css | 3 +- app/client/ui/AppHeader.ts | 15 +- app/client/ui/AppUI.ts | 1 + app/client/ui/ColumnFilterMenu.ts | 35 +- app/client/ui/DocHistory.ts | 21 +- app/client/ui/DocMenu.ts | 6 +- app/client/ui/DocMenuCss.ts | 54 +- app/client/ui/ExampleCard.ts | 12 +- app/client/ui/FieldConfig.ts | 41 +- app/client/ui/FilterBar.ts | 14 +- app/client/ui/GridViewMenus.ts | 14 +- app/client/ui/HomeIntro.ts | 3 +- app/client/ui/HomeLeftPane.ts | 7 +- app/client/ui/LeftPanelCommon.ts | 24 +- app/client/ui/MakeCopyMenu.ts | 6 +- app/client/ui/MenuToggle.ts | 18 +- app/client/ui/NotifyUI.ts | 57 +- app/client/ui/OnBoardingPopups.ts | 21 +- app/client/ui/OpenVideoTour.ts | 14 +- app/client/ui/PagePanels.ts | 51 +- app/client/ui/PageWidgetPicker.ts | 27 +- app/client/ui/Pages.ts | 9 +- app/client/ui/PinnedDocs.ts | 22 +- app/client/ui/RightPanel.ts | 56 +- app/client/ui/RightPanelStyles.ts | 10 +- app/client/ui/ShareMenu.ts | 24 +- app/client/ui/SiteSwitcher.ts | 8 +- app/client/ui/ThemeConfig.ts | 62 +++ app/client/ui/Tools.ts | 14 +- app/client/ui/TopBar.ts | 8 +- app/client/ui/TopBarCss.ts | 12 +- app/client/ui/TreeViewComponentCss.ts | 19 +- app/client/ui/TriggerFormulas.ts | 10 +- app/client/ui/UserImage.ts | 5 +- app/client/ui/UserItem.ts | 31 +- app/client/ui/UserManager.ts | 21 +- app/client/ui/ViewSectionMenu.ts | 40 +- app/client/ui/VisibleFieldsConfig.ts | 22 +- app/client/ui/WidgetTitle.ts | 16 +- app/client/ui/cssInput.ts | 10 +- app/client/ui/errorPages.ts | 6 +- app/client/ui/inputs.ts | 14 +- app/client/ui/modals.ts | 71 --- app/client/ui/tooltips.ts | 14 +- app/client/ui/transientInput.ts | 14 +- app/client/ui/welcomeTour.ts | 18 +- app/client/ui2018/ColorSelect.ts | 10 +- app/client/ui2018/breadcrumbs.ts | 30 +- app/client/ui2018/buttonSelect.ts | 29 +- app/client/ui2018/buttons.ts | 39 +- app/client/ui2018/checkbox.ts | 36 +- app/client/ui2018/cssVars.ts | 630 +++++++++++++++++++++- app/client/ui2018/draggableList.ts | 3 +- app/client/ui2018/editableLabel.ts | 6 +- app/client/ui2018/icons.ts | 12 +- app/client/ui2018/links.ts | 12 +- app/client/ui2018/loaders.ts | 14 +- app/client/ui2018/menus.ts | 73 ++- app/client/ui2018/modals.ts | 15 +- app/client/ui2018/pages.ts | 20 +- app/client/ui2018/search.ts | 23 +- app/client/ui2018/select.ts | 13 +- app/client/widgets/CellStyle.ts | 5 +- app/client/widgets/ChoiceListEditor.ts | 6 +- app/client/widgets/ChoiceListEntry.ts | 13 +- app/client/widgets/ChoiceToken.ts | 17 +- app/client/widgets/ConditionalStyle.ts | 12 +- app/client/widgets/FieldBuilder.css | 4 +- app/client/widgets/FieldBuilder.ts | 8 +- app/client/widgets/FormulaEditor.ts | 4 +- app/client/widgets/NumericTextBox.ts | 18 +- app/client/widgets/ReferenceEditor.ts | 19 +- app/client/widgets/ReferenceListEditor.ts | 4 +- app/client/widgets/TextBox.css | 3 +- app/client/widgets/TextEditor.css | 11 +- app/common/Prefs.ts | 6 +- app/common/ThemePrefs-ti.ts | 327 +++++++++++ app/common/ThemePrefs.ts | 430 +++++++++++++++ app/common/Themes.ts | 50 ++ app/common/gristUrls.ts | 3 + app/common/themes/GristDark.ts | 396 ++++++++++++++ app/common/themes/GristLight.ts | 396 ++++++++++++++ app/server/lib/ICreate.ts | 8 +- app/server/lib/sendAppPage.ts | 1 + static/account.html | 2 +- static/app.html | 3 +- static/error.html | 2 +- static/img/prismpattern.png | Bin 0 -> 9697 bytes 122 files changed, 3616 insertions(+), 1075 deletions(-) create mode 100644 app/client/ui/AccountPageCss.ts create mode 100644 app/client/ui/ThemeConfig.ts delete mode 100644 app/client/ui/modals.ts create mode 100644 app/common/ThemePrefs-ti.ts create mode 100644 app/common/ThemePrefs.ts create mode 100644 app/common/Themes.ts create mode 100644 app/common/themes/GristDark.ts create mode 100644 app/common/themes/GristLight.ts create mode 100644 static/img/prismpattern.png diff --git a/app/client/aclui/ACLUsers.ts b/app/client/aclui/ACLUsers.ts index d5c69ff8..5c549325 100644 --- a/app/client/aclui/ACLUsers.ts +++ b/app/client/aclui/ACLUsers.ts @@ -5,7 +5,7 @@ import {createUserImage} from 'app/client/ui/UserImage'; import {cssMemberImage, cssMemberListItem, cssMemberPrimary, cssMemberSecondary, cssMemberText} from 'app/client/ui/UserItem'; import {basicButton, basicButtonLink} from 'app/client/ui2018/buttons'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menuCssClass, menuDivider} from 'app/client/ui2018/menus'; import {PermissionDataWithExtraUsers} from 'app/common/ActiveDocAPI'; @@ -130,7 +130,7 @@ const cssUserItem = styled(cssMemberListItem, ` padding: 8px 16px; align-items: center; &:hover { - background-color: ${colors.lightGrey}; + background-color: ${theme.lightHover}; } `); @@ -146,7 +146,9 @@ const cssUserButton = styled('div', ` white-space: nowrap; gap: 4px; &:hover { - background-color: ${colors.darkGrey}; + --icon-color: ${theme.controlFg}; + color: ${theme.controlFg}; + background-color: ${theme.hover}; } &-disabled { visibility: hidden; diff --git a/app/client/aclui/AccessRules.ts b/app/client/aclui/AccessRules.ts index fc3e914f..9ce68b81 100644 --- a/app/client/aclui/AccessRules.ts +++ b/app/client/aclui/AccessRules.ts @@ -12,7 +12,7 @@ import {TableData} from 'app/client/models/TableData'; import {shadowScroll} from 'app/client/ui/shadowScroll'; import {bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons'; import {squareCheckbox} from 'app/client/ui2018/checkbox'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {cssIconButton, icon} from 'app/client/ui2018/icons'; import {menu, menuItemAsync} from 'app/client/ui2018/menus'; @@ -1445,45 +1445,50 @@ const cssSectionHeading = styled('div', ` align-items: center; margin-bottom: 8px; font-weight: bold; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssTableName = styled('span', ` - color: ${colors.dark}; + color: ${theme.text}; `); const cssInput = styled(textInput, ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; width: 100%; border: 1px solid transparent; cursor: pointer; &:hover { - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; } &:focus { - box-shadow: inset 0 0 0 1px ${colors.cursor}; - border-color: ${colors.cursor}; + box-shadow: inset 0 0 0 1px ${theme.controlFg}; + border-color: ${theme.controlFg}; cursor: unset; } &[disabled] { - color: ${colors.dark}; - background-color: ${colors.mediumGreyOpaque}; + color: ${theme.inputDisabledFg}; + background-color: ${theme.inputDisabledBg}; box-shadow: unset; border-color: transparent; } + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); const cssConditionError = styled('div', ` margin-top: 4px; width: 100%; - color: ${colors.error}; + color: ${theme.errorText}; `); /** * Fairly general table styles. */ const cssTableRounded = styled('div', ` - border: 1px solid ${colors.slate}; + border: 1px solid ${theme.accessRulesTableBorder}; border-radius: 8px; overflow: hidden; `); @@ -1491,7 +1496,7 @@ const cssTableRounded = styled('div', ` // Row with a border const cssTableRow = styled('div', ` display: flex; - border-bottom: 1px solid ${colors.slate}; + border-bottom: 1px solid ${theme.accessRulesTableBorder}; &:last-child { border-bottom: none; } @@ -1499,8 +1504,8 @@ const cssTableRow = styled('div', ` // Darker table header const cssTableHeaderRow = styled(cssTableRow, ` - background-color: ${colors.mediumGrey}; - color: ${colors.dark}; + background-color: ${theme.accessRulesTableHeaderBg}; + color: ${theme.accessRulesTableHeaderFg}; `); // Cell for table column header. @@ -1517,7 +1522,7 @@ const cssCell = styled('div', ` overflow: hidden; &-rborder { - border-right: 1px solid ${colors.slate}; + border-right: 1px solid ${theme.accessRulesTableBorder}; } &-center { text-align: center; @@ -1551,6 +1556,7 @@ const cssRuleBody = styled('div', ` `); const cssRuleDescription = styled('div', ` + color: ${theme.text}; display: flex; align-items: center; margin: 16px 0 8px 0; @@ -1568,6 +1574,6 @@ const cssCenterContent = styled('div', ` `); const cssDefaultLabel = styled('div', ` - color: ${colors.slate}; + color: ${theme.accessRulesTableBodyFg}; font-weight: bold; `); diff --git a/app/client/app.css b/app/client/app.css index 4c63fae2..1b468e69 100644 --- a/app/client/app.css +++ b/app/client/app.css @@ -44,7 +44,6 @@ --color-hint-text: #888; --scroll-bar-width: 12px; - --scroll-bar-bg: #f0f0f0; /* fonts */ --font-navbar-title: "Helvetica", "Arial", sans-serif; @@ -96,12 +95,12 @@ body { .show_scrollbar::-webkit-scrollbar { width: var(--scroll-bar-width); height: var(--scroll-bar-width); - background-color: var(--scroll-bar-bg); + background-color: var(--scroll-bar-bg, #f0f0f0); } .show_scrollbar::-webkit-scrollbar-thumb { - background-color: rgba(0,0,0,0.3); + background-color: var(--scroll-bar-fg, #a8a8a8); -webkit-border-radius: 100px; - border: 2px solid var(--scroll-bar-bg); + border: 2px solid var(--scroll-bar-bg, #f0f0f0); } .show_scrollbar::-webkit-scrollbar-thumb:vertical { min-height: 4rem; @@ -110,13 +109,16 @@ body { min-width: 4rem; } .show_scrollbar::-webkit-scrollbar-thumb:hover { - background-color: rgba(0,0,0,0.4); /* Some darker color when you click it */ + background-color: var(--scroll-bar-hover-fg, #8f8f8f); -webkit-border-radius: 100px; } .show_scrollbar::-webkit-scrollbar-thumb:active { - background-color: rgba(0,0,0,0.5); /* Some darker color when you click it */ + background-color: var(--scroll-bar-active-fg, #7c7c7c); -webkit-border-radius: 100px; } +.show_scrollbar::-webkit-scrollbar-corner { + background-color: var(--scroll-bar-bg, #f0f0f0); +} div.dev_warning { position: absolute; z-index: 10; diff --git a/app/client/components/AceEditor.js b/app/client/components/AceEditor.js index 6202d348..3f1fa561 100644 --- a/app/client/components/AceEditor.js +++ b/app/client/components/AceEditor.js @@ -1,10 +1,12 @@ var ace = require('brace'); var _ = require('underscore'); -// Used to load python language settings and 'chrome' ace style +// Used to load python language settings and color themes require('brace/mode/python'); require('brace/theme/chrome'); +require('brace/theme/dracula'); require('brace/ext/language_tools'); var {setupAceEditorCompletions} = require('./AceEditorCompletions'); +var {getGristConfig} = require('../../common/urlUtils'); var dom = require('../lib/dom'); var dispose = require('../lib/dispose'); var modelUtil = require('../models/modelUtil'); @@ -198,7 +200,14 @@ AceEditor.prototype._setup = function() { }); this.session = this.editor.getSession(); this.session.setMode('ace/mode/python'); - this.editor.setTheme('ace/theme/chrome'); + + const gristTheme = this.gristDoc?.docPageModel.appModel.currentTheme; + this._setAceTheme(gristTheme?.get()); + if (!getGristConfig().enableCustomCss && gristTheme) { + this.autoDispose(gristTheme.addListener((theme) => { + this._setAceTheme(theme); + })); + } // Default line numbers to hidden this.editor.renderer.setShowGutter(false); @@ -270,6 +279,12 @@ AceEditor.prototype._getContentHeight = function() { return Math.max(1, this.session.getScreenLength()) * this.editor.renderer.lineHeight; }; +AceEditor.prototype._setAceTheme = function(gristTheme) { + const {enableCustomCss} = getGristConfig(); + const gristAppearance = gristTheme?.appearance; + const aceTheme = gristAppearance === 'dark' && !enableCustomCss ? 'dracula' : 'chrome'; + this.editor.setTheme(`ace/theme/${aceTheme}`); +}; let _RangeConstructor = null; //singleton, load it lazily AceEditor.makeRange = function(a, b, c, d) { diff --git a/app/client/components/ActionLog.css b/app/client/components/ActionLog.css index df0a3688..78006cee 100644 --- a/app/client/components/ActionLog.css +++ b/app/client/components/ActionLog.css @@ -27,7 +27,11 @@ } .action_info_from_self { - color: #333333; + color: var(--grist-theme-document-history-activity-text-light, #333333); +} + +.action_desc { + color: var(--grist-theme-document-history-activity-text-light, unset); } .action_log_item.undone > .action_info, @@ -46,10 +50,12 @@ } .action_log_rename_pre { + color: #333333; background: #faa; } .action_log_rename_post { + color: #333333; background: #afa; } @@ -62,7 +68,7 @@ text-align: center; margin-top: 0; padding-top: 0; - color: #000; + color: var(--grist-theme-document-history-activity-text, #000); } .action_log_table td { @@ -100,6 +106,7 @@ } .action_log_cell_remove { + color: #333333; background: #faa; text-decoration: line-through; padding-left: 2px; @@ -111,6 +118,7 @@ } .action_log_cell_add { + color: #333333; background: #afa; padding-left: 2px; padding-right: 2px; diff --git a/app/client/components/ChartView.ts b/app/client/components/ChartView.ts index 310768ea..d17abc9c 100644 --- a/app/client/components/ChartView.ts +++ b/app/client/components/ChartView.ts @@ -16,7 +16,7 @@ import {cssLabel, cssRow, cssSeparator} from 'app/client/ui/RightPanelStyles'; import {cssFieldEntry, cssFieldLabel, IField, VisibleFieldsConfig } from 'app/client/ui/VisibleFieldsConfig'; import {IconName} from 'app/client/ui2018/IconList'; import {squareCheckbox} from 'app/client/ui2018/checkbox'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {cssDragger} from 'app/client/ui2018/draggableList'; import {icon} from 'app/client/ui2018/icons'; import {IOptionFull, linkSelect, menu, menuItem, menuText, select} from 'app/client/ui2018/menus'; @@ -33,6 +33,7 @@ import debounce = require('lodash/debounce'); import defaults = require('lodash/defaults'); import defaultsDeep = require('lodash/defaultsDeep'); import isNumber = require('lodash/isNumber'); +import merge = require('lodash/merge'); import sum = require('lodash/sum'); import union = require('lodash/union'); import type {Annotations, Config, Datum, ErrorBar, Layout, LayoutAxis, Margin, @@ -221,6 +222,8 @@ export class ChartView extends Disposable { this.listenTo(this.sortedRows, 'rowNotify', this._update); this.autoDispose(this.sortedRows.getKoArray().subscribe(this._update)); this.autoDispose(this._formatterComp.subscribe(this._update)); + this.autoDispose(this.gristDoc.docPageModel.appModel.currentTheme.addListener(() => + this._update())); } public prepareToPrint(onOff: boolean) { @@ -333,7 +336,7 @@ export class ChartView extends Disposable { // meantime and cause error later. So let's check again. if (this.isDisposed()) { return; } - const layout: Partial = defaultsDeep(plotData.layout, getPlotlyLayout(options)); + const layout: Partial = defaultsDeep(plotData.layout, this._getPlotlyLayout(options)); const config: Partial = {...plotData.config, displayModeBar: false}; // react() can be used in place of newPlot(), and is faster when updating an existing plot. await Plotly.react(this._chartDom, plotData.data, layout, config); @@ -348,6 +351,50 @@ export class ChartView extends Disposable { private _isCompatibleSeries(col: ColumnRec) { return isNumericOnly(this._chartType.peek()) ? isNumericLike(col) : true; } + + private _getPlotlyLayout(options: ChartOptions): Partial { + // Note that each call to getPlotlyLayout() creates a new layout object. We are intentionally + // avoiding reuse because Plotly caches too many layout calculations when the object is reused. + const yaxis: Partial = {automargin: true, title: {standoff: 0}}; + const xaxis: Partial = {automargin: true, title: {standoff: 0}}; + if (options.logYAxis) { yaxis.type = 'log'; } + if (options.invertYAxis) { yaxis.autorange = 'reversed'; } + const layout = { + // Margins include labels, titles, legend, and may get auto-expanded beyond this. + margin: { + l: 50, + r: 50, + b: 40, // Space below chart which includes x-axis labels + t: 30, // Space above the chart (doesn't include any text) + pad: 4 + } as Margin, + yaxis, + xaxis, + ...(options.stacked ? {barmode: 'relative'} : {}), + }; + return merge(layout, this._getPlotlyTheme()); + } + + private _getPlotlyTheme(): Partial { + const appModel = this.gristDoc.docPageModel.appModel; + const {colors} = appModel.currentTheme.get(); + return { + paper_bgcolor: colors['chart-bg'], + plot_bgcolor: colors['chart-bg'], + xaxis: { + color: colors['chart-x-axis'], + }, + yaxis: { + color: colors['chart-y-axis'], + }, + font: { + color: colors['chart-fg'], + }, + legend: { + bgcolor: colors['chart-legend-bg'], + }, + }; + } } /** @@ -420,32 +467,6 @@ function extractErrorBars(series: Series[], options: ChartOptions): Map { - // Note that each call to getPlotlyLayout() creates a new layout object. We are intentionally - // avoiding reuse because Plotly caches too many layout calculations when the object is reused. - const yaxis: Partial = {automargin: true, title: {standoff: 0}}; - const xaxis: Partial = {automargin: true, title: {standoff: 0}}; - if (options.logYAxis) { yaxis.type = 'log'; } - if (options.invertYAxis) { yaxis.autorange = 'reversed'; } - return { - // Margins include labels, titles, legend, and may get auto-expanded beyond this. - margin: { - l: 50, - r: 50, - b: 40, // Space below chart which includes x-axis labels - t: 30, // Space above the chart (doesn't include any text) - pad: 4 - } as Margin, - legend: { - // Translucent background, so chart data is still visible if legend overlaps it. - bgcolor: "#FFFFFF80", - }, - yaxis, - xaxis, - ...(options.stacked ? {barmode: 'relative'} : {}), - }; -} - /** * The grainjs component for side-pane configuration options for a Chart section. */ @@ -1257,7 +1278,7 @@ const cssRowLabel = styled('div', ` margin-right: 8px; font-weight: initial; /* negate bootstrap */ - color: ${colors.dark}; + color: ${theme.text}; overflow: hidden; text-overflow: ellipsis; user-select: none; @@ -1265,7 +1286,7 @@ const cssRowLabel = styled('div', ` const cssRowHelp = styled(cssRow, ` font-size: ${vars.smallFontSize}; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssAddIcon = styled(icon, ` @@ -1275,15 +1296,15 @@ const cssAddIcon = styled(icon, ` const cssAddYAxis = styled('div', ` display: flex; cursor: pointer; - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.controlFg}; + --icon-color: ${theme.controlFg}; &:not(:first-child) { margin-top: 8px; } &:hover, &:focus, &:active { - color: ${colors.darkGreen}; - --icon-color: ${colors.darkGreen}; + color: ${theme.controlHoverFg}; + --icon-color: ${theme.controlHoverFg}; } `); @@ -1299,7 +1320,7 @@ const cssRemoveIcon = styled(icon, ` const cssHintRow = styled('div', ` margin: -4px 16px 8px 16px; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssRangeInput = styled('input', ` diff --git a/app/client/components/CodeEditorPanel.css b/app/client/components/CodeEditorPanel.css index 7233bb6f..c946bbea 100644 --- a/app/client/components/CodeEditorPanel.css +++ b/app/client/components/CodeEditorPanel.css @@ -15,9 +15,38 @@ } .g-code-viewer.hljs { + color: var(--grist-theme-code-view-text, #444); background-color: inherit; } .g-code-panel-denied { text-align: center; } + +.g-code-viewer .hljs-keyword { + color: var(--grist-theme-code-view-keyword, #444); +} + +.g-code-viewer .hljs-comment { + color: var(--grist-theme-code-view-comment, #888888); +} + +.g-code-viewer .hljs-meta { + color: var(--grist-theme-code-view-meta, #1F7199); +} + +.g-code-viewer .hljs-title { + color: var(--grist-theme-code-view-title, #880000); +} + +.g-code-viewer .hljs-params { + color: var(--grist-theme-code-view-params, #444); +} + +.g-code-viewer .hljs-string { + color: var(--grist-theme-code-view-string, #880000); +} + +.g-code-viewer .hljs-number { + color: var(--grist-theme-code-view-number, #880000); +} diff --git a/app/client/components/DataTables.ts b/app/client/components/DataTables.ts index 26bf07bd..a05b9d7f 100644 --- a/app/client/components/DataTables.ts +++ b/app/client/components/DataTables.ts @@ -47,7 +47,7 @@ export class DataTables extends Disposable { cssItem( testId('table'), cssLeft( - dom.domComputed((use) => cssGreenIcon( + dom.domComputed((use) => cssTableTypeIcon( use(tableRec.summarySourceTable) !== 0 ? 'PivotLight' : 'TypeTable', testId(`table-id-${use(tableRec.tableId)}`) )), @@ -107,7 +107,7 @@ export class DataTables extends Disposable { const tableName = [ use(table.tableNameDef), isSummaryTable ? use(table.groupDesc) : '' ].filter(p => Boolean(p?.trim())).join(' '); - return dom('span', tableName); + return cssTableName(tableName); } else { return dom('div', // to disable flex grow in the widget dom.domComputed(fromKo(table.rawViewSection), vs => @@ -178,9 +178,9 @@ const cssItem = styled('div', ` width: 100%; height: calc(1em * 56/13); /* 56px for 13px font */ max-width: 750px; - border: 1px solid ${css.colors.mediumGrey}; + border: 1px solid ${css.theme.rawDataTableBorder}; &:hover { - border-color: ${css.colors.slate}; + border-color: ${css.theme.rawDataTableBorderHover}; } `); @@ -224,8 +224,8 @@ const cssRight = styled('div', ` flex: none; `); -const cssGreenIcon = styled(icon, ` - --icon-color: ${css.colors.lightGreen}; +const cssTableTypeIcon = styled(icon, ` + --icon-color: ${css.theme.accentIcon}; `); const cssLine = styled('span', ` @@ -246,7 +246,7 @@ const cssTableRowsWrapper = styled('div', ` width: 80px; overflow: hidden; align-items: baseline; - color: ${css.colors.slate}; + color: ${css.theme.lightText}; line-height: 18px; padding: 0px 2px; `); @@ -256,12 +256,12 @@ const cssHoverWrapper = styled('div', ` overflow: hidden; cursor: default; align-items: baseline; - color: ${css.colors.slate}; + color: ${css.theme.lightText}; transition: background 0.05s; padding: 0px 2px; line-height: 18px; &:hover { - background: ${css.colors.lightGrey}; + background: ${css.theme.lightHover}; } `); @@ -272,6 +272,7 @@ const cssTableId = styled(cssLine, ` const cssTableRows = cssTableId; const cssTableTitle = styled('div', ` + color: ${css.theme.text}; white-space: nowrap; `); @@ -294,3 +295,7 @@ const cssTableList = styled('div', ` const cssLoadingDots = styled(loadingDots, ` --dot-size: 6px; `); + +const cssTableName = styled('span', ` + color: ${css.theme.text}; +`); diff --git a/app/client/components/DetailView.css b/app/client/components/DetailView.css index 5338b3d5..bd38265a 100644 --- a/app/client/components/DetailView.css +++ b/app/client/components/DetailView.css @@ -9,7 +9,7 @@ left: 0; width: 100%; - background: white; + background: var(--grist-theme-page-panels-main-panel-bg, white); z-index: 1; margin-top: -3px; } @@ -37,15 +37,15 @@ min-height: 16px; white-space: pre; word-wrap: break-word; - color: black; + color: var(--grist-theme-cell-fg, black); } .g_record_detail_value.record-add { - background-color: #f6f6ff; + background-color: var(--grist-theme-table-add-new-bg, #f6f6ff); } .g_record_detail_value.scissors { - outline: 2px dashed var(--grist-color-cursor); + outline: 2px dashed var(--grist-theme-cursor, var(--grist-color-cursor)); } .g_record_detail_value.draft { @@ -55,7 +55,7 @@ .detail_row_num { font-size: var(--grist-x-small-font-size); font-weight: normal; - color: var(--grist-color-slate); + color: var(--grist-theme-text-light, var(--grist-color-slate)); padding: 8px; display: flex; align-items: center; @@ -163,7 +163,7 @@ .detail_theme_record_compact { /* 12px is enough margin on the right to include most of the floating scrollbar on MacOS */ padding: 4px 16px 0px 16px; - background-color: var(--grist-color-medium-grey); + background-color: var(--grist-theme-card-compact-widget-bg, var(--grist-color-medium-grey)); } .detail_theme_record_compact.detailview_record_single { @@ -175,12 +175,12 @@ } .detail_theme_record_compact > .g_record_detail_inner { - background-color: white; + background-color: var(--grist-theme-card-compact-record-bg, white); position: relative; } .detail_theme_record_compact > .g_record_detail_inner > .layout_root { - border: 1px solid var(--grist-color-dark-grey); + border: 1px solid var(--grist-theme-card-compact-border, var(--grist-color-dark-grey)); border-right: none; border-bottom: none; } @@ -196,8 +196,8 @@ .detail_theme_field_compact { border-top: none; border-left: none; - border-right: 1px solid var(--grist-color-dark-grey); - border-bottom: 1px solid var(--grist-color-dark-grey); + border-right: 1px solid var(--grist-theme-card-compact-border, var(--grist-color-dark-grey)); + border-bottom: 1px solid var(--grist-theme-card-compact-border, var(--grist-color-dark-grey)); padding: 1px 1px 1px 5px; margin: 0; line-height: 1.2; @@ -206,7 +206,7 @@ .detail_theme_field_compact > .g_record_detail_label { font-weight: normal; font-size: var(--grist-small-font-size); - color: var(--grist-color-slate); + color: var(--grist-theme-card-compact-label, var(--grist-color-slate)); min-height: 0px; white-space: nowrap; @@ -227,7 +227,7 @@ .detail_theme_field_form > .g_record_detail_label { font-size: var(--grist-small-font-size); - color: var(--grist-color-slate); + color: var(--grist-theme-card-form-label, var(--grist-color-slate)); font-weight: bold; min-height: 0px; white-space: nowrap; @@ -241,7 +241,7 @@ * needs to learn to match the value box's style. Right now, the cell editor style is hard-coded. */ .detail_theme_field_form > .g_record_detail_value { - border: 1px solid lightgrey; + border: 1px solid var(--grist-theme-card-form-border, lightgrey); } .detail_theme_record_form { @@ -253,7 +253,7 @@ } .detail_theme_record_form.detailview_record_detail { - border-bottom: 1px solid var(--grist-color-dark-grey); + border-bottom: 1px solid var(--grist-theme-card-list-form-border, var(--grist-color-dark-grey)); padding-bottom: 12px; } @@ -272,20 +272,20 @@ } .detail_theme_record_blocks.detailview_record_detail { - border-bottom: 1px solid var(--grist-color-dark-grey); + border-bottom: 1px solid var(--grist-theme-card-list-blocks-border, var(--grist-color-dark-grey)); padding-bottom: 8px; } .detail_theme_field_blocks { padding: 6px; margin: 8px; - background-color: var(--grist-color-medium-grey); + background-color: var(--grist-theme-card-blocks-bg, var(--grist-color-medium-grey)); border-radius: 2px; } .detail_theme_field_blocks > .g_record_detail_label { font-size: var(--grist-small-font-size); - color: var(--grist-color-slate); + color: var(--grist-theme-card-blocks-label, var(--grist-color-slate)); font-weight: normal; white-space: nowrap; overflow: hidden; diff --git a/app/client/components/DocumentUsage.ts b/app/client/components/DocumentUsage.ts index 4e1140d0..03fc1fd8 100644 --- a/app/client/components/DocumentUsage.ts +++ b/app/client/components/DocumentUsage.ts @@ -2,7 +2,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel'; import {urlState} from 'app/client/models/gristUrlState'; import {docListHeader} from 'app/client/ui/DocMenuCss'; import {infoTooltip} from 'app/client/ui/tooltips'; -import {colors, mediaXSmall} from 'app/client/ui2018/cssVars'; +import {mediaXSmall, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {loadingDots, loadingSpinner} from 'app/client/ui2018/loaders'; import {APPROACHING_LIMIT_RATIO, DataLimitStatus} from 'app/common/DocUsage'; @@ -341,12 +341,11 @@ const cssLightlyBoldedText = styled('div', ` font-weight: 500; `); -const cssIconAndText = styled('div', ` +const cssWarningMessage = styled('div', ` + color: ${theme.text}; + --icon-color: ${theme.text}; display: flex; gap: 16px; -`); - -const cssWarningMessage = styled(cssIconAndText, ` margin-top: 16px; `); @@ -392,6 +391,7 @@ const cssUsageMetrics = styled('div', ` `); const cssUsageMetric = styled('div', ` + color: ${theme.text}; display: flex; flex-direction: column; width: 180px; @@ -408,14 +408,14 @@ const cssProgressBarContainer = styled('div', ` width: 100%; height: 4px; border-radius: 5px; - background: ${colors.darkGrey}; + background: ${theme.progressBarBg}; `); const cssProgressBarFill = styled(cssProgressBarContainer, ` - background: ${colors.lightGreen}; + background: ${theme.progressBarFg}; &-approaching-limit { - background: ${colors.error}; + background: ${theme.progressBarErrorFg}; } `); diff --git a/app/client/components/Drafts.ts b/app/client/components/Drafts.ts index 4f7f9bf3..b64ca226 100644 --- a/app/client/components/Drafts.ts +++ b/app/client/components/Drafts.ts @@ -6,7 +6,7 @@ import { import { GristDoc } from "app/client/components/GristDoc"; import { ITooltipControl, showTooltip, tooltipCloseButton } from "app/client/ui/tooltips"; import { FieldEditorStateEvent } from "app/client/widgets/FieldEditor"; -import { colors, testId } from "app/client/ui2018/cssVars"; +import { testId, theme } from "app/client/ui2018/cssVars"; import { cssLink } from "app/client/ui2018/links"; /** @@ -408,7 +408,7 @@ class EditorAdapter extends Disposable implements Editor { const styledTooltip = styled('div', ` display: flex; align-items: center; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.controlFg}; & > .${cssLink.className} { margin-left: 8px; @@ -430,7 +430,7 @@ function cellTooltip(clb: () => any) { // Discard notification dom const styledNotification = styled('div', ` cursor: pointer; - color: ${colors.lightGreen}; + color: ${theme.controlFg}; &:hover { text-decoration: underline; } diff --git a/app/client/components/FormulaTransform.ts b/app/client/components/FormulaTransform.ts index 7a6b5683..e4a5e36a 100644 --- a/app/client/components/FormulaTransform.ts +++ b/app/client/components/FormulaTransform.ts @@ -26,7 +26,10 @@ export class FormulaTransform extends ColumnTransform { * Build the transform menu for a formula transform */ public buildDom() { - this.editor = this.autoDispose(AceEditor.create({ observable: this.transformColumn.formula })); + this.editor = this.autoDispose(AceEditor.create({ + gristDoc: this.gristDoc, + observable: this.transformColumn.formula, + })); return [ dom('div.transform_menu', dom('div.transform_editor', diff --git a/app/client/components/GridView.css b/app/client/components/GridView.css index 47146162..0aa6e359 100644 --- a/app/client/components/GridView.css +++ b/app/client/components/GridView.css @@ -1,4 +1,5 @@ .gridview_data_pane { + background-color: var(--grist-theme-table-body-bg, unset); position: relative; width: 100%; overflow: hidden; @@ -22,7 +23,7 @@ overscroll-behavior: none; z-index: 2; /* scrollbar should be over the overlay background */ - border-top: 1px solid lightgrey; + border-top: 1px solid var(--grist-theme-table-header-border, lightgrey); } .gridview_data_pane > .gridview_data_scroll { @@ -48,7 +49,7 @@ } .field.column_name { - border-bottom: 1px solid lightgray; + border-bottom: 1px solid var(--grist-theme-table-header-border, lightgray); line-height: var(--gridview-header-height); height: calc(var(--gridview-header-height) + 1px); /* Also should match height for overlay elements */ } @@ -70,8 +71,9 @@ width: 4rem; /* Also should match width for .gridview_header_corner, and the overlay elements */ flex: none; - border-bottom: 1px solid var(--grist-color-dark-grey); - background-color: var(--grist-color-light-grey); + border-bottom: 1px solid var(--grist-theme-table-header-border-dark, var(--grist-color-dark-grey)); + color: var(--grist-theme-table-header-fg, unset); + background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)); z-index: 2; /* goes over data cells */ padding-top: 2px; @@ -100,7 +102,7 @@ * do not want !important, as it interferes with row selection. */ .gridview_data_row_num { - background-color: var(--grist-color-light-grey) !important; + background-color: var(--grist-theme-table-header-bg , var(--grist-color-light-grey)) !important; } .gridview_header_backdrop_top { display: none; @@ -109,7 +111,7 @@ display: none; } .gridview_data_header { - background-color: var(--grist-color-light-grey) !important; + background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)) !important; } .print-widget .gridview_header_backdrop_left, .print-widget .gridview_data_corner_overlay { display: none; @@ -125,15 +127,15 @@ display: table-header-group; break-inside: avoid; position: static; - border-top: 1px solid var(--grist-color-dark-grey); - border-left: 1px solid var(--grist-color-dark-grey); + border-top: 1px solid var(--grist-theme-table-header-border-dark, var(--grist-color-dark-grey)); + border-left: 1px solid var(--grist-theme-table-header-border-dark, var(--grist-color-dark-grey)); } .print-widget .gridview_data_header { padding-left: 4rem !important; } .print-widget .gridview_data_pane .print-all-rows { display: table-row-group; - border-left: 1px solid var(--grist-color-dark-grey); + border-left: 1px solid var(--grist-theme-table-body-border, var(--grist-color-dark-grey)); } .print-widget .gridview_data_pane .print-row { display: table-row; @@ -149,14 +151,14 @@ .scroll_shadow_top, .scroll_shadow_left { position:absolute; - background-color: var(--grist-color-light-grey) !important; + background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)) !important; } .gridview_data_corner_overlay { width: 4rem; height: calc(var(--gridview-header-height) + 1px); /* matches gridview_data_header height (+border) */ top: 1px; /* go under 1px border on scrollpane */ - border-bottom: 1px solid lightgray; + border-bottom: 1px solid var(--grist-theme-table-header-border, lightgray); z-index: 3; cursor: pointer; } @@ -176,7 +178,7 @@ this value is the position where this movement should stop. */ left: calc(4em + (var(--frozen-width, 0) - min(var(--frozen-scroll-offset, 0), var(--frozen-offset, 0))) * 1px); - box-shadow: -6px 0 6px 6px #444; + box-shadow: -6px 0 6px 6px var(--grist-theme-table-scroll-shadow, #444); /* shadow should only show to the right of it (10px should be enough) */ -webkit-clip-path: polygon(0 0, 10px 0, 10px 100%, 0 100%); clip-path: polygon(0 0, 10px 0, 10px 100%, 0 100%); @@ -188,7 +190,7 @@ height: 100%; width: 0px; left: 4em; - box-shadow: -8px 0 14px 4px #444; + box-shadow: -8px 0 14px 4px var(--grist-theme-table-scroll-shadow, #444); -webkit-clip-path: polygon(0 0, 10px 0, 10px 100%, 0 100%); clip-path: polygon(0 0, 28px 0, 24px 100%, 0 100%); z-index: 3; @@ -204,7 +206,7 @@ as this component will be hidden when the scroll starts */ left: calc(4em + var(--frozen-width, 0) * 1px); - background-color: #999999; + background-color: var(--grist-theme-table-frozen-columns-border, #999999); z-index: 3; user-select: none; pointer-events: none @@ -215,7 +217,7 @@ height: 0; width: 100%; /* needs to be wide enough to flow off the side*/ top: calc(var(--gridview-header-height) + 1px); /* matches gridview_data_header height (+border) */ - box-shadow: 0 -6px 6px 6px #444; + box-shadow: 0 -6px 6px 6px var(--grist-theme-table-scroll-shadow, #444); /* should only show below it (10px should be enough) */ -webkit-clip-path: polygon(0 0, 0 10px, 100% 10px, 100% 0); @@ -228,7 +230,7 @@ height:100%; top: 1px; /* go under 1px border on scrollpane */ z-index: 1; - border-right: 1px solid lightgray; + border-right: 1px solid var(--grist-theme-table-header-border, lightgray); } .gridview_left_border { @@ -236,7 +238,7 @@ width: 0px; /* Matches rowid width (+border) */ height: 100%; z-index: 3; - border-right: 1px solid var(--grist-color-dark-grey) !important; + border-right: 1px solid var(--grist-theme-table-body-border, var(--grist-color-dark-grey)) !important; user-select: none; pointer-events: none } @@ -245,7 +247,7 @@ width: 100%; height: calc(var(--gridview-header-height) + 1px); /* matches gridview_data_header height (+border) */ top: 1px; /* go under 1px border on scrollpane */ - border-bottom: 1px solid lightgray; + border-bottom: 1px solid var(--grist-theme-table-header-border, lightgray); z-index: 1; } @@ -266,7 +268,7 @@ width: 0px; height: 100%; position: absolute; - border: 2px solid gray; + border: 2px solid var(--grist-theme-table-drag-drop-indicator, gray); z-index: 20; top: 0px; } @@ -275,10 +277,10 @@ width: 0px; height: 100%; position: absolute; - border: 1px solid gray; + border: 1px solid var(--grist-theme-table-drag-drop-indicator, gray); z-index: 15; top: 0px; - background-color: #F0F0F0; + background-color: var(--grist-theme-table-drag-drop-shadow, #F0F0F0); opacity: 0.5; } @@ -286,7 +288,7 @@ width: 100%; height: 0px; position: absolute; - border: 2px solid gray; + border: 2px solid var(--grist-theme-table-drag-drop-indicator, gray); z-index: 20; left: 0px; } @@ -295,10 +297,10 @@ width: 100%; height: 0px; position: absolute; - border: 1px solid gray; + border: 1px solid var(--grist-theme-table-drag-drop-indicator, gray); z-index: 15; left: 0px; - background-color: #F0F0F0; + background-color: var(--grist-theme-table-drag-drop-shadow, #F0F0F0); opacity: 0.5; pointer-events: none; /* prevents row drag shadow from stealing row headers clicks */ } @@ -323,12 +325,12 @@ } .gridview_row .record.record-hlines .field.frozen { - box-shadow: 0px 1px 0px var(--grist-color-dark-grey); + box-shadow: 0px 1px 0px var(--grist-theme-table-body-border, var(--grist-color-dark-grey)); } /* selected field has a transparent color - with frozen fields we can't do it */ .gridview_row .field.frozen.selected { - background-color: var(--grist-color-selection-opaque); + background-color: var(--grist-theme-selection-opaque-bg, var(--grist-color-selection-opaque)); } /* make room for a frozen line by adding margin to first not frozen field - in header and in data */ @@ -342,22 +344,25 @@ background: white !important; } .column_names .column_name.frozen { - background: var(--grist-color-light-grey) !important; + background: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)) !important; } } /* Column hover effect */ .gridview_row .field.hover-column, /* normal field in a row */ -.gridview_row .field.frozen.hover-column, /* frozen field in a row */ +.gridview_row .field.hover-column .field_clip, .column_name.hover-column, /* column name */ -.column_name.hover-column.selected /* selected column name */ { +.column_name.hover-column.selected, /* selected column name */ +.gridview_row .field.frozen.hover-column /* frozen field in a row */ { /* for frozen fields can't use alpha channel */ - background-color: var(--grist-color-selection-opaque); + background-color: var(--grist-theme-selection-opaque-bg, var(--grist-color-selection-opaque)); + color: var(--grist-theme-selection-opaque-fg, unset); } /* For zebra stripes, make the selection little darker */ .record-zebra.record-even .field.hover-column { - background-color: var(--grist-color-selection-darker-opaque); + background-color: var(--grist-theme-selection-opaque-dark-bg, var(--grist-color-selection-darker-opaque)); + color: var(--grist-theme-selection-opaque-fg, unset); } /* When column has a hover, remove menu button. */ .column_name.hover-column .menu_toggle { diff --git a/app/client/components/Importer.ts b/app/client/components/Importer.ts index cdae0845..d07cca47 100644 --- a/app/client/components/Importer.ts +++ b/app/client/components/Importer.ts @@ -16,7 +16,7 @@ import {SortedRowSet} from 'app/client/models/rowset'; import {buildHighlightedCode} from 'app/client/ui/CodeHighlight'; import {openFilePicker} from 'app/client/ui/FileDialog'; import {bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {IOptionFull, linkSelect, menu, menuDivider, menuItem, multiSelect} from 'app/client/ui2018/menus'; import {cssModalButtons, cssModalTitle} from 'app/client/ui2018/modals'; @@ -663,7 +663,7 @@ export class Importer extends DisposableWithEvents { fields && fields.length > 0 ? cssUnmatchedFields( dom('div', - cssGreenText( + cssAccentText( `${fields.length} unmatched ${fields.length > 1 ? 'fields' : 'field'}` ), ' in import:' @@ -939,11 +939,11 @@ const cssActionLink = styled('div', ` display: inline-flex; align-items: center; cursor: pointer; - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.controlFg}; + --icon-color: ${theme.controlFg}; &:hover { - color: ${colors.darkGreen}; - --icon-color: ${colors.darkGreen}; + color: ${theme.controlHoverFg}; + --icon-color: ${theme.controlHoverFg}; } `); @@ -972,7 +972,7 @@ const cssPreviewWrapper = styled('div', ` // This partly duplicates cssSectionHeader from HomeLeftPane.ts const cssSectionHeader = styled('div', ` margin-bottom: 8px; - color: ${colors.slate}; + color: ${theme.lightText}; text-transform: uppercase; font-weight: 500; font-size: ${vars.xsmallFontSize}; @@ -994,9 +994,9 @@ const cssTableInfo = styled('div', ` margin: 4px 0px; width: 300px; border-radius: 3px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.importerTableInfoBorder}; &:hover, &-selected { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); @@ -1009,7 +1009,7 @@ const cssTableLine = styled('div', ` const cssToFrom = styled('span', ` flex: none; margin-right: 8px; - color: ${colors.slate}; + color: ${theme.lightText}; text-transform: uppercase; font-weight: 500; font-size: ${vars.xsmallFontSize}; @@ -1062,11 +1062,11 @@ const cssOverlay = styled('div', ` height: 100%; width: 100%; z-index: 10; - background: ${colors.mediumGrey}; + background: ${theme.importerSkippedTableOverlay}; `); const cssPreviewGrid = styled(cssPreview, ` - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.importerPreviewBorder}; position: relative; `); @@ -1079,7 +1079,7 @@ const cssMergeOptionsToggle = styled('div', ` `); const cssMergeOptionsMessage = styled('div', ` - color: ${colors.slate}; + color: ${theme.lightText}; margin-bottom: 8px; `); @@ -1099,14 +1099,14 @@ const cssFieldFormula = styled(buildHighlightedCode, ` cursor: pointer; margin-top: 1px; padding-left: 4px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); const cssColumnMatchIcon = styled(icon, ` flex-shrink: 0; width: 20px; height: 32px; - background-color: ${colors.darkGrey}; + background-color: ${theme.importerMatchIcon}; margin-right: 4px; `); @@ -1138,10 +1138,10 @@ const cssDestinationFieldSettings = styled('div', ` line-height: 0px; border-radius: 3px; cursor: pointer; - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); @@ -1154,9 +1154,9 @@ const cssUnmatchedFieldsList = styled('div', ` text-overflow: ellipsis; overflow: hidden; padding-right: 16px; - color: ${colors.slate}; + color: ${theme.lightText}; `); -const cssGreenText = styled('span', ` - color: ${colors.lightGreen}; +const cssAccentText = styled('span', ` + color: ${theme.accentText}; `); diff --git a/app/client/components/ParseOptions.ts b/app/client/components/ParseOptions.ts index 843480a6..54f55394 100644 --- a/app/client/components/ParseOptions.ts +++ b/app/client/components/ParseOptions.ts @@ -1,6 +1,6 @@ import {bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons'; import {squareCheckbox} from 'app/client/ui2018/checkbox'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {cssModalButtons} from 'app/client/ui2018/modals'; import {ParseOptionSchema} from 'app/plugin/FileParserAPI'; import {Computed, dom, DomContents, IDisposableOwner, input, Observable, styled} from 'grainjs'; @@ -107,12 +107,18 @@ const cssParseOptionName = styled('div', ` margin-bottom: 8px; `); const cssInputText = styled(input, ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; position: relative; display: inline-block; outline: none; height: 28px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-radius: 3px; padding: 0 6px; width: 100%; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); diff --git a/app/client/components/Preferences.css b/app/client/components/Preferences.css index dae33464..29c75514 100644 --- a/app/client/components/Preferences.css +++ b/app/client/components/Preferences.css @@ -33,6 +33,7 @@ } .preference_desc { + color: var(--grist-theme-document-history-activity-text, unset); margin-left: 5px; cursor: pointer; font-weight: normal; diff --git a/app/client/components/RawDataPage.ts b/app/client/components/RawDataPage.ts index fcc414c0..476234ab 100644 --- a/app/client/components/RawDataPage.ts +++ b/app/client/components/RawDataPage.ts @@ -4,7 +4,7 @@ import {DocumentUsage} from 'app/client/components/DocumentUsage'; import {GristDoc} from 'app/client/components/GristDoc'; import {printViewSection} from 'app/client/components/Printing'; import {buildViewSectionDom, ViewSectionHelper} from 'app/client/components/ViewLayout'; -import {colors, mediaSmall, vars} from 'app/client/ui2018/cssVars'; +import {mediaSmall, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {Computed, Disposable, dom, fromKo, makeTestId, Observable, styled} from 'grainjs'; import {reportError} from 'app/client/models/errors'; @@ -112,7 +112,7 @@ const cssContainer = styled('div', ` `); const cssOverlay = styled('div', ` - background-color: ${colors.backdrop}; + background-color: ${theme.modalBackdrop}; inset: 0px; height: 100%; width: 100%; @@ -127,7 +127,7 @@ const cssOverlay = styled('div', ` `); const cssSectionWrapper = styled('div', ` - background: white; + background: ${theme.mainPanelBg}; height: 100%; display: flex; flex-direction: column; @@ -154,9 +154,9 @@ const cssCloseButton = styled(icon, ` height: 24px; width: 24px; cursor: pointer; - --icon-color: ${vars.primaryBg}; + --icon-color: ${theme.modalBackdropCloseButtonFg}; &:hover { - --icon-color: ${colors.lighterGreen}; + --icon-color: ${theme.modalBackdropCloseButtonHoverFg}; } @media ${mediaSmall} { & { diff --git a/app/client/components/RecordLayout.css b/app/client/components/RecordLayout.css index 47083477..4bcce27d 100644 --- a/app/client/components/RecordLayout.css +++ b/app/client/components/RecordLayout.css @@ -11,11 +11,11 @@ cursor: move; z-index: 5; - background-color: rgba(192, 192, 192, 0.2); - border-left: 1px solid white; - border-top: 1px solid white; - border-right: 1px solid var(--grist-color-dark-grey); - border-bottom: 1px solid var(--grist-color-dark-grey); + background-color: var(--grist-theme-card-editing-layout-bg, rgba(192, 192, 192, 0.2)); + border-left: 1px solid var(--grist-theme-card-editing-layout-border, white); + border-top: 1px solid var(--grist-theme-card-editing-layout-border, white); + border-right: 1px solid var(--grist-theme-card-editing-layout-border, var(--grist-color-dark-grey)); + border-bottom: 1px solid var(--grist-theme-card-editing-layout-border, var(--grist-color-dark-grey)); } .dropdown-menu .g_record_layout_newfield { diff --git a/app/client/components/TypeTransform.ts b/app/client/components/TypeTransform.ts index f38018e7..fd62069a 100644 --- a/app/client/components/TypeTransform.ts +++ b/app/client/components/TypeTransform.ts @@ -47,7 +47,10 @@ export class TypeTransform extends ColumnTransform { const disableButtons = Observable.create(null, false); this._reviseTypeChange.set(false); - this.editor = this.autoDispose(AceEditor.create({ observable: this.transformColumn.formula })); + this.editor = this.autoDispose(AceEditor.create({ + gristDoc: this.gristDoc, + observable: this.transformColumn.formula, + })); return dom('div', testId('type-transform-top'), dom.maybe(this._transformWidget, transformWidget => transformWidget.buildTransformConfigDom()), diff --git a/app/client/components/ViewConfigTab.js b/app/client/components/ViewConfigTab.js index d0f78a35..eef53ca2 100644 --- a/app/client/components/ViewConfigTab.js +++ b/app/client/components/ViewConfigTab.js @@ -17,7 +17,7 @@ const {addFilterMenu} = require('app/client/ui/FilterBar'); const {cssIcon, cssRow} = require('app/client/ui/RightPanelStyles'); const {basicButton, primaryButton} = require('app/client/ui2018/buttons'); const {labeledLeftSquareCheckbox} = require("app/client/ui2018/checkbox"); -const {colors} = require('app/client/ui2018/cssVars'); +const {theme} = require('app/client/ui2018/cssVars'); const {cssDragger} = require('app/client/ui2018/draggableList'); const {menu, menuItem, select} = require('app/client/ui2018/menus'); const {confirmModal} = require('app/client/ui2018/modals'); @@ -236,7 +236,7 @@ ViewConfigTab.prototype._buildSortRow = function(colRef, sortSpec, columns) { ), cssMenu( cssBigIconWrapper( - cssIcon('Dots', grainjsDom.cls(cssBgLightGreen.className, hasSpecs)), + cssIcon('Dots', grainjsDom.cls(cssBgAccent.className, hasSpecs)), testId('sort-options-icon'), ), menu(_ctl => flags.map(({computed, allowedTypes, flag, label}) => { @@ -571,7 +571,7 @@ const cssMenuIcon = styled(cssIcon, ` margin: 0 8px 0 0; .${cssMenuItem.className}-sel > & { - background-color: ${colors.light}; + background-color: ${theme.iconButtonFg}; } `); @@ -586,37 +586,37 @@ const cssSortIconBtn = styled(cssIcon, ` flex: none; margin: 0 6px; cursor: pointer; - background-color: ${colors.slate}; + background-color: ${theme.controlSecondaryFg}; &:hover { - background-color: ${colors.dark}; + background-color: ${theme.controlSecondaryHoverFg}; } `); const cssSortIconPrimaryBtn = styled(cssSortIconBtn, ` - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; &:hover { - background-color: ${colors.darkGreen}; + background-color: ${theme.controlHoverFg}; } `); const cssTextBtn = styled('div', ` - color: ${colors.lightGreen}; + color: ${theme.controlFg}; cursor: pointer; &:hover { - color: ${colors.darkGreen}; + color: ${theme.controlHoverFg}; } `); const cssPlusIcon = styled(cssIcon, ` - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; cursor: pointer; margin: 0px 4px 3px 0; .${cssTextBtn.className}:hover > & { - background-color: ${colors.darkGreen}; + background-color: ${theme.controlHoverFg}; } `); @@ -673,24 +673,24 @@ const cssMenu = styled('div', ` border-radius: 3px; border: 1px solid transparent; &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); -const cssBgLightGreen = styled(`div`, ` - background: ${colors.lightGreen} +const cssBgAccent = styled(`div`, ` + background: ${theme.accentIcon} `) const cssOptionMenuItem = styled('div', ` &:hover { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } & label { flex: 1; cursor: pointer; } &.disabled * { - color: ${colors.darkGrey} important; + color: ${theme.menuItemDisabledFg} important; cursor: not-allowed; } `) diff --git a/app/client/components/ViewLayout.css b/app/client/components/ViewLayout.css index a3d9bfb0..5d60c8e6 100644 --- a/app/client/components/ViewLayout.css +++ b/app/client/components/ViewLayout.css @@ -12,14 +12,13 @@ cursor: default; height: 24px; margin-left: -16px; /* to include drag handle that shows up on hover */ - color: var(--grist-color-slate); + color: var(--grist-theme-text-light, var(--grist-color-slate)); font-size: var(--grist-small-font-size); font-weight: 500; white-space: nowrap; } .viewsection_content { - background-color: #ffffff; overflow: visible; margin: 12px; } @@ -91,19 +90,19 @@ .view_data_pane_container { position: relative; flex: auto; - border: 1px solid var(--grist-color-dark-grey); + border: 1px solid var(--grist-theme-widget-border, var(--grist-color-dark-grey)); } @media not print { .active_section > .view_data_pane_container { - box-shadow: -2px 0 0 0px var(--grist-color-light-green); - border-left: 1px solid var(--grist-color-light-green); + box-shadow: -2px 0 0 0px var(--grist-theme-widget-active-border, var(--grist-color-light-green)); + border-left: 1px solid var(--grist-theme-widget-active-border, var(--grist-color-light-green)); } .active_section > .view_data_pane_container.viewsection_type_detail { /* this color is a translucent version of grist-color-light-green */ - box-shadow: -2px 0 0 0px var(--grist-color-inactive-cursor); - border-left: 1px solid var(--grist-color-inactive-cursor); + box-shadow: -2px 0 0 0px var(--grist-theme-cursor-inactive, var(--grist-color-inactive-cursor)); + border-left: 1px solid var(--grist-theme-cursor-inactive, var(--grist-color-inactive-cursor)); } } @@ -111,7 +110,7 @@ .active_section--no-indicator > .view_data_pane_container, .active_section--no-indicator > .view_data_pane_container.viewsection_type_detail { box-shadow: none; - border-left: 1px solid var(--grist-color-dark-grey); + border-left: 1px solid var(--grist-theme-widget-border, var(--grist-color-dark-grey)); } .disable_viewpane { diff --git a/app/client/components/ViewLayout.ts b/app/client/components/ViewLayout.ts index 0bd40549..3eec3dc9 100644 --- a/app/client/components/ViewLayout.ts +++ b/app/client/components/ViewLayout.ts @@ -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 {colors, mediaSmall, testId} from 'app/client/ui2018/cssVars'; +import {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'; @@ -335,7 +335,7 @@ const cssTestClick = styled(`div`, ` const cssSigmaIcon = styled(icon, ` bottom: 1px; margin-right: 5px; - background-color: ${colors.slate} + background-color: ${theme.lightText} `); const cssViewLeaf = styled('div', ` @@ -352,12 +352,12 @@ const cssViewLeafInactive = styled('div', ` overflow: hidden; background: repeating-linear-gradient( -45deg, - ${colors.mediumGreyOpaque}, - ${colors.mediumGreyOpaque} 10px, - ${colors.lightGrey} 10px, - ${colors.lightGrey} 20px + ${theme.widgetInactiveStripesDark}, + ${theme.widgetInactiveStripesDark} 10px, + ${theme.widgetInactiveStripesLight} 10px, + ${theme.widgetInactiveStripesLight} 20px ); - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.widgetBorder}; border-radius: 4px; padding: 0 2px; } diff --git a/app/client/components/viewCommon.css b/app/client/components/viewCommon.css index 7e09dace..2a87ccba 100644 --- a/app/client/components/viewCommon.css +++ b/app/client/components/viewCommon.css @@ -10,7 +10,7 @@ border-width: 0px; border-style: none; - border-color: var(--grist-color-dark-grey); + border-color: var(--grist-theme-table-body-border, var(--grist-color-dark-grey)); border-left-style: solid; /* left border, against rownumbers div, always on */ border-bottom-width: 1px; /* style: none, set by record-hlines*/ /* Record background is white by default. @@ -19,8 +19,8 @@ selected fields - this still remains white. TODO: consider making this color the single source */ - background: var(--grist-row-background-color, white); - color: var(--grist-row-color, black); + background: var(--grist-row-background-color, var(--grist-theme-cell-bg, white)); + color: var(--grist-row-color, var(--grist-theme-cell-fg, black)); } .record.record-hlines { /* Overwrites style, width set on element */ @@ -28,11 +28,11 @@ } .record.record-zebra.record-even { - background-color: var(--grist-row-background-color-zebra, #f8f8f8); + background-color: var(--grist-row-background-color-zebra, var(--grist-theme-cell-zebra-bg, #f8f8f8)); } .record.record-add { - background-color: #f6f6ff !important; /* important to win over zebra stripes */ + background-color: var(--grist-theme-table-add-new-bg, #f6f6ff) !important; /* important to win over zebra stripes */ } .field { @@ -47,15 +47,16 @@ } .record-vlines > .field { - border-right-color: var(--grist-color-dark-grey); /* set border visibility */ + /* set border visibility */ + border-right-color: var(--grist-theme-table-body-border, var(--grist-color-dark-grey)); } .field.scissors { - outline: 2px dashed var(--grist-color-cursor); + outline: 2px dashed var(--grist-theme-cursor, var(--grist-color-cursor)); } .field.selected { - background-color: var(--grist-color-selection); + background-color: var(--grist-theme-selection, var(--grist-color-selection)); } .field.draft { @@ -136,58 +137,38 @@ width: 100%; height: 100%; /* one pixel outline around the cell, and one inside the cell */ - outline: 1px solid var(--grist-color-inactive-cursor); - box-shadow: inset 0 0 0 1px var(--grist-color-inactive-cursor); + outline: 1px solid var(--grist-theme-cursor-inactive, var(--grist-color-inactive-cursor)); + box-shadow: inset 0 0 0 1px var(--grist-theme-cursor-inactive, var(--grist-color-inactive-cursor)); pointer-events: none; } .active_cursor { - outline: 1px solid var(--grist-color-cursor); - box-shadow: inset 0 0 0 1px var(--grist-color-cursor); + outline: 1px solid var(--grist-theme-cursor, var(--grist-color-cursor)); + box-shadow: inset 0 0 0 1px var(--grist-theme-cursor, var(--grist-color-cursor)); } } -/* These classes are used to flash the cursor to indicate that editing in a cell is disabled. */ -.cursor_read_only { - outline: 1px solid #ff9a00; - box-shadow: inset 0 0 0 1px #ff9a00; -} - -.cursor_read_only_fade { - outline-color: var(--grist-color-cursor); - box-shadow: inset 0 0 0 1px var(--grist-color-cursor); - transition: outline-color 0.5s ease-in, box-shadow 0.5s ease-in; -} - -.cursor_read_only_lock { - top: 0px; - height: 100%; - padding: 0 4px; - line-height: inherit; - background-color: #ff9a00; - color: white; - opacity: 1; -} - -.cursor_read_only_fade > .cursor_read_only_lock { - opacity: 0; - transition: opacity 0.5s ease-in; -} - .column_name { - background-color: var(--grist-color-light-grey); + color: var(--grist-theme-table-header-fg, unset); + background-color: var(--grist-theme-table-header-bg, var(--grist-color-light-grey)); text-align: center; cursor: pointer; /* Column headers always show vertical gridlines, to make it clear how to resize them */ - border-right-color: var(--grist-color-dark-grey); + border-right-color: var(--grist-theme-table-header-border-dark, var(--grist-color-dark-grey)); +} + +.column_names.record { + border-left-color: var(--grist-theme-table-header-border, var(--grist-color-dark-grey)); } .column_name.selected { - background-color: var(--grist-color-medium-grey-opaque); + color: var(--grist-theme-table-header-selected-fg, unset); + background-color: var(--grist-theme-table-header-selected-bg, var(--grist-color-medium-grey-opaque)); } .gridview_data_row_num.selected { - background-color: var(--grist-color-medium-grey-opaque); + color: var(--grist-theme-table-header-selected-fg, unset); + background-color: var(--grist-theme-table-header-selected-bg, var(--grist-color-medium-grey-opaque)); } .gridview_data_row_info.linked_dst::before { diff --git a/app/client/lib/ACSelect.ts b/app/client/lib/ACSelect.ts index 354f6b61..37cf0cc6 100644 --- a/app/client/lib/ACSelect.ts +++ b/app/client/lib/ACSelect.ts @@ -1,6 +1,6 @@ import {ACIndex, ACItem, buildHighlightedDom} from 'app/client/lib/ACIndex'; import {Autocomplete, IAutocompleteOptions} from 'app/client/lib/autocomplete'; -import {colors} from "app/client/ui2018/cssVars"; +import {theme} from "app/client/ui2018/cssVars"; import {icon} from "app/client/ui2018/icons"; import {menuCssClass} from 'app/client/ui2018/menus'; import {dom, DomElementArg, Holder, IDisposableOwner, Observable, styled} from 'grainjs'; @@ -96,11 +96,12 @@ const cssSelectBtn = styled('div', ` position: relative; width: 100%; height: 30px; - color: ${colors.dark}; - --icon-color: ${colors.dark}; + color: ${theme.selectButtonFg}; + --icon-color: ${theme.selectButtonFg}; `); const cssSelectItem = styled('li', ` + color: ${theme.menuItemFg}; display: block; white-space: pre; overflow: hidden; @@ -110,12 +111,14 @@ const cssSelectItem = styled('li', ` cursor: pointer; &.selected { - background-color: var(--weaseljs-selected-background-color, #5AC09C); - color: var(--weaseljs-selected-color, white); + background-color: ${theme.menuItemSelectedBg}; + color: ${theme.menuItemSelectedFg}; } `); const cssInput = styled('input', ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; appearance: none; -webkit-appearance: none; -moz-appearance: none; @@ -123,20 +126,23 @@ const cssInput = styled('input', ` width: 100%; padding: 0 6px; outline: none; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-radius: 3px; cursor: pointer; line-height: 16px; cursor: pointer; &:disabled { - color: grey; - background-color: initial; + color: ${theme.inputDisabledFg}; + background-color: ${theme.inputDisabledBg}; } &:focus { cursor: initial; outline: none; - box-shadow: 0px 0px 2px 2px #5E9ED6; + box-shadow: 0px 0px 2px 2px ${theme.inputFocus}; + } + &::placeholder { + color: ${theme.inputPlaceholderFg}; } `); @@ -147,8 +153,8 @@ const cssIcon = styled(icon, ` `); const cssMatchText = styled('span', ` - color: ${colors.lightGreen}; + color: ${theme.autocompleteMatchText}; .selected > & { - color: ${colors.lighterGreen}; + color: ${theme.autocompleteSelectedMatchText}; } `); diff --git a/app/client/lib/TokenField.ts b/app/client/lib/TokenField.ts index 0a561f0b..b9e46cee 100644 --- a/app/client/lib/TokenField.ts +++ b/app/client/lib/TokenField.ts @@ -18,7 +18,7 @@ import { ACItem } from 'app/client/lib/ACIndex'; import { modKeyProp } from 'app/client/lib/browserInfo'; import { Autocomplete, IAutocompleteOptions } from 'app/client/lib/autocomplete'; -import { colors, testId } from 'app/client/ui2018/cssVars'; +import { colors, testId, theme } from 'app/client/ui2018/cssVars'; import { icon } from 'app/client/ui2018/icons'; import { csvDecodeRow, csvEncodeRow } from 'app/common/csvFormat'; import { computedArray, IDisposableCtor, IObsArraySplice, ObsArray, obsArray, Observable } from 'grainjs'; @@ -679,6 +679,8 @@ const cssInputWrapper = styled('div', ` `); const cssTokenInput = styled('input', ` + color: ${theme.cellEditorFg}; + background-color: ${theme.cellEditorBg}; flex: auto; -webkit-appearance: none; -moz-appearance: none; diff --git a/app/client/lib/Validator.ts b/app/client/lib/Validator.ts index 4c4beec0..d17b90a6 100644 --- a/app/client/lib/Validator.ts +++ b/app/client/lib/Validator.ts @@ -1,5 +1,5 @@ +import { theme } from 'app/client/ui2018/cssVars'; import { Disposable, dom, Observable, styled } from 'grainjs'; -import { colors } from 'app/client/ui2018/cssVars'; /** * Simple validation controls. Renders as a red text with a validation message. @@ -117,5 +117,5 @@ export class Validator extends Disposable { } const cssError = styled('div.validator', ` - color: ${colors.error}; + color: ${theme.errorText}; `); diff --git a/app/client/lib/koForm.css b/app/client/lib/koForm.css index 249316a9..9fa1d679 100644 --- a/app/client/lib/koForm.css +++ b/app/client/lib/koForm.css @@ -103,7 +103,7 @@ div:hover > .kf_tooltip { min-width: 16px; min-height: 16px; padding: 4px; - background-color: white; + background-color: var(--grist-theme-popup-bg, white); border-radius: 2px; box-shadow: 0 1px 1px 1px rgba(0,0,0,0.15); line-height: 1.1rem; @@ -129,7 +129,7 @@ div:hover > .kf_tooltip { right: 20px; width: 10px; height: 10px; - background-color: white; + background-color: var(--grist-theme-popup-bg, white); transform: rotate(45deg); z-index: 11; } @@ -393,6 +393,7 @@ div:hover > .kf_tooltip { } .kf_label { + color: var(--grist-theme-text, unset); white-space: nowrap; font-size: 1.1rem; cursor: default; @@ -436,7 +437,7 @@ div:hover > .kf_tooltip { top: 0; left: 0; padding: 0; - color: #333; + color: var(--grist-theme-input-fg, #333); } .elabel_content_measure { diff --git a/app/client/models/AppModel.ts b/app/client/models/AppModel.ts index 6898df53..e2de5668 100644 --- a/app/client/models/AppModel.ts +++ b/app/client/models/AppModel.ts @@ -5,6 +5,7 @@ import {urlState} from 'app/client/models/gristUrlState'; import {Notifier} from 'app/client/models/NotifyModel'; import {getFlavor, ProductFlavor} from 'app/client/ui/CustomThemes'; import {buildNewSiteModal, buildUpgradeModal} from 'app/client/ui/ProductUpgrades'; +import {attachCssThemeVars, prefersDarkModeObs} from 'app/client/ui2018/cssVars'; import {OrgUsageSummary} from 'app/common/DocUsage'; import {Features, isLegacyPlan, Product} from 'app/common/Features'; import {GristLoadConfig} from 'app/common/gristUrls'; @@ -13,6 +14,9 @@ import {LocalPlugin} from 'app/common/plugin'; import {UserPrefs} from 'app/common/Prefs'; import {isOwner} from 'app/common/roles'; import {getTagManagerScript} from 'app/common/tagManager'; +import {getDefaultThemePrefs, Theme, ThemeAppearance, ThemeColors, ThemePrefs, + ThemePrefsChecker} from 'app/common/ThemePrefs'; +import {getThemeColors} from 'app/common/Themes'; import {getGristConfig} from 'app/common/urlUtils'; import {getOrgName, Organization, OrgError, SUPPORT_EMAIL, UserAPI, UserAPIImpl} from 'app/common/UserAPI'; import {getUserPrefObs, getUserPrefsObs} from 'app/client/models/UserPrefs'; @@ -75,7 +79,10 @@ export interface AppModel { currentProduct: Product|null; // The current org's product. currentFeatures: Features; // Features of the current org's product. + userPrefsObs: Observable; + themePrefs: Observable; + currentTheme: Computed; pageType: Observable; @@ -209,6 +216,11 @@ export class AppModelImpl extends Disposable implements AppModel { public readonly isLegacySite = Boolean(this.currentProduct && isLegacyPlan(this.currentProduct.name)); public readonly userPrefsObs = getUserPrefsObs(this); + public readonly themePrefs = getUserPrefObs(this.userPrefsObs, 'theme', { + defaultValue: getDefaultThemePrefs(), + checker: ThemePrefsChecker, + }) as Observable; + public readonly currentTheme = this._getCurrentThemeObs(); // Get the current PageType from the URL. public readonly pageType: Observable = Computed.create(this, urlState().state, @@ -223,6 +235,10 @@ export class AppModelImpl extends Disposable implements AppModel { public readonly orgError?: OrgError, ) { super(); + + this._applyTheme(); + this.autoDispose(this.currentTheme.addListener(() => this._applyTheme())); + this._recordSignUpIfIsNewUser(); const state = urlState().state.get(); @@ -311,6 +327,39 @@ export class AppModelImpl extends Disposable implements AppModel { dataLayer.push({event: 'new-sign-up'}); getUserPrefObs(this.userPrefsObs, 'recordSignUpEvent').set(undefined); } + + private _getCurrentThemeObs() { + return Computed.create(this, this.themePrefs, prefersDarkModeObs(), + (_use, themePrefs, prefersDarkMode) => { + let appearance: ThemeAppearance; + if (!themePrefs.syncWithOS) { + appearance = themePrefs.appearance; + } else { + appearance = prefersDarkMode ? 'dark' : 'light'; + } + + const nameOrColors = themePrefs.colors[appearance]; + let colors: ThemeColors; + if (typeof nameOrColors === 'string') { + colors = getThemeColors(nameOrColors); + } else { + colors = nameOrColors; + } + + return {appearance, colors}; + }, + ); + } + + /** + * Applies a theme based on the user's current theme preferences. + */ + private _applyTheme() { + // Custom CSS is incompatible with custom themes. + if (getGristConfig().enableCustomCss) { return; } + + attachCssThemeVars(this.currentTheme.get()); + } } export function getHomeUrl(): string { diff --git a/app/client/models/UserPrefs.ts b/app/client/models/UserPrefs.ts index 90cb17be..ad29cf76 100644 --- a/app/client/models/UserPrefs.ts +++ b/app/client/models/UserPrefs.ts @@ -2,6 +2,7 @@ import {localStorageObs} from 'app/client/lib/localStorageObs'; import {AppModel} from 'app/client/models/AppModel'; import {UserOrgPrefs, UserPrefs} from 'app/common/Prefs'; import {Computed, Observable} from 'grainjs'; +import {CheckerT} from 'ts-interface-checker'; interface PrefsTypes { userOrgPrefs: UserOrgPrefs; @@ -12,15 +13,14 @@ function makePrefFunctions

(prefsTypeName: P) { type PrefsType = PrefsTypes[P]; /** - * Creates an observable that returns UserOrgPrefs, and which stores them when set. + * Creates an observable that returns a PrefsType, and which stores changes when set. * * For anon user, the prefs live in localStorage. Note that the observable isn't actually watching * for changes on the server, it will only change when set. */ function getPrefsObs(appModel: AppModel): Observable { - const savedPrefs = appModel.currentValidUser ? appModel.currentOrg?.[prefsTypeName] : undefined; - if (savedPrefs) { - const prefsObs = Observable.create(null, savedPrefs!); + if (appModel.currentValidUser) { + const prefsObs = Observable.create(null, appModel.currentOrg?.[prefsTypeName] ?? {}); return Computed.create(null, (use) => use(prefsObs)) .onWrite(prefs => { prefsObs.set(prefs); @@ -41,10 +41,30 @@ function makePrefFunctions

(prefsTypeName: P) { * stores it when set. */ function getPrefObs( - prefsObs: Observable, prefName: Name - ): Observable { - return Computed.create(null, (use) => use(prefsObs)[prefName]) - .onWrite(value => prefsObs.set({...prefsObs.get(), [prefName]: value})); + prefsObs: Observable, + prefName: Name, + options: { + defaultValue?: Exclude; + checker?: CheckerT; + } = {} + ): Observable { + const {defaultValue, checker} = options; + return Computed.create(null, (use) => { + const prefs = use(prefsObs); + if (!(prefName in prefs)) { return defaultValue; } + + const value = prefs[prefName]; + if (checker) { + try { + checker.check(value); + } catch (e) { + console.error(`getPrefObs: preference ${prefName.toString()} has value of invalid type`, e); + return defaultValue; + } + } + + return value; + }).onWrite(value => prefsObs.set({...prefsObs.get(), [prefName]: value})); } return {getPrefsObs, getPrefObs}; diff --git a/app/client/ui/AccountPage.ts b/app/client/ui/AccountPage.ts index 1048cc73..90162ecd 100644 --- a/app/client/ui/AccountPage.ts +++ b/app/client/ui/AccountPage.ts @@ -1,17 +1,19 @@ import {AppModel, reportError} from 'app/client/models/AppModel'; import {urlState} from 'app/client/models/gristUrlState'; +import * as css from 'app/client/ui/AccountPageCss'; import {ApiKey} from 'app/client/ui/ApiKey'; import {AppHeader} from 'app/client/ui/AppHeader'; import {buildChangePasswordDialog} from 'app/client/ui/ChangePasswordDialog'; import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon'; import {MFAConfig} from 'app/client/ui/MFAConfig'; import {pagePanels} from 'app/client/ui/PagePanels'; +import {ThemeConfig} from 'app/client/ui/ThemeConfig'; import {createTopBarHome} from 'app/client/ui/TopBar'; import {transientInput} from 'app/client/ui/transientInput'; -import {cssBreadcrumbs, cssBreadcrumbsLink, separator} from 'app/client/ui2018/breadcrumbs'; +import {cssBreadcrumbs, separator} from 'app/client/ui2018/breadcrumbs'; import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox'; -import {icon} from 'app/client/ui2018/icons'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {cssLink} from 'app/client/ui2018/links'; +import {getGristConfig} from 'app/common/urlUtils'; import {FullUser} from 'app/common/UserAPI'; import {Computed, Disposable, dom, domComputed, makeTestId, Observable, styled} from 'grainjs'; @@ -46,19 +48,21 @@ export class AccountPage extends Disposable { }, headerMain: this._buildHeaderMain(), contentMain: this._buildContentMain(), + testId, }); } private _buildContentMain() { + const {enableCustomCss} = getGristConfig(); return domComputed(this._userObs, (user) => user && ( - cssContainer(cssAccountPage( - cssHeader('Account settings'), - cssDataRow( - cssSubHeader('Email'), - cssEmail(user.email), + css.container(css.accountPage( + css.header('Account settings'), + css.dataRow( + css.inlineSubHeader('Email'), + css.email(user.email), ), - cssDataRow( - cssSubHeader('Name'), + css.dataRow( + css.inlineSubHeader('Name'), domComputed(this._isEditingName, (isEditing) => ( isEditing ? [ transientInput( @@ -69,16 +73,16 @@ export class AccountPage extends Disposable { }, { size: '5' }, // Lower size so that input can shrink below ~152px. dom.on('input', (_ev, el) => this._nameEdit.set(el.value)), - cssFlexGrow.cls(''), + css.flexGrow.cls(''), ), - cssTextBtn( - cssIcon('Settings'), 'Save', + css.textBtn( + css.icon('Settings'), 'Save', // No need to save on 'click'. The transient input already does it on close. ), ] : [ - cssName(user.name), - cssTextBtn( - cssIcon('Settings'), 'Edit', + css.name(user.name), + css.textBtn( + css.icon('Settings'), 'Edit', dom.on('click', () => this._isEditingName.set(true)), ), ] @@ -87,17 +91,17 @@ export class AccountPage extends Disposable { ), // show warning for invalid name but not for the empty string dom.maybe(use => use(this._nameEdit) && !use(this._isNameValid), cssWarnings), - cssHeader('Password & Security'), - cssDataRow( - cssSubHeader('Login Method'), - cssLoginMethod(user.loginMethod), - user.loginMethod === 'Email + Password' ? cssTextBtn('Change Password', + css.header('Password & Security'), + css.dataRow( + css.inlineSubHeader('Login Method'), + css.loginMethod(user.loginMethod), + user.loginMethod === 'Email + Password' ? css.textBtn('Change Password', dom.on('click', () => this._showChangePasswordDialog()), ) : null, testId('login-method'), ), user.loginMethod !== 'Email + Password' ? null : dom.frag( - cssDataRow( + css.dataRow( labeledSquareCheckbox( this._allowGoogleLogin, 'Allow signing in to this account with Google', @@ -105,16 +109,21 @@ export class AccountPage extends Disposable { ), testId('allow-google-login'), ), - cssSubHeaderFullWidth('Two-factor authentication'), - cssDescription( + css.subHeader('Two-factor authentication'), + css.description( "Two-factor authentication is an extra layer of security for your Grist account designed " + "to ensure that you're the only person who can access your account, even if someone " + "knows your password." ), dom.create(MFAConfig, user), ), - cssHeader('API'), - cssDataRow(cssSubHeader('API Key'), cssContent( + // Custom CSS is incompatible with custom themes. + enableCustomCss ? null : [ + css.header('Theme'), + dom.create(ThemeConfig, this._appModel), + ], + css.header('API'), + css.dataRow(css.inlineSubHeader('API Key'), css.content( dom.create(ApiKey, { apiKey: this._apiKey, onCreate: () => this._createApiKey(), @@ -131,7 +140,7 @@ export class AccountPage extends Disposable { private _buildHeaderMain() { return dom.frag( cssBreadcrumbs({ style: 'margin-left: 16px;' }, - cssBreadcrumbsLink( + cssLink( urlState().setLinkUrl({}), 'Home', testId('home'), @@ -204,103 +213,12 @@ export function checkName(name: string): boolean { * Builds dom to show marning messages to the user. */ function buildNameWarningsDom() { - return cssWarning( + return css.warning( "Names only allow letters, numbers and certain special characters", testId('username-warning'), ); } -const cssContainer = styled('div', ` - display: flex; - justify-content: center; - overflow: auto; -`); - -const cssHeader = styled('div', ` - height: 32px; - line-height: 32px; - margin: 28px 0 16px 0; - color: ${colors.dark}; - font-size: ${vars.xxxlargeFontSize}; - font-weight: ${vars.headerControlTextWeight}; -`); - -const cssAccountPage = styled('div', ` - max-width: 600px; - padding: 16px; -`); - -const cssDataRow = styled('div', ` - margin: 8px 0px; - display: flex; - align-items: baseline; -`); - -const cssSubHeaderFullWidth = styled('div', ` - padding: 8px 0; - display: inline-block; - vertical-align: top; - font-weight: bold; -`); - -const cssSubHeader = styled(cssSubHeaderFullWidth, ` - min-width: 110px; -`); - -const cssContent = styled('div', ` - flex: 1 1 300px; -`); - -const cssTextBtn = styled('button', ` - font-size: ${vars.mediumFontSize}; - color: ${colors.lightGreen}; - cursor: pointer; - margin-left: 16px; - background-color: transparent; - border: none; - padding: 0; - text-align: left; - min-width: 110px; - - &:hover { - color: ${colors.darkGreen}; - } -`); - -const cssIcon = styled(icon, ` - background-color: ${colors.lightGreen}; - margin: 0 4px 2px 0; - - .${cssTextBtn.className}:hover > & { - background-color: ${colors.darkGreen}; - } -`); - const cssWarnings = styled(buildNameWarningsDom, ` margin: -8px 0 0 110px; `); - -const cssDescription = styled('div', ` - color: #8a8a8a; - font-size: 13px; -`); - -const cssFlexGrow = styled('div', ` - flex-grow: 1; -`); - -const cssName = styled(cssFlexGrow, ` - word-break: break-word; -`); - -const cssEmail = styled('div', ` - word-break: break-word; -`); - -const cssLoginMethod = styled(cssFlexGrow, ` - word-break: break-word; -`); - -const cssWarning = styled('div', ` - color: red; -`); diff --git a/app/client/ui/AccountPageCss.ts b/app/client/ui/AccountPageCss.ts new file mode 100644 index 00000000..034b62a4 --- /dev/null +++ b/app/client/ui/AccountPageCss.ts @@ -0,0 +1,108 @@ +import {theme, vars} from 'app/client/ui2018/cssVars'; +import {icon as gristIcon} from 'app/client/ui2018/icons'; +import {styled} from 'grainjs'; + +export const container = styled('div', ` + display: flex; + justify-content: center; + overflow: auto; +`); + +export const accountPage = styled('div', ` + max-width: 600px; + margin-top: auto; + margin-bottom: auto; + padding: 16px; +`); + +export const content = styled('div', ` + flex: 1 1 300px; +`); + +export const textBtn = styled('button', ` + font-size: ${vars.mediumFontSize}; + color: ${theme.controlFg}; + cursor: pointer; + margin-left: 16px; + background-color: transparent; + border: none; + padding: 0; + text-align: left; + min-width: 110px; + + &:hover { + color: ${theme.controlHoverFg}; + } +`); + +export const icon = styled(gristIcon, ` + background-color: ${theme.controlFg}; + margin: 0 4px 2px 0; + + .${textBtn.className}:hover > & { + background-color: ${theme.controlHoverFg}; + } +`); + +export const description = styled('div', ` + color: ${theme.lightText}; + font-size: 13px; +`); + +export const flexGrow = styled('div', ` + flex-grow: 1; +`); + +export const name = styled(flexGrow, ` + color: ${theme.text}; + word-break: break-word; +`); + +export const email = styled('div', ` + color: ${theme.text}; + word-break: break-word; +`); + +export const loginMethod = styled(flexGrow, ` + color: ${theme.text}; + word-break: break-word; +`); + +export const warning = styled('div', ` + color: ${theme.errorText}; +`); + +export const header = styled('div', ` + height: 32px; + line-height: 32px; + margin: 28px 0 16px 0; + color: ${theme.text}; + font-size: ${vars.xxxlargeFontSize}; + font-weight: ${vars.headerControlTextWeight}; +`); + +export const subHeader = styled('div', ` + color: ${theme.text}; + padding: 8px 0; + vertical-align: top; + font-weight: bold; + display: block; +`); + +export const inlineSubHeader = styled(subHeader, ` + display: inline-block; + min-width: 110px; +`); + +export const dataRow = styled('div', ` + margin: 8px 0px; + display: flex; + align-items: baseline; +`); + +export const betaTag = styled('span', ` + text-transform: uppercase; + vertical-align: super; + font-size: ${vars.xsmallFontSize}; + color: ${theme.accentText}; +`); diff --git a/app/client/ui/AccountWidget.ts b/app/client/ui/AccountWidget.ts index dd6589d6..9adc5182 100644 --- a/app/client/ui/AccountWidget.ts +++ b/app/client/ui/AccountWidget.ts @@ -7,7 +7,7 @@ import {manageTeamUsers} from 'app/client/ui/OpenUserManager'; import {createUserImage} from 'app/client/ui/UserImage'; import * as viewport from 'app/client/ui/viewport'; import {primaryButton} from 'app/client/ui2018/buttons'; -import {colors, mediaDeviceNotSmall, testId, vars} from 'app/client/ui2018/cssVars'; +import {mediaDeviceNotSmall, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu, menuDivider, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; @@ -165,14 +165,14 @@ const cssUserName = styled('div', ` margin-left: 8px; font-size: ${vars.mediumFontSize}; font-weight: ${vars.headerControlTextWeight}; - color: ${colors.dark}; + color: ${theme.text}; `); const cssEmail = styled('div', ` margin-top: 4px; font-size: ${vars.smallFontSize}; font-weight: initial; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssSmallIconWrap = styled('div', ` @@ -181,16 +181,16 @@ const cssSmallIconWrap = styled('div', ` `); const cssOtherEmail = styled('div', ` - color: ${colors.slate}; + color: ${theme.lightText}; .${cssMenuItem.className}-sel & { - color: ${colors.light}; + color: ${theme.menuItemSelectedFg}; } `); const cssCheckmark = styled(icon, ` flex: none; margin-left: 16px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); // Note that this css class hides the item when the device width is small (not based on viewport diff --git a/app/client/ui/AddNewButton.ts b/app/client/ui/AddNewButton.ts index a76d9360..4442e5c8 100644 --- a/app/client/ui/AddNewButton.ts +++ b/app/client/ui/AddNewButton.ts @@ -1,4 +1,4 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {dom, DomElementArg, Observable, styled} from "grainjs"; @@ -20,7 +20,7 @@ export const cssAddNewButton = styled('div', ` align-items: center; margin: 22px 0px 22px 0px; height: 40px; - color: ${colors.light}; + color: ${theme.controlPrimaryFg}; border: none; border-radius: 4px; @@ -30,19 +30,19 @@ export const cssAddNewButton = styled('div', ` font-weight: bold; overflow: hidden; - --circle-color: ${colors.lightGreen}; + --circle-color: ${theme.addNewCircleSmallBg}; &:hover, &.weasel-popup-open { - --circle-color: ${colors.darkGreen}; + --circle-color: ${theme.addNewCircleSmallHoverBg}; } &-open { margin: 22px 16px 22px 16px; - background-color: ${colors.lightGreen}; - --circle-color: ${colors.darkGreen}; + background-color: ${theme.controlPrimaryBg}; + --circle-color: ${theme.addNewCircleBg}; } &-open:hover, &-open.weasel-popup-open { - background-color: ${colors.darkGreen}; - --circle-color: ${colors.darkerGreen}; + background-color: ${theme.controlPrimaryHoverBg}; + --circle-color: ${theme.addNewCircleHoverBg}; } `); const cssLeftMargin = styled('div', ` @@ -53,6 +53,7 @@ const cssLeftMargin = styled('div', ` } `); const cssAddText = styled('div', ` + color: ${theme.controlPrimaryFg}; flex: 0 0.5 content; white-space: nowrap; min-width: 0px; @@ -70,6 +71,6 @@ const cssPlusButton = styled('div', ` text-align: center; `); const cssPlusIcon = styled(icon, ` - background-color: ${colors.light}; + background-color: ${theme.addNewCircleFg}; margin-top: 6px; `); diff --git a/app/client/ui/ApiKey.ts b/app/client/ui/ApiKey.ts index 23ad6988..fa48c8be 100644 --- a/app/client/ui/ApiKey.ts +++ b/app/client/ui/ApiKey.ts @@ -1,4 +1,5 @@ import { basicButton, textButton } from 'app/client/ui2018/buttons'; +import { theme, vars } from 'app/client/ui2018/cssVars'; import { icon } from 'app/client/ui2018/icons'; import { confirmModal } from 'app/client/ui2018/modals'; import { Disposable, dom, IDomArgs, makeTestId, Observable, observable, styled } from 'grainjs'; @@ -119,11 +120,17 @@ export class ApiKey extends Disposable { } const description = styled('div', ` - color: #8a8a8a; - font-size: 13px; + margin-top: 8px; + color: ${theme.lightText}; + font-size: ${vars.mediumFontSize}; `); const cssInput = styled('input', ` + background-color: transparent; + color: ${theme.inputFg}; + border: 1px solid ${theme.inputBorder}; + padding: 4px; + border-radius: 3px; outline: none; flex: 1 0 0; `); diff --git a/app/client/ui/App.css b/app/client/ui/App.css index ebe03729..61221b38 100644 --- a/app/client/ui/App.css +++ b/app/client/ui/App.css @@ -9,7 +9,8 @@ body { font-size: 1.2rem; margin: 0; padding: 0; - background: url('img/gplaypattern.png'); + background: var(--grist-theme-bg, url('img/gplaypattern.png')); + background-color: var(--grist-theme-bg-color, unset); } .g-help { diff --git a/app/client/ui/AppHeader.ts b/app/client/ui/AppHeader.ts index 5fc54d20..907bbdf0 100644 --- a/app/client/ui/AppHeader.ts +++ b/app/client/ui/AppHeader.ts @@ -2,9 +2,8 @@ import {urlState} from 'app/client/models/gristUrlState'; import {buildAppMenuBillingItem} from 'app/client/ui/BillingButtons'; import {getTheme} from 'app/client/ui/CustomThemes'; import {cssLeftPane} from 'app/client/ui/PagePanels'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme, vars} from 'app/client/ui2018/cssVars'; import * as version from 'app/common/version'; -import {BindableValue, Disposable, dom, styled} from "grainjs"; import {menu, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus'; import {isTemplatesOrg, Organization} from 'app/common/UserAPI'; import {AppModel} from 'app/client/models/AppModel'; @@ -13,7 +12,7 @@ import {DocPageModel} from 'app/client/models/DocPageModel'; import * as roles from 'app/common/roles'; import {manageTeamUsersApp} from 'app/client/ui/OpenUserManager'; import {maybeAddSiteSwitcherSection} from 'app/client/ui/SiteSwitcher'; -import {DomContents} from 'grainjs'; +import {BindableValue, Disposable, dom, DomContents, styled} from 'grainjs'; // Maps a name of a Product (from app/gen-server/entity/Product.ts) to a tag (pill) to show next // to the org name. @@ -33,12 +32,12 @@ export class AppHeader extends Disposable { } public buildDom() { - const theme = getTheme(this._appModel.topAppModel.productFlavor); + const productFlavor = getTheme(this._appModel.topAppModel.productFlavor); const currentOrg = this._appModel.currentOrg; return cssAppHeader( - cssAppHeader.cls('-widelogo', theme.wideLogo || false), + cssAppHeader.cls('-widelogo', productFlavor.wideLogo || false), // Show version when hovering over the application icon. cssAppLogo( {title: `Ver ${version.version} (${version.gitcommit})`}, @@ -96,10 +95,11 @@ const cssAppHeader = styled('div', ` width: 100%; height: 100%; align-items: center; + background-color: ${theme.leftPanelBg}; &, &:hover, &:focus { text-decoration: none; outline: none; - color: ${colors.dark}; + color: ${theme.text}; } `); @@ -124,6 +124,7 @@ const cssAppLogo = styled('a', ` `); const cssDropdownIcon = styled(icon, ` + --icon-color: ${theme.text}; flex-shrink: 0; margin-right: 8px; `); @@ -138,7 +139,7 @@ const cssOrg = styled('div', ` font-weight: 500; &:hover { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } .${cssLeftPane.className}-open & { diff --git a/app/client/ui/AppUI.ts b/app/client/ui/AppUI.ts index 2f5081b3..5ee0cbfb 100644 --- a/app/client/ui/AppUI.ts +++ b/app/client/ui/AppUI.ts @@ -105,6 +105,7 @@ function pagePanelsHome(owner: IDisposableOwner, appModel: AppModel, app: App) { headerMain: createTopBarHome(appModel), contentMain: createDocMenu(pageModel), contentTop: buildHomeBanners(appModel), + testId, }); } diff --git a/app/client/ui/ColumnFilterMenu.ts b/app/client/ui/ColumnFilterMenu.ts index b8006144..608ee57d 100644 --- a/app/client/ui/ColumnFilterMenu.ts +++ b/app/client/ui/ColumnFilterMenu.ts @@ -11,10 +11,11 @@ import {FilterInfo} from 'app/client/models/entities/ViewSectionRec'; import {RowId, RowSource} from 'app/client/models/rowset'; import {ColumnFilterFunc, SectionFilter} from 'app/client/models/SectionFilter'; import {TableData} from 'app/client/models/TableData'; +import {cssInput} from 'app/client/ui/cssInput'; import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; import {cssLabel as cssCheckboxLabel, cssCheckboxSquare, cssLabelText, Indeterminate, labeledTriStateSquareCheckbox } from 'app/client/ui2018/checkbox'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menuCssClass, menuDivider} from 'app/client/ui2018/menus'; import {CellValue} from 'app/common/DocActions'; @@ -593,7 +594,7 @@ const cssMenu = styled('div', ` max-width: 400px; max-height: 90vh; outline: none; - background-color: white; + background-color: ${theme.menuBg}; padding-top: 0; padding-bottom: 12px; `); @@ -608,15 +609,15 @@ const cssMenuHeader = styled('div', ` `); const cssSelectAll = styled('div', ` display: flex; - color: ${colors.lightGreen}; + color: ${theme.controlFg}; cursor: default; user-select: none; &-disabled { - color: ${colors.slate}; + color: ${theme.controlSecondaryFg}; } `); const cssDotSeparator = styled('span', ` - color: ${colors.lightGreen}; + color: ${theme.controlFg}; margin: 0 4px; user-select: none; `); @@ -637,14 +638,13 @@ const cssMenuItem = styled('div', ` `); export const cssItemValue = styled(cssLabelText, ` margin-right: 12px; - color: ${colors.dark}; white-space: pre; `); const cssItemCount = styled('div', ` flex-grow: 1; align-self: normal; text-align: right; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssMenuFooter = styled('div', ` display: flex; @@ -656,6 +656,8 @@ const cssApplyButton = styled(primaryButton, ` margin-right: 4px; `); const cssSearch = styled(input, ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; flex-grow: 1; min-width: 1px; -webkit-appearance: none; @@ -668,22 +670,26 @@ const cssSearch = styled(input, ` border: none; outline: none; + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); const cssSearchIcon = styled(icon, ` + --icon-color: ${theme.lightText}; flex-shrink: 0; margin-left: auto; margin-right: 4px; `); const cssNoResults = styled(cssMenuItem, ` font-style: italic; - color: ${colors.slate}; + color: ${theme.lightText}; justify-content: center; `); const cssSortIcon = styled(icon, ` - --icon-color: ${colors.slate}; + --icon-color: ${theme.controlSecondaryFg}; margin-left: auto; &-active { - --icon-color: ${colors.lightGreen} + --icon-color: ${theme.controlFg} } `); const cssLabel = styled(cssCheckboxLabel, ` @@ -695,6 +701,7 @@ const cssToken = styled('div', ` margin-right: 12px; `); const cssRangeHeader = styled(cssMenuItem, ` + color: ${theme.text}; padding: unset; border-radius: 0 0 3px 0; text-transform: uppercase; @@ -704,14 +711,14 @@ const cssRangeHeader = styled(cssMenuItem, ` const cssRangeContainer = styled(cssMenuItem, ` display: flex; justify-content: left; + align-items: center; column-gap: 10px; `); const cssRangeInputSeparator = styled('span', ` font-weight: 600; - position: relative; - top: 3px; - color: var(--grist-color-slate); + color: ${theme.lightText}; `); -const cssRangeInput = styled('input', ` +const cssRangeInput = styled(cssInput, ` + height: unset; width: 120px; `); diff --git a/app/client/ui/DocHistory.ts b/app/client/ui/DocHistory.ts index 76b35754..538c066e 100644 --- a/app/client/ui/DocHistory.ts +++ b/app/client/ui/DocHistory.ts @@ -5,7 +5,7 @@ import {urlState} from 'app/client/models/gristUrlState'; import {getTimeFromNow} from 'app/client/models/HomeModel'; import {buildConfigContainer} from 'app/client/ui/RightPanel'; import {buttonSelect} from 'app/client/ui2018/buttonSelect'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu, menuAnnotate, menuItemLink} from 'app/client/ui2018/menus'; import {buildUrlId, parseUrlId} from 'app/common/gristUrls'; @@ -108,7 +108,7 @@ export class DocHistory extends Disposable implements IDomComponent { const cssSubTabs = styled('div', ` padding: 16px; - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; `); const cssSnapshot = styled('div', ` @@ -117,24 +117,25 @@ const cssSnapshot = styled('div', ` const cssSnapshotTime = styled('div', ` text-align: right; - color: ${colors.slate}; + color: ${theme.lightText}; font-size: ${vars.smallFontSize}; `); const cssSnapshotCard = styled('div', ` - border: 1px solid ${colors.mediumGrey}; + border: 1px solid ${theme.documentHistorySnapshotBorder}; padding: 8px; - background: white; + color: ${theme.documentHistorySnapshotFg}; + background: ${theme.documentHistorySnapshotBg}; border-radius: 8px; overflow: hidden; display: flex; align-items: center; - --icon-color: ${colors.slate}; + --icon-color: ${theme.controlSecondaryFg}; &-current { - background-color: ${colors.dark}; - color: ${colors.light}; - --icon-color: ${colors.light}; + background-color: ${theme.documentHistorySnapshotSelectedBg}; + color: ${theme.documentHistorySnapshotSelectedFg}; + --icon-color: ${theme.documentHistorySnapshotSelectedFg}; } `); @@ -152,6 +153,6 @@ const cssMenuDots = styled('div', ` border-radius: 3px; cursor: default; &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); diff --git a/app/client/ui/DocMenu.ts b/app/client/ui/DocMenu.ts index fadc25ba..698ca980 100644 --- a/app/client/ui/DocMenu.ts +++ b/app/client/ui/DocMenu.ts @@ -17,7 +17,7 @@ import {transition} from 'app/client/ui/transitions'; import {showWelcomeQuestions} from 'app/client/ui/WelcomeQuestions'; import {createVideoTourTextButton} from 'app/client/ui/OpenVideoTour'; import {buttonSelect, cssButtonSelect} from 'app/client/ui2018/buttonSelect'; -import {colors, isNarrowScreenObs} from 'app/client/ui2018/cssVars'; +import {isNarrowScreenObs, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {loadingSpinner} from 'app/client/ui2018/loaders'; import {menu, menuItem, menuText, select} from 'app/client/ui2018/menus'; @@ -84,7 +84,7 @@ function createLoadedDocMenu(owner: IDisposableOwner, home: HomeModel) { // TODO: this is shown on all pages, but there is a hack in currentWSPinnedDocs that // removes all pinned docs when on trash page. dom.maybe((use) => use(home.currentWSPinnedDocs).length > 0, () => [ - css.docListHeader(css.docHeaderIconDark('PinBig'), 'Pinned Documents'), + css.docListHeader(css.pinnedDocsIcon('PinBig'), 'Pinned Documents'), createPinnedDocs(home, home.currentWSPinnedDocs), ]), @@ -393,7 +393,7 @@ function buildWorkspaceDocBlock(home: HomeModel, workspace: Workspace, flashDocI // The flash value may change to true, and then immediately to false. We highlight it // using a transition, and scroll into view, when it turns back to false. transition(flash, { - prepare(elem, val) { if (!val) { elem.style.backgroundColor = colors.slate.toString(); } }, + prepare(elem, val) { if (!val) { elem.style.backgroundColor = theme.lightText.toString(); } }, run(elem, val) { if (!val) { elem.style.backgroundColor = ''; scrollIntoViewIfNeeded(elem); } }, }) ), diff --git a/app/client/ui/DocMenuCss.ts b/app/client/ui/DocMenuCss.ts index 7672fa6b..aa10acc2 100644 --- a/app/client/ui/DocMenuCss.ts +++ b/app/client/ui/DocMenuCss.ts @@ -1,5 +1,5 @@ import {transientInput} from 'app/client/ui/transientInput'; -import {colors, mediaSmall, vars} from 'app/client/ui2018/cssVars'; +import {mediaSmall, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {styled} from 'grainjs'; import {bigBasicButton} from 'app/client/ui2018/buttons'; @@ -41,7 +41,7 @@ export const docList = styled('div', ` const listHeader = styled('div', ` min-height: 32px; line-height: 32px; - color: ${colors.dark}; + color: ${theme.text}; font-size: ${vars.xxxlargeFontSize}; font-weight: ${vars.headerControlTextWeight}; `); @@ -81,6 +81,7 @@ export const allDocsTemplates = styled('div', ` `); export const docBlock = styled('div', ` + color: ${theme.text}; max-width: 550px; min-width: 300px; margin-bottom: 28px; @@ -96,6 +97,7 @@ export const templatesDocBlock = styled(docBlock, ` `); export const otherSitesBlock = styled('div', ` + color: ${theme.text}; margin-bottom: 32px; `); @@ -112,16 +114,18 @@ export const siteButton = styled(bigBasicButton, ` flex: 0 0 auto; `); -export const docHeaderIconDark = styled(icon, ` +export const docHeaderIcon = styled(icon, ` margin-right: 8px; margin-top: -3px; + --icon-color: ${theme.lightText}; `); -export const docHeaderIcon = styled(docHeaderIconDark, ` - --icon-color: ${colors.slate}; +export const pinnedDocsIcon = styled(docHeaderIcon, ` + --icon-color: ${theme.text}; `); export const featuredTemplatesIcon = styled(icon, ` + --icon-color: ${theme.text}; margin-right: 8px; width: 20px; height: 20px; @@ -141,7 +145,7 @@ const docBlockHeader = ` line-height: 40px; margin-bottom: 8px; margin-right: -16px; - color: ${colors.dark}; + color: ${theme.text}; font-size: ${vars.mediumFontSize}; font-weight: bold; &, &:hover, &:focus { @@ -156,6 +160,7 @@ export const docBlockHeaderLink = styled('a', docBlockHeader); export const templateBlockHeader = styled('div', docBlockHeader); export const wsLeft = styled('div', ` + color: ${theme.text}; flex: 1 0 50%; min-width: 0px; margin-right: 24px; @@ -166,11 +171,11 @@ export const docRowWrapper = styled('div', ` margin: 0px -16px 8px -16px; border-radius: 3px; font-size: ${vars.mediumFontSize}; - color: ${colors.dark}; - --icon-color: ${colors.slate}; + color: ${theme.text}; + --icon-color: ${theme.lightText}; &:hover, &.weasel-popup-open, &-renaming { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); @@ -188,7 +193,7 @@ export const docRowLink = styled('a', ` color: inherit; } &-no-access, &-no-access:hover, &-no-access:focus { - color: ${colors.slate}; + color: ${theme.disabledText}; cursor: not-allowed; } `); @@ -211,13 +216,13 @@ export const docName = styled('div', ` export const docPinIcon = styled(icon, ` flex: none; margin-left: 4px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); export const docPublicIcon = styled(icon, ` flex: none; margin-left: auto; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); export const docEditorInput = styled(transientInput, ` @@ -231,7 +236,7 @@ export const docEditorInput = styled(transientInput, ` export const docRowUpdatedAt = styled('div', ` flex: 1 1 50%; - color: ${colors.slate}; + color: ${theme.lightText}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -247,20 +252,20 @@ export const docMenuTrigger = styled('div', ` line-height: 0px; border-radius: 3px; cursor: default; - --icon-color: ${colors.darkGrey}; + --icon-color: ${theme.docMenuDocOptionsFg}; .${docRowLink.className}:hover > & { - --icon-color: ${colors.slate}; + --icon-color: ${theme.docMenuDocOptionsHoverFg}; } &:hover, &.weasel-popup-open { - background-color: ${colors.darkGrey}; - --icon-color: ${colors.slate}; + background-color: ${theme.docMenuDocOptionsHoverBg}; + --icon-color: ${theme.docMenuDocOptionsHoverFg}; } `); export const moveDocModalBody = styled('div', ` display: flex; flex-direction: column; - border-bottom: 1px solid ${colors.darkGrey}; + border-bottom: 1px solid ${theme.modalBorderDark}; margin: 0 -64px; height: 200px; `); @@ -275,11 +280,11 @@ export const moveDocListItem = styled('div', ` font-size: ${vars.mediumFontSize}; &-selected { - background-color: ${colors.lightGreen}; - color: white; + background-color: ${theme.moveDocsSelectedBg}; + color: ${theme.moveDocsSelectedFg}; } &-disabled { - color: ${colors.darkGrey}; + color: ${theme.moveDocsDisabledFg}; cursor: default; } `); @@ -319,13 +324,14 @@ export const sortSelector = styled('div', ` line-height: unset; align-items: center; border-radius: ${vars.controlBorderRadius}; - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.controlFg}; + --icon-color: ${theme.controlFg}; + background-color: unset; &:focus, &:hover { outline: none; box-shadow: none; - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } @media ${mediaSmall} { & { diff --git a/app/client/ui/ExampleCard.ts b/app/client/ui/ExampleCard.ts index 267c3c2e..7b05ad48 100644 --- a/app/client/ui/ExampleCard.ts +++ b/app/client/ui/ExampleCard.ts @@ -1,6 +1,6 @@ import {IExampleInfo} from 'app/client/ui/ExampleInfo'; import {prepareForTransition, TransitionWatcher} from 'app/client/ui/transitions'; -import {colors, mediaXSmall, testId, vars} from 'app/client/ui2018/cssVars'; +import {mediaXSmall, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {cssLink} from 'app/client/ui2018/links'; import {dom, styled} from 'grainjs'; @@ -98,8 +98,8 @@ const cssCard = styled('div', ` margin-right: 24px; max-width: 624px; padding: 32px 56px 32px 32px; - background-color: white; - box-shadow: 0 2px 18px 0 rgba(31,37,50,0.31), 0 0 1px 0 rgba(76,86,103,0.24); + background-color: ${theme.popupBg}; + box-shadow: 0 2px 18px 0 ${theme.popupInnerShadow}, 0 0 1px 0 ${theme.popupOuterShadow}; display: flex; overflow: hidden; transition-property: opacity, transform; @@ -129,10 +129,12 @@ const cssImage = styled('img', ` `); const cssBody = styled('div', ` + color: ${theme.text}; min-width: 0px; `); const cssTitle = styled('div', ` + color: ${theme.text}; font-size: var(--title-font-size); font-weight: ${vars.headerControlTextWeight}; margin-bottom: 16px; @@ -165,10 +167,10 @@ export const cssCloseButton = styled('div', ` padding: 4px; border-radius: 4px; cursor: pointer; - --icon-color: ${colors.slate}; + --icon-color: ${theme.popupCloseButtonFg}; &:hover { - background-color: ${colors.mediumGreyOpaque}; + background-color: ${theme.hover}; } `); diff --git a/app/client/ui/FieldConfig.ts b/app/client/ui/FieldConfig.ts index 909bda3e..f6230ad2 100644 --- a/app/client/ui/FieldConfig.ts +++ b/app/client/ui/FieldConfig.ts @@ -5,7 +5,7 @@ import {buildHighlightedCode, cssCodeBlock} from 'app/client/ui/CodeHighlight'; import {cssBlockedCursor, cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; import {buildFormulaTriggers} from 'app/client/ui/TriggerFormulas'; import {textButton} from 'app/client/ui2018/buttons'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {cssIconButton, icon} from 'app/client/ui2018/icons'; import {IconName} from 'app/client/ui2018/IconList'; @@ -42,13 +42,13 @@ export function buildNameConfig(owner: MultiHolder, origColumn: ColumnRec, curso cssRow( dom.cls(cssBlockedCursor.className, origColumn.disableModify), cssColLabelBlock( - editor = textInput(fromKo(origColumn.label), + editor = cssInput(fromKo(origColumn.label), async val => { await origColumn.label.saveOnly(val); editedLabel.set(''); }, dom.on('input', (ev, elem) => { if (!untieColId.peek()) { editedLabel.set(elem.value); } }), dom.boolAttr('disabled', origColumn.disableModify), testId('field-label'), ), - textInput(editableColId, + cssInput(editableColId, saveColId, dom.boolAttr('disabled', use => use(origColumn.disableModify) || !use(origColumn.untieColIdFromLabel)), cssCodeBlock.cls(''), @@ -332,10 +332,10 @@ export const cssFieldFormula = styled(buildHighlightedCode, ` cursor: pointer; margin-top: 4px; padding-left: 24px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; &-disabled-icon.formula_field_sidepane::before { - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; } &-disabled { pointer-events: none; @@ -344,20 +344,20 @@ export const cssFieldFormula = styled(buildHighlightedCode, ` const cssToggleButton = styled(cssIconButton, ` margin-left: 8px; - background-color: var(--grist-color-medium-grey-opaque); - box-shadow: inset 0 0 0 1px ${colors.darkGrey}; + background-color: ${theme.rightPanelToggleButtonDisabledBg}; + box-shadow: inset 0 0 0 1px ${theme.inputBorder}; &-selected, &-selected:hover { box-shadow: none; - background-color: ${colors.dark}; - --icon-color: ${colors.light}; + background-color: ${theme.rightPanelToggleButtonEnabledBg}; + --icon-color: ${theme.rightPanelToggleButtonEnabledFg}; } &-selected:hover { - --icon-color: ${colors.darkGrey}; + --icon-color: ${theme.rightPanelToggleButtonEnabledHoverFg}; } &-disabled, &-disabled:hover { - --icon-color: ${colors.light}; - background-color: var(--grist-color-medium-grey-opaque); + --icon-color: ${theme.rightPanelToggleButtonDisabledFg}; + background-color: ${theme.rightPanelToggleButtonDisabledBg}; } `); @@ -374,7 +374,7 @@ const cssColTieBlock = styled('div', ` const cssColTieConnectors = styled('div', ` position: absolute; - border: 2px solid var(--grist-color-dark-grey); + border: 2px solid ${theme.inputBorder}; top: -9px; bottom: -9px; right: 11px; @@ -386,3 +386,18 @@ const cssColTieConnectors = styled('div', ` const cssEmptySeparator = styled('div', ` margin-top: 16px; `); + +const cssInput = styled(textInput, ` + color: ${theme.inputFg}; + background-color: ${theme.mainPanelBg}; + border: 1px solid ${theme.inputBorder}; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } + + &:disabled { + background-color: ${theme.inputDisabledBg}; + color: ${theme.inputDisabledFg}; + } +`); diff --git a/app/client/ui/FilterBar.ts b/app/client/ui/FilterBar.ts index 1711d0b8..7a6e5d94 100644 --- a/app/client/ui/FilterBar.ts +++ b/app/client/ui/FilterBar.ts @@ -3,7 +3,7 @@ import { ColumnRec, ViewFieldRec, ViewSectionRec } from "app/client/models/DocMo import { FilterInfo } from "app/client/models/entities/ViewSectionRec"; import { attachColumnFilterMenu } from "app/client/ui/ColumnFilterMenu"; import { cssButton, cssButtonGroup } from "app/client/ui2018/buttons"; -import { colors, testId } from "app/client/ui2018/cssVars"; +import { testId, theme } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; import { menu, menuItemAsync } from "app/client/ui2018/menus"; import { dom, IDisposableOwner, IDomArgs, styled } from "grainjs"; @@ -117,14 +117,14 @@ const cssBtn = styled('div', ` margin: 0 4px; } &-grayed { - color: ${colors.light}; - --icon-color: ${colors.light}; - background-color: ${colors.slate}; - border-color: ${colors.slate}; + color: ${theme.filterBarButtonSavedFg}; + --icon-color: ${theme.filterBarButtonSavedFg}; + background-color: ${theme.filterBarButtonSavedBg}; + border-color: ${theme.filterBarButtonSavedBg}; } &-grayed:hover { - background-color: ${colors.darkGrey}; - border-color: ${colors.darkGrey}; + background-color: ${theme.filterBarButtonSavedHoverBg}; + border-color: ${theme.filterBarButtonSavedHoverBg}; } `); const primaryButton = (...args: IDomArgs) => ( diff --git a/app/client/ui/GridViewMenus.ts b/app/client/ui/GridViewMenus.ts index 012d6076..b366f595 100644 --- a/app/client/ui/GridViewMenus.ts +++ b/app/client/ui/GridViewMenus.ts @@ -1,6 +1,6 @@ import { allCommands } from 'app/client/components/commands'; import { ViewFieldRec } from 'app/client/models/entities/ViewFieldRec'; -import { testId, vars } from 'app/client/ui2018/cssVars'; +import { testId, theme } from 'app/client/ui2018/cssVars'; import { icon } from 'app/client/ui2018/icons'; import { menuDivider, menuItem, menuItemCmd } from 'app/client/ui2018/menus'; import { Sort } from 'app/common/SortSpec'; @@ -297,9 +297,9 @@ const cssCustomMenuItem = styled('div', ` padding: 8px 8px; display: flex; &:not(:hover) { - background-color: white; - color: black; - --icon-color: black; + background-color: ${theme.menuBg}; + color: ${theme.menuItemFg}; + --icon-color: ${theme.menuItemFg}; } &:last-of-type { padding-right: 24px; @@ -310,9 +310,9 @@ const cssCustomMenuItem = styled('div', ` flex: 1 0 auto; } &-selected, &-selected:not(:hover) { - background-color: ${vars.primaryBg}; - color: white; - --icon-color: white; + background-color: ${theme.menuItemSelectedBg}; + color: ${theme.menuItemSelectedFg}; + --icon-color: ${theme.menuItemSelectedFg}; } `); diff --git a/app/client/ui/HomeIntro.ts b/app/client/ui/HomeIntro.ts index d2e7b2ef..75debae4 100644 --- a/app/client/ui/HomeIntro.ts +++ b/app/client/ui/HomeIntro.ts @@ -5,7 +5,7 @@ import * as css from 'app/client/ui/DocMenuCss'; import {createDocAndOpen, importDocAndOpen} from 'app/client/ui/HomeLeftPane'; import {manageTeamUsersApp} from 'app/client/ui/OpenUserManager'; import {bigBasicButton, cssButton} from 'app/client/ui2018/buttons'; -import {testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {cssLink} from 'app/client/ui2018/links'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; @@ -131,6 +131,7 @@ function makeCreateButtons(homeModel: HomeModel) { } const cssParagraph = styled(css.docBlock, ` + color: ${theme.text}; line-height: 1.6; `); diff --git a/app/client/ui/HomeLeftPane.ts b/app/client/ui/HomeLeftPane.ts index fe46d1eb..057e5ac1 100644 --- a/app/client/ui/HomeLeftPane.ts +++ b/app/client/ui/HomeLeftPane.ts @@ -9,7 +9,7 @@ import {docImport, importFromPlugin} from 'app/client/ui/HomeImports'; import {cssLinkText, cssPageEntry, cssPageIcon, cssPageLink, cssSpacer} from 'app/client/ui/LeftPanelCommon'; import {createVideoTourToolsButton} from 'app/client/ui/OpenVideoTour'; import {transientInput} from 'app/client/ui/transientInput'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu, menuIcon, menuItem, upgradableMenuItem, upgradeText} from 'app/client/ui2018/menus'; import {confirmModal} from 'app/client/ui2018/modals'; @@ -248,6 +248,7 @@ export const cssEditorInput = styled(transientInput, ` flex: 1 1 0px; min-width: 0px; color: initial; + background-color: ${theme.inputBg}; margin-right: 16px; font-size: inherit; `); @@ -265,9 +266,9 @@ const cssMenuTrigger = styled('div', ` display: block; } &:hover, &.weasel-popup-open { - background-color: ${colors.darkGrey}; + background-color: ${theme.pageOptionsHoverBg}; } .${cssPageEntry.className}-selected &:hover, .${cssPageEntry.className}-selected &.weasel-popup-open { - background-color: ${colors.slate}; + background-color: ${theme.pageOptionsSelectedHoverBg}; } `); diff --git a/app/client/ui/LeftPanelCommon.ts b/app/client/ui/LeftPanelCommon.ts index 0c3a3628..dd5180dd 100644 --- a/app/client/ui/LeftPanelCommon.ts +++ b/app/client/ui/LeftPanelCommon.ts @@ -15,7 +15,7 @@ */ import {beaconOpenMessage} from 'app/client/lib/helpScout'; import {AppModel} from 'app/client/models/AppModel'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; import {dom, DomContents, Observable, styled} from 'grainjs'; @@ -83,7 +83,7 @@ export const cssTools = styled('div', ` export const cssSectionHeader = styled('div', ` margin: 24px 0 8px 24px; - color: ${colors.slate}; + color: ${theme.lightText}; text-transform: uppercase; font-weight: 500; font-size: ${vars.xsmallFontSize}; @@ -96,22 +96,22 @@ export const cssSectionHeader = styled('div', ` export const cssPageEntry = styled('div', ` margin: 0px 16px 0px 0px; border-radius: 0 3px 3px 0; - color: ${colors.dark}; - --icon-color: ${colors.slate}; + color: ${theme.text}; + --icon-color: ${theme.lightText}; cursor: default; &:hover, &.weasel-popup-open, &-renaming { - background-color: ${colors.mediumGrey}; + background-color: ${theme.pageHoverBg}; } &-selected, &-selected:hover, &-selected.weasel-popup-open { - background-color: ${colors.darkBg}; - color: ${colors.light}; - --icon-color: ${colors.light}; + background-color: ${theme.activePageBg}; + color: ${theme.activePageFg}; + --icon-color: ${theme.activePageFg}; } &-disabled, &-disabled:hover, &-disabled.weasel-popup-open { background-color: initial; - color: ${colors.darkGrey}; - --icon-color: ${colors.darkGrey}; + color: ${theme.disabledPageFg}; + --icon-color: ${theme.disabledPageFg}; } .${cssTools.className}-collapsed > & { margin-right: 0; @@ -171,12 +171,12 @@ export const cssPageEntryMain = styled(cssPageEntry, ` export const cssPageEntrySmall = styled(cssPageEntry, ` flex: none; border-radius: 3px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.controlFg}; & > .${cssPageLink.className} { padding: 0 8px 0 16px; } &:hover { - --icon-color: ${colors.darkGreen}; + --icon-color: ${theme.controlHoverFg}; } .${cssTools.className}-collapsed & { display: none; diff --git a/app/client/ui/MakeCopyMenu.ts b/app/client/ui/MakeCopyMenu.ts index b4fc9118..a79bdf93 100644 --- a/app/client/ui/MakeCopyMenu.ts +++ b/app/client/ui/MakeCopyMenu.ts @@ -9,7 +9,7 @@ import {getWorkspaceInfo, ownerName, workspaceName} from 'app/client/models/Work import {cssInput} from 'app/client/ui/cssInput'; import {bigBasicButton, bigPrimaryButtonLink} from 'app/client/ui2018/buttons'; import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {loadingSpinner} from 'app/client/ui2018/loaders'; import {select} from 'app/client/ui2018/menus'; import {confirmModal, cssModalBody, cssModalButtons, cssModalWidth, modal, saveModal} from 'app/client/ui2018/modals'; @@ -284,7 +284,7 @@ export const cssField = styled('div', ` export const cssLabel = styled('label', ` font-weight: normal; font-size: ${vars.mediumFontSize}; - color: ${colors.dark}; + color: ${theme.text}; margin: 8px 16px 0 0; white-space: nowrap; width: 80px; @@ -292,7 +292,7 @@ export const cssLabel = styled('label', ` `); const cssWarningText = styled('div', ` - color: red; + color: ${theme.errorText}; margin-top: 8px; `); diff --git a/app/client/ui/MenuToggle.ts b/app/client/ui/MenuToggle.ts index 6013f423..88885918 100644 --- a/app/client/ui/MenuToggle.ts +++ b/app/client/ui/MenuToggle.ts @@ -1,6 +1,6 @@ -import { dom, DomArg, IDisposableOwner, styled } from "grainjs"; +import { theme } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; -import { colors } from "app/client/ui2018/cssVars"; +import { dom, DomArg, IDisposableOwner, styled } from "grainjs"; /** * Creates a toggle button - little square button with a dropdown icon inside, used @@ -15,18 +15,18 @@ export function menuToggle(obs: IDisposableOwner, ...args: DomArg[]) { } const cssMenuToggle = styled('div.menu_toggle', ` - background: white; + background: ${theme.menuToggleBg}; cursor: pointer; - --icon-color: ${colors.slate}; - border: 1px solid ${colors.slate}; + --icon-color: ${theme.menuToggleFg}; + border: 1px solid ${theme.menuToggleBorder}; border-radius: 4px; &:hover { - --icon-color: ${colors.darkGreen}; - border-color: ${colors.darkGreen}; + --icon-color: ${theme.menuToggleHoverFg}; + border-color: ${theme.menuToggleHoverFg}; } &:active { - --icon-color: ${colors.darkerGreen}; - border-color: ${colors.darkerGreen}; + --icon-color: ${theme.menuToggleActiveFg}; + border-color: ${theme.menuToggleActiveFg}; } & > .menu_toggle_icon { display: block; /* don't create a line */ diff --git a/app/client/ui/NotifyUI.ts b/app/client/ui/NotifyUI.ts index 8f47bf58..eacca12f 100644 --- a/app/client/ui/NotifyUI.ts +++ b/app/client/ui/NotifyUI.ts @@ -4,7 +4,7 @@ import {ConnectState} from 'app/client/models/ConnectState'; import {urlState} from 'app/client/models/gristUrlState'; import {Expirable, IAppError, Notification, Notifier, NotifyAction, Progress} from 'app/client/models/NotifyModel'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {IconName} from "app/client/ui2018/IconList"; import {menuCssClass} from 'app/client/ui2018/menus'; @@ -193,8 +193,8 @@ function buildConnectStateButton(state: ConnectState): Element { const cssDropdownWrapper = styled('div', ` - background-color: white; - border: 1px solid ${colors.darkGrey}; + background-color: ${theme.notificationsPanelBodyBg}; + border: 1px solid ${theme.notificationsPanelBorder}; padding: 0px; `); @@ -208,17 +208,18 @@ const cssDropdownHeader = styled('div', ` justify-content: space-between; align-items: center; padding: 24px; - background-color: ${colors.lightGrey}; - outline: 1px solid ${colors.darkGrey}; + background-color: ${theme.notificationsPanelHeaderBg}; + outline: 1px solid ${theme.notificationsPanelBorder}; `); const cssDropdownHeaderTitle = styled('span', ` + color: ${theme.text}; font-weight: bold; `); const cssDropdownFeedbackLink = styled('div', ` display: flex; - color: ${colors.lightGreen}; + color: ${theme.controlFg}; cursor: pointer; user-select: none; &:hover { @@ -227,21 +228,21 @@ const cssDropdownFeedbackLink = styled('div', ` `); const cssDropdownFeedbackIcon = styled(icon, ` - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; margin-right: 4px; `); const cssDropdownStatus = styled('div', ` padding: 16px 48px 24px 48px; text-align: center; - border-top: 1px solid ${colors.darkGrey}; + border-top: 1px solid ${theme.notificationsPanelBorder}; `); const cssDropdownStatusText = styled('div', ` display: inline-block; margin: 8px 0 0 0; text-align: left; - color: ${colors.slate}; + color: ${theme.lightText}; `); // z-index below is set above other assorted children of which include z-index such as 999 @@ -279,7 +280,7 @@ const cssToastActions = styled('div', ` display: flex; align-items: flex-end; margin-top: 16px; - color: ${colors.lightGreen}; + color: ${theme.toastControlFg}; `); const cssToastWrapper = styled('div', ` @@ -292,8 +293,8 @@ const cssToastWrapper = styled('div', ` padding: 12px; border-radius: 3px; - color: ${colors.light}; - background-color: ${vars.toastBg}; + color: ${theme.toastText}; + background-color: ${theme.toastBg}; pointer-events: auto; @@ -301,28 +302,28 @@ const cssToastWrapper = styled('div', ` transition: opacity ${Expirable.fadeDelay}ms; &-error { - border-left: 6px solid ${colors.error}; + border-left: 6px solid ${theme.toastErrorBg}; padding-left: 6px; - --icon-color: ${colors.error}; + --icon-color: ${theme.toastErrorIcon}; } &-success { - border-left: 6px solid ${colors.darkGreen}; + border-left: 6px solid ${theme.toastSuccessBg}; padding-left: 6px; - --icon-color: ${colors.darkGreen}; + --icon-color: ${theme.toastSuccessIcon}; } &-warning { - border-left: 6px solid ${colors.warningBg}; + border-left: 6px solid ${theme.toastWarningBg}; padding-left: 6px; - --icon-color: ${colors.warning}; + --icon-color: ${theme.toastWarningIcon}; } &-info { - border-left: 6px solid ${colors.lightBlue}; + border-left: 6px solid ${theme.toastInfoBg}; padding-left: 6px; - --icon-color: ${colors.lightBlue}; + --icon-color: ${theme.toastInfoIcon}; } &-info .${cssToastActions.className} { - color: ${colors.lighterBlue}; + color: ${theme.toastInfoControlFg}; } &-left-icon { @@ -340,9 +341,9 @@ const cssToastWrapper = styled('div', ` } .${cssDropdownContent.className} > & { background-color: unset; - color: unset; + color: ${theme.text}; border-radius: 0px; - border-top: 1px solid ${colors.darkGrey}; + border-top: 1px solid ${theme.notificationsPanelBorder}; margin: 0px; padding: 16px 20px; } @@ -389,8 +390,8 @@ const cssToastMemos = styled('div', ` const cssToastMemo = styled('div', ` margin: 3px; - color: ${colors.dark}; - background: ${colors.light}; + color: ${theme.text}; + background: ${theme.notificationsPanelBodyBg}; padding: 3px; `); @@ -399,16 +400,16 @@ const cssProgressBarWrapper = styled('div', ` margin-bottom: 11px; height: 3px; border-radius: 3px; - background-color: ${colors.light}; + background-color: ${theme.progressBarBg}; `); const cssProgressBarSize = styled('span', ` - color: ${colors.slate}; + color: ${theme.toastLightText}; `); const cssProgressBarStatus = styled('div', ` height: 3px; min-width: 3px; border-radius: 3px; - background-color: ${colors.lightGreen}; + background-color: ${theme.progressBarFg}; `); diff --git a/app/client/ui/OnBoardingPopups.ts b/app/client/ui/OnBoardingPopups.ts index 692d92c3..0f52dad9 100644 --- a/app/client/ui/OnBoardingPopups.ts +++ b/app/client/ui/OnBoardingPopups.ts @@ -27,7 +27,7 @@ import { createPopper, Placement } from '@popperjs/core'; import { FocusLayer } from 'app/client/lib/FocusLayer'; import * as Mousetrap from 'app/client/lib/Mousetrap'; import { bigBasicButton, bigPrimaryButton } from "app/client/ui2018/buttons"; -import { colors, vars } from "app/client/ui2018/cssVars"; +import { theme, vars } from "app/client/ui2018/cssVars"; import range = require("lodash/range"); import {IGristUrlState} from "app/common/gristUrls"; import {urlState} from "app/client/models/gristUrlState"; @@ -321,13 +321,13 @@ function buildArrow() { const Container = styled('div', ` align-self: center; - border: 2px solid ${colors.lightGreen}; + border: 2px solid ${theme.accentBorder}; border-radius: 3px; z-index: 1000; max-width: 490px; position: relative; - background-color: white; - box-shadow: 0 2px 18px 0 rgba(31,37,50,0.31), 0 0 1px 0 rgba(76,86,103,0.24); + background-color: ${theme.popupBg}; + box-shadow: 0 2px 18px 0 ${theme.popupInnerShadow}, 0 0 1px 0 ${theme.popupOuterShadow}; outline: unset; `); @@ -339,9 +339,9 @@ const ArrowContainer = styled('div', ` position: absolute; & path { - stroke: ${colors.lightGreen}; + stroke: ${theme.accentBorder}; stroke-width: 2px; - fill: white; + fill: ${theme.popupBg}; } ${sideSelectorChunk('top')} > & { @@ -376,7 +376,7 @@ const ArrowContainer = styled('div', ` const ContentWrapper = styled('div', ` position: relative; padding: 32px; - background-color: white; + background-color: ${theme.popupBg}; `); const Footer = styled('div', ` @@ -402,9 +402,9 @@ const Dot = styled('div', ` border-radius: 3px; margin-right: 12px; align-self: center; - background-color: ${colors.lightGreen}; + background-color: ${theme.progressBarFg}; &-done { - background-color: ${colors.darkGrey}; + background-color: ${theme.progressBarBg}; } `); @@ -424,10 +424,11 @@ const Overlay = styled('div', ` const cssTitle = styled('div', ` font-size: ${vars.xxxlargeFontSize}; font-weight: ${vars.headerControlTextWeight}; - color: ${colors.dark}; + color: ${theme.text}; margin: 0 0 16px 0; line-height: 32px; `); const cssBody = styled('div', ` + color: ${theme.text}; `); diff --git a/app/client/ui/OpenVideoTour.ts b/app/client/ui/OpenVideoTour.ts index 28a55133..33bae5cc 100644 --- a/app/client/ui/OpenVideoTour.ts +++ b/app/client/ui/OpenVideoTour.ts @@ -1,6 +1,6 @@ import * as commands from 'app/client/components/commands'; import {cssLinkText, cssPageEntryMain, cssPageIcon, cssPageLink} from 'app/client/ui/LeftPanelCommon'; -import {colors} from 'app/client/ui2018/cssVars'; +import {theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {modal} from 'app/client/ui2018/modals'; import {commonUrls, shouldHideUiElement} from 'app/common/gristUrls'; @@ -106,21 +106,21 @@ const cssVideo = styled('iframe', ` `); const cssVideoTourTextButton = styled('div', ` - color: ${colors.lightGreen}; + color: ${theme.controlFg}; cursor: pointer; &:hover { - color: ${colors.darkGreen}; + color: ${theme.controlHoverFg}; } `); const cssVideoIcon = styled(icon, ` - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; cursor: pointer; margin: 0px 4px 3px 0; .${cssVideoTourTextButton.className}:hover > & { - background-color: ${colors.darkGreen}; + background-color: ${theme.controlHoverFg}; } `); @@ -130,10 +130,10 @@ const cssCloseButton = styled('div', ` padding: 4px; border-radius: 4px; cursor: pointer; - --icon-color: ${colors.slate}; + --icon-color: ${theme.modalCloseButtonFg}; &:hover { - background-color: ${colors.mediumGreyOpaque}; + background-color: ${theme.hover}; } `); diff --git a/app/client/ui/PagePanels.ts b/app/client/ui/PagePanels.ts index f00b9146..8005074e 100644 --- a/app/client/ui/PagePanels.ts +++ b/app/client/ui/PagePanels.ts @@ -6,7 +6,7 @@ import {watchElementForBlur} from 'app/client/lib/FocusLayer'; import {urlState} from "app/client/models/gristUrlState"; import {resizeFlexVHandle} from 'app/client/ui/resizeHandle'; import {transition, TransitionWatcher} from 'app/client/ui/transitions'; -import {colors, cssHideForNarrowScreen, isScreenResizing, mediaNotSmall, mediaSmall} from 'app/client/ui2018/cssVars'; +import {cssHideForNarrowScreen, isScreenResizing, mediaNotSmall, mediaSmall, theme} from 'app/client/ui2018/cssVars'; import {isNarrowScreenObs} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {dom, DomArg, MultiHolder, noTestId, Observable, styled, subscribe, TestId} from "grainjs"; @@ -102,7 +102,7 @@ export function pagePanels(page: PageContents) { testId('left-panel'), cssOverflowContainer( contentWrapper = cssLeftPanelContainer( - cssTopHeader(left.header), + cssLeftPaneHeader(left.header), left.content, ), ), @@ -264,7 +264,7 @@ export function pagePanels(page: PageContents) { cssRightPane( testId('right-panel'), - cssTopHeader(right.header), + cssRightPaneHeader(right.header), right.content, dom.style('width', (use) => use(right.panelOpen) ? use(right.panelWidth) + 'px' : ''), @@ -341,7 +341,7 @@ const cssPageContainer = styled(cssVBox, ` right: 0; bottom: 0; min-width: 600px; - background-color: ${colors.lightGrey}; + background-color: ${theme.pageBg}; @media ${mediaSmall} { & { @@ -359,7 +359,7 @@ const cssContentMain = styled(cssHBox, ` `); export const cssLeftPane = styled(cssVBox, ` position: relative; - background-color: ${colors.lightGrey}; + background-color: ${theme.leftPanelBg}; width: 48px; margin-right: 0px; transition: width 0.4s; @@ -415,7 +415,7 @@ const cssMainPane = styled(cssVBox, ` position: relative; flex: 1 1 0px; min-width: 0px; - background-color: white; + background-color: ${theme.mainPanelBg}; z-index: 1; &-left-overlap { margin-left: 48px; @@ -423,7 +423,7 @@ const cssMainPane = styled(cssVBox, ` `); const cssRightPane = styled(cssVBox, ` position: relative; - background-color: ${colors.lightGrey}; + background-color: ${theme.rightPanelBg}; width: 0px; margin-left: 0px; overflow: hidden; @@ -461,13 +461,13 @@ const cssRightPane = styled(cssVBox, ` display: none; } `); -const cssTopHeader = styled('div', ` - height: 48px; +const cssHeader = styled('div', ` + height: 49px; flex: none; display: flex; align-items: center; justify-content: space-between; - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; @media print { & { @@ -479,9 +479,18 @@ const cssTopHeader = styled('div', ` display: none; } `); +const cssTopHeader = styled(cssHeader, ` + background-color: ${theme.topHeaderBg}; +`); +const cssLeftPaneHeader = styled(cssHeader, ` + background-color: ${theme.leftPanelBg}; +`); +const cssRightPaneHeader = styled(cssHeader, ` + background-color: ${theme.rightPanelBg}; +`); const cssBottomFooter = styled ('div', ` height: ${bottomFooterHeightPx}px; - background-color: white; + background-color: ${theme.bottomFooterBg}; z-index: 20; display: flex; flex-direction: row; @@ -492,7 +501,7 @@ const cssBottomFooter = styled ('div', ` bottom: 0; left: 0; right: 0; - border-top: 1px solid ${colors.mediumGrey}; + border-top: 1px solid ${theme.pagePanelsBorder}; @media ${mediaNotSmall} { & { display: none; @@ -508,8 +517,8 @@ const cssBottomFooter = styled ('div', ` } `); const cssResizeFlexVHandle = styled(resizeFlexVHandle, ` - --resize-handle-color: ${colors.mediumGrey}; - --resize-handle-highlight: ${colors.lightGreen}; + --resize-handle-color: ${theme.pagePanelsBorder}; + --resize-handle-highlight: ${theme.pagePanelsBorderResizing}; @media print { & { @@ -521,7 +530,7 @@ const cssResizeDisabledBorder = styled('div', ` flex: none; width: 1px; height: 100%; - background-color: ${colors.mediumGrey}; + background-color: ${theme.pagePanelsBorder}; position: absolute; top: 0; bottom: 0; @@ -535,20 +544,20 @@ const cssPanelOpener = styled(icon, ` padding: 8px 8px; cursor: pointer; -webkit-mask-size: 16px 16px; - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; transition: transform 0.4s; - &:hover { background-color: ${colors.darkGreen}; } + &:hover { background-color: ${theme.controlHoverFg}; } &-open { transform: rotateY(180deg); } `); const cssPanelOpenerNarrowScreenBtn = styled('div', ` width: 32px; height: 32px; - --icon-color: ${colors.slate}; + --icon-color: ${theme.sidePanelOpenerFg}; cursor: pointer; border-radius: 4px; &-open { - background-color: ${colors.lightGreen}; - --icon-color: white; + background-color: ${theme.sidePanelOpenerActiveBg}; + --icon-color: ${theme.sidePanelOpenerActiveFg}; } `); const cssPanelOpenerNarrowScreen = styled(icon, ` @@ -562,7 +571,7 @@ const cssContentOverlay = styled('div', ` left: 0; bottom: 0; right: 0; - background-color: grey; + background-color: ${theme.pageBackdrop}; opacity: 0.5; display: none; z-index: 9; diff --git a/app/client/ui/PageWidgetPicker.ts b/app/client/ui/PageWidgetPicker.ts index bc58ef3f..adaaf6d0 100644 --- a/app/client/ui/PageWidgetPicker.ts +++ b/app/client/ui/PageWidgetPicker.ts @@ -3,7 +3,7 @@ import { ColumnRec, DocModel, TableRec, ViewSectionRec } from 'app/client/models import { linkId, NoLink } from 'app/client/ui/selectBy'; import { getWidgetTypes, IWidgetType } from 'app/client/ui/widgetTypes'; import { bigPrimaryButton } from "app/client/ui2018/buttons"; -import { colors, vars } from "app/client/ui2018/cssVars"; +import { theme, vars } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; import { spinnerModal } from 'app/client/ui2018/modals'; import { isLongerThan, nativeCompare } from "app/common/gutil"; @@ -421,15 +421,15 @@ function header(label: string) { } const cssContainer = styled('div', ` - --outline: 1px solid rgba(217,217,217,0.60); + --outline: 1px solid ${theme.widgetPickerBorder}; max-height: 386px; - box-shadow: 0 2px 20px 0 rgba(38,38,51,0.20); + box-shadow: 0 2px 20px 0 ${theme.widgetPickerShadow}; border-radius: 2px; display: flex; flex-direction: column; user-select: none; - background-color: white; + background-color: ${theme.widgetPickerPrimaryBg}; `); const cssPopupWrapper = styled('div', ` @@ -450,17 +450,19 @@ const cssPanel = styled('div', ` overflow: auto; padding-bottom: 18px; &:nth-of-type(2n) { - background-color: ${colors.lightGrey}; + background-color: ${theme.widgetPickerSecondaryBg}; outline: var(--outline); } `); const cssHeader = styled('div', ` + color: ${theme.text}; margin: 24px 0 24px 24px; font-size: ${vars.mediumFontSize}; `); const cssEntry = styled('div', ` + color: ${theme.widgetPickerItemFg}; padding: 0 0 0 24px; height: 32px; display: flex; @@ -471,10 +473,10 @@ const cssEntry = styled('div', ` overflow: hidden; cursor: pointer; &-selected { - background-color: ${colors.mediumGrey}; + background-color: ${theme.widgetPickerItemSelectedBg}; } &-disabled { - color: ${colors.mediumGrey}; + color: ${theme.widgetPickerItemDisabledBg}; cursor: default; } &-disabled&-selected { @@ -485,14 +487,14 @@ const cssEntry = styled('div', ` const cssIcon = styled(icon, ` margin-right: 8px; flex-shrink: 0; - --icon-color: ${colors.slate}; + --icon-color: ${theme.widgetPickerIcon}; .${cssEntry.className}-disabled > & { - opacity: 0.2; + opacity: 0.25; } `); const cssTypeIcon = styled(cssIcon, ` - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.widgetPickerPrimaryIcon}; `); const cssLabel = styled('span', ` @@ -518,7 +520,7 @@ const cssPivot = styled(cssEntry, ` const cssBigIcon = styled(icon, ` width: 24px; height: 24px; - background-color: ${colors.darkGreen}; + background-color: ${theme.widgetPickerSummaryIcon}; `); const cssFooter = styled('div', ` @@ -536,11 +538,14 @@ const cssFooterContent = styled('div', ` `); const cssSmallLabel = styled('span', ` + color: ${theme.text}; font-size: ${vars.xsmallFontSize}; margin-right: 8px; `); const cssSelect = styled(select, ` + color: ${theme.selectButtonFg}; + background-color: ${theme.selectButtonBg}; flex: 1 0 160px; width: 160px; `); diff --git a/app/client/ui/Pages.ts b/app/client/ui/Pages.ts index 6d97e310..a44915c9 100644 --- a/app/client/ui/Pages.ts +++ b/app/client/ui/Pages.ts @@ -8,7 +8,7 @@ import {find as findInTree, fromTableData, TreeItemRecord, TreeRecord, TreeTableData} from 'app/client/models/TreeModel'; import {TreeViewComponent} from 'app/client/ui/TreeViewComponent'; import {labeledCircleCheckbox} from 'app/client/ui2018/checkbox'; -import {colors} from 'app/client/ui2018/cssVars'; +import {theme} from 'app/client/ui2018/cssVars'; import {cssLink} from 'app/client/ui2018/links'; import {ISaveModalOptions, saveModal} from 'app/client/ui2018/modals'; import {buildPageDom, PageActions} from 'app/client/ui2018/pages'; @@ -182,7 +182,7 @@ const cssOptions = styled('div', ` const cssBlockCheckbox = styled('div', ` display: flex; padding: 10px 8px; - border: 1px solid ${colors.mediumGrey}; + border: 1px solid ${theme.modalBorder}; border-radius: 3px; cursor: pointer; & input::before, & input::after { @@ -190,7 +190,7 @@ const cssBlockCheckbox = styled('div', ` left: unset; } &:hover { - border-color: ${colors.lightGreen}; + border-color: ${theme.accentBorder}; } &-block { pointer-events: none; @@ -208,7 +208,8 @@ const cssWarning = styled('div', ` `); const cssTableName = styled('div', ` - background: #eee; + color: black; + background-color: #eee; padding: 3px 6px; border-radius: 4px; `); diff --git a/app/client/ui/PinnedDocs.ts b/app/client/ui/PinnedDocs.ts index 09ebc9ac..674f24d1 100644 --- a/app/client/ui/PinnedDocs.ts +++ b/app/client/ui/PinnedDocs.ts @@ -2,7 +2,7 @@ import {docUrl, urlState} from 'app/client/models/gristUrlState'; import {getTimeFromNow, HomeModel} from 'app/client/models/HomeModel'; import {makeDocOptionsMenu, makeRemovedDocOptionsMenu} from 'app/client/ui/DocMenu'; import {transientInput} from 'app/client/ui/transientInput'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {colors, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu} from 'app/client/ui2018/menus'; import * as roles from 'app/common/roles'; @@ -115,11 +115,11 @@ const pinnedDocWrapper = styled('div', ` position: relative; width: 210px; margin: 16px 24px 16px 0; - border: 1px solid ${colors.mediumGrey}; + border: 1px solid ${theme.pinnedDocBorder}; border-radius: 1px; vertical-align: top; &:hover { - border: 1px solid ${colors.slate}; + border: 1px solid ${theme.pinnedDocBorderHover}; } /* TODO: Specify a gap on flexbox parents of pinnedDocWrapper instead. */ @@ -132,16 +132,16 @@ const pinnedDoc = styled('a', ` display: flex; flex-direction: column; width: 100%; - color: black; + color: ${theme.text}; text-decoration: none; cursor: pointer; &:hover { - color: black; + color: ${theme.text}; text-decoration: none; } &-no-access, &-no-access:hover { - color: ${colors.slate}; + color: ${theme.disabledText}; cursor: not-allowed; } `); @@ -216,6 +216,7 @@ const pinnedDocOptions = styled('div', ` const pinnedDocFooter = styled('div', ` width: 100%; font-size: ${vars.mediumFontSize}; + background-color: ${theme.pinnedDocFooterBg}; `); const pinnedDocTitle = styled('div', ` @@ -238,17 +239,18 @@ const pinnedDocEditorInput = styled(transientInput, ` padding: 0; border: none; outline: none; - background-color: ${colors.mediumGrey}; + color: ${theme.text}; + background-color: ${theme.pinnedDocEditorBg}; `); const cssPinnedDocTimestamp = styled('div', ` margin: 8px 16px 16px 16px; - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssPinnedDocDesc = styled(cssPinnedDocTimestamp, ` margin: 8px 16px 16px 16px; - color: ${colors.slate}; + color: ${theme.lightText}; height: 48px; line-height: 16px; -webkit-box-orient: vertical; @@ -271,5 +273,5 @@ const cssPublicIcon = styled(icon, ` position: absolute; top: 16px; left: 16px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); diff --git a/app/client/ui/RightPanel.ts b/app/client/ui/RightPanel.ts index a1f366b0..a6aefa57 100644 --- a/app/client/ui/RightPanel.ts +++ b/app/client/ui/RightPanel.ts @@ -30,7 +30,7 @@ import {CustomSectionConfig} from 'app/client/ui/CustomSectionConfig'; import {VisibleFieldsConfig} from 'app/client/ui/VisibleFieldsConfig'; import {IWidgetType, widgetTypes} from 'app/client/ui/widgetTypes'; import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {testId, theme, vars} from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {IconName} from 'app/client/ui2018/IconList'; import {icon} from 'app/client/ui2018/icons'; @@ -567,7 +567,7 @@ function tabContentToDom(content: Observable|TabContent[]|IDomComp } const cssOverlay = styled('div', ` - background-color: white; + background-color: ${theme.rightPanelDisabledOverlay}; opacity: 0.8; position: absolute; top: 0; @@ -578,19 +578,21 @@ const cssOverlay = styled('div', ` `); const cssBottomText = styled('span', ` + color: ${theme.text}; position: absolute; bottom: -40px; padding: 4px 16px; `); const cssLabel = styled('div', ` + color: ${theme.text}; text-transform: uppercase; margin: 16px 16px 12px 16px; font-size: ${vars.xsmallFontSize}; `); - const cssRow = styled('div', ` + color: ${theme.text}; display: flex; margin: 8px 16px; align-items: center; @@ -598,7 +600,7 @@ const cssRow = styled('div', ` margin-top: 24px; } &-disabled { - color: ${colors.slate}; + color: ${theme.disabledText}; } `); @@ -613,29 +615,29 @@ const cssButtonRow = styled(cssRow, ` const cssIcon = styled(icon, ` flex: 0 0 auto; - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; `); const cssTopBarItem = styled('div', ` flex: 1 1 0px; height: 100%; - background-color: ${colors.lightGrey}; + background-color: ${theme.rightPanelTabBg}; font-weight: ${vars.headerControlTextWeight}; - color: ${colors.dark}; - --icon-color: ${colors.slate}; + color: ${theme.rightPanelTabFg}; + --icon-color: ${theme.rightPanelTabIcon}; display: flex; align-items: center; cursor: default; &-selected { - background-color: ${colors.lightGreen}; + background-color: ${theme.rightPanelTabSelectedBg}; font-weight: initial; - color: ${colors.light}; - --icon-color: ${colors.light}; + color: ${theme.rightPanelTabSelectedFg}; + --icon-color: ${theme.rightPanelTabSelectedFg}; } &:not(&-selected):hover { - background-color: ${colors.mediumGrey}; - --icon-color: ${colors.lightGreen}; + background-color: ${theme.rightPanelTabHoverBg}; + --icon-color: ${theme.rightPanelTabIconHover}; } `); @@ -659,7 +661,7 @@ const cssHoverCircle = styled('div', ` justify-content: center; &:hover { - background-color: ${colors.darkGreen}; + background-color: ${theme.rightPanelTabCloseButtonHoverBg}; } `); @@ -678,7 +680,7 @@ const cssSubTabContainer = styled('div', ` `); const cssSubTab = styled('div', ` - color: ${colors.lightGreen}; + color: ${theme.rightPanelSubtabFg}; flex: auto; height: 100%; display: flex; @@ -686,21 +688,21 @@ const cssSubTab = styled('div', ` justify-content: flex-end; text-align: center; padding-bottom: 8px; - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; cursor: default; &-selected { - color: ${colors.dark}; - border-bottom: 1px solid ${colors.lightGreen}; + color: ${theme.rightPanelSubtabSelectedFg}; + border-bottom: 1px solid ${theme.rightPanelSubtabSelectedUnderline}; } &:not(&-selected):hover { - color: ${colors.darkGreen}; + color: ${theme.rightPanelSubtabHoverFg}; } &:hover { - border-bottom: 1px solid ${colors.lightGreen}; + border-bottom: 1px solid ${theme.rightPanelSubtabHoverUnderline}; } .${cssSubTabContainer.className}:hover > &-selected:not(:hover) { - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; } `); @@ -710,7 +712,7 @@ const cssTabContents = styled('div', ` `); const cssSeparator = styled('div', ` - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; margin-top: 16px; `); @@ -731,7 +733,7 @@ const cssConfigContainer = styled('div', ` const cssDataLabel = styled('div', ` flex: 0 0 81px; - color: ${colors.slate}; + color: ${theme.lightText}; font-size: ${vars.xsmallFontSize}; margin-left: 4px; margin-top: 2px; @@ -751,7 +753,7 @@ const cssList = styled('div', ` const cssListItem = styled('li', ` - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; border-radius: 2px; margin-bottom: 4px; white-space: nowrap; @@ -763,10 +765,12 @@ const cssListItem = styled('li', ` const cssTextInput = styled(textInput, ` flex: 1 0 auto; + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; &:disabled { - color: ${colors.slate}; - background-color: ${colors.lightGrey}; + color: ${theme.inputDisabledFg}; + background-color: ${theme.inputDisabledBg}; pointer-events: none; } `); diff --git a/app/client/ui/RightPanelStyles.ts b/app/client/ui/RightPanelStyles.ts index 184e8ff1..4e47f133 100644 --- a/app/client/ui/RightPanelStyles.ts +++ b/app/client/ui/RightPanelStyles.ts @@ -1,13 +1,14 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {styled} from 'grainjs'; export const cssIcon = styled(icon, ` flex: 0 0 auto; - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; `); export const cssLabel = styled('div', ` + color: ${theme.text}; text-transform: uppercase; margin: 16px 16px 12px 16px; font-size: ${vars.xsmallFontSize}; @@ -23,11 +24,12 @@ export const cssRow = styled('div', ` display: flex; margin: 8px 16px; align-items: center; + color: ${theme.text}; &-top-space { margin-top: 24px; } &-disabled { - color: ${colors.slate}; + color: ${theme.disabledText}; } `); @@ -46,6 +48,6 @@ export const cssButtonRow = styled(cssRow, ` `); export const cssSeparator = styled('div', ` - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; margin-top: 16px; `); diff --git a/app/client/ui/ShareMenu.ts b/app/client/ui/ShareMenu.ts index 2b9aee98..f040126d 100644 --- a/app/client/ui/ShareMenu.ts +++ b/app/client/ui/ShareMenu.ts @@ -6,7 +6,7 @@ import {makeCopy, replaceTrunkWithFork} from 'app/client/ui/MakeCopyMenu'; import {sendToDrive} from 'app/client/ui/sendToDrive'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {primaryButton} from 'app/client/ui2018/buttons'; -import {colors, mediaXSmall, testId} from 'app/client/ui2018/cssVars'; +import {mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu, menuAnnotate, menuDivider, menuIcon, menuItem, menuItemLink, menuText} from 'app/client/ui2018/menus'; import {buildUrlId, parseUrlId} from 'app/common/gristUrls'; @@ -265,9 +265,9 @@ const cssShareButton = styled('div', ` margin: 5px; white-space: nowrap; - --share-btn-bg: ${colors.lightGreen}; + --share-btn-bg: ${theme.controlPrimaryBg}; &-combined:hover, &-combined.weasel-popup-open { - --share-btn-bg: ${colors.darkGreen}; + --share-btn-bg: ${theme.controlPrimaryHoverBg}; } `); @@ -289,14 +289,14 @@ const cssShareAction = styled(primaryButton, ` const cssShareCircle = styled(cssHoverCircle, ` z-index: 1; background-color: var(--share-btn-bg); - border: 1px solid white; + border: 1px solid ${theme.topHeaderBg}; &:hover, &.weasel-popup-open { - background-color: ${colors.darkGreen}; + background-color: ${theme.controlPrimaryHoverBg}; } `); const cssShareIcon = styled(cssTopBarBtn, ` - background-color: white; + background-color: ${theme.controlPrimaryFg}; height: 30px; width: 30px; `); @@ -310,8 +310,8 @@ const cssMenuSplitLinkText = styled('div', ` flex: auto; padding: var(--weaseljs-menu-item-padding, 8px 24px); &:not(:hover) { - background-color: white; - color: black; + background-color: ${theme.menuBg}; + color: ${theme.menuItemFg}; } `); @@ -320,11 +320,11 @@ const cssMenuIconLink = styled('a', ` flex: none; padding: 8px 24px; - background-color: white; - --icon-color: ${colors.lightGreen}; + background-color: ${theme.menuBg}; + --icon-color: ${theme.menuItemLinkFg}; &:hover { - background-color: ${colors.mediumGreyOpaque}; - --icon-color: ${colors.darkGreen}; + background-color: ${theme.menuItemLinkselectedBg}; + --icon-color: ${theme.menuItemLinkSelectedFg}; } `); diff --git a/app/client/ui/SiteSwitcher.ts b/app/client/ui/SiteSwitcher.ts index 26e94f57..e179af67 100644 --- a/app/client/ui/SiteSwitcher.ts +++ b/app/client/ui/SiteSwitcher.ts @@ -3,9 +3,9 @@ import {getOrgName} from 'app/common/UserAPI'; import {dom, makeTestId, styled} from 'grainjs'; import {AppModel} from 'app/client/models/AppModel'; import {urlState} from 'app/client/models/gristUrlState'; +import {theme} from 'app/client/ui2018/cssVars'; import {menuDivider, menuIcon, menuItem, menuItemLink, menuSubHeader} from 'app/client/ui2018/menus'; import {icon} from 'app/client/ui2018/icons'; -import {colors} from 'app/client/ui2018/cssVars'; const testId = makeTestId('test-site-switcher-'); @@ -49,14 +49,14 @@ export function buildSiteSwitcher(appModel: AppModel) { } const cssOrgSelected = styled('div', ` - background-color: ${colors.dark}; - color: ${colors.light}; + background-color: ${theme.siteSwitcherActiveBg}; + color: ${theme.siteSwitcherActiveFg}; `); const cssOrgCheckmark = styled(icon, ` flex: none; margin-left: 16px; - --icon-color: ${colors.light}; + --icon-color: ${theme.siteSwitcherActiveFg}; display: none; .${cssOrgSelected.className} > & { display: block; diff --git a/app/client/ui/ThemeConfig.ts b/app/client/ui/ThemeConfig.ts new file mode 100644 index 00000000..729e0e6a --- /dev/null +++ b/app/client/ui/ThemeConfig.ts @@ -0,0 +1,62 @@ +import {AppModel} from 'app/client/models/AppModel'; +import * as css from 'app/client/ui/AccountPageCss'; +import {labeledSquareCheckbox} from 'app/client/ui2018/checkbox'; +import {select} from 'app/client/ui2018/menus'; +import {ThemeAppearance} from 'app/common/ThemePrefs'; +import {Computed, Disposable, dom, makeTestId, styled} from 'grainjs'; + +const testId = makeTestId('test-theme-config-'); + +export class ThemeConfig extends Disposable { + private _themePrefs = this._appModel.themePrefs; + + private _appearance = Computed.create(this, this._themePrefs, (_use, prefs) => { + return prefs.appearance; + }).onWrite((value) => this._updateAppearance(value)); + + private _syncWithOS = Computed.create(this, this._themePrefs, (_use, prefs) => { + return prefs.syncWithOS; + }).onWrite((value) => this._updateSyncWithOS(value)); + + constructor(private _appModel: AppModel) { + super(); + } + + public buildDom() { + return dom('div', + css.subHeader('Appearance ', css.betaTag('Beta')), + css.dataRow( + cssAppearanceSelect( + select( + this._appearance, + [ + {value: 'light', label: 'Light'}, + {value: 'dark', label: 'Dark'}, + ], + ), + testId('appearance'), + ), + ), + css.dataRow( + labeledSquareCheckbox( + this._syncWithOS, + 'Switch appearance automatically to match system', + testId('sync-with-os'), + ), + ), + testId('container'), + ); + } + + private _updateAppearance(appearance: ThemeAppearance) { + this._themePrefs.set({...this._themePrefs.get(), appearance}); + } + + private _updateSyncWithOS(syncWithOS: boolean) { + this._themePrefs.set({...this._themePrefs.get(), syncWithOS}); + } +} + +const cssAppearanceSelect = styled('div', ` + width: 120px; +`); diff --git a/app/client/ui/Tools.ts b/app/client/ui/Tools.ts index 1990551e..f27c01a4 100644 --- a/app/client/ui/Tools.ts +++ b/app/client/ui/Tools.ts @@ -7,7 +7,7 @@ import {createHelpTools, cssLinkText, cssPageEntry, cssPageEntryMain, cssPageEnt cssPageIcon, cssPageLink, cssSectionHeader, cssSpacer, cssSplitPageEntry, cssTools} from 'app/client/ui/LeftPanelCommon'; import {hoverTooltip, tooltipCloseButton} from 'app/client/ui/tooltips'; -import {colors} from 'app/client/ui2018/cssVars'; +import {theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {cssLink} from 'app/client/ui2018/links'; import {menuAnnotate} from 'app/client/ui2018/menus'; @@ -206,7 +206,7 @@ function addRevertViewAsUI() { const cssConvertTooltip = styled('div', ` display: flex; align-items: center; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.controlFg}; & > .${cssLink.className} { margin-left: 8px; @@ -223,10 +223,10 @@ const cssExampleCardOpener = styled('div', ` width: 24px; padding: 4px; line-height: 0px; - --icon-color: ${colors.light}; - background-color: ${colors.lightGreen}; + --icon-color: ${theme.iconButtonFg}; + background-color: ${theme.iconButtonPrimaryBg}; &:hover { - background-color: ${colors.darkGreen}; + background-color: ${theme.iconButtonPrimaryHoverBg}; } .${cssTools.className}-collapsed & { display: none; @@ -234,9 +234,9 @@ const cssExampleCardOpener = styled('div', ` `); const cssRevertViewAsButton = styled(cssExampleCardOpener, ` - background-color: ${colors.darkGrey}; + background-color: ${theme.iconButtonSecondaryBg}; &:hover { - background-color: ${colors.slate}; + background-color: ${theme.iconButtonSecondaryHoverBg}; } `); diff --git a/app/client/ui/TopBar.ts b/app/client/ui/TopBar.ts index 6358dc8a..7ea4753f 100644 --- a/app/client/ui/TopBar.ts +++ b/app/client/ui/TopBar.ts @@ -10,7 +10,7 @@ import {buildShareMenuButton} from 'app/client/ui/ShareMenu'; import {cssHoverCircle, cssTopBarBtn} from 'app/client/ui/TopBarCss'; import {docBreadcrumbs} from 'app/client/ui2018/breadcrumbs'; import {basicButton} from 'app/client/ui2018/buttons'; -import {colors, cssHideForNarrowScreen, testId} from 'app/client/ui2018/cssVars'; +import {cssHideForNarrowScreen, testId, theme} from 'app/client/ui2018/cssVars'; import {IconName} from 'app/client/ui2018/IconList'; import {waitGrainObs} from 'app/common/gutil'; import * as roles from 'app/common/roles'; @@ -129,14 +129,14 @@ function topBarUndoBtn(iconName: IconName, ...domArgs: DomElementArg[]): Element } const cssTopBarUndoBtn = styled(cssTopBarBtn, ` - background-color: ${colors.slate}; + background-color: ${theme.topBarButtonSecondaryFg}; .${cssHoverCircle.className}:hover & { - background-color: ${colors.lightGreen}; + background-color: ${theme.topBarButtonPrimaryFg}; } .${cssHoverCircle.className}-disabled:hover & { - background-color: ${colors.darkGrey}; + background-color: ${theme.topBarButtonDisabledFg}; cursor: default; } `); diff --git a/app/client/ui/TopBarCss.ts b/app/client/ui/TopBarCss.ts index c9956c36..1bc77600 100644 --- a/app/client/ui/TopBarCss.ts +++ b/app/client/ui/TopBarCss.ts @@ -1,4 +1,4 @@ -import {colors} from 'app/client/ui2018/cssVars'; +import {theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {styled} from 'grainjs'; @@ -9,7 +9,7 @@ export const cssHoverCircle = styled('div', ` border-radius: 16px; &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } &-disabled:hover { @@ -23,12 +23,12 @@ export const cssTopBarBtn = styled(icon, ` padding: 8px 8px; cursor: pointer; -webkit-mask-size: 16px 16px; - background-color: ${colors.lightGreen}; + background-color: ${theme.topBarButtonPrimaryFg}; .${cssHoverCircle.className}-disabled & { - background-color: ${colors.darkGrey}; + background-color: ${theme.topBarButtonDisabledFg}; cursor: default; } - &-slate { background-color: ${colors.slate}; } - &-error { background-color: ${colors.error}; } + &-slate { background-color: ${theme.topBarButtonSecondaryFg}; } + &-error { background-color: ${theme.topBarButtonErrorFg}; } `); diff --git a/app/client/ui/TreeViewComponentCss.ts b/app/client/ui/TreeViewComponentCss.ts index 73ab944f..e82eed52 100644 --- a/app/client/ui/TreeViewComponentCss.ts +++ b/app/client/ui/TreeViewComponentCss.ts @@ -1,4 +1,4 @@ -import { colors, vars } from "app/client/ui2018/cssVars"; +import { theme } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; import { styled } from "grainjs"; @@ -42,27 +42,28 @@ export const itemHeader = styled('div', ` min-width: 0; border-radius: 0 2px 2px 0; border: solid 1px transparent; + color: ${theme.text}; .${itemHeaderWrapper.className}-not-dragging:hover > & { - background-color: ${colors.mediumGrey}; + background-color: ${theme.pageHoverBg}; } .${itemHeaderWrapper.className}-not-dragging > &.selected { - background-color: ${colors.darkBg}; - color: white; + background-color: ${theme.activePageBg}; + color: ${theme.activePageFg}; } &.highlight { - border-color: ${vars.controlFg}; + border-color: ${theme.controlFg}; } `); export const dropdown = styled(icon, ` - background-color: ${colors.slate}; + background-color: ${theme.controlSecondaryFg}; .${itemHeaderWrapper.className}-not-dragging > .${itemHeader.className}.selected & { - background-color: white; + background-color: ${theme.activePageFg}; } `); export const itemLabelRight = styled('div', ` - --icon-color: ${colors.slate}; + --icon-color: ${theme.controlSecondaryFg}; width: 16px; .${treeViewContainer.className}-close & { display: none; @@ -114,6 +115,6 @@ export const offset = styled('div', ` export const target = styled('div', ` position: absolute; height: 2px; - background: ${vars.controlFg}; + background: ${theme.controlFg}; pointer-events: none; `); diff --git a/app/client/ui/TriggerFormulas.ts b/app/client/ui/TriggerFormulas.ts index 63f36b51..a61b7209 100644 --- a/app/client/ui/TriggerFormulas.ts +++ b/app/client/ui/TriggerFormulas.ts @@ -5,7 +5,7 @@ import {cssRow} from 'app/client/ui/RightPanelStyles'; import {shadowScroll} from 'app/client/ui/shadowScroll'; import {basicButton, primaryButton} from "app/client/ui2018/buttons"; import {labeledSquareCheckbox} from "app/client/ui2018/checkbox"; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from "app/client/ui2018/icons"; import {menuCssClass, menuDivider} from 'app/client/ui2018/menus'; import {cssSelectBtn} from 'app/client/ui2018/select'; @@ -228,7 +228,7 @@ const cssSelectSummary = styled('div', ` &:empty::before { content: "Select fields"; - color: ${colors.slate}; + color: ${theme.selectButtonPlaceholderFg}; } `); @@ -244,8 +244,8 @@ const cssSelectorMenu = styled(cssMenu, ` const cssItemsList = styled(shadowScroll, ` flex: auto; min-height: 80px; - border-top: 1px solid ${colors.darkGrey}; - border-bottom: 1px solid ${colors.darkGrey}; + border-top: 1px solid ${theme.menuBorder}; + border-bottom: 1px solid ${theme.menuBorder}; margin-top: 8px; padding: 8px 0; `); @@ -263,7 +263,7 @@ const cssSelectorItem = styled(cssMenuItem, ` `); const cssSelectorNote = styled('span', ` - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssSelectorFooter = styled(cssSelectorItem, ` diff --git a/app/client/ui/UserImage.ts b/app/client/ui/UserImage.ts index 83076501..9cf1281c 100644 --- a/app/client/ui/UserImage.ts +++ b/app/client/ui/UserImage.ts @@ -1,4 +1,4 @@ -import {colors} from 'app/client/ui2018/cssVars'; +import {colors, theme} from 'app/client/ui2018/cssVars'; import {FullUser} from 'app/common/LoginSessionAPI'; import {dom, DomElementArg, styled} from 'grainjs'; @@ -120,8 +120,7 @@ const cssUserPicture = styled('img', ` width: 100%; height: 100%; object-fit: cover; - background-color: white; + background-color: ${theme.menuBg}; border-radius: 100px; - border: 1px solid white; /* make sure edge of circle with initials is not visible */ box-sizing: content-box; /* keep the border outside of the size of the image */ `); diff --git a/app/client/ui/UserItem.ts b/app/client/ui/UserItem.ts index 0170f809..8602f224 100644 --- a/app/client/ui/UserItem.ts +++ b/app/client/ui/UserItem.ts @@ -1,4 +1,4 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {colors, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {input, styled} from 'grainjs'; import {cssMenuItem} from 'popweasel'; @@ -54,16 +54,16 @@ export const cssMemberText = styled('div', ` export const cssMemberPrimary = styled('span', ` font-weight: bold; - color: ${colors.dark}; + color: ${theme.text}; padding: 2px 0; .${cssMenuItem.className}-sel & { - color: white; + color: ${theme.menuItemSelectedFg}; } `); export const cssMemberSecondary = styled('span', ` - color: ${colors.slate}; + color: ${theme.lightText}; /* the following just undo annoying bootstrap styles that apply to all labels */ margin: 0px; font-weight: normal; @@ -71,12 +71,12 @@ export const cssMemberSecondary = styled('span', ` white-space: nowrap; .${cssMenuItem.className}-sel & { - color: white; + color: ${theme.menuItemSelectedFg}; } `); export const cssMemberType = styled('span', ` - color: ${colors.slate}; + color: ${theme.lightText}; /* the following just undo annoying bootstrap styles that apply to all labels */ margin: 0px; font-weight: normal; @@ -84,12 +84,12 @@ export const cssMemberType = styled('span', ` white-space: nowrap; .${cssMenuItem.className}-sel & { - color: white; + color: ${theme.menuItemSelectedFg}; } `); export const cssMemberTypeProblem = styled('span', ` - color: ${colors.error}; + color: ${theme.errorText}; /* the following just undo annoying bootstrap styles that apply to all labels */ margin: 0px; font-weight: normal; @@ -97,7 +97,7 @@ export const cssMemberTypeProblem = styled('span', ` white-space: nowrap; .${cssMenuItem.className}-sel & { - color: white; + color: ${theme.menuItemSelectedFg}; } `); @@ -113,6 +113,7 @@ export const cssMemberBtn = styled('div', ` `); export const cssRemoveIcon = styled(icon, ` + background-color: ${theme.lightText}; margin: 12px 0; `); @@ -122,26 +123,32 @@ export const cssEmailInputContainer = styled('div', ` height: 42px; padding: 0 3px; margin: 16px 63px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-radius: 3px; font-size: ${vars.mediumFontSize}; outline: none; &-green { - border: 1px solid ${colors.lightGreen}; + border: 1px solid ${theme.inputValid}; } `); export const cssEmailInput = styled(input, ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; flex: 1 1 0; line-height: 42px; font-size: ${vars.mediumFontSize}; font-family: ${vars.fontFamily}; outline: none; border: none; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); export const cssMailIcon = styled(icon, ` margin: 12px 8px 12px 13px; - background-color: ${colors.slate}; + background-color: ${theme.lightText}; `); diff --git a/app/client/ui/UserManager.ts b/app/client/ui/UserManager.ts index fa2a4631..f93e385b 100644 --- a/app/client/ui/UserManager.ts +++ b/app/client/ui/UserManager.ts @@ -32,7 +32,7 @@ import {cssEmailInput, cssEmailInputContainer, cssMailIcon, cssMemberBtn, cssMem cssMemberPrimary, cssMemberSecondary, cssMemberText, cssMemberType, cssMemberTypeProblem, cssRemoveIcon} from 'app/client/ui/UserItem'; import {basicButton, bigBasicButton, bigPrimaryButton} from 'app/client/ui2018/buttons'; -import {colors, mediaXSmall, testId, vars} from 'app/client/ui2018/cssVars'; +import {colors, mediaXSmall, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {cssLink} from 'app/client/ui2018/links'; import {loadingSpinner} from 'app/client/ui2018/loaders'; @@ -662,7 +662,7 @@ const cssAccessDetailsBody = styled('div', ` const cssUserManagerBody = styled(cssAccessDetailsBody, ` height: 374px; - border-bottom: 1px solid ${colors.darkGrey}; + border-bottom: 1px solid ${theme.modalBorderDark}; `); const cssSpinner = styled('div', ` @@ -695,7 +695,7 @@ const cssOptionRow = styled('div', ` const cssOptionBtn = styled('span', ` display: inline-flex; font-size: ${vars.mediumFontSize}; - color: ${colors.lightGreen}; + color: ${theme.controlFg}; cursor: pointer; `); @@ -703,14 +703,15 @@ const cssPublicMemberIcon = styled(icon, ` width: 40px; height: 40px; margin: 0 4px; - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); const cssPublicAccessIcon = styled(icon, ` - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.accentIcon}; `); const cssUndoIcon = styled(icon, ` + --icon-color: ${theme.controlSecondaryFg}; margin: 12px 0; `); @@ -718,7 +719,7 @@ const cssRoleBtn = styled('div', ` display: flex; justify-content: flex-end; font-size: ${vars.mediumFontSize}; - color: ${colors.lightGreen}; + color: ${theme.controlFg}; margin: 12px 24px; cursor: pointer; @@ -730,7 +731,7 @@ const cssRoleBtn = styled('div', ` const cssCollapseIcon = styled(icon, ` margin-top: 1px; - background-color: var(--grist-color-light-green); + background-color: ${theme.controlFg}; `); const cssInputMenuItem = styled(menuItem, ` @@ -747,8 +748,8 @@ const cssUserImagePlus = styled(cssUserImage, ` } .${cssMenuItem.className}-sel & { - background-color: white; - color: ${colors.lightGreen}; + background-color: ${theme.menuItemIconSelectedFg}; + color: ${theme.menuItemSelectedBg}; } `); @@ -762,7 +763,7 @@ const cssOrgName = styled('div', ` `); const cssOrgDomain = styled('span', ` - color: ${colors.lightGreen}; + color: ${theme.accentText}; `); const cssFadeInFromTop = keyframes(` diff --git a/app/client/ui/ViewSectionMenu.ts b/app/client/ui/ViewSectionMenu.ts index 67fb7191..9f275f90 100644 --- a/app/client/ui/ViewSectionMenu.ts +++ b/app/client/ui/ViewSectionMenu.ts @@ -7,7 +7,7 @@ import {addFilterMenu} from 'app/client/ui/FilterBar'; import {hoverTooltip} from 'app/client/ui/tooltips'; import {makeViewLayoutMenu} from 'app/client/ui/ViewLayoutMenu'; import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {menu} from 'app/client/ui2018/menus'; import {Sort} from 'app/common/SortSpec'; @@ -292,7 +292,7 @@ const cssMenu = styled('div', ` } &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); @@ -310,13 +310,13 @@ const cssMenuIconWrapper = styled(cssIconWrapper, ` height: 22px; &:hover, &.weasel-popup-open { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } &-changed { - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; } &-changed:hover, &-changed:hover.weasel-popup-open { - background-color: ${colors.darkGreen}; + background-color: ${theme.controlHoverFg}; } `); @@ -326,7 +326,7 @@ const cssFilterMenuWrapper = styled('div', ` border-radius: 3px; align-items: center; &-unsaved { - border: 1px solid ${colors.lightGreen}; + border: 1px solid ${theme.accentBorder}; } & .${cssMenu.className} { border: none; @@ -337,18 +337,18 @@ const cssFilterMenuWrapper = styled('div', ` const cssIcon = styled(icon, ` flex: none; cursor: pointer; - background-color: ${colors.slate}; + background-color: ${theme.lightText}; .${cssMenuIconWrapper.className}-changed & { - background-color: white; + background-color: ${theme.controlPrimaryFg}; } .${clsOldUI.className} & { - background-color: white; + background-color: ${theme.controlPrimaryFg}; } &-green { - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; } `); @@ -363,20 +363,21 @@ const cssDotsIconWrapper = styled(cssIconWrapper, ` const cssFilterIconWrapper = styled(cssIconWrapper, ` border-radius: 2px 0px 0px 2px; .${cssFilterMenuWrapper.className}-unsaved & { - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; } `); const cssFilterIcon = styled(cssIcon, ` .${cssFilterIconWrapper.className}-any & { - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; } .${cssFilterMenuWrapper.className}-unsaved & { - background-color: white; + background-color: ${theme.controlPrimaryFg}; } `); const cssMenuInfoHeader = styled('div', ` + color: ${theme.menuSubheaderFg}; font-weight: ${vars.bigControlTextWeight}; padding: 8px 24px 8px 24px; cursor: default; @@ -389,18 +390,19 @@ const cssMenuText = styled('div', ` cursor: default; white-space: nowrap; &-green { - color: ${colors.lightGreen}; + color: ${theme.accentText}; } &-gray { - color: ${colors.slate}; + color: ${theme.lightText}; } `); const cssGrayedMenuText = styled(cssMenuText, ` - color: ${colors.slate}; + color: ${theme.lightText}; `); const cssMenuTextLabel = styled('span', ` + color: ${theme.menuItemFg}; flex-grow: 1; padding: 0 4px; overflow: hidden; @@ -418,13 +420,13 @@ const cssSmallIconWrapper = styled('div', ` margin: 0 5px 0 5px; &-green { - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; } &-gray { - background-color: ${colors.slate}; + background-color: ${theme.lightText}; } & > .${cssIcon.className} { - background-color: white; + background-color: ${theme.controlPrimaryFg}; } `); diff --git a/app/client/ui/VisibleFieldsConfig.ts b/app/client/ui/VisibleFieldsConfig.ts index f8219650..1cbebaec 100644 --- a/app/client/ui/VisibleFieldsConfig.ts +++ b/app/client/ui/VisibleFieldsConfig.ts @@ -7,7 +7,7 @@ import { getFieldType } from "app/client/ui/RightPanel"; import { IWidgetType } from "app/client/ui/widgetTypes"; import { basicButton, cssButton, primaryButton } from 'app/client/ui2018/buttons'; import * as checkbox from "app/client/ui2018/checkbox"; -import { colors, vars } from "app/client/ui2018/cssVars"; +import { theme, vars } from "app/client/ui2018/cssVars"; import { cssDragger } from "app/client/ui2018/draggableList"; import { icon } from "app/client/ui2018/icons"; import * as gutil from 'app/common/gutil'; @@ -200,7 +200,7 @@ export class VisibleFieldsConfig extends Disposable { dom.maybe( (use) => Boolean(use(use(this._section.viewFields).getObservable()).length), () => ( - cssGreenLabel( + cssControlLabel( icon('Tick'), 'Select All', dom.on('click', () => this._setVisibleCheckboxes(fieldsDraggable, true)), @@ -236,7 +236,7 @@ export class VisibleFieldsConfig extends Disposable { dom.maybe( (use) => Boolean(use(this._hiddenFields.getObservable()).length && !use(this._collapseHiddenFields)), () => ( - cssGreenLabel( + cssControlLabel( icon('Tick'), 'Select All', dom.on('click', () => this._setHiddenCheckboxes(hiddenFieldsDraggable, true)), @@ -453,7 +453,7 @@ export const cssDragRow = styled('div', ` export const cssFieldEntry = styled('div', ` display: flex; - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; width: 100%; border-radius: 2px; margin: 0 8px 0 0; @@ -463,10 +463,11 @@ export const cssFieldEntry = styled('div', ` overflow: hidden; text-overflow: ellipsis; - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; `); const cssHideIcon = styled(icon, ` + --icon-color: ${theme.lightText}; display: none; cursor: pointer; flex: none; @@ -477,12 +478,14 @@ const cssHideIcon = styled(icon, ` `); export const cssFieldLabel = styled('span', ` + color: ${theme.text}; flex: 1 1 auto; text-overflow: ellipsis; overflow: hidden; `); const cssFieldListHeader = styled('span', ` + color: ${theme.text}; flex: 1 1 0px; font-size: ${vars.xsmallFontSize}; text-transform: uppercase; @@ -492,15 +495,15 @@ const cssRow = styled('div', ` display: flex; margin: 16px; overflow: hidden; - --icon-color: ${colors.slate}; + --icon-color: ${theme.lightText}; & > .${cssButton.className} { margin-right: 8px; } `); -const cssGreenLabel = styled('div', ` - --icon-color: ${colors.lightGreen}; - color: ${colors.lightGreen}; +const cssControlLabel = styled('div', ` + --icon-color: ${theme.controlFg}; + color: ${theme.controlFg}; cursor: pointer; `); @@ -511,6 +514,7 @@ const cssHeader = styled(cssRow, ` `); const cssHeaderIcon = styled(icon, ` + --icon-color: ${theme.lightText}; flex: none; margin-right: 4px; `); diff --git a/app/client/ui/WidgetTitle.ts b/app/client/ui/WidgetTitle.ts index fa1d754a..bd61118a 100644 --- a/app/client/ui/WidgetTitle.ts +++ b/app/client/ui/WidgetTitle.ts @@ -1,7 +1,7 @@ import {FocusLayer} from 'app/client/lib/FocusLayer'; import {ViewSectionRec} from 'app/client/models/entities/ViewSectionRec'; import {basicButton, cssButton, primaryButton} from 'app/client/ui2018/buttons'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {cssTextInput} from 'app/client/ui2018/editableLabel'; import {menuCssClass} from 'app/client/ui2018/menus'; import {ModalControl} from 'app/client/ui2018/modals'; @@ -189,7 +189,7 @@ const cssTitle = styled('div', ` text-overflow: ellipsis; align-self: start; &:hover { - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } &-empty { min-width: 48px; @@ -202,12 +202,13 @@ const cssRenamePopup = styled('div', ` flex-direction: column; min-width: 280px; padding: 16px; - background-color: white; + background-color: ${theme.popupBg}; border-radius: 2px; outline: none; `); const cssLabel = styled('label', ` + color: ${theme.text}; font-size: ${vars.xsmallFontSize}; font-weight: ${vars.bigControlTextWeight}; margin: 0 0 8px 0; @@ -235,11 +236,16 @@ const cssInput = styled(( opts: IInputOptions, ...args) => input(obs, opts, cssTextInput.cls(''), ...args), ` text-overflow: ellipsis; + color: ${theme.inputFg}; + background-color: transparent; &:disabled { - color: ${colors.slate}; - background-color: ${colors.lightGrey}; + color: ${theme.inputDisabledFg}; + background-color: ${theme.inputDisabledBg}; pointer-events: none; } + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } .${cssInputWithIcon.className} > &:disabled { padding-right: 28px; } diff --git a/app/client/ui/cssInput.ts b/app/client/ui/cssInput.ts index b008a4ad..205d320f 100644 --- a/app/client/ui/cssInput.ts +++ b/app/client/ui/cssInput.ts @@ -1,12 +1,18 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {styled} from 'grainjs'; export const cssInput = styled('input', ` + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; height: 30px; width: 100%; font-size: ${vars.mediumFontSize}; border-radius: 3px; padding: 5px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; outline: none; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); diff --git a/app/client/ui/errorPages.ts b/app/client/ui/errorPages.ts index d82f94d0..fa124a0d 100644 --- a/app/client/ui/errorPages.ts +++ b/app/client/ui/errorPages.ts @@ -5,7 +5,7 @@ import {leftPanelBasic} from 'app/client/ui/LeftPanelCommon'; import {pagePanels} from 'app/client/ui/PagePanels'; import {createTopBarHome} from 'app/client/ui/TopBar'; import {bigBasicButtonLink, bigPrimaryButtonLink} from 'app/client/ui2018/buttons'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {getPageTitleSuffix, GristLoadConfig} from 'app/common/gristUrls'; import {getGristConfig} from 'app/common/urlUtils'; import {dom, DomElementArg, makeTestId, observable, styled} from 'grainjs'; @@ -144,12 +144,12 @@ const cssErrorHeader = styled('div', ` font-size: ${vars.xxxlargeFontSize}; margin: 24px; text-align: center; - color: ${colors.dark}; + color: ${theme.text}; `); const cssErrorText = styled('div', ` font-size: ${vars.mediumFontSize}; - color: ${colors.dark}; + color: ${theme.text}; margin: 0 auto 24px auto; max-width: 400px; text-align: center; diff --git a/app/client/ui/inputs.ts b/app/client/ui/inputs.ts index 64978ee9..776cc476 100644 --- a/app/client/ui/inputs.ts +++ b/app/client/ui/inputs.ts @@ -1,4 +1,4 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {dom, DomElementArg, Observable, styled} from 'grainjs'; export const cssInput = styled('input', ` @@ -7,10 +7,16 @@ export const cssInput = styled('input', ` line-height: 20px; width: 100%; padding: 14px; - border: 1px solid #D9D9D9; + border: 1px solid ${theme.inputBorder}; border-radius: 4px; outline: none; display: block; + color: ${theme.inputFg}; + background-color: ${theme.inputBg}; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } &[type=number] { -moz-appearance: textfield; @@ -22,11 +28,11 @@ export const cssInput = styled('input', ` } &-invalid { - border: 1px solid ${colors.error}; + border: 1px solid ${theme.inputInvalid}; } &-valid { - border: 1px solid ${colors.lightGreen}; + border: 1px solid ${theme.inputValid}; } `); diff --git a/app/client/ui/modals.ts b/app/client/ui/modals.ts deleted file mode 100644 index a0bf4c7c..00000000 --- a/app/client/ui/modals.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {Disposable} from 'app/client/lib/dispose'; -import {dom, styled} from 'grainjs'; - -const modalBacker = styled('div', ` - position: fixed; - display: flex; - flex-direction: column; - justify-content: center; - width: 100%; - height: 100%; - top: 0; - left: 0; - z-index: 100; - background-color: rgba(0, 0, 0, 0.5); -`); - -const modal = styled('div', ` - background-color: white; - color: black; - margin: 0 auto; - border-radius: 4px; - box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.2); - border: 1px solid #aaa; - padding: 10px; -`); - -export const modalHeader = styled('div', ` - font-size: 12pt; - color: #859394; - padding: 5px; -`); - -export const modalButtonRow = styled('div', ` - width: 70%; - margin: 0 auto; - text-align: center; - - & > button { - width: 80px; - } -`); - -/** - * A simple modal. Shows up in the middle of the screen with a tinted backdrop. - * Created with the given body content and width. - * - * Closed and disposed via clicking anywhere outside the modal. May also be closed by - * calling the `dispose()` function. - */ -export class Modal1 extends Disposable { - private _dom: Element; - - public create( - body: Element, - width: number = 300 - ) { - this._dom = modalBacker( - modal({style: `width: ${width}px;`, tabindex: "-1"}, - dom.cls('clipboard_focus'), - body, - dom.on('click', (e) => e.stopPropagation()) - ), - dom.on('click', () => this.dispose()) - ); - document.body.appendChild(this._dom); - - this.autoDisposeCallback(() => { - document.body.removeChild(this._dom); - }); - } -} diff --git a/app/client/ui/tooltips.ts b/app/client/ui/tooltips.ts index e9dc8029..0bc5d36d 100644 --- a/app/client/ui/tooltips.ts +++ b/app/client/ui/tooltips.ts @@ -6,7 +6,7 @@ */ import {prepareForTransition} from 'app/client/ui/transitions'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {IconName} from 'app/client/ui2018/IconList'; import {icon} from 'app/client/ui2018/icons'; import {dom, DomContents, DomElementArg, DomElementMethod, styled} from 'grainjs'; @@ -225,11 +225,11 @@ export function infoTooltip(tipContent: DomContents, ...domArgs: DomElementArg[] const cssTooltip = styled('div', ` position: absolute; z-index: 5000; /* should be higher than a modal */ - background-color: rgba(0, 0, 0, 0.75); + background-color: ${theme.tooltipBg}; border-radius: 3px; box-shadow: 0 0 2px rgba(0,0,0,0.5); text-align: center; - color: white; + color: ${theme.tooltipFg}; width: auto; font-family: sans-serif; font-size: 10pt; @@ -246,19 +246,19 @@ const cssTooltipCloseButton = styled('div', ` line-height: 16px; text-align: center; margin: -4px -4px -4px 8px; - --icon-color: white; + --icon-color: ${theme.tooltipCloseButtonFg}; border-radius: 16px; &:hover { - background-color: white; - --icon-color: black; + background-color: ${theme.tooltipCloseButtonHoverBg}; + --icon-color: ${theme.tooltipCloseButtonHoverFg}; } `); const cssIconTooltip = styled(icon, ` height: 12px; width: 12px; - background-color: ${colors.slate}; + background-color: ${theme.tooltipIcon}; flex-shrink: 0; `); diff --git a/app/client/ui/transientInput.ts b/app/client/ui/transientInput.ts index 416f2b11..c39f8b25 100644 --- a/app/client/ui/transientInput.ts +++ b/app/client/ui/transientInput.ts @@ -7,7 +7,8 @@ */ import {reportError} from 'app/client/models/AppModel'; -import {dom, DomArg} from 'grainjs'; +import {theme} from 'app/client/ui2018/cssVars'; +import {dom, DomArg, styled} from 'grainjs'; export interface ITransientInputOptions { initialValue: string; @@ -36,7 +37,7 @@ export function transientInput({initialValue, save, close}: ITransientInputOptio setTimeout(() => { input.focus(); input.select(); }, 10); } - const input = dom('input', {type: 'text', placeholder: 'Enter name'}, + const input = cssInput({type: 'text', placeholder: 'Enter name'}, dom.prop('value', initialValue), dom.on('blur', () => onSave(false)), dom.onKeyDown({ @@ -48,3 +49,12 @@ export function transientInput({initialValue, save, close}: ITransientInputOptio delayedFocus(); return input; } + +const cssInput = styled('input', ` + background-color: transparent; + color: ${theme.inputFg}; + + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } +`); diff --git a/app/client/ui/welcomeTour.ts b/app/client/ui/welcomeTour.ts index fb0deae6..2b6610f6 100644 --- a/app/client/ui/welcomeTour.ts +++ b/app/client/ui/welcomeTour.ts @@ -1,7 +1,7 @@ import * as commands from 'app/client/components/commands'; import { urlState } from 'app/client/models/gristUrlState'; import { IOnBoardingMsg, startOnBoarding } from "app/client/ui/OnBoardingPopups"; -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { icon } from "app/client/ui2018/icons"; import { cssLink } from "app/client/ui2018/links"; import { dom, styled } from "grainjs"; @@ -54,7 +54,7 @@ export const welcomeTour: IOnBoardingMsg[] = [ selector: '.tour-share-icon', title: 'Sharing', body: () => [ - dom('p', 'Use the Share button (', Icon('Share'), ') to share the document or export data.') + dom('p', 'Use the Share button (', TopBarButtonIcon('Share'), ') to share the document or export data.') ], placement: 'bottom', cropPadding: true, @@ -90,7 +90,7 @@ export function startWelcomeTour(onFinishCB: () => void) { const KeyContent = styled('span', ` font-style: normal; font-family: inherit; - color: ${colors.darkGreen}; + color: ${theme.shortcutKeyPrimaryFg}; `); const KeyStrong = styled(KeyContent, ` @@ -102,20 +102,20 @@ const Key = styled('div', ` padding: 2px 5px; border-radius: 4px; margin: 0px 2px; - border: 1px solid ${colors.slate}; - color: black; - background-color: white; + border: 1px solid ${theme.shortcutKeyBorder}; + color: ${theme.shortcutKeyFg}; + background-color: ${theme.shortcutKeyBg}; font-family: inherit; font-style: normal; white-space: nowrap; `); -const Icon = styled(icon, ` - --icon-color: ${colors.lightGreen}; +const TopBarButtonIcon = styled(icon, ` + --icon-color: ${theme.topBarButtonPrimaryFg}; `); const GreyIcon = styled(icon, ` - --icon-color: ${colors.slate}; + --icon-color: ${theme.shortcutKeySecondaryFg}; margin-right: 8px; `); diff --git a/app/client/ui2018/ColorSelect.ts b/app/client/ui2018/ColorSelect.ts index ba447f33..e7f00391 100644 --- a/app/client/ui2018/ColorSelect.ts +++ b/app/client/ui2018/ColorSelect.ts @@ -1,6 +1,6 @@ import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; import {isLight, swatches} from 'app/client/ui2018/ColorPalette'; -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {textInput} from 'app/client/ui2018/editableLabel'; import {IconName} from 'app/client/ui2018/IconList'; import {icon} from 'app/client/ui2018/icons'; @@ -348,7 +348,8 @@ const cssFontOption = styled('div', ` display: grid; place-items: center; flex-grow: 1; - background: white; + background: ${colors.light}; + --icon-color: ${colors.dark}; height: 24px; cursor: pointer; &:hover:not(&-selected) { @@ -418,12 +419,13 @@ const cssContent = styled('div', ` `); const cssHexBox = styled(textInput, ` - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-left: none; font-size: ${vars.smallFontSize}; display: flex; align-items: center; - color: ${colors.slate}; + color: ${theme.lightText}; + background-color: ${theme.inputBg}; width: 56px; outline: none; padding: 0 3px; diff --git a/app/client/ui2018/breadcrumbs.ts b/app/client/ui2018/breadcrumbs.ts index 028476f4..c29f14dc 100644 --- a/app/client/ui2018/breadcrumbs.ts +++ b/app/client/ui2018/breadcrumbs.ts @@ -6,35 +6,27 @@ * Workspace is a clickable link and document and page names are editable labels. */ import { urlState } from 'app/client/models/gristUrlState'; -import { colors, cssHideForNarrowScreen, mediaNotSmall, testId } from 'app/client/ui2018/cssVars'; +import { cssHideForNarrowScreen, mediaNotSmall, testId, theme } from 'app/client/ui2018/cssVars'; import { editableLabel } from 'app/client/ui2018/editableLabel'; import { icon } from 'app/client/ui2018/icons'; +import { cssLink } from 'app/client/ui2018/links'; import { UserOverride } from 'app/common/DocListAPI'; import { userOverrideParams } from 'app/common/gristUrls'; import { BindableValue, dom, Observable, styled } from 'grainjs'; import { tooltip } from 'popweasel'; export const cssBreadcrumbs = styled('div', ` - color: ${colors.slate}; + color: ${theme.lightText}; white-space: nowrap; cursor: default; `); -export const cssBreadcrumbsLink = styled('a', ` - color: ${colors.lightGreen}; - text-decoration: none; - - &:hover { - text-decoration: underline; - } -`); - export const separator = styled('span', ` padding: 0 2px; `); const cssIcon = styled(icon, ` - background-color: ${colors.lightGreen}; + background-color: ${theme.accentIcon}; margin-top: -2px; `); @@ -43,7 +35,7 @@ const cssPublicIcon = styled(cssIcon, ` margin-top: -4px; `); -const cssWorkspaceName = styled(cssBreadcrumbsLink, ` +const cssWorkspaceName = styled(cssLink, ` margin-left: 8px; `); @@ -54,7 +46,7 @@ const cssWorkspaceNarrowScreen = styled(icon, ` margin-bottom: 4px; margin-left: -7px; margin-right: 8px; - background-color: ${colors.slate}; + background-color: ${theme.lightText}; cursor: pointer; @media ${mediaNotSmall} { & { @@ -65,21 +57,21 @@ const cssWorkspaceNarrowScreen = styled(icon, ` const cssEditableName = styled('input', ` &:hover, &:focus { - color: ${colors.dark}; + color: ${theme.text}; } `); const cssTag = styled('span', ` - background-color: ${colors.slate}; - color: white; + background-color: ${theme.breadcrumbsTagBg}; + color: ${theme.breadcrumbsTagFg}; border-radius: 3px; padding: 0 4px; margin-left: 4px; `); const cssAlertTag = styled(cssTag, ` - background-color: ${colors.error}; - --icon-color: white; + background-color: ${theme.breadcrumbsTagAlertBg}; + --icon-color: ${theme.breadcrumbsTagFg}; a { cursor: pointer; } diff --git a/app/client/ui2018/buttonSelect.ts b/app/client/ui2018/buttonSelect.ts index 1ca768cb..faf34146 100644 --- a/app/client/ui2018/buttonSelect.ts +++ b/app/client/ui2018/buttonSelect.ts @@ -1,4 +1,4 @@ -import {colors, testId, vars} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {IconName} from 'app/client/ui2018/IconList'; import {icon} from 'app/client/ui2018/icons'; import {isColorDark} from 'app/common/gutil'; @@ -142,7 +142,7 @@ export const cssButtonSelect = styled('div', ` display: flex; /* Vars */ - color: ${colors.dark}; + color: ${theme.text}; flex: 1 1 0; `); @@ -165,8 +165,9 @@ const cssSelectorBtn = styled('div', ` white-space: nowrap; padding: 4px 10px; - border: 1px solid ${colors.darkGrey}; - --icon-color: ${colors.slate}; + background-color: ${theme.buttonGroupBg}; + border: 1px solid ${theme.buttonGroupBorder}; + --icon-color: ${theme.buttonGroupIcon}; margin-left: -1px; @@ -184,15 +185,15 @@ const cssSelectorBtn = styled('div', ` } &:hover:not(&-selected) { - border: 1px solid ${colors.hover}; + border: 1px solid ${theme.buttonGroupBorderHover}; z-index: 5; /* Update z-index so selected borders take precedent */ } &-selected { - color: ${colors.light}; - --icon-color: ${colors.light}; - border: 1px solid ${colors.dark}; - background-color: ${colors.dark}; + color: ${theme.buttonGroupSelectedFg}; + --icon-color: ${theme.buttonGroupSelectedFg}; + border: 1px solid ${theme.buttonGroupSelectedBorder}; + background-color: ${theme.buttonGroupSelectedBg}; z-index: 10; /* Update z-index so selected borders take precedent */ } @@ -202,18 +203,18 @@ const cssSelectorBtn = styled('div', ` border-radius: ${vars.controlBorderRadius}; margin-left: 0px; padding: 8px; - color: ${colors.slate}; - --icon-color: ${colors.slate}; + color: ${theme.buttonGroupLightFg}; + --icon-color: ${theme.buttonGroupLightFg}; } .${cssButtonSelect.className}-light > &-selected { border: none; - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.buttonGroupLightSelectedFg}; + --icon-color: ${theme.buttonGroupLightSelectedFg}; background-color: initial; } .${cssButtonSelect.className}-light > &:hover { border: none; - background-color: ${colors.mediumGrey}; + background-color: ${theme.hover}; } `); diff --git a/app/client/ui2018/buttons.ts b/app/client/ui2018/buttons.ts index b38c7591..6c4dd985 100644 --- a/app/client/ui2018/buttons.ts +++ b/app/client/ui2018/buttons.ts @@ -12,7 +12,7 @@ * `primaryButton('Primary button', dom.prop('disabled', true))` */ -import { colors, vars } from 'app/client/ui2018/cssVars'; +import { theme, vars } from 'app/client/ui2018/cssVars'; import { tbind } from 'app/common/tbind'; import { dom, DomElementArg, styled } from 'grainjs'; @@ -29,11 +29,12 @@ export const cssButton = styled('button', ` padding: 4px 8px; background-color: transparent; - color: ${vars.controlFg}; - --icon-color: ${vars.controlFg}; + color: ${theme.controlFg}; + --icon-color: ${theme.controlFg}; border: ${vars.controlBorder}; border-radius: ${vars.controlBorderRadius}; + border-color: ${theme.controlBorder}; cursor: pointer; @@ -44,29 +45,29 @@ export const cssButton = styled('button', ` } &-primary { - background-color: ${vars.primaryBg}; - color: ${vars.primaryFg}; - --icon-color: ${vars.primaryFg}; - border-color: ${vars.primaryBg}; + background-color: ${theme.controlPrimaryBg}; + color: ${theme.controlPrimaryFg}; + --icon-color: ${theme.controlPrimaryFg}; + border-color: ${theme.controlPrimaryBg}; } &:hover { - color: ${vars.controlFgHover}; - --icon-color: ${vars.controlFgHover}; - border-color: ${vars.controlFgHover}; + color: ${theme.controlHoverFg}; + --icon-color: ${theme.controlHoverFg}; + border-color: ${theme.controlHoverFg}; } &-primary:hover { - color: ${vars.primaryFg}; - --icon-color: ${vars.primaryFg}; - background-color: ${vars.primaryBgHover}; - border-color: ${vars.primaryBgHover}; + color: ${theme.controlPrimaryFg}; + --icon-color: ${theme.controlPrimaryFg}; + background-color: ${theme.controlPrimaryHoverBg}; + border-color: ${theme.controlPrimaryHoverBg}; } &:disabled { cursor: not-allowed; - color: ${colors.light}; - --icon-color: ${colors.light}; - background-color: ${colors.slate}; - border-color: ${colors.slate}; + color: ${theme.controlDisabledFg}; + --icon-color: ${theme.controlDisabledFg}; + background-color: ${theme.controlDisabledBg}; + border-color: ${theme.controlDisabledBg}; } `); @@ -107,7 +108,7 @@ export const textButton = styled(cssButton, ` padding: 0px; background-color: inherit !important; &:disabled { - color: ${colors.inactiveCursor}; + color: ${theme.controlPrimaryDisabled}; } `); diff --git a/app/client/ui2018/checkbox.ts b/app/client/ui2018/checkbox.ts index 3a60dfb5..a7de9b78 100644 --- a/app/client/ui2018/checkbox.ts +++ b/app/client/ui2018/checkbox.ts @@ -15,7 +15,7 @@ * labeledSquareCheckbox(observable(false), 'Include other values', dom.prop('disabled', true)), */ -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { Computed, dom, DomArg, styled } from 'grainjs'; import { Observable } from 'grainjs'; @@ -28,9 +28,9 @@ export const cssLabel = styled('label', ` outline: none; user-select: none; - --color: ${colors.darkGrey}; + --color: ${theme.checkboxBorder}; &:hover { - --color: ${colors.hover}; + --color: ${theme.checkboxBorderHover}; } `); @@ -53,20 +53,14 @@ export const cssCheckboxSquare = styled('input', ` --radius: 3px; &:checked:enabled, &:indeterminate:enabled { - --color: ${colors.lightGreen}; + --color: ${theme.controlPrimaryBg}; } &:disabled { - --color: ${colors.darkGrey}; + --color: ${theme.checkboxDisabledBg}; cursor: not-allowed; } - .${cssLabel.className}:hover > &:checked:enabled, - .${cssLabel.className}:hover > &:indeterminate:enabled, { - --color: ${colors.darkGreen}; - } - - &::before, &::after { content: ''; @@ -86,6 +80,14 @@ export const cssCheckboxSquare = styled('input', ` background-color: var(--color); } + &:not(:checked):indeterminate::after { + -webkit-mask-image: var(--icon-Minus); + } + + &:not(:disabled)::after { + background-color: ${theme.checkboxBg}; + } + &:checked::after, &:indeterminate::after { content: ''; position: absolute; @@ -95,15 +97,7 @@ export const cssCheckboxSquare = styled('input', ` -webkit-mask-size: contain; -webkit-mask-position: center; -webkit-mask-repeat: no-repeat; - background-color: ${colors.light}; - } - - &:not(:checked):indeterminate::after { - -webkit-mask-image: var(--icon-Minus); - } - - &:not(:disabled)::after { - background-color: ${colors.light}; + background-color: ${theme.controlPrimaryFg}; } `); @@ -113,7 +107,7 @@ export const cssCheckboxCircle = styled(cssCheckboxSquare, ` export const cssLabelText = styled('span', ` margin-left: 8px; - color: ${colors.dark}; + color: ${theme.text}; font-weight: initial; /* negate bootstrap */ overflow: hidden; text-overflow: ellipsis; diff --git a/app/client/ui2018/cssVars.ts b/app/client/ui2018/cssVars.ts index d9d7bc60..217f11ca 100644 --- a/app/client/ui2018/cssVars.ts +++ b/app/client/ui2018/cssVars.ts @@ -8,6 +8,7 @@ */ 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'); @@ -15,17 +16,32 @@ import values = require('lodash/values'); const VAR_PREFIX = 'grist'; class CustomProp { - constructor(public name: string, public value: string) { } + constructor(public name: string, public value?: string, public fallback?: string | CustomProp) { + + } + + public decl(): string | undefined { + if (this.value === undefined) { return undefined; } - public decl() { return `--${VAR_PREFIX}-${this.name}: ${this.value};`; } - public toString() { - return `var(--${VAR_PREFIX}-${this.name})`; + 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)'), @@ -46,7 +62,7 @@ export const colors = { lightBlue: new CustomProp('color-light-blue', '#3B82F6'), orange: new CustomProp('color-orange', '#F9AE41'), - cursor: new CustomProp('color-cursor', '#16B378'), // cursor is lightGreen + 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'), @@ -117,6 +133,512 @@ export const vars = { 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 = ` @@ -237,17 +759,107 @@ export function isScreenResizing(): Observable { return _isScreenResizingObs; } +let _prefersDarkModeObs: Observable|undefined; + /** - * Attaches the global css properties to the document's root to them available in the page. + * Returns a singleton observable for whether the user agent prefers dark mode. + */ +export function prefersDarkModeObs(): Observable { + if (!_prefersDarkModeObs) { + const query = window.matchMedia('(prefers-color-scheme: dark)'); + const obs = Observable.create(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 theme = getTheme(productFlavor); - if (theme.bodyClassName) { - document.body.classList.add(theme.bodyClassName); + 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; +} diff --git a/app/client/ui2018/draggableList.ts b/app/client/ui2018/draggableList.ts index 5b823593..99a4dac2 100644 --- a/app/client/ui2018/draggableList.ts +++ b/app/client/ui2018/draggableList.ts @@ -1,4 +1,4 @@ -import {testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {styled} from 'grainjs'; @@ -6,6 +6,7 @@ import {styled} from 'grainjs'; // Drag icon for use in koForm draggableList. export const cssDragger = styled((...args: any[]) => icon('DragDrop', testId('dragger'), ...args), ` + --icon-color: ${theme.controlSecondaryFg}; visibility: hidden; align-self: center; flex-shrink: 0; diff --git a/app/client/ui2018/editableLabel.ts b/app/client/ui2018/editableLabel.ts index c7ed2496..52ff835b 100644 --- a/app/client/ui2018/editableLabel.ts +++ b/app/client/ui2018/editableLabel.ts @@ -9,7 +9,7 @@ * * TODO: Consider merging this into grainjs's input widget. */ -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { dom, DomArg, styled } from 'grainjs'; import { Observable } from 'grainjs'; import noop = require('lodash/noop'); @@ -45,7 +45,7 @@ export const cssLabelText = styled(rawTextInput, ` export const cssTextInput = styled('input', ` outline: none; height: 28px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-radius: 3px; padding: 0 6px; `); @@ -97,7 +97,7 @@ export function editableLabel(label: Observable, options: EditableLabelO /** * Provides a text input element that pretty much behaves like the editableLabel only it shows as a - * regular input within a rigid static frame. It takes in an observable that is setf on Enter or loss + * regular input within a rigid static frame. It takes in an observable that is set on Enter or loss * of focus. Escape cancels editing. Validation logic (if any) should happen in the save function, * to reject a value simply throw an error, this will revert to the the saved one. */ diff --git a/app/client/ui2018/icons.ts b/app/client/ui2018/icons.ts index 361eb8d8..ee1e97d1 100644 --- a/app/client/ui2018/icons.ts +++ b/app/client/ui2018/icons.ts @@ -49,7 +49,7 @@ * `); */ -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { dom, DomElementArg, styled } from 'grainjs'; import { IconName } from './IconList'; @@ -65,7 +65,7 @@ const iconDiv = styled('div', ` -webkit-mask-size: contain; width: 16px; height: 16px; - background-color: var(--icon-color, black); + background-color: var(--icon-color, var(--grist-theme-text, black)); `); export const cssIconBackground = styled(iconDiv, ` @@ -85,7 +85,7 @@ export function icon(name: IconName, ...domArgs: DomElementArg[]): HTMLElement { } /** - * Container box for an slate-colored icon to serve as a button, with a grey background on hover. + * Container box for an icon to serve as a button.. */ export const cssIconButton = styled('div', ` flex: none; @@ -95,9 +95,9 @@ export const cssIconButton = styled('div', ` border-radius: 3px; line-height: 0px; cursor: default; - --icon-color: ${colors.slate}; + --icon-color: ${theme.controlSecondaryFg}; &:hover, &.weasel-popup-open { - background-color: ${colors.darkGrey}; - --icon-color: ${colors.slate}; + background-color: ${theme.controlSecondaryHoverBg}; + --icon-color: ${theme.controlSecondaryFg}; } `); diff --git a/app/client/ui2018/links.ts b/app/client/ui2018/links.ts index 1fbb844d..d9c5c1b2 100644 --- a/app/client/ui2018/links.ts +++ b/app/client/ui2018/links.ts @@ -1,19 +1,19 @@ import { sameDocumentUrlState, urlState } from 'app/client/models/gristUrlState'; -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { CellValue } from 'app/plugin/GristData'; import { dom, IDomArgs, Observable, styled } from 'grainjs'; /** - * Styling for a simple green link. + * Styling for a simple link. */ export const cssLink = styled('a', ` - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.link}; + --icon-color: ${theme.link}; text-decoration: none; &:hover, &:focus { - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.linkHover}; + --icon-color: ${theme.linkHover}; text-decoration: underline; } `); diff --git a/app/client/ui2018/loaders.ts b/app/client/ui2018/loaders.ts index da18454a..7811d1ca 100644 --- a/app/client/ui2018/loaders.ts +++ b/app/client/ui2018/loaders.ts @@ -1,4 +1,4 @@ -import {colors} from 'app/client/ui2018/cssVars'; +import {theme} from 'app/client/ui2018/cssVars'; import {DomArg, keyframes, styled} from 'grainjs'; const rotate360 = keyframes(` @@ -9,10 +9,10 @@ const rotate360 = keyframes(` const flash = keyframes(` 0% { - background-color: ${colors.lightGreen}; + background-color: ${theme.loaderFg}; } 50%, 100% { - background-color: ${colors.darkGrey}; + background-color: ${theme.loaderBg}; } `); @@ -25,8 +25,8 @@ export const loadingSpinner = styled('div', ` width: 32px; height: 32px; border-radius: 32px; - border: 4px solid ${colors.darkGrey}; - border-top-color: ${colors.lightGreen}; + border: 4px solid ${theme.loaderBg}; + border-top-color: ${theme.loaderFg}; animation: ${rotate360} 1s ease-out infinite; `); @@ -52,8 +52,8 @@ const cssLoadingDot = styled('div', ` border-radius: 50%; width: var(--dot-size); height: var(--dot-size); - background-color: ${colors.lightGreen}; - color: ${colors.lightGreen}; + background-color: ${theme.loaderFg}; + color: ${theme.loaderFg}; animation: ${flash} 1s alternate infinite; &-left { diff --git a/app/client/ui2018/menus.ts b/app/client/ui2018/menus.ts index 0f2d36a1..51e735f5 100644 --- a/app/client/ui2018/menus.ts +++ b/app/client/ui2018/menus.ts @@ -2,7 +2,7 @@ import { Command } from 'app/client/components/commands'; import { NeedUpgradeError, reportError } from 'app/client/models/errors'; import { textButton } from 'app/client/ui2018/buttons'; import { cssCheckboxSquare, cssLabel, cssLabelText } from 'app/client/ui2018/checkbox'; -import { colors, testId, vars } from 'app/client/ui2018/cssVars'; +import { testId, theme, vars } from 'app/client/ui2018/cssVars'; import { IconName } from 'app/client/ui2018/IconList'; import { icon } from 'app/client/ui2018/icons'; import { cssSelectBtn } from 'app/client/ui2018/select'; @@ -60,11 +60,12 @@ export const cssMenuElem = styled('div', ` line-height: initial; max-width: 400px; padding: 8px 0px 16px 0px; - box-shadow: 0 2px 20px 0 rgba(38,38,51,0.6); + box-shadow: 0 2px 20px 0 ${theme.menuShadow}; min-width: 160px; z-index: 999; - --weaseljs-selected-background-color: ${vars.primaryBg}; + --weaseljs-selected-background-color: ${theme.menuItemSelectedBg}; --weaseljs-menu-item-padding: 8px 24px; + background-color: ${theme.menuBg}; @media print { & { @@ -76,13 +77,16 @@ export const cssMenuElem = styled('div', ` const menuItemStyle = ` justify-content: flex-start; align-items: center; - --icon-color: ${colors.lightGreen}; + color: ${theme.menuItemFg}; + --icon-color: ${theme.accentIcon}; .${weasel.cssMenuItem.className}-sel { - --icon-color: ${colors.light}; + color: ${theme.menuItemSelectedFg}; + --icon-color: ${theme.menuItemSelectedFg}; } &.disabled { cursor: default; - opacity: 0.2; + color: ${theme.menuItemDisabledFg}; + --icon-color: ${theme.menuItemDisabledFg}; } `; @@ -237,7 +241,9 @@ export function multiSelect(selectedOptions: MutableObsArray, weasel.setPopupToCreateDom(elem, ctl => buildMultiSelectMenu(ctl), weasel.defaultMenuOptions); }, dom.style('border', use => { - return options.error && use(options.error) ? '1px solid red' : `1px solid ${colors.darkGrey}`; + return options.error && use(options.error) + ? `1px solid ${theme.selectButtonBorderInvalid}` + : `1px solid ${theme.selectButtonBorder}`; }), ...domArgs ); @@ -394,6 +400,7 @@ export function selectOption( } export const menuSubHeader = styled('div', ` + color: ${theme.menuSubheaderFg}; font-size: ${vars.xsmallFontSize}; text-transform: uppercase; font-weight: ${vars.bigControlTextWeight}; @@ -405,7 +412,7 @@ export const menuText = styled('div', ` display: flex; align-items: center; font-size: ${vars.smallFontSize}; - color: ${colors.slate}; + color: ${theme.menuText}; padding: 8px 24px 4px 24px; max-width: 250px; cursor: default; @@ -440,6 +447,7 @@ export function menuAnnotate(text: string, ...args: DomElementArg[]) { } export const menuDivider = styled(weasel.cssMenuDivider, ` + background-color: ${theme.menuBorder}; margin: 8px 0; `); @@ -464,8 +472,8 @@ const cssSelectBtnLink = styled('div', ` display: flex; align-items: center; font-size: ${vars.mediumFontSize}; - color: ${colors.lightGreen}; - --icon-color: ${colors.lightGreen}; + color: ${theme.controlFg}; + --icon-color: ${theme.controlFg}; width: initial; height: initial; line-height: inherit; @@ -480,8 +488,8 @@ const cssSelectBtnLink = styled('div', ` -moz-appearance: none; &:hover, &:focus, &:active { - color: ${colors.darkGreen}; - --icon-color: ${colors.darkGreen}; + color: ${theme.controlHoverFg}; + --icon-color: ${theme.controlHoverFg}; box-shadow: initial; } `); @@ -489,7 +497,7 @@ const cssSelectBtnLink = styled('div', ` const cssOptionIcon = styled(icon, ` height: 16px; width: 16px; - background-color: ${colors.slate}; + background-color: ${theme.menuItemIconFg}; margin: -3px 8px 0 2px; `); @@ -504,7 +512,11 @@ export const cssOptionRowIcon = styled(cssOptionIcon, ` flex: none; .${weasel.cssMenuItem.className}-sel & { - background-color: white; + background-color: ${theme.menuItemSelectedFg}; + } + + .${weasel.cssMenuItem.className}.disabled & { + background-color: ${theme.menuItemDisabledFg}; } `); @@ -512,6 +524,19 @@ const cssOptionLabel = styled('div', ` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + + .${weasel.cssMenuItem.className} & { + color: ${theme.menuItemFg}; + } + + .${weasel.cssMenuItem.className}-sel & { + color: ${theme.menuItemSelectedFg}; + background-color: ${theme.menuItemSelectedBg}; + } + + .${weasel.cssMenuItem.className}.disabled & { + color: ${theme.menuItemDisabledFg}; + } `); const cssInlineCollapseIcon = styled(icon, ` @@ -524,7 +549,7 @@ const cssCollapseIcon = styled(icon, ` right: 12px; top: calc(50% - 8px); pointer-events: none; - background-color: ${colors.dark}; + background-color: ${theme.selectButtonFg}; `); const cssInputButtonMenuElem = styled(cssMenuElem, ` @@ -537,16 +562,20 @@ const cssMenuItemCmd = styled('div', ` const cssCmdKey = styled('span', ` margin-left: 16px; - color: ${colors.slate}; + color: ${theme.menuItemIconFg}; margin-right: -12px; .${weasel.cssMenuItem.className}-sel > & { - color: ${colors.lightGrey}; + color: ${theme.menuItemIconSelectedFg}; + } + + .${weasel.cssMenuItem.className}.disabled > & { + color: ${theme.menuItemDisabledFg}; } `); const cssAnnotateMenuItem = styled('span', ` - color: ${colors.lightGreen}; + color: ${theme.accentText}; text-transform: uppercase; font-size: 8px; vertical-align: super; @@ -555,7 +584,7 @@ const cssAnnotateMenuItem = styled('span', ` font-weight: bold; .${weasel.cssMenuItem.className}-sel > & { - color: white; + color: ${theme.menuItemIconSelectedFg}; } `); @@ -563,9 +592,10 @@ const cssMultiSelectSummary = styled('div', ` flex: 1 1 0px; overflow: hidden; text-overflow: ellipsis; + color: ${theme.selectButtonFg}; &-placeholder { - color: ${colors.slate} + color: ${theme.selectButtonPlaceholderFg}; } `); @@ -575,6 +605,7 @@ const cssMultiSelectMenu = styled(weasel.cssMenu, ` max-height: calc(max(300px, 95vh - 300px)); max-width: 400px; padding-bottom: 0px; + background-color: ${theme.menuBg}; `); const cssCheckboxLabel = styled(cssLabel, ` @@ -583,7 +614,7 @@ const cssCheckboxLabel = styled(cssLabel, ` const cssCheckboxText = styled(cssLabelText, ` margin-right: 12px; - color: ${colors.dark}; + color: ${theme.text}; white-space: pre; `); diff --git a/app/client/ui2018/modals.ts b/app/client/ui2018/modals.ts index b9885e7c..3b5caf30 100644 --- a/app/client/ui2018/modals.ts +++ b/app/client/ui2018/modals.ts @@ -3,7 +3,7 @@ import {reportError} from 'app/client/models/errors'; import {cssInput} from 'app/client/ui/cssInput'; import {prepareForTransition, TransitionWatcher} from 'app/client/ui/transitions'; import {bigBasicButton, bigPrimaryButton, cssButton} from 'app/client/ui2018/buttons'; -import {colors, mediaSmall, testId, vars} from 'app/client/ui2018/cssVars'; +import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars'; import {loadingSpinner} from 'app/client/ui2018/loaders'; import {waitGrainObs} from 'app/common/gutil'; import {Computed, Disposable, dom, DomContents, DomElementArg, input, keyframes, @@ -474,12 +474,12 @@ export function cssModalWidth(style: ModalWidth) { // the flex container, to ensure the full item can be scrolled in case of overflow. // See https://stackoverflow.com/a/33455342/328565 const cssModalDialog = styled('div', ` - background-color: white; + background-color: ${theme.modalBg}; min-width: 428px; - color: black; + color: ${theme.darkText}; margin: auto; border-radius: 3px; - box-shadow: 0 2px 18px 0 rgba(31,37,50,0.31), 0 0 1px 0 rgba(76,86,103,0.24); + box-shadow: 0 2px 18px 0 ${theme.modalInnerShadow}, 0 0 1px 0 ${theme.modalOuterShadow}; padding: 40px 64px; outline: none; @@ -506,13 +506,14 @@ const cssModalDialog = styled('div', ` export const cssModalTitle = styled('div', ` font-size: ${vars.xxxlargeFontSize}; font-weight: ${vars.headerControlTextWeight}; - color: ${colors.dark}; + color: ${theme.text}; margin: 0 0 16px 0; line-height: 32px; overflow-wrap: break-word; `); export const cssModalBody = styled('div', ` + color: ${theme.text}; margin: 16px 0; `); @@ -530,7 +531,7 @@ const cssFadeIn = keyframes(` `); const cssFadeOut = keyframes(` - from {background-color: ${colors.backdrop}} + from {background-color: ${theme.modalBackdrop}} `); const cssModalBacker = styled('div', ` @@ -543,7 +544,7 @@ const cssModalBacker = styled('div', ` left: 0; padding: 16px; z-index: 999; - background-color: ${colors.backdrop}; + background-color: ${theme.modalBackdrop}; overflow-y: auto; animation-name: ${cssFadeIn}; animation-duration: 0.4s; diff --git a/app/client/ui2018/pages.ts b/app/client/ui2018/pages.ts index 69b687a2..d697d853 100644 --- a/app/client/ui2018/pages.ts +++ b/app/client/ui2018/pages.ts @@ -1,7 +1,7 @@ import { isDesktop } from 'app/client/lib/browserInfo'; import { cssEditorInput } from "app/client/ui/HomeLeftPane"; import { itemHeader, itemHeaderWrapper, treeViewContainer } from "app/client/ui/TreeViewComponentCss"; -import { colors } from "app/client/ui2018/cssVars"; +import { theme } from "app/client/ui2018/cssVars"; import { icon } from "app/client/ui2018/icons"; import { menu, menuItem, menuText } from "app/client/ui2018/menus"; import { dom, domComputed, DomElementArg, makeTestId, observable, Observable, styled } from "grainjs"; @@ -86,7 +86,7 @@ export function buildPageDom(name: Observable, actions: PageActions, ... dom.on('click', (ev) => isTargetSelected(ev.target as HTMLElement) && isRenaming.set(true)), ), cssPageMenuTrigger( - cssPageIcon('Dots'), + cssPageMenuIcon('Dots'), menu(pageMenu, {placement: 'bottom-start', parentSelectorToMark: '.' + itemHeader.className}), dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }), @@ -104,7 +104,6 @@ export function buildPageDom(name: Observable, actions: PageActions, ... } const cssPageItem = styled('a', ` - --icon-color: ${colors.slate}; display: flex; flex-direction: row; height: 28px; @@ -122,9 +121,9 @@ const cssPageItem = styled('a', ` const cssPageInitial = styled('div', ` flex-shrink: 0; - color: white; + color: ${theme.pageInitialsFg}; border-radius: 3px; - background-color: ${colors.slate}; + background-color: ${theme.pageInitialsBg}; width: 16px; height: 16px; text-align: center; @@ -187,20 +186,21 @@ const cssPageMenuTrigger = styled('div', ` } } .${itemHeaderWrapper.className}-not-dragging &:hover, &.weasel-popup-open { - background-color: ${colors.darkGrey}; + background-color: ${theme.pageOptionsHoverBg}; } .${itemHeaderWrapper.className}-not-dragging > .${itemHeader.className}.selected &:hover, .${itemHeaderWrapper.className}-not-dragging > .${itemHeader.className}.selected &.weasel-popup-open { - background-color: ${colors.slate}; + background-color: ${theme.pageOptionsSelectedHoverBg}; } .${itemHeader.className}.weasel-popup-open, .${itemHeader.className}-renaming { - background-color: ${colors.mediumGrey}; + background-color: ${theme.pageHoverBg}; } `); -const cssPageIcon = styled(icon, ` +const cssPageMenuIcon = styled(icon, ` + background-color: ${theme.pageOptionsFg}; .${itemHeader.className}.selected & { - background-color: white; + background-color: ${theme.pageOptionsHoverFg}; } `); diff --git a/app/client/ui2018/search.ts b/app/client/ui2018/search.ts index 4cfc277e..dfa6186f 100644 --- a/app/client/ui2018/search.ts +++ b/app/client/ui2018/search.ts @@ -8,7 +8,7 @@ import { SearchModel } from 'app/client/models/SearchModel'; import { hoverTooltip, IHoverTipOptions } from 'app/client/ui/tooltips'; import { cssHoverCircle, cssTopBarBtn } from 'app/client/ui/TopBarCss'; import { labeledSquareCheckbox } from 'app/client/ui2018/checkbox'; -import { colors, mediaSmall, vars } from 'app/client/ui2018/cssVars'; +import { mediaSmall, theme, vars } from 'app/client/ui2018/cssVars'; import { icon } from 'app/client/ui2018/icons'; import { dom, input, styled } from 'grainjs'; import { noTestId, TestId } from 'grainjs'; @@ -32,7 +32,7 @@ const searchWrapper = styled('div', ` position: relative; &-expand { width: 100% !important; - border: 1px solid grey; + border: 1px solid ${theme.searchBorder}; } @media ${mediaSmall} { & { @@ -58,6 +58,8 @@ const expandedSearch = styled('div', ` `); const searchInput = styled(input, ` + background-color: ${theme.topHeaderBg}; + color: ${theme.inputFg}; outline: none; border: none; margin: 0; @@ -70,6 +72,9 @@ const searchInput = styled(input, ` .${searchWrapper.className}-expand & { width: 100%; } + &::placeholder { + color: ${theme.inputPlaceholderFg}; + } `); const cssArrowBtn = styled('div', ` @@ -80,8 +85,8 @@ const cssArrowBtn = styled('div', ` visibility: hidden; width: 24px; height: 24px; - background-color: ${colors.mediumGrey}; - --icon-color: ${colors.slate}; + background-color: ${theme.searchPrevNextButtonBg}; + --icon-color: ${theme.searchPrevNextButtonFg}; border-radius: 3px; text-align: center; display: flex; @@ -94,31 +99,31 @@ const cssArrowBtn = styled('div', ` const cssCloseBtn = styled(icon, ` cursor: pointer; - background-color: ${colors.lightGreen}; + background-color: ${theme.controlFg}; margin-left: 4px; flex-shrink: 0; `); const cssLabel = styled('span', ` font-size: ${vars.smallFontSize}; - color: ${colors.slate}; + color: ${theme.lightText}; white-space: nowrap; margin-right: 12px; `); const cssOptions = styled('div', ` + background: ${theme.topHeaderBg}; position: absolute; right: 0; - top: 46px; + top: 48px; z-index: 1; - background: white; padding: 2px 4px; overflow: hidden; white-space: nowrap; `); const cssShortcut = styled('span', ` - color: ${colors.slate}; + color: ${theme.lightText}; `); const searchArrowBtnTooltipOptions: IHoverTipOptions = { diff --git a/app/client/ui2018/select.ts b/app/client/ui2018/select.ts index 91fbb707..f6df18bd 100644 --- a/app/client/ui2018/select.ts +++ b/app/client/ui2018/select.ts @@ -1,4 +1,4 @@ -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {styled} from 'grainjs'; // Import popweasel so that the styles we define here are included later in CSS, and take priority @@ -19,12 +19,12 @@ export const cssSelectBtn = styled('div', ` width: 100%; height: 30px; line-height: 16px; - background-color: white; - color: ${colors.dark}; - --icon-color: ${colors.dark}; + background-color: ${theme.selectButtonBg}; + color: ${theme.selectButtonFg}; + --icon-color: ${theme.selectButtonFg}; font-size: ${vars.mediumFontSize}; padding: 5px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.selectButtonBorder}; border-radius: 3px; cursor: pointer; overflow: hidden; @@ -42,7 +42,8 @@ export const cssSelectBtn = styled('div', ` } &.disabled { - color: grey; + --icon-color: ${theme.selectButtonDisabledFg}; + color: ${theme.selectButtonDisabledFg}; cursor: pointer; } `); diff --git a/app/client/widgets/CellStyle.ts b/app/client/widgets/CellStyle.ts index b25f2cfe..0b26305a 100644 --- a/app/client/widgets/CellStyle.ts +++ b/app/client/widgets/CellStyle.ts @@ -3,7 +3,7 @@ import {GristDoc} from 'app/client/components/GristDoc'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {textButton} from 'app/client/ui2018/buttons'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {ConditionalStyle} from 'app/client/widgets/ConditionalStyle'; import {Disposable, dom, DomContents, fromKo, MultiHolder, Observable, styled} from 'grainjs'; @@ -67,6 +67,7 @@ const cssLine = styled('div', ` `); const cssLabel = styled('div', ` + color: ${theme.text}; text-transform: uppercase; font-size: ${vars.xsmallFontSize}; `); @@ -83,6 +84,6 @@ const cssRow = styled('div', ` margin-top: 24px; } &-disabled { - color: ${colors.slate}; + color: ${theme.disabledText}; } `); diff --git a/app/client/widgets/ChoiceListEditor.ts b/app/client/widgets/ChoiceListEditor.ts index 75674031..eb634584 100644 --- a/app/client/widgets/ChoiceListEditor.ts +++ b/app/client/widgets/ChoiceListEditor.ts @@ -2,7 +2,7 @@ import {createGroup} from 'app/client/components/commands'; import {ACIndexImpl, ACItem, ACResults, buildHighlightedDom, normalizeText, HighlightFunc} from 'app/client/lib/ACIndex'; import {IAutocompleteOptions} from 'app/client/lib/autocomplete'; import {IToken, TokenField, tokenFieldStyles} from 'app/client/lib/TokenField'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme} from 'app/client/ui2018/cssVars'; import {menuCssClass} from 'app/client/ui2018/menus'; import {createMobileButtons, getButtonMargins} from 'app/client/widgets/EditorButtons'; import {EditorPlacement} from 'app/client/widgets/EditorPlacement'; @@ -253,7 +253,7 @@ export class ChoiceListEditor extends NewBaseEditor { } const cssCellEditor = styled('div', ` - background-color: white; + background-color: ${theme.cellEditorBg}; font-family: var(--grist-font-family-data); font-size: var(--grist-medium-font-size); `); @@ -332,7 +332,7 @@ const cssInputSizer = styled('div', ` // Set z-index to be higher than the 1000 set for .cell_editor. export const cssChoiceList = styled('div', ` z-index: 1001; - box-shadow: 0 0px 8px 0 rgba(38,38,51,0.6); + box-shadow: 0 0px 8px 0 ${theme.menuShadow}; overflow-y: auto; padding: 8px 0 0 0; --weaseljs-menu-item-padding: 8px 16px; diff --git a/app/client/widgets/ChoiceListEntry.ts b/app/client/widgets/ChoiceListEntry.ts index adfdbc9d..6d403114 100644 --- a/app/client/widgets/ChoiceListEntry.ts +++ b/app/client/widgets/ChoiceListEntry.ts @@ -2,7 +2,7 @@ import {IToken, TokenField} from 'app/client/lib/TokenField'; import {cssBlockedCursor} from 'app/client/ui/RightPanelStyles'; import {basicButton, primaryButton} from 'app/client/ui2018/buttons'; import {colorButton, ColorOption} from 'app/client/ui2018/ColorSelect'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme} from 'app/client/ui2018/cssVars'; import {editableLabel} from 'app/client/ui2018/editableLabel'; import {icon} from 'app/client/ui2018/icons'; import {ChoiceOptionsByName, IChoiceOptions} from 'app/client/widgets/ChoiceTextBox'; @@ -422,17 +422,17 @@ const cssListBox = styled('div', ` line-height: 1.5; padding-left: 4px; padding-right: 4px; - border: 1px solid ${colors.hover}; + border: 1px solid ${theme.choiceEntryBorderHover}; border-radius: 4px; - background-color: white; + background-color: ${theme.choiceEntryBg}; `); const cssListBoxInactive = styled(cssListBox, ` cursor: pointer; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.choiceEntryBorder}; &:hover:not(&-disabled) { - border: 1px solid ${colors.hover}; + border: 1px solid ${theme.choiceEntryBorderHover}; } &-disabled { opacity: 0.6; @@ -445,7 +445,7 @@ const cssListRow = styled('div', ` margin-bottom: 4px; padding: 4px 8px; color: ${colors.dark}; - background-color: ${colors.mediumGrey}; + background-color: ${colors.mediumGreyOpaque}; border-radius: 3px; text-overflow: ellipsis; `); @@ -510,6 +510,7 @@ const cssEditableLabel = styled('div', ` `); const cssTokenInput = styled('input', ` + background-color: ${theme.choiceEntryBg}; padding-top: 4px; padding-bottom: 4px; overflow: hidden; diff --git a/app/client/widgets/ChoiceToken.ts b/app/client/widgets/ChoiceToken.ts index 4ee08061..87813ccb 100644 --- a/app/client/widgets/ChoiceToken.ts +++ b/app/client/widgets/ChoiceToken.ts @@ -1,8 +1,8 @@ -import {dom, DomContents, DomElementArg, styled} from "grainjs"; -import {colors, vars} from "app/client/ui2018/cssVars"; import {Style} from 'app/client/models/Styles'; +import {colors, theme, vars} from 'app/client/ui2018/cssVars'; +import {dom, DomContents, DomElementArg, styled} from 'grainjs'; -export const DEFAULT_FILL_COLOR = colors.mediumGreyOpaque.value; +export const DEFAULT_FILL_COLOR = colors.mediumGreyOpaque.value!; export const DEFAULT_TEXT_COLOR = '#000000'; export interface IChoiceTokenOptions extends Style { @@ -70,8 +70,7 @@ export const cssChoiceACItem = styled('li', ` cursor: pointer; &.selected { - background-color: ${colors.mediumGreyOpaque}; - color: ${colors.dark}; + background-color: ${theme.autocompleteChoiceSelectedBg}; } &-with-new { scroll-margin-bottom: ${ADD_NEW_HEIGHT}; @@ -79,15 +78,11 @@ export const cssChoiceACItem = styled('li', ` &-new { display: flex; align-items: center; - color: ${colors.slate}; position: sticky; bottom: 0px; height: ${ADD_NEW_HEIGHT}; - background-color: white; - border-top: 1px solid ${colors.mediumGreyOpaque}; + background-color: ${theme.menuBg}; + border-top: 1px solid ${theme.menuBorder}; scroll-margin-bottom: initial; } - &-new.selected { - color: ${colors.lightGrey}; - } `); diff --git a/app/client/widgets/ConditionalStyle.ts b/app/client/widgets/ConditionalStyle.ts index d2e4ce4e..121cbc97 100644 --- a/app/client/widgets/ConditionalStyle.ts +++ b/app/client/widgets/ConditionalStyle.ts @@ -6,7 +6,7 @@ import {Style} from 'app/client/models/Styles'; import {cssFieldFormula} from 'app/client/ui/FieldConfig'; import {textButton} from 'app/client/ui2018/buttons'; import {ColorOption, colorSelect} from 'app/client/ui2018/ColorSelect'; -import {colors, vars} from 'app/client/ui2018/cssVars'; +import {theme, vars} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {setupEditorCleanup} from 'app/client/widgets/FieldEditor'; import {cssError, openFormulaEditor} from 'app/client/widgets/FormulaEditor'; @@ -197,12 +197,12 @@ export class ConditionalStyle extends Disposable { const cssIcon = styled(icon, ` flex: 0 0 auto; - --icon-color: ${colors.slate}; `); const cssLabel = styled('div', ` text-transform: uppercase; margin: 16px 16px 12px 16px; + color: ${theme.text}; font-size: ${vars.xsmallFontSize}; `); @@ -214,7 +214,7 @@ const cssRow = styled('div', ` margin-top: 24px; } &-disabled { - color: ${colors.slate}; + color: ${theme.disabledText}; } `); @@ -224,9 +224,9 @@ const cssRemoveButton = styled(cssIcon, ` margin-right: 0px; transform: translateY(4px); cursor: pointer; - --icon-color: ${colors.slate}; + --icon-color: ${theme.controlSecondaryFg}; &:hover { - --icon-color: ${colors.lightGreen}; + --icon-color: ${theme.controlPrimaryFg}; } `); @@ -244,7 +244,7 @@ const cssRuleList = styled('div', ` `); const cssErrorBorder = styled('div', ` - border-color: ${colors.error}; + border-color: ${theme.inputInvalid}; `); const cssRuleError = styled(cssError, ` diff --git a/app/client/widgets/FieldBuilder.css b/app/client/widgets/FieldBuilder.css index 16bd7fe5..974bbfd1 100644 --- a/app/client/widgets/FieldBuilder.css +++ b/app/client/widgets/FieldBuilder.css @@ -12,7 +12,7 @@ } .fieldbuilder_settings { - background-color: #e8e8e8; + background-color: var(--grist-theme-right-panel-field-settings-bg, #e8e8e8); margin: 1rem -1px -4px -1px; padding-bottom: 1px; } @@ -28,5 +28,5 @@ float: right; padding: 0 1rem; border-radius: 5px; - background-color: lightgrey; + background-color: var(--grist-theme-right-panel-field-settings-button-bg, lightgrey); } diff --git a/app/client/widgets/FieldBuilder.ts b/app/client/widgets/FieldBuilder.ts index 70e0d8e3..94324bb6 100644 --- a/app/client/widgets/FieldBuilder.ts +++ b/app/client/widgets/FieldBuilder.ts @@ -17,7 +17,7 @@ import { CombinedStyle, Style } from 'app/client/models/Styles'; import { FieldSettingsMenu } from 'app/client/ui/FieldMenus'; import { cssBlockedCursor, cssLabel, cssRow } from 'app/client/ui/RightPanelStyles'; import { buttonSelect } from 'app/client/ui2018/buttonSelect'; -import { colors } from 'app/client/ui2018/cssVars'; +import { theme } from 'app/client/ui2018/cssVars'; import { IOptionFull, menu, select } from 'app/client/ui2018/menus'; import { DiffBox } from 'app/client/widgets/DiffBox'; import { buildErrorDom } from 'app/client/widgets/ErrorDom'; @@ -494,10 +494,8 @@ export class FieldBuilder extends Disposable { if (this.isDisposed()) { return null; } const fromRules = computedRule()?.style?.fillColor; let fill = fromRules || this.field.fillColor(); - // If user set white color - remove it to play nice with zebra strips. - // If there is no color we are using fully transparent white color (for tests mainly). fill = fill ? fill.toUpperCase() : fill; - return (fill === '#FFFFFF' ? '' : fill) || ''; + return fill || ''; })).onlyNotifyUnequal(); const fontBold = buildFontOptions(this, computedRule, 'fontBold'); @@ -641,6 +639,6 @@ const cssTypeSelectMenu = styled('div', ` `); const cssSeparator = styled('div', ` - border-bottom: 1px solid ${colors.mediumGrey}; + border-bottom: 1px solid ${theme.pagePanelsBorder}; margin-top: 16px; `); diff --git a/app/client/widgets/FormulaEditor.ts b/app/client/widgets/FormulaEditor.ts index 77cb5e79..e7c1e28e 100644 --- a/app/client/widgets/FormulaEditor.ts +++ b/app/client/widgets/FormulaEditor.ts @@ -2,7 +2,7 @@ import * as AceEditor from 'app/client/components/AceEditor'; import {createGroup} from 'app/client/components/commands'; import {DataRowModel} from 'app/client/models/DataRowModel'; import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {colors, testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {createMobileButtons, getButtonMargins} from 'app/client/widgets/EditorButtons'; import {EditorPlacement, ISize} from 'app/client/widgets/EditorPlacement'; @@ -427,5 +427,5 @@ const cssCollapseIcon = styled(icon, ` `); export const cssError = styled('div', ` - color: ${colors.error}; + color: ${theme.errorText}; `); diff --git a/app/client/widgets/NumericTextBox.ts b/app/client/widgets/NumericTextBox.ts index 02901858..a293572c 100644 --- a/app/client/widgets/NumericTextBox.ts +++ b/app/client/widgets/NumericTextBox.ts @@ -5,7 +5,7 @@ import {ViewFieldRec} from 'app/client/models/entities/ViewFieldRec'; import {reportError} from 'app/client/models/errors'; import {cssLabel, cssRow} from 'app/client/ui/RightPanelStyles'; import {ISelectorOption, makeButtonSelect} from 'app/client/ui2018/buttonSelect'; -import {colors, testId} from 'app/client/ui2018/cssVars'; +import {testId, theme} from 'app/client/ui2018/cssVars'; import {icon} from 'app/client/ui2018/icons'; import {NTextBox} from 'app/client/widgets/NTextBox'; import {clamp} from 'app/common/gutil'; @@ -153,8 +153,8 @@ function decimals( const cssDecimalsBox = styled('div', ` position: relative; flex: auto; - --icon-color: ${colors.slate}; - color: ${colors.slate}; + --icon-color: ${theme.lightText}; + color: ${theme.lightText}; font-weight: normal; display: flex; align-items: center; @@ -171,9 +171,10 @@ const cssNumLabel = styled('div', ` const cssNumInput = styled('input', ` padding: 4px 32px 4px 40px; - border: 1px solid ${colors.darkGrey}; + border: 1px solid ${theme.inputBorder}; border-radius: 3px; - color: ${colors.dark}; + background-color: ${theme.inputBg}; + color: ${theme.inputFg}; width: 100%; text-align: right; appearance: none; @@ -191,13 +192,14 @@ const cssSpinner = styled('div', ` `); const cssSpinnerBtn = styled('div', ` + --icon-color: ${theme.controlSecondaryFg}; flex: 1 1 0px; min-height: 0px; position: relative; cursor: pointer; overflow: hidden; &:hover { - --icon-color: ${colors.dark}; + --icon-color: ${theme.controlSecondaryHoverFg}; } `); @@ -213,11 +215,11 @@ const cssSpinnerBottom = styled(icon, ` const cssModeSelect = styled(makeButtonSelect, ` flex: 4 4 0px; - background-color: white; + background-color: ${theme.inputBg}; `); const cssSignSelect = styled(makeButtonSelect, ` flex: 1 1 0px; - background-color: white; + background-color: ${theme.inputBg}; margin-left: 16px; `); diff --git a/app/client/widgets/ReferenceEditor.ts b/app/client/widgets/ReferenceEditor.ts index e09d9cb5..b591c877 100644 --- a/app/client/widgets/ReferenceEditor.ts +++ b/app/client/widgets/ReferenceEditor.ts @@ -2,7 +2,7 @@ import { ACResults, buildHighlightedDom, normalizeText, HighlightFunc } from 'ap import { Autocomplete } from 'app/client/lib/autocomplete'; import { ICellItem } from 'app/client/models/ColumnACIndexes'; import { reportError } from 'app/client/models/errors'; -import { colors, testId, vars } from 'app/client/ui2018/cssVars'; +import { colors, testId, theme, vars } from 'app/client/ui2018/cssVars'; import { icon } from 'app/client/ui2018/icons'; import { menuCssClass } from 'app/client/ui2018/menus'; import { FieldOptions } from 'app/client/widgets/NewBaseEditor'; @@ -171,25 +171,26 @@ const cssRefItem = styled('li', ` outline: none; padding: var(--weaseljs-menu-item-padding, 8px 24px); cursor: pointer; + color: ${theme.menuItemFg}; &.selected { - background-color: var(--weaseljs-selected-background-color, #5AC09C); - color: var(--weaseljs-selected-color, white); + background-color: ${theme.menuItemSelectedBg}; + color: ${theme.menuItemSelectedFg}; } &-with-new { scroll-margin-bottom: ${addNewHeight}; } &-new { - color: ${colors.slate}; + color: ${theme.lightText}; position: sticky; bottom: 0px; height: ${addNewHeight}; - background-color: white; - border-top: 1px solid ${colors.mediumGrey}; + background-color: ${theme.menuBg}; + border-top: 1px solid ${theme.menuBorder}; scroll-margin-bottom: initial; } &-new.selected { - color: ${colors.lightGrey}; + color: ${theme.menuItemSelectedFg}; } `); @@ -221,8 +222,8 @@ const cssRefEditIcon = styled(icon, ` `); const cssMatchText = styled('span', ` - color: ${colors.lightGreen}; + color: ${theme.autocompleteMatchText}; .selected > & { - color: ${colors.lighterGreen}; + color: ${theme.autocompleteSelectedMatchText}; } `); diff --git a/app/client/widgets/ReferenceListEditor.ts b/app/client/widgets/ReferenceListEditor.ts index cb757c70..b21f68ed 100644 --- a/app/client/widgets/ReferenceListEditor.ts +++ b/app/client/widgets/ReferenceListEditor.ts @@ -3,7 +3,7 @@ import { ACItem, ACResults, normalizeText, HighlightFunc } from 'app/client/lib/ import { IAutocompleteOptions } from 'app/client/lib/autocomplete'; import { IToken, TokenField, tokenFieldStyles } from 'app/client/lib/TokenField'; import { reportError } from 'app/client/models/errors'; -import { colors, testId } from 'app/client/ui2018/cssVars'; +import { colors, testId, theme } from 'app/client/ui2018/cssVars'; import { menuCssClass } from 'app/client/ui2018/menus'; import { cssChoiceToken } from 'app/client/widgets/ChoiceToken'; import { createMobileButtons, getButtonMargins } from 'app/client/widgets/EditorButtons'; @@ -286,7 +286,7 @@ export class ReferenceListEditor extends NewBaseEditor { } const cssCellEditor = styled('div', ` - background-color: white; + background-color: ${theme.cellEditorBg}; font-family: var(--grist-font-family-data); font-size: var(--grist-medium-font-size); `); diff --git a/app/client/widgets/TextBox.css b/app/client/widgets/TextBox.css index d9a4c376..3c43ec82 100644 --- a/app/client/widgets/TextBox.css +++ b/app/client/widgets/TextBox.css @@ -1,7 +1,8 @@ .record-add .field_clip { - background-color: inherit; + background-color: var(--grist-theme-table-add-new-bg, inherit); } .transform_field { + color: black; background-color: #FEFFE8; } diff --git a/app/client/widgets/TextEditor.css b/app/client/widgets/TextEditor.css index 4faae513..f0603d4a 100644 --- a/app/client/widgets/TextEditor.css +++ b/app/client/widgets/TextEditor.css @@ -5,11 +5,11 @@ } .default_editor { - box-shadow: 0 0 3px 2px var(--grist-color-cursor); + box-shadow: 0 0 3px 2px var(--grist-theme-cursor, var(--grist-color-cursor)); } .readonly_editor { - box-shadow: 0 0 3px 2px var(--grist-color-slate); + box-shadow: 0 0 3px 2px var(--grist-theme-cursor-readonly, var(--grist-color-slate)); } /* make room for lock icon */ @@ -42,7 +42,7 @@ /* Make overflow hidden, since editor might be 1 pixel bigger due to fix for devices * with different pixel ratio */ .formula_editor { - background-color: white; + background-color: var(--grist-theme-formula-editor-bg, white); padding: 4px 0 2px 21px; z-index: 10; overflow: hidden; @@ -63,7 +63,7 @@ } .celleditor_cursor_editor { - background-color: white; + background-color: var(--grist-theme-cell-editor-bg, white); /* the following are copied from .field_clip */ padding: 3px 3px 0px 3px; @@ -82,7 +82,8 @@ border: none; resize: none; z-index: 10; - color: black; + background-color: var(--grist-theme-cell-editor-bg, unset); + color: var(--grist-theme-cell-editor-fg, black); /* Inherit styles, same as for .celleditor_content_measure, to ensure that sizes correspond. */ font-family: inherit; diff --git a/app/common/Prefs.ts b/app/common/Prefs.ts index c1d6ee8b..be53e29c 100644 --- a/app/common/Prefs.ts +++ b/app/common/Prefs.ts @@ -1,4 +1,5 @@ import {StringUnion} from 'app/common/StringUnion'; +import {ThemePrefs} from 'app/common/ThemePrefs'; export const SortPref = StringUnion("name", "date"); export type SortPref = typeof SortPref.type; @@ -12,7 +13,7 @@ export interface Prefs { placeholder?: string; } -// A collection of preferences related to a user or org (or combination). +// A collection of preferences related to a user. export interface UserPrefs extends Prefs { // Whether to ask the user to fill out a form about their use-case, on opening the DocMenu page. // Set to true on first login, then reset when the form is closed, so that it only shows once. @@ -20,8 +21,11 @@ export interface UserPrefs extends Prefs { // Whether to record a new sign-up event via Google Tag Manager. Set to true on first login, then // reset on first page load (after the event is sent), so that it's only recorded once. recordSignUpEvent?: boolean; + // Theme-related preferences. + theme?: ThemePrefs; } +// A collection of preferences related to a combination of user and org. export interface UserOrgPrefs extends Prefs { docMenuSort?: SortPref; docMenuView?: ViewPref; diff --git a/app/common/ThemePrefs-ti.ts b/app/common/ThemePrefs-ti.ts new file mode 100644 index 00000000..d7acfcea --- /dev/null +++ b/app/common/ThemePrefs-ti.ts @@ -0,0 +1,327 @@ +/** + * This module was automatically generated by `ts-interface-builder` + */ +import * as t from "ts-interface-checker"; +// tslint:disable:object-literal-key-quotes + +export const ThemePrefs = t.iface([], { + "appearance": "ThemeAppearance", + "syncWithOS": "boolean", + "colors": t.iface([], { + "light": "ThemeNameOrColors", + "dark": "ThemeNameOrColors", + }), +}); + +export const ThemeAppearance = t.union(t.lit('light'), t.lit('dark')); + +export const ThemeNameOrColors = t.union("ThemeName", "ThemeColors"); + +export const ThemeName = t.union(t.lit('GristLight'), t.lit('GristDark')); + +export const Theme = t.iface([], { + "appearance": "ThemeAppearance", + "colors": "ThemeColors", +}); + +export const ThemeColors = t.iface([], { + "text": "string", + "text-light": "string", + "text-dark": "string", + "text-error": "string", + "text-danger": "string", + "text-disabled": "string", + "page-bg": "string", + "page-backdrop": "string", + "page-panels-main-panel-bg": "string", + "page-panels-left-panel-bg": "string", + "page-panels-right-panel-bg": "string", + "page-panels-top-header-bg": "string", + "page-panels-bottom-footer-bg": "string", + "page-panels-border": "string", + "page-panels-border-resizing": "string", + "page-panels-side-panel-opener-fg": "string", + "page-panels-side-panel-opener-active-fg": "string", + "page-panels-side-panel-opener-active-bg": "string", + "add-new-circle-fg": "string", + "add-new-circle-bg": "string", + "add-new-circle-hover-bg": "string", + "add-new-circle-small-fg": "string", + "add-new-circle-small-bg": "string", + "add-new-circle-small-hover-bg": "string", + "top-bar-button-primary-fg": "string", + "top-bar-button-secondary-fg": "string", + "top-bar-button-disabled-fg": "string", + "top-bar-button-error-fg": "string", + "notifications-panel-header-bg": "string", + "notifications-panel-body-bg": "string", + "notifications-panel-border": "string", + "toast-text": "string", + "toast-text-light": "string", + "toast-bg": "string", + "toast-error-icon": "string", + "toast-error-bg": "string", + "toast-success-icon": "string", + "toast-success-bg": "string", + "toast-warning-icon": "string", + "toast-warning-bg": "string", + "toast-info-icon": "string", + "toast-info-bg": "string", + "toast-control-fg": "string", + "toast-control-info-fg": "string", + "tooltip-fg": "string", + "tooltip-bg": "string", + "tooltip-icon": "string", + "tooltip-close-button-fg": "string", + "tooltip-close-button-hover-fg": "string", + "tooltip-close-button-hover-bg": "string", + "modal-bg": "string", + "modal-backdrop": "string", + "modal-border": "string", + "modal-border-dark": "string", + "modal-border-hover": "string", + "modal-shadow-inner": "string", + "modal-shadow-outer": "string", + "modal-close-button-fg": "string", + "modal-backdrop-close-button-fg": "string", + "modal-backdrop-close-button-hover-fg": "string", + "popup-bg": "string", + "popup-shadow-inner": "string", + "popup-shadow-outer": "string", + "popup-close-button-fg": "string", + "progress-bar-fg": "string", + "progress-bar-error-fg": "string", + "progress-bar-bg": "string", + "link": "string", + "link-hover": "string", + "hover": "string", + "hover-light": "string", + "cell-editor-fg": "string", + "cell-editor-bg": "string", + "cursor": "string", + "cursor-inactive": "string", + "cursor-readonly": "string", + "table-header-fg": "string", + "table-header-selected-fg": "string", + "table-header-bg": "string", + "table-header-selected-bg": "string", + "table-header-border": "string", + "table-header-border-dark": "string", + "table-body-bg": "string", + "table-body-border": "string", + "table-add-new-bg": "string", + "table-scroll-shadow": "string", + "table-frozen-columns-border": "string", + "table-drag-drop-indicator": "string", + "table-drag-drop-shadow": "string", + "card-compact-widget-bg": "string", + "card-compact-record-bg": "string", + "card-blocks-bg": "string", + "card-form-label": "string", + "card-compact-label": "string", + "card-blocks-label": "string", + "card-form-border": "string", + "card-compact-border": "string", + "card-editing-layout-bg": "string", + "card-editing-layout-border": "string", + "card-list-form-border": "string", + "card-list-blocks-border": "string", + "selection": "string", + "selection-opaque-fg": "string", + "selection-opaque-bg": "string", + "selection-opaque-dark-bg": "string", + "widget-border": "string", + "widget-active-border": "string", + "widget-inactive-stripes-light": "string", + "widget-inactive-stripes-dark": "string", + "pinned-doc-footer-bg": "string", + "pinned-doc-border": "string", + "pinned-doc-border-hover": "string", + "pinned-doc-editor-bg": "string", + "raw-data-table-border": "string", + "raw-data-table-border-hover": "string", + "control-fg": "string", + "control-primary-fg": "string", + "control-primary-bg": "string", + "control-secondary-fg": "string", + "control-hover-fg": "string", + "control-primary-hover-bg": "string", + "control-secondary-hover-fg": "string", + "control-secondary-hover-bg": "string", + "control-disabled-fg": "string", + "control-disabled-bg": "string", + "control-primary-disabled": "string", + "control-border": "string", + "checkbox-bg": "string", + "checkbox-disabled-bg": "string", + "checkbox-border": "string", + "checkbox-border-hover": "string", + "move-docs-selected-fg": "string", + "move-docs-selected-bg": "string", + "move-docs-disabled-bg": "string", + "filter-bar-button-saved-fg": "string", + "filter-bar-button-saved-bg": "string", + "filter-bar-button-saved-hover-bg": "string", + "icon-button-fg": "string", + "icon-button-primary-bg": "string", + "icon-button-primary-hover-bg": "string", + "icon-button-secondary-bg": "string", + "icon-button-secondary-hover-bg": "string", + "left-panel-page-hover-bg": "string", + "left-panel-active-page-fg": "string", + "left-panel-active-page-bg": "string", + "left-panel-disabled-page-fg": "string", + "left-panel-page-options-fg": "string", + "left-panel-page-options-hover-fg": "string", + "left-panel-page-options-hover-bg": "string", + "left-panel-page-options-selected-hover-bg": "string", + "left-panel-page-initials-fg": "string", + "left-panel-page-initials-bg": "string", + "right-panel-tab-fg": "string", + "right-panel-tab-bg": "string", + "right-panel-tab-icon": "string", + "right-panel-tab-icon-hover": "string", + "right-panel-tab-hover-bg": "string", + "right-panel-tab-selected-fg": "string", + "right-panel-tab-selected-bg": "string", + "right-panel-tab-close-button-hover-bg": "string", + "right-panel-subtab-fg": "string", + "right-panel-subtab-selected-fg": "string", + "right-panel-subtab-selected-underline": "string", + "right-panel-subtab-hover-fg": "string", + "right-panel-subtab-hover-underline": "string", + "right-panel-disabled-overlay": "string", + "right-panel-toggle-button-enabled-fg": "string", + "right-panel-toggle-button-enabled-bg": "string", + "right-panel-toggle-button-enabled-hover-fg": "string", + "right-panel-toggle-button-disabled-fg": "string", + "right-panel-toggle-button-disabled-bg": "string", + "right-panel-field-settings-bg": "string", + "right-panel-field-settings-button-bg": "string", + "document-history-snapshot-fg": "string", + "document-history-snapshot-selected-fg": "string", + "document-history-snapshot-bg": "string", + "document-history-snapshot-selected-bg": "string", + "document-history-snapshot-border": "string", + "document-history-activity-text": "string", + "document-history-activity-text-light": "string", + "accent-icon": "string", + "accent-border": "string", + "accent-text": "string", + "input-fg": "string", + "input-bg": "string", + "input-disabled-fg": "string", + "input-disabled-bg": "string", + "input-placeholder-fg": "string", + "input-border": "string", + "input-valid": "string", + "input-invalid": "string", + "input-focus": "string", + "input-readonly-bg": "string", + "input-readonly-border": "string", + "choice-entry-bg": "string", + "choice-entry-border": "string", + "choice-entry-border-hover": "string", + "select-button-fg": "string", + "select-button-placeholder-fg": "string", + "select-button-disabled-fg": "string", + "select-button-bg": "string", + "select-button-border": "string", + "select-button-border-invalid": "string", + "menu-text": "string", + "menu-light-text": "string", + "menu-bg": "string", + "menu-subheader-fg": "string", + "menu-border": "string", + "menu-shadow": "string", + "menu-item-fg": "string", + "menu-item-selected-fg": "string", + "menu-item-selected-bg": "string", + "menu-item-disabled-fg": "string", + "menu-item-icon-fg": "string", + "menu-item-icon-selected-fg": "string", + "menu-item-link-fg": "string", + "menu-item-link-selected-fg": "string", + "menu-item-link-selected-bg": "string", + "autocomplete-match-text": "string", + "autocomplete-selected-match-text": "string", + "autocomplete-item-selected-bg": "string", + "search-border": "string", + "search-prev-next-button-fg": "string", + "search-prev-next-button-bg": "string", + "loader-fg": "string", + "loader-bg": "string", + "site-switcher-active-fg": "string", + "site-switcher-active-bg": "string", + "doc-menu-doc-options-fg": "string", + "doc-menu-doc-options-hover-fg": "string", + "doc-menu-doc-options-hover-bg": "string", + "shortcut-key-fg": "string", + "shortcut-key-primary-fg": "string", + "shortcut-key-secondary-fg": "string", + "shortcut-key-bg": "string", + "shortcut-key-border": "string", + "breadcrumbs-tag-fg": "string", + "breadcrumbs-tag-bg": "string", + "breadcrumbs-tag-alert-bg": "string", + "widget-picker-primary-bg": "string", + "widget-picker-secondary-bg": "string", + "widget-picker-item-fg": "string", + "widget-picker-item-selected-bg": "string", + "widget-picker-item-disabled-bg": "string", + "widget-picker-icon": "string", + "widget-picker-primary-icon": "string", + "widget-picker-summary-icon": "string", + "widget-picker-border": "string", + "widget-picker-shadow": "string", + "code-view-text": "string", + "code-view-keyword": "string", + "code-view-comment": "string", + "code-view-meta": "string", + "code-view-title": "string", + "code-view-params": "string", + "code-view-string": "string", + "code-view-number": "string", + "importer-table-info-border": "string", + "importer-preview-border": "string", + "importer-skipped-table-overlay": "string", + "importer-match-icon": "string", + "menu-toggle-fg": "string", + "menu-toggle-hover-fg": "string", + "menu-toggle-active-fg": "string", + "menu-toggle-bg": "string", + "menu-toggle-border": "string", + "button-group-fg": "string", + "button-group-light-fg": "string", + "button-group-bg": "string", + "button-group-icon": "string", + "button-group-border": "string", + "button-group-border-hover": "string", + "button-group-selected-fg": "string", + "button-group-light-selected-fg": "string", + "button-group-selected-bg": "string", + "button-group-selected-border": "string", + "access-rules-table-header-fg": "string", + "access-rules-table-header-bg": "string", + "access-rules-table-body-fg": "string", + "access-rules-table-border": "string", + "cell-fg": "string", + "cell-bg": "string", + "cell-zebra-bg": "string", + "formula-editor-bg": "string", + "chart-fg": "string", + "chart-bg": "string", + "chart-legend-bg": "string", + "chart-x-axis": "string", + "chart-y-axis": "string", +}); + +const exportedTypeSuite: t.ITypeSuite = { + ThemePrefs, + ThemeAppearance, + ThemeNameOrColors, + ThemeName, + Theme, + ThemeColors, +}; +export default exportedTypeSuite; diff --git a/app/common/ThemePrefs.ts b/app/common/ThemePrefs.ts new file mode 100644 index 00000000..d984a42f --- /dev/null +++ b/app/common/ThemePrefs.ts @@ -0,0 +1,430 @@ +import ThemePrefsTI from 'app/common/ThemePrefs-ti'; +import {CheckerT, createCheckers} from 'ts-interface-checker'; + +export interface ThemePrefs { + appearance: ThemeAppearance; + syncWithOS: boolean; + colors: { + light: ThemeNameOrColors; + dark: ThemeNameOrColors; + } +} + +export type ThemeAppearance = 'light' | 'dark'; + +export type ThemeNameOrColors = ThemeName | ThemeColors; + +export type ThemeName = 'GristLight' | 'GristDark'; + +export interface Theme { + appearance: ThemeAppearance; + colors: ThemeColors; +} + +export interface ThemeColors { + /* Text */ + 'text': string; + 'text-light': string; + 'text-dark': string; + 'text-error': string; + 'text-danger': string; + 'text-disabled': string; + + /* Page */ + 'page-bg': string; + 'page-backdrop': string; + + /* Page Panels */ + 'page-panels-main-panel-bg': string; + 'page-panels-left-panel-bg': string; + 'page-panels-right-panel-bg': string; + 'page-panels-top-header-bg': string; + 'page-panels-bottom-footer-bg': string; + 'page-panels-border': string; + 'page-panels-border-resizing': string; + 'page-panels-side-panel-opener-fg': string; + 'page-panels-side-panel-opener-active-fg': string; + 'page-panels-side-panel-opener-active-bg': string; + + /* Add New */ + 'add-new-circle-fg': string; + 'add-new-circle-bg': string; + 'add-new-circle-hover-bg': string; + 'add-new-circle-small-fg': string; + 'add-new-circle-small-bg': string; + 'add-new-circle-small-hover-bg': string; + + /* Top Bar */ + 'top-bar-button-primary-fg': string; + 'top-bar-button-secondary-fg': string; + 'top-bar-button-disabled-fg': string; + 'top-bar-button-error-fg': string; + + /* Notifications */ + 'notifications-panel-header-bg': string; + 'notifications-panel-body-bg': string; + 'notifications-panel-border': string; + + /* Toasts */ + 'toast-text': string; + 'toast-text-light': string; + 'toast-bg': string; + 'toast-error-icon': string; + 'toast-error-bg': string; + 'toast-success-icon': string; + 'toast-success-bg': string; + 'toast-warning-icon': string; + 'toast-warning-bg': string; + 'toast-info-icon': string; + 'toast-info-bg': string; + 'toast-control-fg': string; + 'toast-control-info-fg': string; + + /* Tooltips */ + 'tooltip-fg': string; + 'tooltip-bg': string; + 'tooltip-icon': string; + 'tooltip-close-button-fg': string; + 'tooltip-close-button-hover-fg': string; + 'tooltip-close-button-hover-bg': string; + + /* Modals */ + 'modal-bg': string; + 'modal-backdrop': string; + 'modal-border': string; + 'modal-border-dark': string; + 'modal-border-hover': string; + 'modal-shadow-inner': string; + 'modal-shadow-outer': string; + 'modal-close-button-fg': string; + 'modal-backdrop-close-button-fg': string; + 'modal-backdrop-close-button-hover-fg': string; + + /* Popups */ + 'popup-bg': string; + 'popup-shadow-inner': string; + 'popup-shadow-outer': string; + 'popup-close-button-fg': string; + + /* Progress Bars */ + 'progress-bar-fg': string; + 'progress-bar-error-fg': string; + 'progress-bar-bg': string; + + /* Links */ + 'link': string; + 'link-hover': string; + + /* Hover */ + 'hover': string; + 'hover-light': string; + + /* Cell Editor */ + 'cell-editor-fg': string; + 'cell-editor-bg': string; + + /* Cursor */ + 'cursor': string; + 'cursor-inactive': string; + 'cursor-readonly': string; + + /* Tables */ + 'table-header-fg': string; + 'table-header-selected-fg': string; + 'table-header-bg': string; + 'table-header-selected-bg': string; + 'table-header-border': string; + 'table-header-border-dark': string; + 'table-body-bg': string; + 'table-body-border': string; + 'table-add-new-bg': string; + 'table-scroll-shadow': string; + 'table-frozen-columns-border': string; + 'table-drag-drop-indicator': string; + 'table-drag-drop-shadow': string; + + /* Cards */ + 'card-compact-widget-bg': string; + 'card-compact-record-bg': string; + 'card-blocks-bg': string; + 'card-form-label': string; + 'card-compact-label': string; + 'card-blocks-label': string; + 'card-form-border': string; + 'card-compact-border': string; + 'card-editing-layout-bg': string; + 'card-editing-layout-border': string; + + /* Card Lists */ + 'card-list-form-border': string; + 'card-list-blocks-border': string; + + /* Selection */ + 'selection': string; + 'selection-opaque-fg': string; + 'selection-opaque-bg': string; + 'selection-opaque-dark-bg': string; + + /* Widgets */ + 'widget-border': string; + 'widget-active-border': string; + 'widget-inactive-stripes-light': string; + 'widget-inactive-stripes-dark': string; + + /* Pinned Docs */ + 'pinned-doc-footer-bg': string; + 'pinned-doc-border': string; + 'pinned-doc-border-hover': string; + 'pinned-doc-editor-bg': string; + + /* Raw Data */ + 'raw-data-table-border': string; + 'raw-data-table-border-hover': string; + + /* Controls */ + 'control-fg': string; + 'control-primary-fg': string; + 'control-primary-bg': string; + 'control-secondary-fg': string; + 'control-hover-fg': string; + 'control-primary-hover-bg': string; + 'control-secondary-hover-fg': string; + 'control-secondary-hover-bg': string; + 'control-disabled-fg': string; + 'control-disabled-bg': string; + 'control-primary-disabled': string; + 'control-border': string; + + /* Checkboxes */ + 'checkbox-bg': string; + 'checkbox-disabled-bg': string; + 'checkbox-border': string; + 'checkbox-border-hover': string; + + /* Move Docs */ + 'move-docs-selected-fg': string; + 'move-docs-selected-bg': string; + 'move-docs-disabled-bg': string; + + /* Filter Bar */ + 'filter-bar-button-saved-fg': string; + 'filter-bar-button-saved-bg': string; + 'filter-bar-button-saved-hover-bg': string; + + /* Icon Buttons */ + 'icon-button-fg': string; + 'icon-button-primary-bg': string; + 'icon-button-primary-hover-bg': string; + 'icon-button-secondary-bg': string; + 'icon-button-secondary-hover-bg': string; + + /* Left Panel */ + 'left-panel-page-hover-bg': string; + 'left-panel-active-page-fg': string; + 'left-panel-active-page-bg': string; + 'left-panel-disabled-page-fg': string; + 'left-panel-page-options-fg': string; + 'left-panel-page-options-hover-fg': string; + 'left-panel-page-options-hover-bg': string; + 'left-panel-page-options-selected-hover-bg': string; + 'left-panel-page-initials-fg': string; + 'left-panel-page-initials-bg': string; + + /* Right Panel */ + 'right-panel-tab-fg': string; + 'right-panel-tab-bg': string; + 'right-panel-tab-icon': string; + 'right-panel-tab-icon-hover': string; + 'right-panel-tab-hover-bg': string; + 'right-panel-tab-selected-fg': string; + 'right-panel-tab-selected-bg': string; + 'right-panel-tab-close-button-hover-bg': string; + 'right-panel-subtab-fg': string; + 'right-panel-subtab-selected-fg': string; + 'right-panel-subtab-selected-underline': string; + 'right-panel-subtab-hover-fg': string; + 'right-panel-subtab-hover-underline': string; + 'right-panel-disabled-overlay': string; + 'right-panel-toggle-button-enabled-fg': string; + 'right-panel-toggle-button-enabled-bg': string; + 'right-panel-toggle-button-enabled-hover-fg': string; + 'right-panel-toggle-button-disabled-fg': string; + 'right-panel-toggle-button-disabled-bg': string; + 'right-panel-field-settings-bg': string; + 'right-panel-field-settings-button-bg': string; + + /* Document History */ + 'document-history-snapshot-fg': string; + 'document-history-snapshot-selected-fg': string; + 'document-history-snapshot-bg': string; + 'document-history-snapshot-selected-bg': string; + 'document-history-snapshot-border': string; + 'document-history-activity-text': string; + 'document-history-activity-text-light': string; + + /* Accents */ + 'accent-icon': string; + 'accent-border': string; + 'accent-text': string; + + /* Inputs */ + 'input-fg': string; + 'input-bg': string; + 'input-disabled-fg': string; + 'input-disabled-bg': string; + 'input-placeholder-fg': string; + 'input-border': string; + 'input-valid': string; + 'input-invalid': string; + 'input-focus': string; + 'input-readonly-bg': string; + 'input-readonly-border': string; + + /* Choice Entry */ + 'choice-entry-bg': string; + 'choice-entry-border': string; + 'choice-entry-border-hover': string; + + /* Select Buttons */ + 'select-button-fg': string; + 'select-button-placeholder-fg': string; + 'select-button-disabled-fg': string; + 'select-button-bg': string; + 'select-button-border': string; + 'select-button-border-invalid': string; + + /* Menus */ + 'menu-text': string; + 'menu-light-text': string; + 'menu-bg': string; + 'menu-subheader-fg': string; + 'menu-border': string; + 'menu-shadow': string; + + /* Menu Items */ + 'menu-item-fg': string; + 'menu-item-selected-fg': string; + 'menu-item-selected-bg': string; + 'menu-item-disabled-fg': string; + 'menu-item-icon-fg': string; + 'menu-item-icon-selected-fg': string; + 'menu-item-link-fg': string; + 'menu-item-link-selected-fg': string; + 'menu-item-link-selected-bg': string; + + /* Autocomplete */ + 'autocomplete-match-text': string; + 'autocomplete-selected-match-text': string; + 'autocomplete-item-selected-bg': string; + + /* Search */ + 'search-border': string; + 'search-prev-next-button-fg': string; + 'search-prev-next-button-bg': string; + + /* Loaders */ + 'loader-fg': string; + 'loader-bg': string; + + /* Site Switcher */ + 'site-switcher-active-fg': string; + 'site-switcher-active-bg': string; + + /* Doc Menu */ + 'doc-menu-doc-options-fg': string; + 'doc-menu-doc-options-hover-fg': string; + 'doc-menu-doc-options-hover-bg': string; + + /* Shortcut Keys */ + 'shortcut-key-fg': string; + 'shortcut-key-primary-fg': string; + 'shortcut-key-secondary-fg': string; + 'shortcut-key-bg': string; + 'shortcut-key-border': string; + + /* Breadcrumbs */ + 'breadcrumbs-tag-fg': string; + 'breadcrumbs-tag-bg': string; + 'breadcrumbs-tag-alert-bg': string; + + /* Page Widget Picker */ + 'widget-picker-primary-bg': string; + 'widget-picker-secondary-bg': string; + 'widget-picker-item-fg': string; + 'widget-picker-item-selected-bg': string; + 'widget-picker-item-disabled-bg': string; + 'widget-picker-icon': string; + 'widget-picker-primary-icon': string; + 'widget-picker-summary-icon': string; + 'widget-picker-border': string; + 'widget-picker-shadow': string; + + /* Code View */ + 'code-view-text': string; + 'code-view-keyword': string; + 'code-view-comment': string; + 'code-view-meta': string; + 'code-view-title': string; + 'code-view-params': string; + 'code-view-string': string; + 'code-view-number': string; + + /* Importer */ + 'importer-table-info-border': string; + 'importer-preview-border': string; + 'importer-skipped-table-overlay': string; + 'importer-match-icon': string; + + /* Menu Toggles */ + 'menu-toggle-fg': string; + 'menu-toggle-hover-fg': string; + 'menu-toggle-active-fg': string; + 'menu-toggle-bg': string; + 'menu-toggle-border': string; + + /* Button Groups */ + 'button-group-fg': string; + 'button-group-light-fg': string; + 'button-group-bg': string; + 'button-group-icon': string; + 'button-group-border': string; + 'button-group-border-hover': string; + 'button-group-selected-fg': string; + 'button-group-light-selected-fg': string; + 'button-group-selected-bg': string; + 'button-group-selected-border': string; + + /* Access Rules */ + 'access-rules-table-header-fg': string; + 'access-rules-table-header-bg': string; + 'access-rules-table-body-fg': string; + 'access-rules-table-border': string; + + /* Cells */ + 'cell-fg': string; + 'cell-bg': string; + 'cell-zebra-bg': string; + + /* Formula Editor */ + 'formula-editor-bg': string; + + /* Charts */ + 'chart-fg': string; + 'chart-bg': string; + 'chart-legend-bg': string; + 'chart-x-axis': string; + 'chart-y-axis': string; +} + +export const ThemePrefsChecker = createCheckers(ThemePrefsTI).ThemePrefs as CheckerT; + +export function getDefaultThemePrefs(): ThemePrefs { + return { + appearance: 'light', + syncWithOS: false, + colors: { + light: 'GristLight', + dark: 'GristDark', + } + }; +} diff --git a/app/common/Themes.ts b/app/common/Themes.ts new file mode 100644 index 00000000..1c566607 --- /dev/null +++ b/app/common/Themes.ts @@ -0,0 +1,50 @@ +import {ThemeColors, ThemeName} from 'app/common/ThemePrefs'; +import {GristDark} from 'app/common/themes/GristDark'; +import {GristLight} from 'app/common/themes/GristLight'; + +const THEMES: Readonly> = { + GristLight, + GristDark, +}; + +export function getThemeColors(themeName: ThemeName): ThemeColors { + return THEMES[themeName]; +} + +/** + * A simple JS script that handles setting an appropriate background image + * based on the appearance setting most recently set by the client. + * + * The motivation for this snippet is to avoid the FOUC-like effect that can + * occur when Grist is loading a page, typically manifesting as a momentary flash + * from a light background to a dark background (and vice versa). By predicting what + * the appearance should be based on what was last stored in local storage, we can set + * a suitable background image before the application (and consequently, the user's + * theme preferences) have loaded, which should avoid the flash in most cases. + * + * Simpler alternatives exist, like using the user agent's preferred color scheme, but + * don't account for the fact that Grist allows users to manually set their preferred + * appearance. While this solution isn't perfect, it's a significant improvement over the + * default behavior. + */ +export function getThemeBackgroundSnippet() { + /* Note that we only need to set the property if the appearance is dark; a fallback + * to the light background (gplaypattern.png) already exists in App.css. */ + return ` + +`; +} diff --git a/app/common/gristUrls.ts b/app/common/gristUrls.ts index 469acbbd..56cc81cb 100644 --- a/app/common/gristUrls.ts +++ b/app/common/gristUrls.ts @@ -558,6 +558,9 @@ export interface GristLoadConfig { // String to append to the end of the HTML document.title pageTitleSuffix?: string; + + // If custom CSS should be included in the head of each page. + enableCustomCss?: boolean; } export const HideableUiElements = StringUnion("helpCenter", "billing", "templates", "multiSite", "multiAccounts"); diff --git a/app/common/themes/GristDark.ts b/app/common/themes/GristDark.ts new file mode 100644 index 00000000..d78e90cc --- /dev/null +++ b/app/common/themes/GristDark.ts @@ -0,0 +1,396 @@ +import {ThemeColors} from 'app/common/ThemePrefs'; + +export const GristDark: ThemeColors = { + /* Text */ + 'text': '#EFEFEF', + 'text-light': '#A4A4A4', + 'text-dark': '#FFFFFF', + 'text-error': '#FF6666', + 'text-danger': '#FFA500', + 'text-disabled': '#A4A4A4', + + /* Page */ + 'page-bg': '#262633', + 'page-backdrop': 'black', + + /* Page Panels */ + 'page-panels-main-panel-bg': '#32323F', + 'page-panels-left-panel-bg': '#262633', + 'page-panels-right-panel-bg': '#262633', + 'page-panels-top-header-bg': '#32323F', + 'page-panels-bottom-footer-bg': '#32323F', + 'page-panels-border': '#57575F', + 'page-panels-border-resizing': '#1DA270', + 'page-panels-side-panel-opener-fg': '#A4A4A4', + 'page-panels-side-panel-opener-active-fg': '#FFFFFF', + 'page-panels-side-panel-opener-active-bg': '#1DA270', + + /* Add New */ + 'add-new-circle-fg': '#FFFFFF', + 'add-new-circle-bg': '#157A54', + 'add-new-circle-hover-bg': '#0A5438', + 'add-new-circle-small-fg': '#FFFFFF', + 'add-new-circle-small-bg': '#1DA270', + 'add-new-circle-small-hover-bg': '#157A54', + + /* Top Bar */ + 'top-bar-button-primary-fg': '#1DA270', + 'top-bar-button-secondary-fg': '#A4A4A4', + 'top-bar-button-disabled-fg': '#69697D', + 'top-bar-button-error-fg': 'FF6666', + + /* Notifications */ + 'notifications-panel-header-bg': '#262633', + 'notifications-panel-body-bg': '#32323F', + 'notifications-panel-border': '#69697D', + + /* Toasts */ + 'toast-text': '#FFFFFF', + 'toast-text-light': '#929299', + 'toast-bg': '#040404', + 'toast-error-icon': '#D0021B', + 'toast-error-bg': '#D0021B', + 'toast-success-icon': '#009058', + 'toast-success-bg': '#009058', + 'toast-warning-icon': '#F9AE41', + 'toast-warning-bg': '#DD962C', + 'toast-info-icon': '#3B82F6', + 'toast-info-bg': '#3B82F6', + 'toast-control-fg': '#16B378', + 'toast-control-info-fg': '#87B2F9', + + /* Tooltips */ + 'tooltip-fg': 'white', + 'tooltip-bg': 'rgba(0, 0, 0, 0.75)', + 'tooltip-icon': '#A4A4A4', + 'tooltip-close-button-fg': 'white', + 'tooltip-close-button-hover-fg': 'black', + 'tooltip-close-button-hover-bg': 'white', + + /* Modals */ + 'modal-bg': '#32323F', + 'modal-backdrop': 'rgba(0,0,0,0.6)', + 'modal-border': '#57575F', + 'modal-border-dark': '#69697D', + 'modal-border-hover': '#A4A4A4', + 'modal-shadow-inner': '#000000', + 'modal-shadow-outer': '#000000', + 'modal-close-button-fg': '#A4A4A4', + 'modal-backdrop-close-button-fg': '#1DA270', + 'modal-backdrop-close-button-hover-fg': '#157A54', + + /* Popups */ + 'popup-bg': '#32323F', + 'popup-shadow-inner': '#000000', + 'popup-shadow-outer': '#000000', + 'popup-close-button-fg': '#A4A4A4', + + /* Progress Bars */ + 'progress-bar-fg': '#1DA270', + 'progress-bar-error-fg': '#FF6666', + 'progress-bar-bg': '#69697D', + + /* Links */ + 'link': '#1DA270', + 'link-hover': '#1DA270', + + /* Hover */ + 'hover': 'rgba(111,111,117,0.6)', + 'hover-light': 'rgba(111,111,117,0.4)', + + /* Cell Editor */ + 'cell-editor-fg': '#FFFFFF', + 'cell-editor-bg': '#32323F', + + /* Cursor */ + 'cursor': '#1DA270', + 'cursor-inactive': 'rgba(29,162,112,0.5)', + 'cursor-readonly': '#A4A4A4', + + /* Tables */ + 'table-header-fg': '#EFEFEF', + 'table-header-selected-fg': '#EFEFEF', + 'table-header-bg': '#262633', + 'table-header-selected-bg': '#414358', + 'table-header-border': '#57575F', + 'table-header-border-dark': '#69697D', + 'table-body-bg': '#32323F', + 'table-body-border': '#69697D', + 'table-add-new-bg': '#4A4A5D', + 'table-scroll-shadow': '#000000', + 'table-frozen-columns-border': '#A4A4A4', + 'table-drag-drop-indicator': '#A4A4A4', + 'table-drag-drop-shadow': 'rgba(111,111,117,0.6)', + + /* Cards */ + 'card-compact-widget-bg': '#262633', + 'card-compact-record-bg': '#32323F', + 'card-blocks-bg': '#404150', + 'card-form-label': '#A4A4A4', + 'card-compact-label': '#A4A4A4', + 'card-blocks-label': '#A4A4A4', + 'card-form-border': '#69697D', + 'card-compact-border': '#69697D', + 'card-editing-layout-bg': 'rgba(85, 85, 99, 0.2)', + 'card-editing-layout-border': '#69697D', + + /* Card Lists */ + 'card-list-form-border': '#57575F', + 'card-list-blocks-border': '#57575F', + + /* Selection */ + 'selection': 'rgba(22,179,120,0.15)', + 'selection-opaque-fg': 'white', + 'selection-opaque-bg': '#2F4748', + 'selection-opaque-dark-bg': '#253E3E', + + /* Widgets */ + 'widget-border': '#57575F', + 'widget-active-border': '#1DA270', + 'widget-inactive-stripes-light': '#262633', + 'widget-inactive-stripes-dark': '#32323F', + + /* Pinned Docs */ + 'pinned-doc-footer-bg': '#32323F', + 'pinned-doc-border': '#57575F', + 'pinned-doc-border-hover': '#A4A4A4', + 'pinned-doc-editor-bg': '#57575F', + + /* Raw Data */ + 'raw-data-table-border': '#57575F', + 'raw-data-table-border-hover': '#A4A4A4', + + /* Controls */ + 'control-fg': '#1DA270', + 'control-primary-fg': '#FFFFFF', + 'control-primary-bg': '#1DA270', + 'control-secondary-fg': '#A4A4A4', + 'control-hover-fg': '#157A54', + 'control-primary-hover-bg': '#157A54', + 'control-secondary-hover-fg': '#EFEFEF', + 'control-secondary-hover-bg': '#57575F', + 'control-disabled-fg': '#A4A4A4', + 'control-disabled-bg': '#69697D', + 'control-primary-disabled': '#5F8C7B', + 'control-border': '#11B683', + + /* Checkboxes */ + 'checkbox-bg': '#32323F', + 'checkbox-disabled-bg': '#69697D', + 'checkbox-border': '#69697D', + 'checkbox-border-hover': '#57575F', + + /* Move Docs */ + 'move-docs-selected-fg': '#FFFFFF', + 'move-docs-selected-bg': '#1DA270', + 'move-docs-disabled-bg': '#69697D', + + /* Filter Bar */ + 'filter-bar-button-saved-fg': '#FFFFFF', + 'filter-bar-button-saved-bg': '#555563', + 'filter-bar-button-saved-hover-bg': '#69697D', + + /* Icon Buttons */ + 'icon-button-fg': '#FFFFFF', + 'icon-button-primary-bg': '#1DA270', + 'icon-button-primary-hover-bg': '#157A54', + 'icon-button-secondary-bg': '#69697D', + 'icon-button-secondary-hover-bg': '#A4A4A4', + + /* Left Panel */ + 'left-panel-page-hover-bg': 'rgba(111,111,117,0.25)', + 'left-panel-active-page-fg': '#EFEFEF', + 'left-panel-active-page-bg': '#555563', + 'left-panel-disabled-page-fg': '#69697D', + 'left-panel-page-options-fg': '#A4A4A4', + 'left-panel-page-options-hover-fg': '#FFFFFF', + 'left-panel-page-options-hover-bg': '#69697D', + 'left-panel-page-options-selected-hover-bg': '#A4A4A4', + 'left-panel-page-initials-fg': 'white', + 'left-panel-page-initials-bg': '#929299', + + /* Right Panel */ + 'right-panel-tab-fg': '#EFEFEF', + 'right-panel-tab-bg': '#262633', + 'right-panel-tab-icon': '#A4A4A4', + 'right-panel-tab-icon-hover': '#1DA270', + 'right-panel-tab-hover-bg': 'rgba(111,111,117,0.6)', + 'right-panel-tab-selected-fg': '#FFFFFF', + 'right-panel-tab-selected-bg': '#1DA270', + 'right-panel-tab-close-button-hover-bg': '#157A54', + 'right-panel-subtab-fg': '#1DA270', + 'right-panel-subtab-selected-fg': '#EFEFEF', + 'right-panel-subtab-selected-underline': '#1DA270', + 'right-panel-subtab-hover-fg': '#157A54', + 'right-panel-subtab-hover-underline': '#1DA270', + 'right-panel-disabled-overlay': '#262633', + 'right-panel-toggle-button-enabled-fg': '#FFFFFF', + 'right-panel-toggle-button-enabled-bg': '#555563', + 'right-panel-toggle-button-enabled-hover-fg': '#D9D9D9', + 'right-panel-toggle-button-disabled-fg': '#FFFFFF', + 'right-panel-toggle-button-disabled-bg': '#E8E8E8', + 'right-panel-field-settings-bg': '#414358', + 'right-panel-field-settings-button-bg': '#57575F', + + /* Document History */ + 'document-history-snapshot-fg': '#EFEFEF', + 'document-history-snapshot-selected-fg': '#EFEFEF', + 'document-history-snapshot-bg': '#32323F', + 'document-history-snapshot-selected-bg': '#555563', + 'document-history-snapshot-border': '#69697D', + 'document-history-activity-text': '#EFEFEF', + 'document-history-activity-text-light': '#A4A4A4', + + /* Accents */ + 'accent-icon': '#1DA270', + 'accent-border': '#1DA270', + 'accent-text': '#1DA270', + + /* Inputs */ + 'input-fg': '#EFEFEF', + 'input-bg': '#32323F', + 'input-disabled-fg': '#A4A4A4', + 'input-disabled-bg': '#262633', + 'input-placeholder-fg': '#A4A4A4', + 'input-border': '#69697D', + 'input-valid': '#1DA270', + 'input-invalid': '#FF6666', + 'input-focus': '#5E9ED6', + 'input-readonly-bg': '#262633', + 'input-readonly-border': '#69697D', + + /* Choice Entry */ + 'choice-entry-bg': '#32323F', + 'choice-entry-border': '#69697D', + 'choice-entry-border-hover': '#A4A4A4', + + /* Select Buttons */ + 'select-button-fg': '#EFEFEF', + 'select-button-placeholder-fg': '#A4A4A4', + 'select-button-disabled-fg': '#A4A4A4', + 'select-button-bg': '#32323F', + 'select-button-border': '#69697D', + 'select-button-border-invalid': '#FF6666', + + /* Menus */ + 'menu-text': '#A4A4A4', + 'menu-light-text': '#A4A4A4', + 'menu-bg': '#32323F', + 'menu-subheader-fg': '#EFEFEF', + 'menu-border': '#69697D', + 'menu-shadow': '#000000', + + /* Menu Items */ + 'menu-item-fg': '#FFFFFF', + 'menu-item-selected-fg': '#FFFFFF', + 'menu-item-selected-bg': '#1DA270', + 'menu-item-disabled-fg': '#69697D', + 'menu-item-icon-fg': '#A4A4A4', + 'menu-item-icon-selected-fg': '#FFFFFF', + 'menu-item-link-fg': '#1DA270', + 'menu-item-link-selected-fg': '#157A54', + 'menu-item-link-selected-bg': '#484859', + + /* Autocomplete */ + 'autocomplete-match-text': '#1DA270', + 'autocomplete-selected-match-text': '#0A5438', + 'autocomplete-item-selected-bg': '#69697D', + + /* Search */ + 'search-border': '#69697D', + 'search-prev-next-button-fg': '#A4A4A4', + 'search-prev-next-button-bg': '#24242F', + + /* Loading Spinners */ + 'loader-fg': '#1DA270', + 'loader-bg': '#69697D', + + /* Site Switcher */ + 'site-switcher-active-fg': '#FFFFFF', + 'site-switcher-active-bg': '#000000', + + /* Doc Menu */ + 'doc-menu-doc-options-fg': '#69697D', + 'doc-menu-doc-options-hover-fg': '#A4A4A4', + 'doc-menu-doc-options-hover-bg': '#69697D', + + /* Shortcut Keys */ + 'shortcut-key-fg': '#FFFFFF', + 'shortcut-key-primary-fg': '#17B378', + 'shortcut-key-secondary-fg': '#A4A4A4', + 'shortcut-key-bg': '#32323F', + 'shortcut-key-border': '#A4A4A4', + + /* Breadcrumbs */ + 'breadcrumbs-tag-fg': 'white', + 'breadcrumbs-tag-bg': '#929299', + 'breadcrumbs-tag-alert-bg': '#D0021B', + + /* Page Widget Picker */ + 'widget-picker-primary-bg': '#32323F', + 'widget-picker-secondary-bg': '#262633', + 'widget-picker-item-fg': '#FFFFFF', + 'widget-picker-item-selected-bg': 'rgba(111,111,117,0.6)', + 'widget-picker-item-disabled-bg': 'rgba(111,111,117,0.6)', + 'widget-picker-icon': '#A4A4A4', + 'widget-picker-primary-icon': '#1DA270', + 'widget-picker-summary-icon': '#1DA270', + 'widget-picker-border': 'rgba(111,111,117,0.6)', + 'widget-picker-shadow': '#000000', + + /* Code View */ + 'code-view-text': '#D2D2D2', + 'code-view-keyword': '#D2D2D2', + 'code-view-comment': '#888888', + 'code-view-meta': '#7CD4FF', + 'code-view-title': '#ED7373', + 'code-view-params': '#D2D2D2', + 'code-view-string': '#ED7373', + 'code-view-number': '#ED7373', + + /* Importer */ + 'importer-table-info-border': '#69697D', + 'importer-preview-border': '#69697D', + 'importer-skipped-table-overlay': 'rgba(111,111,117,0.6)', + 'importer-match-icon': '#69697D', + + /* Menu Toggles */ + 'menu-toggle-fg': '#A4A4A4', + 'menu-toggle-hover-fg': '#1DA270', + 'menu-toggle-active-fg': '#157A54', + 'menu-toggle-bg': '#32323F', + 'menu-toggle-border': '#A4A4A4', + + /* Button Groups */ + 'button-group-fg': '#EFEFEF', + 'button-group-light-fg': '#A4A4A4', + 'button-group-bg': 'unset', + 'button-group-icon': '#A4A4A4', + 'button-group-border': '#69697D', + 'button-group-border-hover': '#555563', + 'button-group-selected-fg': '#EFEFEF', + 'button-group-light-selected-fg': '#1DA270', + 'button-group-selected-bg': '#555563', + 'button-group-selected-border': '#555563', + + /* Access Rules */ + 'access-rules-table-header-fg': '#EFEFEF', + 'access-rules-table-header-bg': '#57575F', + 'access-rules-table-body-fg': '#A4A4A4', + 'access-rules-table-border': '#A4A4A4', + + /* Cells */ + 'cell-fg': '#FFFFFF', + 'cell-bg': '#32323F', + 'cell-zebra-bg': '#262633', + + /* Formula Editor */ + 'formula-editor-bg': '#282A36', + + /* Charts */ + 'chart-fg': '#A4A4A4', + 'chart-bg': '#32323F', + 'chart-legend-bg': '#32323F80', + 'chart-x-axis': '#A4A4A4', + 'chart-y-axis': '#A4A4A4', +}; diff --git a/app/common/themes/GristLight.ts b/app/common/themes/GristLight.ts new file mode 100644 index 00000000..8477bf01 --- /dev/null +++ b/app/common/themes/GristLight.ts @@ -0,0 +1,396 @@ +import {ThemeColors} from 'app/common/ThemePrefs'; + +export const GristLight: ThemeColors = { + /* Text */ + 'text': '#262633', + 'text-light': '#929299', + 'text-dark': 'black', + 'text-error': '#D0021B', + 'text-danger': '#FFA500', + 'text-disabled': '#929299', + + /* Page */ + 'page-bg': '#F7F7F7', + 'page-backdrop': 'grey', + + /* Page Panels */ + 'page-panels-main-panel-bg': 'white', + 'page-panels-left-panel-bg': '#F7F7F7', + 'page-panels-right-panel-bg': '#F7F7F7', + 'page-panels-top-header-bg': 'white', + 'page-panels-bottom-footer-bg': 'white', + 'page-panels-border': 'rgba(217,217,217,0.6)', + 'page-panels-border-resizing': '#16B378', + 'page-panels-side-panel-opener-fg': '#929299', + 'page-panels-side-panel-opener-active-fg': 'white', + 'page-panels-side-panel-opener-active-bg': '#16B378', + + /* Add New */ + 'add-new-circle-fg': '#FFFFFF', + 'add-new-circle-bg': '#009058', + 'add-new-circle-hover-bg': '#007548', + 'add-new-circle-small-fg': '#FFFFFF', + 'add-new-circle-small-bg': '#16B378', + 'add-new-circle-small-hover-bg': '#009058', + + /* Top Bar */ + 'top-bar-button-primary-fg': '#16B378', + 'top-bar-button-secondary-fg': '#929299', + 'top-bar-button-disabled-fg': '#D9D9D9', + 'top-bar-button-error-fg': '#D0021B', + + /* Notifications */ + 'notifications-panel-header-bg': '#F7F7F7', + 'notifications-panel-body-bg': 'white', + 'notifications-panel-border': '#D9D9D9', + + /* Toasts */ + 'toast-text': '#FFFFFF', + 'toast-text-light': '#929299', + 'toast-bg': '#040404', + 'toast-error-icon': '#D0021B', + 'toast-error-bg': '#D0021B', + 'toast-success-icon': '#009058', + 'toast-success-bg': '#009058', + 'toast-warning-icon': '#F9AE41', + 'toast-warning-bg': '#DD962C', + 'toast-info-icon': '#3B82F6', + 'toast-info-bg': '#3B82F6', + 'toast-control-fg': '#16B378', + 'toast-control-info-fg': '#87B2F9', + + /* Tooltips */ + 'tooltip-fg': 'white', + 'tooltip-bg': 'rgba(0, 0, 0, 0.75)', + 'tooltip-icon': '#929299', + 'tooltip-close-button-fg': 'white', + 'tooltip-close-button-hover-fg': 'black', + 'tooltip-close-button-hover-bg': 'white', + + /* Modals */ + 'modal-bg': 'white', + 'modal-backdrop': 'rgba(38,38,51,0.9)', + 'modal-border': '#E8E8E8', + 'modal-border-dark': '#D9D9D9', + 'modal-border-hover': '#929299', + 'modal-shadow-inner': 'rgba(31,37,50,0.31)', + 'modal-shadow-outer': 'rgba(76,86,103,0.24)', + 'modal-close-button-fg': '#929299', + 'modal-backdrop-close-button-fg': '#16B378', + 'modal-backdrop-close-button-hover-fg': '#B1FFE2', + + /* Popups */ + 'popup-bg': 'white', + 'popup-shadow-inner': 'rgba(31, 37, 50, 0.31)', + 'popup-shadow-outer': 'rgba(76, 86, 103, 0.24)', + 'popup-close-button-fg': '#929299', + + /* Progress Bars */ + 'progress-bar-fg': '#16B378', + 'progress-bar-error-fg': '#D0021B', + 'progress-bar-bg': '#D9D9D9', + + /* Links */ + 'link': '#16B378', + 'link-hover': '#16B378', + + /* Hover */ + 'hover': 'rgba(217,217,217,0.6)', + 'hover-light': '#F7F7F7', + + /* Cell Editor */ + 'cell-editor-fg': '#262633', + 'cell-editor-bg': '#FFFFFF', + + /* Cursor */ + 'cursor': '#16B378', + 'cursor-inactive': '#A2E1C9', + 'cursor-readonly': '#929299', + + /* Tables */ + 'table-header-fg': 'unset', + 'table-header-selected-fg': 'unset', + 'table-header-bg': '#F7F7F7', + 'table-header-selected-bg': '#E8E8E8', + 'table-header-border': 'lightgray', + 'table-header-border-dark': '#D9D9D9', + 'table-body-bg': 'unset', + 'table-body-border': '#D9D9D9', + 'table-add-new-bg': 'inherit', + 'table-scroll-shadow': '#444444', + 'table-frozen-columns-border': '#999999', + 'table-drag-drop-indicator': 'gray', + 'table-drag-drop-shadow': '#F0F0F0', + + /* Cards */ + 'card-compact-widget-bg': 'rgba(217,217,217,0.6)', + 'card-compact-record-bg': 'white', + 'card-blocks-bg': 'rgba(217,217,217,0.6)', + 'card-form-label': '#929299', + 'card-compact-label': '#929299', + 'card-blocks-label': '#929299', + 'card-form-border': 'lightgrey', + 'card-compact-border': '#D9D9D9', + 'card-editing-layout-bg': 'rgba(192, 192, 192, 0.2)', + 'card-editing-layout-border': '#D9D9D9', + + /* Card Lists */ + 'card-list-form-border': '#D9D9D9', + 'card-list-blocks-border': '#D9D9D9', + + /* Selection */ + 'selection': 'rgba(22,179,120,0.15)', + 'selection-opaque-fg': 'black', + 'selection-opaque-bg': '#DCF4EB', + 'selection-opaque-dark-bg': '#D6EEE5', + + /* Widgets */ + 'widget-border': '#D9D9D9', + 'widget-active-border': '#16B378', + 'widget-inactive-stripes-light': '#F7F7F7', + 'widget-inactive-stripes-dark': '#E8E8E8', + + /* Pinned Docs */ + 'pinned-doc-footer-bg': 'white', + 'pinned-doc-border': 'rgba(217,217,217,0.6)', + 'pinned-doc-border-hover': '#929299', + 'pinned-doc-editor-bg': 'rgba(217,217,217,0.6)', + + /* Raw Data */ + 'raw-data-table-border': 'rgba(217,217,217,0.6)', + 'raw-data-table-border-hover': '#929299', + + /* Controls */ + 'control-fg': '#16B378', + 'control-primary-fg': '#FFFFFF', + 'control-primary-bg': '#16B378', + 'control-secondary-fg': '#929299', + 'control-hover-fg': '#009058', + 'control-primary-hover-bg': '#009058', + 'control-secondary-hover-fg': '#262633', + 'control-secondary-hover-bg': '#D9D9D9', + 'control-disabled-fg': '#FFFFFF', + 'control-disabled-bg': '#929299', + 'control-primary-disabled': '#A2E1C9', + 'control-border': '#11B683', + + /* Checkboxes */ + 'checkbox-bg': '#FFFFFF', + 'checkbox-disabled-bg': '#D9D9D9', + 'checkbox-border': '#D9D9D9', + 'checkbox-border-hover': '#BFBFBF', + + /* Move Docs */ + 'move-docs-selected-fg': 'white', + 'move-docs-selected-bg': '#16B378', + 'move-docs-disabled-bg': '#D9D9D9', + + /* Filter Bar */ + 'filter-bar-button-saved-fg': '#FFFFFF', + 'filter-bar-button-saved-bg': '#929299', + 'filter-bar-button-saved-hover-bg': '#D9D9D9', + + /* Icon Buttons */ + 'icon-button-fg': '#FFFFFF', + 'icon-button-primary-bg': '#16B378', + 'icon-button-primary-hover-bg': '#009058', + 'icon-button-secondary-bg': '#D9D9D9', + 'icon-button-secondary-hover-bg': '#929299', + + /* Left Panel */ + 'left-panel-page-hover-bg': 'rgba(217,217,217,0.6)', + 'left-panel-active-page-fg': '#FFFFFF', + 'left-panel-active-page-bg': '#262633', + 'left-panel-disabled-page-fg': '#D9D9D9', + 'left-panel-page-options-fg': '#929299', + 'left-panel-page-options-hover-fg': 'white', + 'left-panel-page-options-hover-bg': '#D9D9D9', + 'left-panel-page-options-selected-hover-bg': '#929299', + 'left-panel-page-initials-fg': 'white', + 'left-panel-page-initials-bg': '#929299', + + /* Right Panel */ + 'right-panel-tab-fg': '#262633', + 'right-panel-tab-bg': '#F7F7F7', + 'right-panel-tab-icon': '#929299', + 'right-panel-tab-icon-hover': '#16B378', + 'right-panel-tab-hover-bg': 'rgba(217,217,217,0.6)', + 'right-panel-tab-selected-fg': '#FFFFFF', + 'right-panel-tab-selected-bg': '#16B378', + 'right-panel-tab-close-button-hover-bg': '#009058', + 'right-panel-subtab-fg': '#16B378', + 'right-panel-subtab-selected-fg': '#262633', + 'right-panel-subtab-selected-underline': '#16B378', + 'right-panel-subtab-hover-fg': '#009058', + 'right-panel-subtab-hover-underline': '#16B378', + 'right-panel-disabled-overlay': 'white', + 'right-panel-toggle-button-enabled-fg': '#FFFFFF', + 'right-panel-toggle-button-enabled-bg': '#262633', + 'right-panel-toggle-button-enabled-hover-fg': '#D9D9D9', + 'right-panel-toggle-button-disabled-fg': '#FFFFFF', + 'right-panel-toggle-button-disabled-bg': '#E8E8E8', + 'right-panel-field-settings-bg': '#E8E8E8', + 'right-panel-field-settings-button-bg': 'lightgrey', + + /* Document History */ + 'document-history-snapshot-fg': '#262633', + 'document-history-snapshot-selected-fg': '#FFFFFF', + 'document-history-snapshot-bg': 'white', + 'document-history-snapshot-selected-bg': '#262633', + 'document-history-snapshot-border': 'rgba(217,217,217,0.6)', + 'document-history-activity-text': '#000000', + 'document-history-activity-text-light': '#333333', + + /* Accents */ + 'accent-icon': '#16B378', + 'accent-border': '#16B378', + 'accent-text': '#16B378', + + /* Inputs */ + 'input-fg': 'black', + 'input-bg': 'white', + 'input-disabled-fg': '#929299', + 'input-disabled-bg': '#F7F7F7', + 'input-placeholder-fg': '#929299', + 'input-border': '#D9D9D9', + 'input-valid': '#16B378', + 'input-invalid': '#D0021B', + 'input-focus': '#5E9ED6', + 'input-readonly-bg': '#F7F7F7', + 'input-readonly-border': '#E8E8E8', + + /* Choice Entry */ + 'choice-entry-bg': 'white', + 'choice-entry-border': '#D9D9D9', + 'choice-entry-border-hover': '#BFBFBF', + + /* Select Buttons */ + 'select-button-fg': '#262633', + 'select-button-placeholder-fg': '#929299', + 'select-button-disabled-fg': 'grey', + 'select-button-bg': 'white', + 'select-button-border': '#D9D9D9', + 'select-button-border-invalid': '#D0021B', + + /* Menus */ + 'menu-text': '#929299', + 'menu-light-text': '#929299', + 'menu-bg': 'white', + 'menu-subheader-fg': 'unset', + 'menu-border': '#E8E8E8', + 'menu-shadow': 'rgba(38, 38, 51, 0.6)', + + /* Menu Items */ + 'menu-item-fg': 'black', + 'menu-item-selected-fg': '#FFFFFF', + 'menu-item-selected-bg': '#16B378', + 'menu-item-disabled-fg': '#D9D9D9', + 'menu-item-icon-fg': '#929299', + 'menu-item-icon-selected-fg': 'white', + 'menu-item-link-fg': '#16B378', + 'menu-item-link-selected-fg': '#009058', + 'menu-item-link-selected-bg': '#E8E8E8', + + /* Autocomplete */ + 'autocomplete-match-text': '#16B378', + 'autocomplete-selected-match-text': '#B1FFE2', + 'autocomplete-item-selected-bg': '#E8E8E8', + + /* Search */ + 'search-border': 'grey', + 'search-prev-next-button-fg': '#929299', + 'search-prev-next-button-bg': 'rgba(217,217,217,0.6)', + + /* Loaders */ + 'loader-fg': '#16B378', + 'loader-bg': '#D9D9D9', + + /* Site Switcher */ + 'site-switcher-active-fg': '#FFFFFF', + 'site-switcher-active-bg': '#262633', + + /* Doc Menu */ + 'doc-menu-doc-options-fg': '#D9D9D9', + 'doc-menu-doc-options-hover-fg': '#929299', + 'doc-menu-doc-options-hover-bg': '#D9D9D9', + + /* Shortcut Keys */ + 'shortcut-key-fg': 'black', + 'shortcut-key-primary-fg': '#009058', + 'shortcut-key-secondary-fg': '#929299', + 'shortcut-key-bg': 'white', + 'shortcut-key-border': '#929299', + + /* Breadcrumbs */ + 'breadcrumbs-tag-fg': 'white', + 'breadcrumbs-tag-bg': '#929299', + 'breadcrumbs-tag-alert-bg': '#D0021B', + + /* Page Widget Picker */ + 'widget-picker-primary-bg': 'white', + 'widget-picker-secondary-bg': '#F7F7F7', + 'widget-picker-item-fg': 'black', + 'widget-picker-item-selected-bg': 'rgba(217,217,217,0.6)', + 'widget-picker-item-disabled-bg': 'rgba(217,217,217,0.6)', + 'widget-picker-icon': '#929299', + 'widget-picker-primary-icon': '#16B378', + 'widget-picker-summary-icon': '#009058', + 'widget-picker-border': 'rgba(217,217,217,0.6)', + 'widget-picker-shadow': 'rgba(38,38,51,0.20)', + + /* Code View */ + 'code-view-text': '#444', + 'code-view-keyword': '#444', + 'code-view-comment': '#888888', + 'code-view-meta': '#1F7199', + 'code-view-title': '#880000', + 'code-view-params': '#444', + 'code-view-string': '#880000', + 'code-view-number': '#880000', + + /* Importer */ + 'importer-table-info-border': '#D9D9D9', + 'importer-preview-border': '#D9D9D9', + 'importer-skipped-table-overlay': 'rgba(217,217,217,0.6)', + 'importer-match-icon': '#D9D9D9', + + /* Menu Toggles */ + 'menu-toggle-fg': '#929299', + 'menu-toggle-hover-fg': '#009058', + 'menu-toggle-active-fg': '#007548', + 'menu-toggle-bg': 'white', + 'menu-toggle-border': '#929299', + + /* Button Groups */ + 'button-group-fg': '#262633', + 'button-group-light-fg': '#929299', + 'button-group-bg': 'unset', + 'button-group-icon': '#929299', + 'button-group-border': '#D9D9D9', + 'button-group-border-hover': '#BFBFBF', + 'button-group-selected-fg': '#FFFFFF', + 'button-group-light-selected-fg': '#16B378', + 'button-group-selected-bg': '#262633', + 'button-group-selected-border': '#262633', + + /* Access Rules */ + 'access-rules-table-header-fg': '#262633', + 'access-rules-table-header-bg': 'rgba(217,217,217,0.6)', + 'access-rules-table-body-fg': '#929299', + 'access-rules-table-border': '#929299', + + /* Cells */ + 'cell-fg': 'black', + 'cell-bg': 'white', + 'cell-zebra-bg': '#F8F8F8', + + /* Formula Editor */ + 'formula-editor-bg': 'white', + + /* Charts */ + 'chart-fg': '#444', + 'chart-bg': '#fff', + 'chart-legend-bg': '#FFFFFF80', + 'chart-x-axis': '#444', + 'chart-y-axis': '#444', +}; diff --git a/app/server/lib/ICreate.ts b/app/server/lib/ICreate.ts index da998d17..f2d027e6 100644 --- a/app/server/lib/ICreate.ts +++ b/app/server/lib/ICreate.ts @@ -1,3 +1,4 @@ +import {getThemeBackgroundSnippet} from 'app/common/Themes'; import {Document} from 'app/gen-server/entity/Document'; import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager'; import {ExternalStorage} from 'app/server/lib/ExternalStorage'; @@ -98,11 +99,12 @@ export function makeSimpleCreator(opts: { } }, getExtraHeadHtml() { - let customHeadHtmlSnippet = ''; + const elements: string[] = []; if (process.env.APP_STATIC_INCLUDE_CUSTOM_CSS === 'true') { - customHeadHtmlSnippet += ''; + elements.push(''); } - return customHeadHtmlSnippet; + elements.push(getThemeBackgroundSnippet()); + return elements.join('\n'); }, }; } diff --git a/app/server/lib/sendAppPage.ts b/app/server/lib/sendAppPage.ts index 0ef1cbf1..be49a855 100644 --- a/app/server/lib/sendAppPage.ts +++ b/app/server/lib/sendAppPage.ts @@ -55,6 +55,7 @@ export function makeGristConfig(homeUrl: string|null, extra: Partial + Account<!-- INSERT TITLE SUFFIX --> - diff --git a/static/app.html b/static/app.html index 0d1f95a7..3c7120c2 100644 --- a/static/app.html +++ b/static/app.html @@ -14,6 +14,7 @@ + <!-- INSERT TITLE --><!-- INSERT TITLE SUFFIX --> @@ -57,8 +58,6 @@ - - diff --git a/static/error.html b/static/error.html index 5f19a84c..0a5543b8 100644 --- a/static/error.html +++ b/static/error.html @@ -5,12 +5,12 @@ + Loading...<!-- INSERT TITLE SUFFIX --> - diff --git a/static/img/prismpattern.png b/static/img/prismpattern.png new file mode 100644 index 0000000000000000000000000000000000000000..0f02b16e7d3280702a48b9eb1d31401db18cc8f1 GIT binary patch literal 9697 zcmZvCc|26@`~S>17+c0jLO3%HS`~`OGNZv6ThXFqDXEks9)oFNOvy|=G;;_=DQS_= zo-9$#Q&jX&Q4(dVEQu`1_PY=Ie7>*W_xnfF%yq8&y59GF?e`&E>*lB=uPIL;5R}*` z%Y#55itvAs6rhBO7fpiywAQ+;w_mYhg{i5jnVFf1iOKTi%gxQrjg5_0u3Win*|NKo z*Xs!c{a7}Oxju?`z2W9b#8Kl|->!%6Ie`)X#HD<^zw+nJJB1l%%V}Q|31XV0>`TC- zqlU|GR9`pM^WS^l;)EoybkU)jsv(i}qY{sA>j$*nZ7l9e&+m=gaPaQy+_xHpati`s zSc*{YTo8PZK+sL%vNv+&2|qe?Ssk>lo;3tQ6wzSOWFXz+mEh%lmRm&lmy-b#<5J?D zl!|YR>}a>_&Vj^Mm-vjno%SJ387ik^V%|~St8#r=#|B09$KlhTZ5W^Sx>LxQ`VA$@ z+^i7_Vy}Xzc3;^}*|bESgjEnndyL6YC4p(O_QztzCo=}JnT)V`5Dk^*XP+0bD4+I6 zD3Wrk!eGZt+EW|Gc@l!k%>T5RSMEoGB2Mtj;{$x53Kcd}GWE{o_m(P%Cywt<%%iQD7%nb+>g>@b zqL)kvD%vF<+HVQpR2Q>sN@;(ks&4+G`T578!&UBeuU%F}?K=4O&aLM+TceS%3ZYf3 z1p`KnE*xQr_F-!-*tf9iwrsVOA+q>$Wt&R=1bY`|#7eqcwq zyq&_{YZ5LZ*D~p7dNxE%lVVX1Hi93@LO2+sLcDKtS=256A&@??GbqxrnGW%*Q0N;F zWbZ-P+xpUod_Di-BW*-UWQH#zaR?oR*ppdh+6W()4KLvRDoug&sUJQtYo&ov088{S zfEjskbQb1DVMDuXGp*&(ey0ACb(y*U@=;3dDG5zOYD$6%hMZ-woE#_j| zM?Nev=@k#2Q$5`op02z);9+_sH_LB_^3}PA!|qpeZdctL-KKM~Y3+yWk!M~x(%7mH zzimav4&^9Y=3L;gB&Y$Y(?bp9*=KN!H=gVKs19#rY?38kH+zOt@P(T2Z0rsbtSi6R zO)NVh^LQ?NLjlAHc61RLlapsR;EX>QL!ZumE{we~i?T@?#Rw>W5K%cwntr(7VdxWG zlf0c*zM1#SeU3baFPtyRRw7|!Qm*UJ`D3jzk1ZKn)!?<;>yjh~FtQyHs6T8D8%*IVKS(QOgdSiQy8iZ zqvsgl2d2mbk;Gq%){?|5%1@E?{#Hv3A`%*hQsJruXJF?GqHJk8n}?c67T%1-$lz;v zeF_-|S?QRXtpx0j1t{-%EaOClZ^%=ws+>ouU2Hlz4soS`fU2(08KX^Y?jj-9lK|pu z8i^TDn)X&ko-dIB62fH2FZE`FXCO78yy&GxUWRjj!PrE` zPpdTry=I{a&AhTXfrhr*HT&BpS)a2`aRMLT@5Q)s@9j+L!*@+ci(edU+DC7tbp>xc zaPL`NN`+RD=~m$PAOyefXB;uo5C8ayMSwJb}3Yb3g2; zKUcAiS4?1HW*u3GDO9Nsvze>n39(3Q{72)8EJT%u@IQ_E+85{SSBFK&56X6Rx(bA- zK;P324_9l~Sl6diu1z(+ivrNNADqxgXcFA+9rZ4p_p5`o`P# z;8AuWKpmF@(qv=dHDXJ$Bh%%tR?Soz8< zs9+Ef&D&-CiORIk$;*p@M-cw47(v@@k5GseD6?exg8iYr?5=0og`pe7@8)>Ccqt1eN< zg%G#aYA#~h^K9;_bCm6`I(!nBJiC{aNmLdM7AjFh*>WUvA}O5^3y&O+#;)8rA>#qO z1+9gMAN0u?xPjjI=vJOVxP_NNMa1K4id_!V_+)q((Ss@#r7#(5?+EVG)I}rm1u{;dES#19owX^<;=teec;eg<(mgiP^@f) zx=fVrPeU>$Bj~|Th2OU5tG>!n?G7bIY_3F&&Xz(Na7Yy%TJ@S(TZ3@FLcR#z%`bQ5 zl^fuBF{o9OhT1$6qQFv1C?9}qB8FH4W`5f?lHub>A{~G!l%u?Z1c>PXHkaKJ7keg= zf$ZkLGXnTX;(&9%*>!sCupSnuotS~(!89)+JlI>0-EZLrKrt{g@;zq?Zt1Y@$hD?@CV9|Zkoje}ZzRQP%(U$j|GMIW{wgVP zNV6WZT6oAnb@%C5R0MuW* zvFYIJ?2>}eRVqAE?aswL#{=(JJq+v++Ag$Qcu8X`b@;M{-wxlHkkX0oD_&JmYBQ}= zLzn#uN=^82AUnG0$!q;4y|1aqS6<5fxbv`$uM$@OZ(Fv$Wq*j-Z$?H^twq^v_|A$Q zq#QdcLuVsR2zyB7sAbCw7bpmGw%ZKfIi|rTJYa+^Mpsa-yyC$`K7>Y>Ji9E<2I+^H zE#q&iNT$LlFD==VqL&S*l?WqShE8OXAUP50e-y5S(M0JS8UKR>NLWWF_Pb#k*`ff^ zc9i#(EP5dg37^u5e2H0rG}GkBI}ZaY>~RICzDFnC!d}AYPRfELf>=}VbAllXTu6nB zH15&Relj!63gB^E1c(c$@NbRFixCAubOmU}!Xbe>P)$J?k0sIof=C2Wv;k-`wTDH@ z9$3zY+3V(Diu4r}xhdRmCHQHzgH{Rl9lDn|wNli!KtWDq6}p3x6FXNQ=s>^@O~^BB zUxH^A!Z?trGuT3cga%+6NXTkY00q%tS>}Egj08Px{Bf(uuP({3ez;=1t3Gw~Q}aYs z$dXYbW!o`sSN;b|eQV~AIaUe2F%MJ6-+R4Ijh|JP=4)>7Q72{8mD{)PcUgl{{nz-5 zW9r9D<@~ypelNv3Z+j39MeL z;+6y^ZZk$X_Eh-v6t}6h>mRCM_~|QzL6@VvAc_qw{0aE6f@kYJPkqSQ_#gfq7Wp#3 zOWtczXjai-2zStJTlKe0>Js4{zHGd<$f$6a5x12US+|c&0Lfh zK!sDJ=*j|U+9Yq(rQtQ+fm6DHi-`!#FoS~MB*ELq_x=dcaR62q9V)pdBGicgBYN-Z zc@o?kh3|=S6w%K=+`B(Y;oNg+zz-{|LWw};Vp-O?;2G}$4#jvGfpa5M!26TqX7+mW zf&&S`2sh`6HpzhXfxN0*k&x-nlvzNMk9d*idsYr(Z-61HYiMc~NT{*3L(Q=~W2-Buj@1&$0$| z;6A@9FwnjYHJ@hv5Lc`wNop)8!S%T=BuRV*f-EsiBHK{8C=X>U_q}2wM+u3dgd;t{ zA;KAM_t})Z`YQ|%#-W;L$#IfhrxNKZhY+CE*ohbT(1^C#yqEDeRgRrH8-;X*RtWcb zZM5|ncB7#p?i|6-c*STwyUwvpkYm_$>Zg|{$;MarT_=LMPZPw+p6keOxE$t1dvy9OBU~KQR+3mXx z2SU}Ts=QOKpg$3~s)-gNXXxbTThQ-b`?|@Lkgr7lBFLXFup#g3R10qy#eiB^EYD6hiklWfyPdBkS6LlM-y4MGTwk57aZ4OnpgY@>~HKeV{j!{&ydr!VoCqrU$X35~CLo z$Unq8h1QfuOLKnAZ(6zi%F zlno#;u&$np4>+9Y+c~pI@_9c?imEyV(WJcrS>W>Ga1Ui%E|Je7xOy|j3h}R!WX^!( zt<-cOAcl=4$7QFUkX&XU3RUO>Du;zAII7y;q?V> z+7DLBgKA!^6X*2^UBC4KPvY6N8EWED>=ZTig6H+>H4F)j#A&RI8maVisX)5a{6gidir zd>DYNi$I+2dPBhYzk*jkzEUAE;^E|t2sO^jh=*pn$Ge-%%V=EJ1$rV8Ic2RBGt1?L zJ>}*)S?ao!U7nVA8 zps~wiAauP=Dy)WFDdB<_lDphJWleDtrw6|e$1k<0xua9+Jf09WU}W(;CgSQ7?V++l z=jI4cC*^|MjeFzC57W$gYlpwz_CLTb_xtGkGkRS02=+_ppuF_(*?qj9-Hu~Lc9{xx zF0HN+C+mj)EweoIIe6YXABDl;F!z)4n zrq~kd0HZE-=<<6CK&p}mB_T-xlwnn=N~ED7j&FjX(S1((ZEyEar-Pk z;H93iEYN%)przoma?a%crStZ% zZNqQQGtM0U++4Zk*1y z7!zgv$fxFb$o-$=zg~`fu(iyHu+VjQ7+KV6spweq#j`7_@ZI%Sg=_NOAKX|IcD6bz zC$cw}S$*Vep7!onTBN=b8s!y<)HihD-I*oROmuje2FODU3GnB3cN;RN3OL zgjEE%Tc&6w3$Ww<5|y!#)#(f(Df1{O8audbNj zv$lXn0eR>bmwgIvGxczLlk!$fu>2pmfNoBOpRG32e!?n&d400f7dyaOfG1CJ3I}Mv z-f03425ta0vD+T(5C`s?emT}l3iPEw2#}ZuO*2c$w^asR04E`E&^!s^UONz$8BnAj z4qu=wESZBJmrEWC50D%cB$3)qi(QX$zJd!hTW3>o{%P#qgUU`bVj(i_dszg~{>R z^|m;!yAEVbkxvp}bU~8k^VRf%4WNjNmV>+Uflm(WQhs>GmkiA`H5e0Er&Ahp#DxZ( z0+krgQ3&IW00;29SRoAmB3TI-2hJ5PR}+>b z6#rY+Qrh>#^t=`_tZ9C5lHV}0=IhQ89ivJt1~n1jc3cfRa>S#RZk z!1()q;fvzC<*N?#_^|u^O}OY6>+9(;{BMcsUmCKV>;DRGGtb>fe1eVd*LfIVi+AO=^Ji}n)>cRg=-%TW?YjAqf=qi%G>5N?uCw= z#`(KpLwp{+0^0i3cC`#QCaLWx@`8+qdIle=Sir8nyr2tQh6l_lJ8{?%BUsc(F4Y79 z&ZD+5QS>B2PLhlFHm$G(z`iw^V4+`WvO{AW)vQ?HASY7Ajy8ywi`Iz<7HaIYTiL@K zSBR3p4-Iz4trByc3O35iI1Zs1g_;$hJR;b54HEj?#7VPaH;6{eG9`diql~1x_3lXh zJ!EtEaMg}G#rm&`UFyO+#`}Y~wFhoSf?kx%-*v)gj(YC!OR)j1m`1k=kdFy*5&Dgy2*1G-ZZ1be-;%1Jz z(5n4G3Y9(fu4oQcY9y6omv!ef@^L)>KH9e6mE4#fyWyeG?{8W=B$pb@923o$^=ou;}ws)MTuMM#L52p!;>bx41*EP|mv zC@*k61(AP_H1JrC;)-=T;6`NhU7W;5ltOYpAJ#3XM(P+@nJAb=sO;pE0pCnSKL+nA zOH-FHveI!X#_r5dgx{wcNeTV@oG-GZL|^r(+~-mld-h2Qxn>jG^;a)9`KqqajV``o z1)rAKasa%$AX5pEYo4cUW~lj`X?!5yvLf(V>0Cd^i#ZB%6dxHrOYStQy^{*&EgW=3 zuN{e%3hDvIev^QYX8^QIm}jzx%r^o-k6>55cFYLCLRSsS;oHW=?wk=DfYmODl$3QJ z{_!n8qH^&2i_|*K==k0I(Sh+5y+?}MXN@{DIjrW@+L@Xx-{|J8U%I4?wU6yTJd|*! zmp-_*SN%%+G0T0rb%|DuTm_1y-%|8j?b4MG&)%#)ShcR8KcZE`^aa-@x~JmB2D|My z45@JRBSy)jdc`kGr$GnNg+#-QmN4_ye5b&_FQKFW+U^1`BlxF+-CHf@#t6Uk+&!?J z6zCsRjEeL35v2I6@y;r^lK`TEjJGY2aJwAB=t%u~&76Pr$zLl`O~Jo-;y!zbY(RO5kbU0cn$I^DV-~gl;+kve7nYwr>BAd12_r)f~ ztXX=jMSrk6-vF8KjKRP7PM6s&zlei(Ap!7dg}lIYMy~mht5Sjp93v=MWH9(Jmb;yQ zo6dYgWUc-q1K)q3kJ8{y3WrdG$K6}-v>TZzZ!?VM$z?s#@VHgk5Bq8a;%x) z96s2Xch}Ifpf{zxGrhpb{7HD`ix(khloQ8 z$`+t^UpRnYDn@wD2L%z&TI)fSpwmZy^R>-J7}r3I;P#)c4a6{cD*SfpCg?G^T%3|; zAuT8Bw8NW;&!m}#1Y}qg#387%`~T<#2^io5LczJubRt*q8!%is-Fpfa2%|Vj`$et) z9|lxer8=Ww8b4rM2TVGRV;VhxcLg+1{|8#jTJ`_6&Eh7!(Eb#?J^K=)*ylL$4!VE4`gOm*K3-Z-qVq&LKo^BL zq@3qSozEK2v?a0YX~bKvRm=|zEYlCbK(i_-(o~WRDG2%$S=_huO9%0xqY|lm8weke z1*qExXM^q(ITAqa+OwQeDg_R?zu_DD5u`*D)Zk|RKWFXUt^!t^Ul7zY2k$TFLe3If zS$tG~-euv9OJgH-cljz=k>Fh1eT&Uq%kV)2V_y$adNsEb?>3U_e`o*Ed-r(uG^2tz z5mW^YBxzH<<>kfGioK48gPKqY@@~;a6T5*~xRM}Zt@R8*F9kspe#5|Bva?7Qv>PH9 zf1g}p%{06?m*GEpt=FYY^I0ELm=PD+0-Oo#%#8IPF4vg@_!fU>X0!VYa3AZh{+{nx z<9S!-gx{=?xF9A6`hIQXV=OCpvTtPY_e5r@-e9=zr^-&mynXz*7X+h z!t#nn5UUY@X%Zc5a!wIfeqm;9rz$;RT*^Mdk^ZzlWzDO_4*)N*_9u;4x#USt$)cIfta=SFs4BDSc@GH1D!X|GP&uf5|&WtXnL z=Jc)ENV{nsvAFVJ>u#IB%&~9p4=5!d+W99!eDm!5L%IX8C%=ncIzBP|i9WtPSbPly zw^r}|vB#K+TKc#Ppm$7@mW$dgM{~iQUvpzxfZxnDc>t!9e?p4}?)A*|j33x|fmmZ^ zofc=S?c>7ehnYUpo~n?YG3C<*JLNe~Wx%1ZXoLA;A#acxT1Dzrgz= zZZbi|4Chs8cByr`cU*;!9GSN&6i={Z>gOt?)LZ?&5=hrikuI%vJiD^ORoMmg{{*iV zWe1~h-(gXY)6}rXpyFiL&1kKIb<*BhjC{js1gIvNioX;B!-01A!DbvQ@E$Paf%`wR zPIE2&>DNNwH71TM;kVNC*o$*vfGhoTdd~-|9rjqy4+o2xJ^hi$9$1}=57*$5VPuU` Y%zHG&&i@Mdn=Jy{-i?)Sx0V0@04#ug>;M1& literal 0 HcmV?d00001