Skip to content

Commit

Permalink
Merge pull request #15 from jonyw4/feat/add-theme-switch
Browse files Browse the repository at this point in the history
Feat/add theme switch
  • Loading branch information
jonyw4 authored Nov 24, 2021
2 parents ed4d32c + 23ca86c commit 179725a
Show file tree
Hide file tree
Showing 25 changed files with 409 additions and 27 deletions.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
},
"devDependencies": {
"@babel/core": "7.16.0",
"@playwright/test": "1.14.1",
"@storybook/addon-actions": "6.3.12",
"@storybook/addon-essentials": "6.3.12",
"@storybook/addon-links": "6.3.12",
Expand All @@ -25,12 +26,14 @@
"babel-loader": "8.2.3",
"eslint": "<8.0.0",
"jest": "27.3.1",
"jest-ts-auto-mock": "^2.0.0",
"lerna": "4.0.0",
"markdown-spellcheck": "1.3.1",
"markdownlint-cli": "0.29.0",
"ts-auto-mock": "^3.5.0",
"ts-jest": "27.0.7",
"typescript": "4.4.4",
"@playwright/test": "1.14.1"
"ttypescript": "^1.5.12",
"typescript": "4.4.4"
},
"dependencies": {}
}
11 changes: 9 additions & 2 deletions webapp/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
transform: {
".(ts|tsx)": "ts-jest"
},
globals: {
"ts-jest": {
compiler: "ttypescript",
},
},
setupFiles: ["<rootDir>tests/config.ts"],
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useLocale } from "../../global";
export function LanguageSelect() {
const { changeLocale, locale } = useLocale();
return (
<div>
<div style={{marginBottom: '0.5em'}}>
<label htmlFor="language-select">🌐 Language</label>
<select
id="language-select"
Expand Down
33 changes: 33 additions & 0 deletions webapp/src/components/atoms/ThemeRadio/ThemeRadio.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Theme } from "../../../domain";
import { useTheme } from "../../global";

export function ThemeRadio() {
const {theme, changeTheme} = useTheme();

const isLight = theme === "light";
const isDark = theme === "dark";

return (
<div style={{ display: "flex" }}>
<label htmlFor="theme-light-radio">☀️ Light Theme</label>
<input
id="theme-light-radio"
type="radio"
name="theme"
value="light"
checked={isLight}
onClick={() => changeTheme("light")}
/>

<label htmlFor="theme-dark-radio">🌒 Dark Theme</label>
<input
id="theme-dark-radio"
type="radio"
name="theme"
value="dark"
checked={isDark}
onClick={() => changeTheme("dark")}
/>
</div>
);
}
30 changes: 30 additions & 0 deletions webapp/src/components/global/UIProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { Theme } from "../../domain";
import { Locale, LocaleContext } from "./locale";
import { ThemeDataContext } from "./theme";

export interface UIProviderProps {
locale: Locale;
children: React.ReactNode;
themeData: {
initialTheme: Theme;
callback: (theme: Theme) => void;
};
}

export function UIProvider({ locale, children, themeData }: UIProviderProps) {
const [theme, setTheme] = React.useState<Theme>(themeData.initialTheme);

const changeTheme = (newTheme: Theme) => {
themeData.callback(newTheme);
setTheme(newTheme);
}

return (
<LocaleContext.Provider value={locale}>
<ThemeDataContext.Provider value={{ theme, changeTheme }}>
{children}
</ThemeDataContext.Provider>
</LocaleContext.Provider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UIProviderProps } from "..";
import { ThemeRepository } from "../../../data";

export const adaptThemeRepositoryToThemeData = (
themeRepository: ThemeRepository
): UIProviderProps['themeData'] => {
return {
initialTheme: themeRepository.getTheme(),
callback: (theme) => themeRepository.changeTheme(theme),
};
};
1 change: 1 addition & 0 deletions webapp/src/components/global/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './adaptThemeRepositoryToThemeData';
2 changes: 2 additions & 0 deletions webapp/src/components/global/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './UIProvider';
export * from './formatDate';
export * from './locale';
export * from './theme';
19 changes: 13 additions & 6 deletions webapp/src/components/global/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@
--color-purple: #AA00FF;
--color-purple-darker: #311B92;
--color-white: #e9e9e9;
}
--color-pure-white: #fff;

body[data-theme='light'] {
--background-color: var(--color-white);
--text-color: var(--color-black);
--link-color: var(--color-purple);
--link-visited-color: var(--color-purple-darker);
--selection-color: var(--color-grape);
}

body[data-theme='dark'] {
body.dark-mode {
--background-color: var(--color-black);
--text-color: var(--color-white);
--link-color: var(--color-purple);
Expand All @@ -39,11 +38,11 @@ body {
font-weight: 400;
font-size: 1.25rem;
letter-spacing: 0.5px;
color: var(--text-color);
background-color: var(--background-color);
padding: 0px 1rem;
max-width: 600px;
margin: 0 auto !important;
color: var(--text-color);
background-color: var(--background-color);
}

a {
Expand Down Expand Up @@ -127,7 +126,11 @@ article img {
margin: 3em auto;
display: block;
width: auto;
max-width: 400px
max-width: 400px;
background: var(--color-pure-white);
padding: 1em;
border: 1px solid #dfdede;
border-radius: 0.2em;
}

label, select {
Expand All @@ -136,4 +139,8 @@ label, select {

label {
margin-right: 0.5em;
}

[type="radio"] {
margin-right: 1em;
}
12 changes: 12 additions & 0 deletions webapp/src/components/global/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import { Theme } from "../../domain";


export interface ThemeData {
theme: Theme;
changeTheme: (theme: Theme) => void;
}

// @ts-ignore
export const ThemeDataContext = React.createContext<ThemeData>();
export const useTheme = () => React.useContext(ThemeDataContext);
9 changes: 5 additions & 4 deletions webapp/src/components/organisms/Footer/Footer.component.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
export function Footer(){
import { LanguageSelect } from "../../atoms/LanguageSelect/LanguageSelect.component";
export function Footer() {
return (
<footer>
<LanguageSelect />
<small>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">
CC BY-NC-SA 4.0 2021
</a>
{" "}
</a>{" "}
© Jonathan Celio
</small>
</footer>
);
}
}
4 changes: 2 additions & 2 deletions webapp/src/components/organisms/Header/Header.component.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ProfileCard } from '../ProfileCard'
import { LanguageSelect } from '../../atoms/LanguageSelect/LanguageSelect.component';
import { ThemeRadio } from "../../atoms/ThemeRadio/ThemeRadio.component";

export function Header(){
return (
<header>
<ProfileCard />
<LanguageSelect />
<ThemeRadio />
</header>
);
}
9 changes: 9 additions & 0 deletions webapp/src/components/organisms/Layout/Layout.component.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Header } from '../Header'
import { Footer } from "../Footer";
import { LayoutProps } from './Layout.props';
import { useTheme } from '../../global';
import React from 'react';

export function Layout({ children }: LayoutProps) {
const { theme } = useTheme();

React.useEffect(() => {
const themeClassName = `${theme}-mode`;
document.querySelector('body').className = themeClassName;
}, [theme])

return (
<>
<Header />
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/data/ThemeRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Theme } from "../domain/Theme";

export interface ThemeRepository {
getTheme(): Theme;
changeTheme(theme: Theme): void;
}
1 change: 1 addition & 0 deletions webapp/src/data/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './ArticleRepository';
export * from './ThemeRepository';
1 change: 1 addition & 0 deletions webapp/src/domain/Theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Theme = 'dark' | 'light';
1 change: 1 addition & 0 deletions webapp/src/domain/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './Article';
export * from './ArticleMetadata';
export * from './Theme';
24 changes: 24 additions & 0 deletions webapp/src/infra/ThemeLocalStorageRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ThemeRepository } from "../data";
import { Theme } from "../domain";

const LOCAL_STORAGE_THEME_KEY = "theme";
const DEFAULT_THEME = "light";

export class ThemeLocalStorageRepository implements ThemeRepository {
getTheme(): Theme {
if(!global.localStorage){
return DEFAULT_THEME;
}

const theme = global.localStorage.getItem(LOCAL_STORAGE_THEME_KEY) as Theme;

if(!theme){
return DEFAULT_THEME;
}

return theme;
}
changeTheme(theme: Theme): void {
global.localStorage.setItem(LOCAL_STORAGE_THEME_KEY, theme);
}
}
1 change: 1 addition & 0 deletions webapp/src/infra/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './ArticleFileSystemRepository';
export * from './ThemeLocalStorageRepository';
11 changes: 8 additions & 3 deletions webapp/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import "normalize.css";
import "../components/global/styles/global.css";
import { LocaleContext } from "../components/global";
import { UIProvider } from "../components/global";
import { useRouter } from "next/router";
import type { AppProps } from "next/app";
import { ThemeLocalStorageRepository } from "../infra/ThemeLocalStorageRepository";
import { adaptThemeRepositoryToThemeData } from "../components/global/adapters";

export default function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
Expand All @@ -12,9 +14,12 @@ export default function MyApp({ Component, pageProps }: AppProps) {
router.push({ pathname, query }, asPath, { locale: nextLocale });
};

const themeRepository = new ThemeLocalStorageRepository();
const themeData = adaptThemeRepositoryToThemeData(themeRepository)

return (
<LocaleContext.Provider value={{ locale: locale, changeLocale }}>
<UIProvider themeData={themeData} locale={{ locale, changeLocale }}>
<Component {...pageProps} />
</LocaleContext.Provider>
</UIProvider>
);
}
2 changes: 1 addition & 1 deletion webapp/src/pages/_document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class MyDocument extends Document {
rel="stylesheet"
/>
</Head>
<body data-theme="dark">
<body>
<Main />
<NextScript />
</body>
Expand Down
1 change: 1 addition & 0 deletions webapp/tests/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "jest-ts-auto-mock";
Loading

1 comment on commit 179725a

@vercel
Copy link

@vercel vercel bot commented on 179725a Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.