Skip to content

Commit 8df551e

Browse files
author
Pascal Wegner
authored
Merge pull request #148 from flextremedev/theme-toggle
Theme toggle
2 parents 729695f + be7168e commit 8df551e

File tree

9 files changed

+151
-13
lines changed

9 files changed

+151
-13
lines changed

packages/pwa/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"date-fns": "^2.28.0",
1717
"next": "latest",
1818
"next-pwa": "^5.4.1",
19+
"next-themes": "^0.1.1",
1920
"react": "^17.0.2",
2021
"react-dom": "^17.0.2",
2122
"xstate": "^4.29.0"

packages/pwa/src/components/Counter/Counter.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as React from 'react';
33

44
import { Arc } from '../Arc/Arc';
55
import { DurationInput } from '../DurationInput/DurationInput';
6+
import { ThemeToggle } from '../ThemeToggle/ThemeToggle';
67

78
const SECONDS_PER_MINUTE = 60;
89

@@ -33,8 +34,13 @@ export function Counter({
3334
const stepLength = 1 - timeLeftAdvancedByOneInSeconds / timeTotalInSeconds;
3435

3536
const counterDesktop = (
36-
<div className="h-full w-full lg:max-w-screen-md hidden lg:flex justify-center flex-col items-center">
37-
<div className="flex flex-col justify-between w-full h-3/6 items-center relative">
37+
<div className="h-full w-full lg:max-w-screen-xl hidden lg:flex justify-between flex-col items-center">
38+
<div className="w-full flex">
39+
<div className="h-20 w-full flex justify-end items-center px-6">
40+
<ThemeToggle />
41+
</div>
42+
</div>
43+
<div className="flex flex-col lg:max-w-screen-md justify-between w-full h-3/6 items-center relative">
3844
<div className="flex flex-1 flex-col items-center z-[1]">
3945
<span className="text-blue-600 text-3xl tracking-wider">ROUND</span>
4046
<span
@@ -60,11 +66,16 @@ export function Counter({
6066
</button>
6167
</div>
6268
</div>
69+
<div />
6370
</div>
6471
);
6572
const counterMobile = (
66-
<div className="h-full flex lg:hidden justify-center flex-col items-center">
67-
<div className="flex-[0.75]" />
73+
<div className="h-full w-full flex lg:hidden justify-center flex-col items-center">
74+
<div className="flex-[0.75] w-full flex">
75+
<div className="h-20 w-full flex justify-end items-center px-6">
76+
<ThemeToggle />
77+
</div>
78+
</div>
6879
<div className="flex-1">
6980
<div className="flex flex-col items-center mb-8 z-[1]">
7081
<span className="text-blue-600 text-2xl tracking-wider mb-1">

packages/pwa/src/components/Form/Form.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FormFields } from '../FormFields/FormFields';
2+
import { ThemeToggle } from '../ThemeToggle/ThemeToggle';
23

34
type FormProps = {
45
rounds: number;
@@ -20,8 +21,12 @@ export const Form = ({
2021
workInterval,
2122
}: FormProps) => {
2223
return (
23-
<div className="h-full flex justify-center flex-col items-center">
24-
<div className="flex-[0.75]" />
24+
<div className="h-full w-full flex max-w-screen-xl justify-center flex-col items-center">
25+
<div className="flex-[0.75] w-full flex">
26+
<div className="h-20 w-full flex justify-end items-center px-6">
27+
<ThemeToggle />
28+
</div>
29+
</div>
2530
<div className="flex-1">
2631
<FormFields
2732
rounds={rounds}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { fireEvent, render, screen } from '@testing-library/react';
2+
import * as nextThemes from 'next-themes';
3+
4+
import { ThemeToggle } from './ThemeToggle';
5+
6+
jest.mock('next-themes');
7+
8+
describe('ThemeToggle', () => {
9+
beforeEach(() => {
10+
jest.clearAllMocks();
11+
});
12+
13+
it.each`
14+
iconName | mode
15+
${'Sun icon'} | ${'dark'}
16+
${'Moon icon'} | ${'light'}
17+
`('should work for $mode mode', async ({ iconName, mode }) => {
18+
const setTheme = jest.fn();
19+
jest
20+
.spyOn(nextThemes, 'useTheme')
21+
.mockReturnValue({ theme: mode, setTheme });
22+
render(<ThemeToggle />);
23+
24+
expect(
25+
screen.getByRole('button', { name: 'theme-toggle' })
26+
).toBeInTheDocument();
27+
28+
expect(screen.getByRole('img', { name: iconName })).toBeInTheDocument();
29+
fireEvent.click(screen.getByRole('button', { name: 'theme-toggle' }));
30+
expect(setTheme).toHaveBeenCalledTimes(1);
31+
expect(setTheme).toHaveBeenCalledWith(mode === 'dark' ? 'light' : 'dark');
32+
});
33+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { useTheme } from 'next-themes';
2+
3+
const MoonIcon = () => {
4+
return (
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
className="h-8 w-8"
8+
viewBox="0 0 20 20"
9+
fill="currentColor"
10+
aria-labelledby="toggle-dark-mode-title"
11+
role="img"
12+
>
13+
<title id="toggle-dark-mode-title">Moon icon</title>
14+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
15+
</svg>
16+
);
17+
};
18+
19+
const SunIcon = () => {
20+
return (
21+
<svg
22+
xmlns="http://www.w3.org/2000/svg"
23+
className="h-8 w-8"
24+
fill="none"
25+
viewBox="0 0 24 24"
26+
stroke="currentColor"
27+
strokeWidth={2}
28+
aria-labelledby="toggle-light-mode-title"
29+
role="img"
30+
>
31+
<title id="toggle-light-mode-title">Sun icon</title>
32+
<path
33+
strokeLinecap="round"
34+
strokeLinejoin="round"
35+
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
36+
/>
37+
</svg>
38+
);
39+
};
40+
41+
export const ThemeToggle = () => {
42+
const { theme, setTheme } = useTheme();
43+
const toggleTheme = () => {
44+
setTheme(theme === 'light' ? 'dark' : 'light');
45+
};
46+
return (
47+
<button aria-label="theme-toggle" className="" onClick={toggleTheme}>
48+
{theme === 'light' ? <MoonIcon /> : <SunIcon />}
49+
</button>
50+
);
51+
};

packages/pwa/src/pages/_app.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/* istanbul ignore file */
2+
import { ThemeProvider } from 'next-themes';
23
import { AppProps } from 'next/app';
34
import Head from 'next/head';
45
import '../styles/globals.css';
@@ -33,7 +34,9 @@ export default function MyApp({ Component, pageProps }: AppProps) {
3334
<link rel="apple-touch-icon" href="/apple-icon.png"></link>
3435
<meta name="theme-color" content="#2C5CED" />
3536
</Head>
36-
<Component {...pageProps} />
37+
<ThemeProvider attribute="class">
38+
<Component {...pageProps} />
39+
</ThemeProvider>
3740
</>
3841
);
3942
}

packages/pwa/src/pages/index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { StateValue } from 'xstate';
77

88
import { Counter } from '../components/Counter/Counter';
99
import { Form } from '../components/Form/Form';
10-
import { FormFields } from '../components/FormFields/FormFields';
1110
import { useBeep } from '../hooks/useBeep';
1211

1312
const DEFAULT_DOCUMENT_TITLE = 'Flextreme Interval Timer';

packages/pwa/tailwind.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const notLast = plugin(({ addVariant, e }) => {
1212

1313
module.exports = {
1414
content: ['./src/**/*.{js,ts,jsx,tsx}'],
15+
darkMode: 'class',
1516
theme: {
1617
extend: {
1718
colors: {

yarn.lock

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7876,7 +7876,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
78767876
dependencies:
78777877
once "^1.4.0"
78787878

7879-
enhanced-resolve@^4.3.0:
7879+
enhanced-resolve@^4.1.0, enhanced-resolve@^4.3.0:
78807880
version "4.5.0"
78817881
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec"
78827882
integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==
@@ -13629,6 +13629,11 @@ next-pwa@^5.4.1:
1362913629
workbox-webpack-plugin "^6.4.2"
1363013630
workbox-window "^6.4.2"
1363113631

13632+
next-themes@^0.1.1:
13633+
version "0.1.1"
13634+
resolved "https://registry.yarnpkg.com/next-themes/-/next-themes-0.1.1.tgz#122113a458bf1d1be5ffed66778ab924c106f82a"
13635+
integrity sha512-Iqxt6rhS/KfK/iHJ0tfFjTcdLEAI0AgwFuAFrMwLOPK5e+MI3I+fzyvBoS+VaOS+NldUiazurhgwYhrfV0VXsQ==
13636+
1363213637
next-tick@~1.0.0:
1363313638
version "1.0.0"
1363413639
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
@@ -15758,7 +15763,7 @@ react-devtools-core@^4.6.0:
1575815763
shell-quote "^1.6.1"
1575915764
ws "^7"
1576015765

15761-
react-dom@^17.0.0, react-dom@^17.0.2:
15766+
react-dom@^17.0.2:
1576215767
version "17.0.2"
1576315768
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
1576415769
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
@@ -15963,7 +15968,7 @@ react-timer-mixin@^0.13.4:
1596315968
resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz#75a00c3c94c13abe29b43d63b4c65a88fc8264d3"
1596415969
integrity sha512-4+ow23tp/Tv7hBM5Az5/Be/eKKF7DIvJ09voz5LyHGQaqqz9WV8YMs31eFvcYQs7d451LSg7kDJV70XYN/Ug/Q==
1596515970

15966-
react@^17.0.0, react@^17.0.2:
15971+
react@^17.0.2:
1596715972
version "17.0.2"
1596815973
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
1596915974
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
@@ -18597,7 +18602,7 @@ watchpack-chokidar2@^2.0.1:
1859718602
dependencies:
1859818603
chokidar "^2.1.8"
1859918604

18600-
watchpack@^1.7.4:
18605+
watchpack@^1.6.1, watchpack@^1.7.4:
1860118606
version "1.7.5"
1860218607
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453"
1860318608
integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==
@@ -18727,7 +18732,36 @@ webpack-sources@^1.1.0, webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-
1872718732
source-list-map "^2.0.0"
1872818733
source-map "~0.6.1"
1872918734

18730-
webpack@4.43.0, webpack@4.44.2, webpack@~4.44.0:
18735+
webpack@4.43.0:
18736+
version "4.43.0"
18737+
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6"
18738+
integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==
18739+
dependencies:
18740+
"@webassemblyjs/ast" "1.9.0"
18741+
"@webassemblyjs/helper-module-context" "1.9.0"
18742+
"@webassemblyjs/wasm-edit" "1.9.0"
18743+
"@webassemblyjs/wasm-parser" "1.9.0"
18744+
acorn "^6.4.1"
18745+
ajv "^6.10.2"
18746+
ajv-keywords "^3.4.1"
18747+
chrome-trace-event "^1.0.2"
18748+
enhanced-resolve "^4.1.0"
18749+
eslint-scope "^4.0.3"
18750+
json-parse-better-errors "^1.0.2"
18751+
loader-runner "^2.4.0"
18752+
loader-utils "^1.2.3"
18753+
memory-fs "^0.4.1"
18754+
micromatch "^3.1.10"
18755+
mkdirp "^0.5.3"
18756+
neo-async "^2.6.1"
18757+
node-libs-browser "^2.2.1"
18758+
schema-utils "^1.0.0"
18759+
tapable "^1.1.3"
18760+
terser-webpack-plugin "^1.4.3"
18761+
watchpack "^1.6.1"
18762+
webpack-sources "^1.4.1"
18763+
18764+
webpack@4.44.2:
1873118765
version "4.44.2"
1873218766
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.44.2.tgz#6bfe2b0af055c8b2d1e90ed2cd9363f841266b72"
1873318767
integrity sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==

0 commit comments

Comments
 (0)