From ac0d17e57e81f0f57180f9b8e596b5290cdbf23b Mon Sep 17 00:00:00 2001 From: Jed Fox <git@jedfox.com> Date: Thu, 20 Jul 2023 12:34:17 -0400 Subject: [PATCH] Begin integrating support for themes (#1367) --- packages/desktop-client/src/components/App.js | 3 +- .../src/components/ThemeSelector.tsx | 33 +++++ .../desktop-client/src/components/Titlebar.js | 3 + .../src/components/settings/Experimental.js | 92 ------------- .../src/components/settings/Experimental.tsx | 121 ++++++++++++++++++ .../src/components/settings/Themes.js | 29 +++++ .../src/components/settings/index.js | 6 +- .../src/hooks/useFeatureFlag.ts | 14 +- .../desktop-client/src/icons/v2/MoonStars.js | 24 ++++ packages/desktop-client/src/icons/v2/Sun.js | 20 +++ .../src/icons/v2/moon-stars.svg | 4 + packages/desktop-client/src/icons/v2/sun.svg | 3 + packages/desktop-client/src/style/colors.ts | 70 ++++++++++ packages/desktop-client/src/style/index.ts | 3 + packages/desktop-client/src/style/palette.ts | 71 ++++++++++ .../src/{style.tsx => style/styles.ts} | 79 +----------- packages/desktop-client/src/style/theme.tsx | 33 +++++ .../desktop-client/src/style/themes/dark.ts | 115 +++++++++++++++++ .../src/style/themes/development.ts | 115 +++++++++++++++++ .../desktop-client/src/style/themes/light.ts | 115 +++++++++++++++++ .../src/client/state-types/prefs.d.ts | 5 +- packages/loot-core/src/server/main.ts | 9 ++ upcoming-release-notes/1367.md | 6 + 23 files changed, 788 insertions(+), 185 deletions(-) create mode 100644 packages/desktop-client/src/components/ThemeSelector.tsx delete mode 100644 packages/desktop-client/src/components/settings/Experimental.js create mode 100644 packages/desktop-client/src/components/settings/Experimental.tsx create mode 100644 packages/desktop-client/src/components/settings/Themes.js create mode 100644 packages/desktop-client/src/icons/v2/MoonStars.js create mode 100644 packages/desktop-client/src/icons/v2/Sun.js create mode 100644 packages/desktop-client/src/icons/v2/moon-stars.svg create mode 100644 packages/desktop-client/src/icons/v2/sun.svg create mode 100644 packages/desktop-client/src/style/colors.ts create mode 100644 packages/desktop-client/src/style/index.ts create mode 100644 packages/desktop-client/src/style/palette.ts rename packages/desktop-client/src/{style.tsx => style/styles.ts} (74%) create mode 100644 packages/desktop-client/src/style/theme.tsx create mode 100644 packages/desktop-client/src/style/themes/dark.ts create mode 100644 packages/desktop-client/src/style/themes/development.ts create mode 100644 packages/desktop-client/src/style/themes/light.ts create mode 100644 upcoming-release-notes/1367.md diff --git a/packages/desktop-client/src/components/App.js b/packages/desktop-client/src/components/App.js index 19e2ea5f7..ca8410104 100644 --- a/packages/desktop-client/src/components/App.js +++ b/packages/desktop-client/src/components/App.js @@ -11,7 +11,7 @@ import { import installPolyfills from '../polyfills'; import { ResponsiveProvider } from '../ResponsiveProvider'; -import { styles, hasHiddenScrollbars } from '../style'; +import { styles, hasHiddenScrollbars, ThemeStyle } from '../style'; import AppBackground from './AppBackground'; import DevelopmentTopBar from './DevelopmentTopBar'; @@ -134,6 +134,7 @@ class App extends Component { <MobileWebMessage /> </div> </div> + <ThemeStyle /> </ResponsiveProvider> ); } diff --git a/packages/desktop-client/src/components/ThemeSelector.tsx b/packages/desktop-client/src/components/ThemeSelector.tsx new file mode 100644 index 000000000..b745303d7 --- /dev/null +++ b/packages/desktop-client/src/components/ThemeSelector.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import { useActions } from '../hooks/useActions'; +import MoonStars from '../icons/v2/MoonStars'; +import Sun from '../icons/v2/Sun'; +import { useResponsive } from '../ResponsiveProvider'; +import { useTheme } from '../style'; + +import { Button } from './common'; + +export function ThemeSelector() { + let theme = useTheme(); + let { saveGlobalPrefs } = useActions(); + + let { isNarrowWidth } = useResponsive(); + + return isNarrowWidth ? null : ( + <Button + bare + onClick={() => { + saveGlobalPrefs({ + theme: theme === 'dark' ? 'light' : 'dark', + }); + }} + > + {theme === 'light' ? ( + <MoonStars style={{ width: 13, height: 13, color: 'inherit' }} /> + ) : ( + <Sun style={{ width: 13, height: 13, color: 'inherit' }} /> + )} + </Button> + ); +} diff --git a/packages/desktop-client/src/components/Titlebar.js b/packages/desktop-client/src/components/Titlebar.js index be94a677e..450250852 100644 --- a/packages/desktop-client/src/components/Titlebar.js +++ b/packages/desktop-client/src/components/Titlebar.js @@ -42,6 +42,7 @@ import { useSidebar } from './FloatableSidebar'; import LoggedInUser from './LoggedInUser'; import { useServerURL } from './ServerContext'; import useSheetValue from './spreadsheet/useSheetValue'; +import { ThemeSelector } from './ThemeSelector'; export let TitlebarContext = createContext(); @@ -288,6 +289,7 @@ function Titlebar({ const serverURL = useServerURL(); let privacyModeFeatureFlag = useFeatureFlag('privacyMode'); + let themesFlag = useFeatureFlag('themes'); let onTogglePrivacy = enabled => { savePrefs({ isPrivacyEnabled: enabled }); }; @@ -372,6 +374,7 @@ function Titlebar({ </Routes> <View style={{ flex: 1 }} /> <UncategorizedButton /> + {themesFlag && <ThemeSelector />} {privacyModeFeatureFlag && ( <PrivacyButton localPrefs={localPrefs} diff --git a/packages/desktop-client/src/components/settings/Experimental.js b/packages/desktop-client/src/components/settings/Experimental.js deleted file mode 100644 index 103e0ba1f..000000000 --- a/packages/desktop-client/src/components/settings/Experimental.js +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useState } from 'react'; - -import { useAllFeatureFlags } from '../../hooks/useFeatureFlag'; -import { colors } from '../../style'; -import { LinkButton, Text, View } from '../common'; -import { Checkbox } from '../forms'; - -import { Setting } from './UI'; - -export default function ExperimentalFeatures({ prefs, savePrefs }) { - let [expanded, setExpanded] = useState(false); - const flags = useAllFeatureFlags(); - let disabled = prefs.budgetType === 'report' && flags.reportBudget; - - return ( - <Setting - primaryAction={ - expanded ? ( - <View style={{ gap: '1em' }}> - <label - style={{ - display: 'flex', - color: disabled ? colors.n5 : 'inherit', - }} - > - <Checkbox - id="report-budget-flag" - checked={flags.reportBudget} - onChange={() => { - savePrefs({ 'flags.reportBudget': !flags.reportBudget }); - }} - disabled={disabled} - />{' '} - <View> - Budget mode toggle - {disabled && ( - <Text style={{ color: colors.r3, fontWeight: 500 }}> - Switch to a rollover budget before turning off this feature - </Text> - )} - </View> - </label> - - <label style={{ display: 'flex' }}> - <Checkbox - id="goal-templates-flag" - checked={flags.goalTemplatesEnabled} - onChange={() => { - savePrefs({ - 'flags.goalTemplatesEnabled': !flags.goalTemplatesEnabled, - }); - }} - />{' '} - <View>Goal templates</View> - </label> - - <label style={{ display: 'flex' }}> - <Checkbox - id="privacy-mode-flag" - checked={flags.privacyMode} - onChange={() => { - savePrefs({ - 'flags.privacyMode': !flags.privacyMode, - }); - }} - />{' '} - <View>Privacy mode</View> - </label> - </View> - ) : ( - <LinkButton - onClick={() => setExpanded(true)} - style={{ - flexShrink: 0, - alignSelf: 'flex-start', - color: colors.p4, - }} - > - I understand the risks, show experimental features - </LinkButton> - ) - } - > - <Text> - <strong>Experimental features.</strong> These features are not fully - tested and may not work as expected. THEY MAY CAUSE IRRECOVERABLE DATA - LOSS. They may do nothing at all. Only enable them if you know what you - are doing. - </Text> - </Setting> - ); -} diff --git a/packages/desktop-client/src/components/settings/Experimental.tsx b/packages/desktop-client/src/components/settings/Experimental.tsx new file mode 100644 index 000000000..e2478e95d --- /dev/null +++ b/packages/desktop-client/src/components/settings/Experimental.tsx @@ -0,0 +1,121 @@ +import { type ReactNode, useState } from 'react'; +import { useSelector } from 'react-redux'; + +import type { FeatureFlag } from 'loot-core/src/client/state-types/prefs'; + +import { useActions } from '../../hooks/useActions'; +import useFeatureFlag from '../../hooks/useFeatureFlag'; +import { colors, useTheme } from '../../style'; +import { LinkButton, Text, View } from '../common'; +import { Checkbox } from '../forms'; + +import { Setting } from './UI'; + +type FeatureToggleProps = { + flag: FeatureFlag; + disableToggle?: boolean; + error?: ReactNode; + children: ReactNode; +}; + +function FeatureToggle({ + flag, + disableToggle = false, + error, + children, +}: FeatureToggleProps) { + let { savePrefs } = useActions(); + let enabled = useFeatureFlag(flag); + + return ( + <label style={{ display: 'flex' }}> + <Checkbox + checked={enabled} + onChange={() => { + savePrefs({ + [`flags.${flag}`]: !enabled, + }); + }} + disabled={disableToggle} + /> + <View style={{ color: disableToggle ? colors.n5 : 'inherit' }}> + {children} + {disableToggle && ( + <Text style={{ color: colors.r3, fontWeight: 500 }}>{error}</Text> + )} + </View> + </label> + ); +} + +function ReportBudgetFeature() { + let budgetType = useSelector(state => state.prefs.local?.budgetType); + let enabled = useFeatureFlag('reportBudget'); + let blockToggleOff = budgetType === 'report' && enabled; + return ( + <FeatureToggle + flag="reportBudget" + disableToggle={blockToggleOff} + error="Switch to a rollover budget before turning off this feature" + > + Budget mode toggle + </FeatureToggle> + ); +} + +function ThemeFeature() { + let theme = useTheme(); + let enabled = useFeatureFlag('themes'); + let blockToggleOff = theme !== 'light' && enabled; + return ( + <FeatureToggle + flag="themes" + disableToggle={blockToggleOff} + error="Switch to the light theme before turning off this feature" + > + Dark mode + </FeatureToggle> + ); +} + +export default function ExperimentalFeatures() { + let [expanded, setExpanded] = useState(false); + + return ( + <Setting + primaryAction={ + expanded ? ( + <View style={{ gap: '1em' }}> + <ReportBudgetFeature /> + + <FeatureToggle flag="goalTemplatesEnabled"> + Goal templates + </FeatureToggle> + + <FeatureToggle flag="privacyMode">Privacy mode</FeatureToggle> + + <ThemeFeature /> + </View> + ) : ( + <LinkButton + onClick={() => setExpanded(true)} + style={{ + flexShrink: 0, + alignSelf: 'flex-start', + color: colors.p4, + }} + > + I understand the risks, show experimental features + </LinkButton> + ) + } + > + <Text> + <strong>Experimental features.</strong> These features are not fully + tested and may not work as expected. THEY MAY CAUSE IRRECOVERABLE DATA + LOSS. They may do nothing at all. Only enable them if you know what you + are doing. + </Text> + </Setting> + ); +} diff --git a/packages/desktop-client/src/components/settings/Themes.js b/packages/desktop-client/src/components/settings/Themes.js new file mode 100644 index 000000000..b48f756b0 --- /dev/null +++ b/packages/desktop-client/src/components/settings/Themes.js @@ -0,0 +1,29 @@ +import React from 'react'; + +import { themeNames, useTheme } from '../../style'; +import { Button, Select, Text } from '../common'; + +import { Setting } from './UI'; + +export default function ThemeSettings({ saveGlobalPrefs }) { + let theme = useTheme(); + return ( + <Setting + primaryAction={ + <Button bounce={false} style={{ padding: 0 }}> + <Select + onChange={value => { + saveGlobalPrefs({ theme: value }); + }} + value={theme} + options={themeNames.map(name => [name, name])} + /> + </Button> + } + > + <Text> + <strong>Themes</strong> change the user interface colors. + </Text> + </Setting> + ); +} diff --git a/packages/desktop-client/src/components/settings/index.js b/packages/desktop-client/src/components/settings/index.js index cbb01e5b2..d72b20d5a 100644 --- a/packages/desktop-client/src/components/settings/index.js +++ b/packages/desktop-client/src/components/settings/index.js @@ -7,6 +7,7 @@ import * as actions from 'loot-core/src/client/actions'; import * as Platform from 'loot-core/src/client/platform'; import { listen } from 'loot-core/src/platform/client/fetch'; +import useFeatureFlag from '../../hooks/useFeatureFlag'; import useLatestVersion, { useIsOutdated } from '../../hooks/useLatestVersion'; import { useResponsive } from '../../ResponsiveProvider'; import { colors } from '../../style'; @@ -24,6 +25,7 @@ import FixSplitsTool from './FixSplits'; import FormatSettings from './Format'; import GlobalSettings from './Global'; import { ResetCache, ResetSync } from './Reset'; +import ThemeSettings from './Themes'; import { AdvancedToggle, Setting } from './UI'; function About() { @@ -123,6 +125,7 @@ function Settings({ }, [loadPrefs]); const { isNarrowWidth } = useResponsive(); + const themesFlag = useFeatureFlag('themes'); return ( <View @@ -169,6 +172,7 @@ function Settings({ /> )} + {themesFlag && <ThemeSettings saveGlobalPrefs={saveGlobalPrefs} />} <FormatSettings prefs={prefs} savePrefs={savePrefs} /> <EncryptionSettings prefs={prefs} pushModal={pushModal} /> <ExportBudget prefs={prefs} /> @@ -178,7 +182,7 @@ function Settings({ <ResetCache /> <ResetSync isEnabled={!!prefs.groupId} resetSync={resetSync} /> <FixSplitsTool /> - <ExperimentalFeatures prefs={prefs} savePrefs={savePrefs} /> + <ExperimentalFeatures /> </AdvancedToggle> </View> </Page> diff --git a/packages/desktop-client/src/hooks/useFeatureFlag.ts b/packages/desktop-client/src/hooks/useFeatureFlag.ts index 7d3a49101..6d47a634e 100644 --- a/packages/desktop-client/src/hooks/useFeatureFlag.ts +++ b/packages/desktop-client/src/hooks/useFeatureFlag.ts @@ -6,6 +6,7 @@ const DEFAULT_FEATURE_FLAG_STATE: Record<FeatureFlag, boolean> = { reportBudget: false, goalTemplatesEnabled: false, privacyMode: false, + themes: false, }; export default function useFeatureFlag(name: FeatureFlag): boolean { @@ -17,16 +18,3 @@ export default function useFeatureFlag(name: FeatureFlag): boolean { : value; }); } - -export function useAllFeatureFlags(): Record<FeatureFlag, boolean> { - return useSelector(state => { - return { - ...DEFAULT_FEATURE_FLAG_STATE, - ...Object.fromEntries( - Object.entries(state.prefs.local) - .filter(([key]) => key.startsWith('flags.')) - .map(([key, value]) => [key.replace('flags.', ''), value]), - ), - }; - }); -} diff --git a/packages/desktop-client/src/icons/v2/MoonStars.js b/packages/desktop-client/src/icons/v2/MoonStars.js new file mode 100644 index 000000000..8bdff417e --- /dev/null +++ b/packages/desktop-client/src/icons/v2/MoonStars.js @@ -0,0 +1,24 @@ +import * as React from 'react'; +const SvgMoonStars = props => ( + <svg + {...props} + xmlns="http://www.w3.org/2000/svg" + fill="currentColor" + className="bi bi-moon-stars" + viewBox="0 0 16 16" + style={{ + color: '#242134', + ...props.style, + }} + > + <path + d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z" + fill="currentColor" + /> + <path + d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" + fill="currentColor" + /> + </svg> +); +export default SvgMoonStars; diff --git a/packages/desktop-client/src/icons/v2/Sun.js b/packages/desktop-client/src/icons/v2/Sun.js new file mode 100644 index 000000000..cadc6aaf8 --- /dev/null +++ b/packages/desktop-client/src/icons/v2/Sun.js @@ -0,0 +1,20 @@ +import * as React from 'react'; +const SvgSun = props => ( + <svg + {...props} + xmlns="http://www.w3.org/2000/svg" + fill="currentColor" + className="bi bi-sun" + viewBox="0 0 16 16" + style={{ + color: '#242134', + ...props.style, + }} + > + <path + d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" + fill="currentColor" + /> + </svg> +); +export default SvgSun; diff --git a/packages/desktop-client/src/icons/v2/moon-stars.svg b/packages/desktop-client/src/icons/v2/moon-stars.svg new file mode 100644 index 000000000..9731bee61 --- /dev/null +++ b/packages/desktop-client/src/icons/v2/moon-stars.svg @@ -0,0 +1,4 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon-stars" viewBox="0 0 16 16"> + <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z"/> + <path d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"/> +</svg> diff --git a/packages/desktop-client/src/icons/v2/sun.svg b/packages/desktop-client/src/icons/v2/sun.svg new file mode 100644 index 000000000..13fc3dbef --- /dev/null +++ b/packages/desktop-client/src/icons/v2/sun.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun" viewBox="0 0 16 16"> + <path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/> +</svg> diff --git a/packages/desktop-client/src/style/colors.ts b/packages/desktop-client/src/style/colors.ts new file mode 100644 index 000000000..9248454d5 --- /dev/null +++ b/packages/desktop-client/src/style/colors.ts @@ -0,0 +1,70 @@ +export const y1 = '#733309'; +export const y2 = '#87540d'; +export const y3 = '#B88115'; +export const y4 = '#D4A31C'; +export const y5 = '#E6BB20'; +export const y6 = '#F2D047'; +export const y7 = '#F5E35D'; +export const y8 = '#FCF088'; +export const y9 = '#FFF7C4'; +export const y10 = '#FFFBEA'; +export const y11 = '#FFFEFA'; +export const r1 = '#610316'; +export const r2 = '#8A041A'; +export const r3 = '#AB091E'; +export const r4 = '#CF1124'; +export const r5 = '#E12D39'; +export const r6 = '#EF4E4E'; +export const r7 = '#F86A6A'; +export const r8 = '#FF9B9B'; +export const r9 = '#FFBDBD'; +export const r10 = '#FFE3E3'; +export const r11 = '#FFF1F1'; +export const b1 = '#034388'; +export const b2 = '#0B5FA3'; +export const b3 = '#1271BF'; +export const b4 = '#1980D4'; +export const b5 = '#2B8FED'; +export const b6 = '#40A5F7'; +export const b7 = '#66B5FA'; +export const b8 = '#8BCAFD'; +export const b9 = '#B3D9FF'; +export const b10 = '#E3F0FF'; +export const b11 = '#F5FCFF'; +export const n1 = '#102A43'; +export const n2 = '#243B53'; +export const n3 = '#334E68'; +export const n4 = '#486581'; +export const n5 = '#627D98'; +export const n6 = '#829AB1'; +export const n7 = '#9FB3C8'; +export const n8 = '#BCCCDC'; +export const n9 = '#D9E2EC'; +export const n10 = '#E8ECF0'; +export const n11 = '#F7FAFC'; +export const g1 = '#014D40'; +export const g2 = '#0C6B58'; +export const g3 = '#147D64'; +export const g4 = '#199473'; +export const g5 = '#27AB83'; +export const g6 = '#3EBD93'; +export const g7 = '#65D6AD'; +export const g8 = '#8EEDC7'; +export const g9 = '#C6F7E2'; +export const g10 = '#EFFCF6'; +export const g11 = '#FAFFFD'; +export const p1 = '#44056E'; +export const p2 = '#580A94'; +export const p3 = '#690CB0'; +export const p4 = '#7A0ECC'; +export const p5 = '#8719E0'; +export const p6 = '#9446ED'; +export const p7 = '#A368FC'; +export const p8 = '#B990FF'; +export const p9 = '#DAC4FF'; +export const p10 = '#F2EBFE'; +export const p11 = '#F9F6FE'; + +export const border = n10; +export const hover = '#fafafa'; +export const selected = b9; diff --git a/packages/desktop-client/src/style/index.ts b/packages/desktop-client/src/style/index.ts new file mode 100644 index 000000000..fb5f2ea25 --- /dev/null +++ b/packages/desktop-client/src/style/index.ts @@ -0,0 +1,3 @@ +export * as colors from './colors'; +export * from './styles'; +export * from './theme'; diff --git a/packages/desktop-client/src/style/palette.ts b/packages/desktop-client/src/style/palette.ts new file mode 100644 index 000000000..9ab65750b --- /dev/null +++ b/packages/desktop-client/src/style/palette.ts @@ -0,0 +1,71 @@ +import * as oldColors from './colors'; + +// Only for use in contextual color definitions +export const gray50 = oldColors.n11; +export const gray100 = oldColors.n10; +export const gray150 = oldColors.n9; +export const gray200 = oldColors.n8; +export const gray300 = oldColors.n7; +export const gray400 = oldColors.n6; +export const gray500 = oldColors.n5; +export const gray600 = oldColors.n4; +export const gray700 = oldColors.n3; +export const gray800 = oldColors.n2; +export const gray900 = oldColors.n1; +export const navy50 = oldColors.b11; +export const navy100 = oldColors.b10; +export const navy150 = oldColors.b9; +export const navy200 = oldColors.b8; +export const navy300 = oldColors.b7; +export const navy400 = oldColors.b6; +export const navy500 = oldColors.b5; +export const navy600 = oldColors.b4; +export const navy700 = oldColors.b3; +export const navy800 = oldColors.b2; +export const navy900 = oldColors.b1; +export const green50 = oldColors.g11; +export const green100 = oldColors.g10; +export const green150 = oldColors.g9; +export const green200 = oldColors.g8; +export const green300 = oldColors.g7; +export const green400 = oldColors.g6; +export const green500 = oldColors.g5; +export const green600 = oldColors.g4; +export const green700 = oldColors.g3; +export const green800 = oldColors.g2; +export const green900 = oldColors.g1; +export const orange50 = oldColors.y11; +export const orange100 = oldColors.y10; +export const orange150 = oldColors.y9; +export const orange200 = oldColors.y8; +export const orange300 = oldColors.y7; +export const orange400 = oldColors.y6; +export const orange500 = oldColors.y5; +export const orange600 = oldColors.y4; +export const orange700 = oldColors.y3; +export const orange800 = oldColors.y2; +export const orange900 = oldColors.y1; +export const red50 = oldColors.r11; +export const red100 = oldColors.r10; +export const red150 = oldColors.r9; +export const red200 = oldColors.r8; +export const red300 = oldColors.r7; +export const red400 = oldColors.r6; +export const red500 = oldColors.r5; +export const red600 = oldColors.r4; +export const red700 = oldColors.r3; +export const red800 = oldColors.r2; +export const red900 = oldColors.r1; +export const purple50 = oldColors.p11; +export const purple100 = oldColors.p10; +export const purple150 = oldColors.p9; +export const purple200 = oldColors.p8; +export const purple300 = oldColors.p7; +export const purple400 = oldColors.p6; +export const purple500 = oldColors.p5; +export const purple600 = oldColors.p4; +export const purple700 = oldColors.p3; +export const purple800 = oldColors.p2; +export const purple900 = oldColors.p1; +export const white = '#ffffff'; +export const black = '#000000'; diff --git a/packages/desktop-client/src/style.tsx b/packages/desktop-client/src/style/styles.ts similarity index 74% rename from packages/desktop-client/src/style.tsx rename to packages/desktop-client/src/style/styles.ts index 49a7d05a5..a90283339 100644 --- a/packages/desktop-client/src/style.tsx +++ b/packages/desktop-client/src/style/styles.ts @@ -2,84 +2,9 @@ import { keyframes } from 'glamor'; import * as Platform from 'loot-core/src/client/platform'; -import tokens from './tokens'; +import tokens from '../tokens'; -export const colors = { - y1: '#733309', - y2: '#87540d', - y3: '#B88115', - y4: '#D4A31C', - y5: '#E6BB20', - y6: '#F2D047', - y7: '#F5E35D', - y8: '#FCF088', - y9: '#FFF7C4', - y10: '#FFFBEA', - y11: '#FFFEFA', - r1: '#610316', - r2: '#8A041A', - r3: '#AB091E', - r4: '#CF1124', - r5: '#E12D39', - r6: '#EF4E4E', - r7: '#F86A6A', - r8: '#FF9B9B', - r9: '#FFBDBD', - r10: '#FFE3E3', - r11: '#FFF1F1', - b1: '#034388', - b2: '#0B5FA3', - b3: '#1271BF', - b4: '#1980D4', - b5: '#2B8FED', - b6: '#40A5F7', - b7: '#66B5FA', - b8: '#8BCAFD', - b9: '#B3D9FF', - b10: '#E3F0FF', - b11: '#F5FCFF', - n1: '#102A43', - n2: '#243B53', - n3: '#334E68', - n4: '#486581', - n5: '#627D98', - n6: '#829AB1', - n7: '#9FB3C8', - n8: '#BCCCDC', - n9: '#D9E2EC', - n10: '#E8ECF0', - n11: '#F7FAFC', - g1: '#014D40', - g2: '#0C6B58', - g3: '#147D64', - g4: '#199473', - g5: '#27AB83', - g6: '#3EBD93', - g7: '#65D6AD', - g8: '#8EEDC7', - g9: '#C6F7E2', - g10: '#EFFCF6', - g11: '#FAFFFD', - p1: '#44056E', - p2: '#580A94', - p3: '#690CB0', - p4: '#7A0ECC', - p5: '#8719E0', - p6: '#9446ED', - p7: '#A368FC', - p8: '#B990FF', - p9: '#DAC4FF', - p10: '#F2EBFE', - p11: '#F9F6FE', - - get border() { - return this.n10; - }, - hover: '#fafafa', - get selected() { - return this.b9; - }, -}; +import * as colors from './colors'; export const styles = { veryLargeText: { diff --git a/packages/desktop-client/src/style/theme.tsx b/packages/desktop-client/src/style/theme.tsx new file mode 100644 index 000000000..e2ec92f30 --- /dev/null +++ b/packages/desktop-client/src/style/theme.tsx @@ -0,0 +1,33 @@ +import { useSelector } from 'react-redux'; + +import type { Theme } from 'loot-core/src/client/state-types/prefs'; +import { isNonProductionEnvironment } from 'loot-core/src/shared/environment'; + +import * as darkTheme from './themes/dark'; +import * as developmentTheme from './themes/development'; +import * as lightTheme from './themes/light'; + +const themes = { + light: lightTheme, + dark: darkTheme, + ...(isNonProductionEnvironment() && { development: developmentTheme }), +}; + +export const themeNames = Object.keys(themes) as Theme[]; + +export function useTheme() { + return useSelector(state => state.prefs.global?.theme) || 'light'; +} + +export function ThemeStyle() { + let theme = useTheme(); + let themeColors = themes[theme]; + let css = Object.keys(themeColors) + .map(key => ` --color-${key}: ${themeColors[key]};`) + .join('\n'); + return <style>{`:root {\n${css}}`}</style>; +} + +export const theme = Object.fromEntries( + Object.keys(lightTheme).map(key => [key, `var(--color-${key})`]), +) as Record<keyof typeof lightTheme, string>; diff --git a/packages/desktop-client/src/style/themes/dark.ts b/packages/desktop-client/src/style/themes/dark.ts new file mode 100644 index 000000000..dcf3ccaf0 --- /dev/null +++ b/packages/desktop-client/src/style/themes/dark.ts @@ -0,0 +1,115 @@ +import * as colorPalette from '../palette'; + +export const pageBackground = colorPalette.gray900; +export const pageBackgroundModalActive = colorPalette.gray800; +export const pageBackgroundTopLeft = colorPalette.gray900; +export const pageBackgroundBottomRight = colorPalette.gray700; +export const pageBackgroundLineTop = colorPalette.purple400; +export const pageBackgroundLineMid = colorPalette.gray900; +export const pageBackgroundLineBottom = colorPalette.gray150; +export const pageText = colorPalette.gray150; +export const pageTextSubdued = colorPalette.gray500; +export const pageTextPositive = colorPalette.purple400; +export const pageTextLink = colorPalette.purple400; +export const modalBackground = colorPalette.gray800; +export const modalBorder = colorPalette.gray600; +export const cardBackground = colorPalette.gray800; +export const cardBorder = colorPalette.purple400; +export const cardShadow = colorPalette.gray700; +export const tableBackground = colorPalette.gray800; +export const tableRowBackgroundHover = colorPalette.gray700; +export const tableText = colorPalette.gray150; +export const tableTextSelected = colorPalette.gray150; +export const tableTextHover = colorPalette.gray400; +export const tableTextEditing = colorPalette.black; +export const tableTextEditingBackground = colorPalette.purple400; +export const tableTextInactive = colorPalette.gray500; +export const tableHeaderText = colorPalette.gray300; +export const tableHeaderBackground = colorPalette.gray700; +export const tableBorder = colorPalette.gray600; +export const tableBorderSelected = colorPalette.purple400; +export const tableBorderHover = colorPalette.purple300; +export const tableBorderSeparator = colorPalette.gray400; +export const tableRowBackgroundHighlight = colorPalette.purple800; +export const tableRowBackgroundHighlightText = colorPalette.gray150; +export const tableRowHeaderBackground = colorPalette.gray700; +export const tableRowHeaderText = colorPalette.gray150; +export const sidebarBackground = colorPalette.gray800; +export const sidebarItemBackground = colorPalette.gray800; +export const sidebarItemBackgroundSelected = colorPalette.gray800; +export const sidebarItemBackgroundHover = colorPalette.gray700; +export const sidebarItemAccent = colorPalette.gray800; +export const sidebarItemAccentSelected = colorPalette.purple400; +export const sidebarItemAccentHover = colorPalette.gray700; +export const sidebarItemText = colorPalette.gray150; +export const sidebarItemTextSelected = colorPalette.purple400; +export const sidebarItemTextHover = colorPalette.gray150; +export const tooltipBackground = colorPalette.gray600; +export const tooltipBorder = colorPalette.gray500; +export const menuBackground = colorPalette.gray600; +export const menuItemBackground = colorPalette.gray600; +export const menuItemBackgroundHover = colorPalette.gray500; +export const menuItemText = colorPalette.gray100; +export const menuItemTextHover = colorPalette.gray50; +export const menuItemTextSelected = colorPalette.gray200; +export const menuItemTextHeader = colorPalette.purple400; +export const menuBorder = colorPalette.gray800; +export const menuBorderHover = colorPalette.purple400; +export const altMenuBackground = colorPalette.gray700; +export const altMenuItemBackground = colorPalette.gray700; +export const altMenuItemBackgroundHover = colorPalette.gray600; +export const altMenuItemText = colorPalette.gray150; +export const altMenuItemTextHover = colorPalette.gray150; +export const altMenuItemTextSelected = colorPalette.gray150; +export const altMenuItemTextHeader = colorPalette.purple500; +export const altMenuBorder = colorPalette.gray200; +export const altMenuBorderHover = colorPalette.purple400; +export const buttonAltMenuText = colorPalette.gray150; +export const buttonAltMenuTextHover = colorPalette.gray100; +export const buttonAltMenuTextSelected = colorPalette.gray100; +export const buttonAltMenuBackground = colorPalette.gray800; +export const buttonAltMenuBackgroundHover = colorPalette.gray600; +export const buttonAltMenuBorder = colorPalette.gray600; +export const buttonPositiveText = colorPalette.black; +export const buttonPositiveTextHover = colorPalette.gray150; +export const buttonPositiveTextSelected = colorPalette.black; +export const buttonPositiveBackground = colorPalette.purple400; +export const buttonPositiveBackgroundHover = colorPalette.gray800; +export const buttonPositiveBorder = colorPalette.purple400; +export const buttonNeutralText = colorPalette.gray150; +export const buttonNeutralTextHover = colorPalette.gray150; +export const buttonNeutralBackground = colorPalette.gray800; +export const buttonNeutralBackgroundHover = colorPalette.gray600; +export const buttonNeutralBorder = colorPalette.gray300; +export const buttonDisabledText = colorPalette.gray500; +export const buttonDisabledBackground = colorPalette.gray800; +export const buttonDisabledBorder = colorPalette.gray500; +export const buttonShadow = colorPalette.gray700; +export const noticeBackground = colorPalette.green800; +export const noticeText = colorPalette.green300; +export const noticeAccent = colorPalette.green500; +export const warningBackground = colorPalette.orange800; +export const warningText = colorPalette.orange200; +export const warningAccent = colorPalette.orange500; +export const errorBackground = colorPalette.red800; +export const errorText = colorPalette.red200; +export const errorAccent = colorPalette.red500; +export const formLabelText = colorPalette.purple150; +export const formInputBackground = colorPalette.gray800; +export const formInputBackgroundSelected = colorPalette.purple400; +export const formInputBackgroundSelection = colorPalette.purple400; +export const formInputBorder = colorPalette.gray600; +export const formInputTextReadOnlySelection = colorPalette.gray800; +export const formInputBorderSelected = colorPalette.purple400; +export const formInputText = colorPalette.gray150; +export const formInputTextSelected = colorPalette.black; +export const formInputTextPlaceholder = colorPalette.gray150; +export const formInputTextSelection = colorPalette.gray800; +export const formInputShadowSelected = colorPalette.purple400; +export const formInputTextHighlight = colorPalette.purple400; +export const pillBackground = colorPalette.gray600; +export const pillText = colorPalette.gray200; +export const pillBorder = colorPalette.gray700; +export const pillBackgroundSelected = colorPalette.purple600; +export const pillTextSelected = colorPalette.gray150; +export const pillBorderSelected = colorPalette.purple400; diff --git a/packages/desktop-client/src/style/themes/development.ts b/packages/desktop-client/src/style/themes/development.ts new file mode 100644 index 000000000..fcfff3e3f --- /dev/null +++ b/packages/desktop-client/src/style/themes/development.ts @@ -0,0 +1,115 @@ +import * as colorPalette from '../palette'; + +export const pageBackground = colorPalette.navy600; +export const pageBackgroundModalActive = colorPalette.navy700; +export const pageBackgroundTopLeft = colorPalette.green300; +export const pageBackgroundBottomRight = colorPalette.red600; +export const pageBackgroundLineTop = colorPalette.gray50; +export const pageBackgroundLineMid = colorPalette.green500; +export const pageBackgroundLineBottom = colorPalette.orange200; +export const pageText = colorPalette.navy300; +export const pageTextSubdued = colorPalette.navy500; +export const pageTextPositive = colorPalette.navy50; +export const pageTextLink = colorPalette.navy400; +export const modalBackground = colorPalette.gray900; +export const modalBorder = colorPalette.gray200; +export const cardBackground = colorPalette.purple700; +export const cardBorder = colorPalette.purple400; +export const cardShadow = colorPalette.purple100; +export const tableBackground = colorPalette.red900; +export const tableRowBackgroundHover = colorPalette.red800; +export const tableText = colorPalette.red200; +export const tableTextSelected = colorPalette.red150; +export const tableTextHover = colorPalette.red400; +export const tableTextEditing = colorPalette.black; +export const tableTextEditingBackground = colorPalette.red200; +export const tableTextInactive = colorPalette.red500; +export const tableHeaderText = colorPalette.red700; +export const tableHeaderBackground = colorPalette.red300; +export const tableBorder = colorPalette.red200; +export const tableBorderSelected = colorPalette.purple400; +export const tableBorderHover = colorPalette.purple300; +export const tableBorderSeparator = colorPalette.gray400; +export const tableRowBackgroundHighlight = colorPalette.red700; +export const tableRowBackgroundHighlightText = colorPalette.red200; +export const tableRowHeaderBackground = colorPalette.red100; +export const tableRowHeaderText = colorPalette.red700; +export const sidebarBackground = colorPalette.orange800; +export const sidebarItemBackground = colorPalette.orange700; +export const sidebarItemBackgroundSelected = colorPalette.orange900; +export const sidebarItemBackgroundHover = colorPalette.orange500; +export const sidebarItemAccent = colorPalette.orange200; +export const sidebarItemAccentSelected = colorPalette.orange400; +export const sidebarItemAccentHover = colorPalette.orange200; +export const sidebarItemText = colorPalette.orange200; +export const sidebarItemTextSelected = colorPalette.orange400; +export const sidebarItemTextHover = colorPalette.orange150; +export const tooltipBackground = colorPalette.white; +export const tooltipBorder = colorPalette.black; +export const menuBackground = colorPalette.green800; +export const menuItemBackground = colorPalette.green700; +export const menuItemBackgroundHover = colorPalette.green500; +export const menuItemText = colorPalette.green200; +export const menuItemTextHover = colorPalette.green50; +export const menuItemTextSelected = colorPalette.green500; +export const menuItemTextHeader = colorPalette.green300; +export const menuBorder = colorPalette.green500; +export const menuBorderHover = colorPalette.green900; +export const altMenuBackground = colorPalette.gray700; +export const altMenuItemBackground = colorPalette.gray700; +export const altMenuItemBackgroundHover = colorPalette.gray600; +export const altMenuItemText = colorPalette.gray150; +export const altMenuItemTextHover = colorPalette.gray150; +export const altMenuItemTextSelected = colorPalette.gray150; +export const altMenuItemTextHeader = colorPalette.purple500; +export const altMenuBorder = colorPalette.gray200; +export const altMenuBorderHover = colorPalette.purple400; +export const buttonAltMenuText = colorPalette.gray150; +export const buttonAltMenuTextHover = colorPalette.gray100; +export const buttonAltMenuTextSelected = colorPalette.gray100; +export const buttonAltMenuBackground = colorPalette.gray800; +export const buttonAltMenuBackgroundHover = colorPalette.gray600; +export const buttonAltMenuBorder = colorPalette.gray600; +export const buttonPositiveText = colorPalette.purple200; +export const buttonPositiveTextHover = colorPalette.purple50; +export const buttonPositiveTextSelected = colorPalette.purple600; +export const buttonPositiveBackground = colorPalette.purple400; +export const buttonPositiveBackgroundHover = colorPalette.purple800; +export const buttonPositiveBorder = colorPalette.purple700; +export const buttonNeutralText = colorPalette.gray50; +export const buttonNeutralTextHover = colorPalette.gray200; +export const buttonNeutralBackground = colorPalette.gray400; +export const buttonNeutralBackgroundHover = colorPalette.gray500; +export const buttonNeutralBorder = colorPalette.gray800; +export const buttonDisabledText = colorPalette.gray500; +export const buttonDisabledBackground = colorPalette.gray800; +export const buttonDisabledBorder = colorPalette.gray500; +export const buttonShadow = colorPalette.gray700; +export const noticeBackground = colorPalette.green800; +export const noticeText = colorPalette.green300; +export const noticeAccent = colorPalette.green500; +export const warningBackground = colorPalette.orange800; +export const warningText = colorPalette.orange200; +export const warningAccent = colorPalette.orange500; +export const errorBackground = colorPalette.red800; +export const errorText = colorPalette.red200; +export const errorAccent = colorPalette.red500; +export const formLabelText = colorPalette.purple200; +export const formInputBackground = colorPalette.purple700; +export const formInputBackgroundSelected = colorPalette.purple400; +export const formInputBackgroundSelection = colorPalette.purple400; +export const formInputBorder = colorPalette.purple600; +export const formInputTextReadOnlySelection = colorPalette.purple800; +export const formInputBorderSelected = colorPalette.purple100; +export const formInputText = colorPalette.purple150; +export const formInputTextSelected = colorPalette.purple800; +export const formInputTextPlaceholder = colorPalette.gray150; +export const formInputTextSelection = colorPalette.gray800; +export const formInputShadowSelected = colorPalette.purple400; +export const formInputTextHighlight = colorPalette.purple400; +export const pillBackground = colorPalette.green800; +export const pillText = colorPalette.green600; +export const pillBorder = colorPalette.green200; +export const pillBackgroundSelected = colorPalette.green100; +export const pillTextSelected = colorPalette.green700; +export const pillBorderSelected = colorPalette.green900; diff --git a/packages/desktop-client/src/style/themes/light.ts b/packages/desktop-client/src/style/themes/light.ts new file mode 100644 index 000000000..79e11a362 --- /dev/null +++ b/packages/desktop-client/src/style/themes/light.ts @@ -0,0 +1,115 @@ +import * as colorPalette from '../palette'; + +export const pageBackground = colorPalette.gray100; +export const pageBackgroundModalActive = colorPalette.gray200; +export const pageBackgroundTopLeft = colorPalette.gray100; +export const pageBackgroundBottomRight = colorPalette.navy100; +export const pageBackgroundLineTop = colorPalette.white; +export const pageBackgroundLineMid = colorPalette.gray100; +export const pageBackgroundLineBottom = colorPalette.navy150; +export const pageText = colorPalette.gray700; +export const pageTextSubdued = colorPalette.gray300; +export const pageTextPositive = colorPalette.purple500; +export const pageTextLink = colorPalette.navy600; +export const modalBackground = colorPalette.white; +export const modalBorder = colorPalette.white; +export const cardBackground = colorPalette.white; +export const cardBorder = colorPalette.purple500; +export const cardShadow = colorPalette.gray700; +export const tableBackground = colorPalette.white; +export const tableRowBackgroundHover = colorPalette.navy100; +export const tableText = colorPalette.gray700; +export const tableTextSelected = colorPalette.gray700; +export const tableTextHover = colorPalette.gray900; +export const tableTextEditing = colorPalette.gray50; +export const tableTextEditingBackground = colorPalette.purple500; +export const tableTextInactive = colorPalette.gray300; +export const tableHeaderText = colorPalette.gray500; +export const tableHeaderBackground = colorPalette.gray50; +export const tableBorder = colorPalette.gray150; +export const tableBorderSelected = colorPalette.purple500; +export const tableBorderHover = colorPalette.purple400; +export const tableBorderSeparator = colorPalette.gray400; +export const tableRowBackgroundHighlight = colorPalette.purple100; +export const tableRowBackgroundHighlightText = colorPalette.gray700; +export const tableRowHeaderBackground = colorPalette.gray50; +export const tableRowHeaderText = colorPalette.gray800; +export const sidebarBackground = colorPalette.navy800; +export const sidebarItemBackground = colorPalette.navy800; +export const sidebarItemBackgroundSelected = colorPalette.navy800; +export const sidebarItemBackgroundHover = colorPalette.navy700; +export const sidebarItemAccent = colorPalette.navy800; +export const sidebarItemAccentSelected = colorPalette.purple300; +export const sidebarItemAccentHover = colorPalette.navy700; +export const sidebarItemText = colorPalette.gray100; +export const sidebarItemTextSelected = colorPalette.purple300; +export const sidebarItemTextHover = colorPalette.gray50; +export const tooltipBackground = colorPalette.gray50; +export const tooltipBorder = colorPalette.gray50; +export const menuBackground = colorPalette.gray50; +export const menuItemBackground = colorPalette.gray50; +export const menuItemBackgroundHover = colorPalette.gray150; +export const menuItemText = colorPalette.gray800; +export const menuItemTextHover = colorPalette.gray800; +export const menuItemTextSelected = colorPalette.gray800; +export const menuItemTextHeader = colorPalette.purple600; +export const menuBorder = colorPalette.gray100; +export const menuBorderHover = colorPalette.purple100; +export const altMenuBackground = colorPalette.navy800; +export const altMenuItemBackground = colorPalette.navy800; +export const altMenuItemBackgroundHover = colorPalette.navy700; +export const altMenuItemText = colorPalette.gray100; +export const altMenuItemTextHover = colorPalette.gray50; +export const altMenuItemTextSelected = colorPalette.purple300; +export const altMenuItemTextHeader = colorPalette.purple300; +export const altMenuBorder = colorPalette.navy700; +export const altMenuBorderHover = colorPalette.purple300; +export const buttonAltMenuText = colorPalette.gray100; +export const buttonAltMenuTextHover = colorPalette.gray50; +export const buttonAltMenuTextSelected = colorPalette.gray50; +export const buttonAltMenuBackground = colorPalette.navy800; +export const buttonAltMenuBackgroundHover = colorPalette.navy700; +export const buttonAltMenuBorder = colorPalette.gray200; +export const buttonPositiveText = colorPalette.gray50; +export const buttonPositiveTextHover = colorPalette.purple600; +export const buttonPositiveTextSelected = colorPalette.gray50; +export const buttonPositiveBackground = colorPalette.purple600; +export const buttonPositiveBackgroundHover = colorPalette.gray50; +export const buttonPositiveBorder = colorPalette.purple600; +export const buttonNeutralText = colorPalette.gray700; +export const buttonNeutralTextHover = colorPalette.gray800; +export const buttonNeutralBackground = colorPalette.gray50; +export const buttonNeutralBackgroundHover = colorPalette.gray100; +export const buttonNeutralBorder = colorPalette.gray200; +export const buttonDisabledText = colorPalette.gray300; +export const buttonDisabledBackground = colorPalette.gray50; +export const buttonDisabledBorder = colorPalette.gray300; +export const buttonShadow = colorPalette.purple500; +export const noticeBackground = colorPalette.green50; +export const noticeText = colorPalette.green500; +export const noticeAccent = colorPalette.green200; +export const warningBackground = colorPalette.orange50; +export const warningText = colorPalette.orange500; +export const warningAccent = colorPalette.orange200; +export const errorBackground = colorPalette.red50; +export const errorText = colorPalette.red500; +export const errorAccent = colorPalette.red200; +export const formLabelText = colorPalette.navy500; +export const formInputBackground = colorPalette.gray50; +export const formInputBackgroundSelected = colorPalette.purple500; +export const formInputBackgroundSelection = colorPalette.purple500; +export const formInputBorder = colorPalette.gray300; +export const formInputTextReadOnlySelection = colorPalette.gray50; +export const formInputBorderSelected = colorPalette.purple500; +export const formInputText = colorPalette.gray700; +export const formInputTextSelected = colorPalette.gray50; +export const formInputTextPlaceholder = colorPalette.gray300; +export const formInputTextSelection = colorPalette.gray100; +export const formInputShadowSelected = colorPalette.purple500; +export const formInputTextHighlight = colorPalette.purple500; +export const pillBackground = colorPalette.gray150; +export const pillText = colorPalette.gray800; +export const pillBorder = colorPalette.gray150; +export const pillBackgroundSelected = colorPalette.purple150; +export const pillTextSelected = colorPalette.gray700; +export const pillBorderSelected = colorPalette.purple500; diff --git a/packages/loot-core/src/client/state-types/prefs.d.ts b/packages/loot-core/src/client/state-types/prefs.d.ts index 2bd95b03d..644b5f84f 100644 --- a/packages/loot-core/src/client/state-types/prefs.d.ts +++ b/packages/loot-core/src/client/state-types/prefs.d.ts @@ -4,7 +4,8 @@ import type * as constants from '../constants'; export type FeatureFlag = | 'reportBudget' | 'goalTemplatesEnabled' - | 'privacyMode'; + | 'privacyMode' + | 'themes'; type NullableValues<T> = { [K in keyof T]: T[K] | null }; @@ -45,9 +46,11 @@ export type LocalPrefs = NullableValues< } & Record<`flags.${FeatureFlag}`, boolean> >; +export type Theme = 'light' | 'dark' | 'development'; export type GlobalPrefs = NullableValues<{ floatingSidebar: boolean; maxMonths: number; + theme: Theme; documentDir: string; // Electron only }>; diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index 00fee7dae..26966dde2 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1472,6 +1472,9 @@ handlers['save-global-prefs'] = async function (prefs) { if ('floatingSidebar' in prefs) { await asyncStorage.setItem('floating-sidebar', '' + prefs.floatingSidebar); } + if ('theme' in prefs) { + await asyncStorage.setItem('theme', prefs.theme); + } return 'ok'; }; @@ -1482,12 +1485,14 @@ handlers['load-global-prefs'] = async function () { [, autoUpdate], [, documentDir], [, encryptKey], + [, theme], ] = await asyncStorage.multiGet([ 'floating-sidebar', 'max-months', 'auto-update', 'document-dir', 'encrypt-key', + 'theme', ]); return { floatingSidebar: floatingSidebar === 'true' ? true : false, @@ -1495,6 +1500,10 @@ handlers['load-global-prefs'] = async function () { autoUpdate: autoUpdate == null || autoUpdate === 'true' ? true : false, documentDir: documentDir || getDefaultDocumentDir(), keyId: encryptKey && JSON.parse(encryptKey).id, + theme: + theme === 'light' || theme === 'dark' || theme === 'development' + ? theme + : 'light', }; }; diff --git a/upcoming-release-notes/1367.md b/upcoming-release-notes/1367.md new file mode 100644 index 000000000..24454c31d --- /dev/null +++ b/upcoming-release-notes/1367.md @@ -0,0 +1,6 @@ +--- +category: Features +authors: [biohzrddd, j-f1] +--- + +Add an initial feature flag and infrastructure for building out dark and custom themes. -- GitLab