Skip to content

Commit

Permalink
feat(app): add theme switcher (#383)
Browse files Browse the repository at this point in the history
* feat(app): add theme switcher

* fix(app): fix ThemeProvider build error

* feat(app): add dark theme colors

* refactor(app): remove violet constant

* refactor(app): refactor ThemeProvider

Co-authored-by: Michał Miszczyszyn <michal@mmiszy.pl>
  • Loading branch information
xStrixU and typeofweb committed Dec 7, 2022
1 parent 8953fb2 commit 1674fc4
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 23 deletions.
13 changes: 8 additions & 5 deletions apps/app/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { AnalyticsWrapper } from "../components/analytics";
import { CtaHeader } from "../components/CtaHeader";
import { Header } from "../components/Header/Header";
import { Footer } from "../components/Footer";
import { AppProviders } from "../providers/AppProviders";

import "../styles/globals.css";

Expand All @@ -20,11 +21,13 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<html lang="pl" className={`${firaSans.variable} ${firaCode.variable}`}>
<body>
<Header />
<CtaHeader />
{children}
<AnalyticsWrapper />
<Footer />
<AppProviders>
<Header />
<CtaHeader />
{children}
<AnalyticsWrapper />
<Footer />
</AppProviders>
</body>
</html>
);
Expand Down
4 changes: 2 additions & 2 deletions apps/app/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { twMerge } from "tailwind-merge";
const variants = {
alert: "text-white font-bold bg-red-branding hover:bg-red-branding-dark disabled:bg-[#bfbfbf]",
branding:
"text-violet-700 border-violet-700 bg-transparent hover:bg-violet-50 focus:shadow-[0_0_10px] focus:shadow-violet-700",
"text-violet-700 dark:text-neutral-200 border-violet-700 dark:border-neutral-200 bg-transparent hover:bg-violet-50 hover:dark:bg-violet-900 focus:shadow-[0_0_10px] focus:shadow-primary",
brandingInverse:
"text-white border-white bg-violet-700 hover:bg-violet-200 focus:shadow-[0_0_10px] focus:shadow-white",
"text-white border-white bg-primary hover:bg-violet-200 hover:dark:bg-violet-700 focus:shadow-[0_0_10px] focus:shadow-white",
alternative:
"text-white border-white bg-yellow-branding hover:bg-yellow-branding-dark focus:shadow-[0_0_10px] focus:shadow-yellow-branding",
};
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/CtaHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ComponentProps, ReactNode } from "react";
import { ReactNode } from "react";
import { ActiveLink } from "./ActiveLink";
import { Button } from "./Button/Button";
import { Container } from "./Container";
Expand Down
23 changes: 23 additions & 0 deletions apps/app/src/components/Header/DarkModeSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

import { twMerge } from "tailwind-merge";
import { useThemeContext } from "../../providers/ThemeProvider";

export const DarkModeSwitch = () => {
const { theme, changeTheme } = useThemeContext();

return (
<input
type="checkbox"
role="switch"
aria-label="Switch between Dark and Light mode"
className={twMerge(
"relative h-6 w-10 cursor-pointer appearance-none rounded-full bg-violet-800 dark:bg-violet-700",
"before:absolute before:top-1 before:left-1 before:h-[16px] before:w-[16px] before:rounded-full before:bg-white before:transition-all before:duration-200",
"[&:checked]:before:left-[calc(100%-0.25rem)] [&:checked]:before:-translate-x-full",
)}
checked={theme === "dark"}
onChange={() => changeTheme(theme === "dark" ? "light" : "dark")}
/>
);
};
2 changes: 2 additions & 0 deletions apps/app/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Logo from "../../../public/devfaq-logo.svg";
import { Container } from "../Container";
import { HeaderNavigation } from "./HeaderNavigation";
import { ActiveNavigationLink } from "./ActiveNagivationLink";
import { DarkModeSwitch } from "./DarkModeSwitch";

export const Header = () => (
<div className="bg-primary">
Expand All @@ -22,6 +23,7 @@ export const Header = () => (
FaceBook
</a>
<ActiveNavigationLink href="#">Zaloguj</ActiveNavigationLink>
<DarkModeSwitch />
</HeaderNavigation>
</Container>
</div>
Expand Down
17 changes: 17 additions & 0 deletions apps/app/src/lib/createSafeContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createContext, useContext } from "react";

export const createSafeContext = <T>() => {
const context = createContext<T | undefined>(undefined);

const useSafeContext = () => {
const ctx = useContext(context);

if (ctx === undefined) {
throw new Error("useContext must be use inside Provider!");
}

return ctx;
};

return [useSafeContext, context.Provider] as const;
};
10 changes: 10 additions & 0 deletions apps/app/src/providers/AppProviders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ReactNode } from "react";
import { ThemeProvider } from "./ThemeProvider";

type AppProvidersProps = Readonly<{
children: ReactNode;
}>;

export const AppProviders = ({ children }: AppProvidersProps) => (
<ThemeProvider>{children}</ThemeProvider>
);
60 changes: 60 additions & 0 deletions apps/app/src/providers/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use client";

import { ReactNode, useEffect, useState } from "react";
import { createSafeContext } from "../lib/createSafeContext";

type Theme = "light" | "dark";

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

const [useThemeContext, ThemeContextProvider] = createSafeContext<ThemeContextValue>();

const getCurrentTheme = (): Theme => {
const localStorageTheme = window.localStorage.getItem("theme");

if (localStorageTheme === "light" || localStorageTheme === "dark") {
return localStorageTheme;
}

return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
};

const ThemeProvider = ({ children }: { readonly children: ReactNode }) => {
const [theme, setTheme] = useState<Theme>("light");

const changeTheme = (theme: Theme) => {
window.localStorage.setItem("theme", theme);

setTheme(theme);
};

useEffect(() => {
setTheme(getCurrentTheme());
}, []);

useEffect(() => {
const target = document.querySelector("html");

if (theme === "dark") {
target?.classList.add("dark");
} else {
target?.classList.remove("dark");
}
}, [theme]);

return (
<ThemeContextProvider
value={{
theme,
changeTheme,
}}
>
{children}
</ThemeContextProvider>
);
};

export { useThemeContext, ThemeProvider };
5 changes: 5 additions & 0 deletions apps/app/src/styles/globals.css
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
@import url("./tailwind.css");
@import url("./variables.css");

body {
@apply bg-gray-50 dark:bg-neutral-800;
}
7 changes: 7 additions & 0 deletions apps/app/src/styles/variables.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:root {
--primary: theme("colors.violet.700");
}

:root.dark {
--primary: theme("colors.violet.800");
}
30 changes: 15 additions & 15 deletions apps/app/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
const defaultTheme = require("tailwindcss/defaultTheme");

const violet = {
50: "oklch(95.27% 0.017 295)",
100: "oklch(87.14% 0.047 295)",
200: "oklch(85.48% 0.045 295)",
300: "oklch(78.29% 0.091 295)",
400: "oklch(68.26% 0.137 295)",
500: "oklch(58.95% 0.179 295)",
600: "oklch(50.69% 0.212 295)",
700: "oklch(47.42% 0.186 295)",
800: "oklch(34.61% 0.166 295)",
900: "oklch(33.84% 0.09 295)",
};

/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{ts,tsx}"],
darkMode: "class",
theme: {
extend: {
colors: {
primary: violet[700],
"white-dark": "#313131",
primary: "var(--primary)",
"red-branding": "oklch(58.07% 0.214 17)",
"red-branding-dark": "oklch(53.89% 0.198 17)",
"yellow-branding": "oklch(82.92% 0.17 80)",
"yellow-branding-dark": "oklch(78.94% 0.16 80)",
violet,
violet: {
50: "oklch(95.27% 0.017 295)",
100: "oklch(87.14% 0.047 295)",
200: "oklch(85.48% 0.045 295)",
300: "oklch(78.29% 0.091 295)",
400: "oklch(68.26% 0.137 295)",
500: "oklch(58.95% 0.179 295)",
600: "oklch(50.69% 0.212 295)",
700: "oklch(47.42% 0.186 295)",
800: "oklch(34.61% 0.166 295)",
900: "oklch(33.84% 0.09 295)",
},
},
fontFamily: {
sans: ["var(--font-fira-sans)", ...defaultTheme.fontFamily.sans],
Expand Down

0 comments on commit 1674fc4

Please sign in to comment.