From 829ba24d9b65dd0a5565dd3e5debfcd29300e49d Mon Sep 17 00:00:00 2001 From: Bruno Bernardino Date: Fri, 17 Dec 2021 18:05:32 +0000 Subject: [PATCH] Add dark theme toggle. --- .github/workflows/ci.yml | 4 +-- components/Button/style.scss | 3 +++ components/Layout/Footer.scss | 9 +++++++ components/Layout/Footer.tsx | 2 +- components/Panels/All.tsx | 13 ++++++++- components/Panels/Settings.tsx | 47 ++++++++++++++++++++++++++++++++- components/TextInput/style.scss | 3 +++ lib/constants.ts | 25 ++++++++++++++---- lib/types.ts | 3 +++ lib/utils.ts | 10 +++++-- serverless.yml | 2 +- styles/__base.scss | 16 +++++++++++ 12 files changed, 124 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 834d9a7..4426b5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,10 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: 10.x + node-version: 12.x - run: | make install - run: | diff --git a/components/Button/style.scss b/components/Button/style.scss index 2aa224f..a8749a8 100644 --- a/components/Button/style.scss +++ b/components/Button/style.scss @@ -70,6 +70,9 @@ button.Button { @media (prefers-color-scheme: dark) { background-color: $color-text-gray; } + @at-root .theme-dark #{&} { + background-color: $color-text-gray; + } &:focus, &:hover { diff --git a/components/Layout/Footer.scss b/components/Layout/Footer.scss index 52cb96a..a466096 100644 --- a/components/Layout/Footer.scss +++ b/components/Layout/Footer.scss @@ -10,6 +10,9 @@ @media (prefers-color-scheme: dark) { background-color: #161616; } + @at-root .theme-dark #{&} { + background-color: #161616; + } &__faq { display: block; @@ -68,12 +71,18 @@ @media (prefers-color-scheme: dark) { color: $color-menu-background-hover; } + @at-root .theme-dark #{&} { + color: $color-menu-background-hover; + } &:hover, &:focus { @media (prefers-color-scheme: dark) { color: #f3f3f3; } + @at-root .theme-dark #{&} { + color: #f3f3f3; + } } } } diff --git a/components/Layout/Footer.tsx b/components/Layout/Footer.tsx index b9de4d0..4421917 100644 --- a/components/Layout/Footer.tsx +++ b/components/Layout/Footer.tsx @@ -28,7 +28,7 @@ const Footer = () => {

Where's the code for this web app?

- + It's in GitHub . diff --git a/components/Panels/All.tsx b/components/Panels/All.tsx index faa2e2f..c944cdd 100644 --- a/components/Panels/All.tsx +++ b/components/Panels/All.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import moment from 'moment'; import styled from 'styled-components'; import { useAsync } from 'react-use'; @@ -42,6 +42,7 @@ const All = () => { const [monthInView, setMonthInView] = useState(moment().format('YYYY-MM')); const [currency, setCurrency] = useState('USD'); const [syncToken, setSyncToken] = useState(''); + const [theme, setTheme] = useState('light'); const [budgets, setBudgets] = useState([]); const [expenses, setExpenses] = useState([]); const db = useRef(null); @@ -113,6 +114,7 @@ const All = () => { if (typeof window !== 'undefined') { const userInfo = getUserInfo(); setCurrency(userInfo.currency); + setTheme(userInfo.theme || 'light'); setSyncToken(userInfo.syncToken); const initializedDb = await initializeDb(userInfo.syncToken); @@ -126,6 +128,13 @@ const All = () => { } }, []); + useEffect(() => { + if (theme === 'dark') { + document.getElementsByTagName('html')[0].classList.add('theme-dark'); + document.getElementsByTagName('body')[0].classList.add('theme-dark'); + } + }, [theme]); + return ( @@ -159,6 +168,8 @@ const All = () => { updateCurrency={setCurrency} syncToken={syncToken} db={db.current} + currentTheme={theme} + updateTheme={setTheme} /> diff --git a/components/Panels/Settings.tsx b/components/Panels/Settings.tsx index e7f0cce..e1b8a7c 100644 --- a/components/Panels/Settings.tsx +++ b/components/Panels/Settings.tsx @@ -16,6 +16,8 @@ import appPackage from '../../package.json'; interface SettingsProps { currentCurrency: T.Currency; updateCurrency: (currency: T.Currency) => void; + currentTheme: T.Theme; + updateTheme: (theme: T.Theme) => void; syncToken: string; db: RxDatabase; } @@ -78,9 +80,14 @@ const HelpButton = styled(Button)` const currencyLabels = ['$', '€', '£']; const currencyValues: T.Currency[] = ['USD', 'EUR', 'GBP']; +const themeLabels = ['Light', 'Dark']; +const themeValues: T.Theme[] = ['light', 'dark']; + const Settings = ({ currentCurrency, updateCurrency, + currentTheme, + updateTheme, syncToken, db, }: SettingsProps) => { @@ -88,11 +95,16 @@ const Settings = ({ const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); const [isImportExportModalOpen, setIsImportExportModalOpen] = useState(false); const [currency, setCurrency] = useState(currentCurrency); + const [theme, setTheme] = useState(currentTheme); useEffect(() => { setCurrency(currentCurrency); }, [currentCurrency]); + useEffect(() => { + setTheme(currentTheme); + }, [currentTheme]); + const saveCurrency = async (newCurrency: T.Currency) => { if (isSubmitting) { // Ignore sequential taps @@ -101,7 +113,7 @@ const Settings = ({ setIsSubmitting(true); - const success = await doLogin(syncToken, newCurrency); + const success = doLogin(syncToken, newCurrency, currentTheme); if (success) { updateCurrency(newCurrency); @@ -113,10 +125,34 @@ const Settings = ({ } }; + const saveTheme = async (newTheme: T.Theme) => { + if (isSubmitting) { + // Ignore sequential taps + return; + } + + setIsSubmitting(true); + + const success = doLogin(syncToken, currentCurrency, newTheme); + + if (success) { + updateTheme(newTheme); + return; + } + + if (success) { + showNotification('Settings saved successfully.'); + } + }; + const selectedCurrencyIndex = currencyValues.findIndex( (_currency) => currency === _currency, ); + const selectedThemeIndex = themeValues.findIndex( + (_theme) => theme === _theme, + ); + return ( <> + + { + setTheme(themeValues[selectedSegmentIndex]); + saveTheme(themeValues[selectedSegmentIndex]); + }} + /> v{appVersion}-{appBuild} diff --git a/components/TextInput/style.scss b/components/TextInput/style.scss index 01261bb..0b917b2 100644 --- a/components/TextInput/style.scss +++ b/components/TextInput/style.scss @@ -15,6 +15,9 @@ $transition-speed: 140ms; @media (prefers-color-scheme: dark) { color: $color-link-hover; } + @at-root .theme-dark #{&} { + color: $color-link-hover; + } } &__input { diff --git a/lib/constants.ts b/lib/constants.ts index 7d4de6b..70fb5b0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,3 +1,5 @@ +import { getUserInfo } from 'lib/utils'; + export const defaultTitle = 'Budget Zen — Simple and Easy Budget Management'; export const defaultDescription = 'Simple and easy budget management.'; export const defaultKeywords = @@ -8,15 +10,28 @@ export const sessionNamespace = 'BudgetZen_appSession'; type Theme = 'dark' | 'light'; export const colors = (theme: Theme = 'light') => { - if ( - typeof window !== 'undefined' && - typeof window.matchMedia === 'function' - ) { - if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + const userInfo = getUserInfo(); + + if (typeof window !== 'undefined') { + if ( + typeof window.matchMedia === 'function' && + window.matchMedia('(prefers-color-scheme: dark)').matches + ) { theme = 'dark'; } } + if ( + typeof document !== 'undefined' && + document.getElementsByTagName('body')[0].classList.contains('theme-dark') + ) { + theme = 'dark'; + } + + if (userInfo.theme === 'dark') { + theme = 'dark'; + } + return { inputLabel: theme === 'dark' ? '#fff' : '#000', inputField: theme === 'dark' ? '#666' : '#666', diff --git a/lib/types.ts b/lib/types.ts index 716d8eb..b2d13ca 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -6,9 +6,12 @@ export interface PlainObject { export type Currency = 'USD' | 'EUR' | 'GBP'; +export type Theme = 'dark' | 'light'; + export interface AuthToken { syncToken: string; currency: Currency; + theme?: Theme; } export interface Expense { diff --git a/lib/utils.ts b/lib/utils.ts index 79bac63..a562d2a 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,7 +1,7 @@ import Swal from 'sweetalert2'; import { sessionNamespace } from 'lib/constants'; -import { AuthToken, Currency } from 'lib/types'; +import { AuthToken, Currency, Theme } from 'lib/types'; export const formatNumber = (currency: Currency = 'USD', number: number) => new Intl.NumberFormat('en-US', { @@ -85,10 +85,15 @@ export const showNotification = ( }); }; -export const doLogin = (syncToken: string, currency: Currency = 'USD') => { +export const doLogin = ( + syncToken: string, + currency: Currency = 'USD', + theme: Theme = 'light', +) => { const authToken: AuthToken = { syncToken, currency, + theme, }; try { @@ -140,6 +145,7 @@ export const getUserInfo: GetUserInfo = () => { return { syncToken: null, currency: 'USD', + theme: 'light', }; }; diff --git a/serverless.yml b/serverless.yml index 2ef36e3..9884f55 100644 --- a/serverless.yml +++ b/serverless.yml @@ -2,7 +2,7 @@ service: name: budgetzen-web myNextAppplication: - component: "@sls-next/serverless-component@1.18.0" + component: "@sls-next/serverless-component@3.6.0" inputs: domain: ["app", "budgetzen.net"] # bucketName: budgetzen-web diff --git a/styles/__base.scss b/styles/__base.scss index 51d740c..1fe79fc 100644 --- a/styles/__base.scss +++ b/styles/__base.scss @@ -12,6 +12,10 @@ body { background: #101010; color: #f3f3f3; } + &.theme-dark { + background: #101010; + color: #f3f3f3; + } } a { @@ -22,6 +26,9 @@ a { @media (prefers-color-scheme: dark) { color: $color-link-hover; } + @at-root .theme-dark #{&} { + color: $color-link-hover; + } &:hover, &:focus { @@ -31,6 +38,9 @@ a { @media (prefers-color-scheme: dark) { color: $color-menu-background-hover; } + @at-root .theme-dark #{&} { + color: $color-menu-background-hover; + } } &.style-less { @@ -52,6 +62,9 @@ pre { @media (prefers-color-scheme: dark) { background-color: rgba(0, 0, 0, 0.8); } + @at-root .theme-dark #{&} { + background-color: rgba(0, 0, 0, 0.8); + } } .wrapper { @@ -116,6 +129,9 @@ button { @media (prefers-color-scheme: dark) { background-color: $color-text; } + @at-root .theme-dark #{&} { + background-color: $color-text; + } } // Tweak CSS for switch