Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
<title>Word Game</title>
<link rel="stylesheet" href="../src/reset.css" />
<link rel="stylesheet" href="../src/styles.css" />
<script>
(function () {
try {
var stored = window.localStorage.getItem('theme');
var theme =
stored === 'light' || stored === 'dark'
? stored
: window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
document.documentElement.setAttribute('data-theme', theme);
} catch (e) {}
})();
</script>
<script type="module" src="../src/index.js"></script>
</head>
<body>
Expand Down
5 changes: 5 additions & 0 deletions src/components/Header/Header.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React from 'react';
import ThemeToggle from '../ThemeToggle';

function Header() {
return (
<header>
<div className="side" />
<h1>Word Game</h1>
<div className="side">
<ThemeToggle />
</div>
</header>
);
}
Expand Down
41 changes: 41 additions & 0 deletions src/components/ThemeToggle/ThemeToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

const STORAGE_KEY = 'theme';

function getInitialTheme() {
const stored = window.localStorage.getItem(STORAGE_KEY);
if (stored === 'light' || stored === 'dark') {
return stored;
}

const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
return prefersDark ? 'dark' : 'light';
}

function ThemeToggle() {
const [theme, setTheme] = React.useState(getInitialTheme);

React.useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
window.localStorage.setItem(STORAGE_KEY, theme);
}, [theme]);

function toggleTheme() {
setTheme(prevTheme => (prevTheme === 'dark' ? 'light' : 'dark'));
}

const isDark = theme === 'dark';

return (
<button
className="theme-toggle"
onClick={toggleTheme}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
<span aria-hidden="true">{isDark ? '☀️' : '🌙'}</span>
</button>
);
}

export default ThemeToggle;
2 changes: 2 additions & 0 deletions src/components/ThemeToggle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ThemeToggle';
export { default } from './ThemeToggle';
53 changes: 48 additions & 5 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,40 @@ html {
--color-gray-500: hsl(0deg 0% 50%);
--color-gray-700: hsl(0deg 0% 75%);
--color-gray-900: hsl(0deg 0% 90%);

/* Light theme (default) */
--color-background: hsl(0deg 0% 100%);
--color-surface: hsl(0deg 0% 96%);
--color-text: var(--color-gray-100);
--color-border: var(--color-gray-700);
--color-cell-incorrect: hsl(0deg 0% 25%);
}

html[data-theme='dark'] {
--color-success: hsl(150deg 60% 40%);
--color-warning: hsl(50deg 90% 45%);
--color-error: hsl(0deg 65% 55%);
--color-gray-100: hsl(0deg 0% 90%);
--color-gray-300: hsl(0deg 0% 65%);
--color-gray-500: hsl(0deg 0% 50%);
--color-gray-700: hsl(0deg 0% 35%);
--color-gray-900: hsl(0deg 0% 20%);

--color-background: hsl(220deg 15% 10%);
--color-surface: hsl(220deg 15% 16%);
--color-text: hsl(0deg 0% 92%);
--color-border: var(--color-gray-700);
--color-cell-incorrect: hsl(0deg 0% 35%);
}

/*
Custom styles
*/
body {
font-family: sans-serif;
background: var(--color-background);
color: var(--color-text);
transition: background-color 150ms ease-in-out, color 150ms ease-in-out;
}

@media (max-height: 600px) {
Expand Down Expand Up @@ -49,10 +76,24 @@ button {
header {
display: flex;
height: var(--header-height);
border-bottom: 1px solid var(--color-gray-700);
border-bottom: 1px solid var(--color-border);
color: var(--color-gray-300);
}

.theme-toggle {
display: grid;
place-content: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
font-size: 1.25rem;
transition: background-color 150ms ease-in-out;
}
.theme-toggle:hover,
.theme-toggle:focus-visible {
background: var(--color-surface);
}

header .side {
width: var(--header-height);
display: grid;
Expand Down Expand Up @@ -102,7 +143,7 @@ h1 {
display: grid;
place-content: center;
aspect-ratio: 1 / 1;
border: 2px solid var(--color-gray-700);
border: 2px solid var(--color-border);
border-radius: var(--radius);
font-size: 2rem;
}
Expand Down Expand Up @@ -130,8 +171,8 @@ h1 {
color: white;
}
.cell.incorrect {
background: var(--color-gray-300);
border-color: var(--color-gray-300);
background: var(--color-cell-incorrect);
border-color: var(--color-cell-incorrect);
color: white;
}
.cell.misplaced {
Expand All @@ -155,10 +196,12 @@ h1 {
display: block;
width: 100%;
font-size: 2rem;
border: 2px solid var(--color-gray-300);
border: 2px solid var(--color-border);
border-radius: 4px;
padding: 8px 16px;
outline-offset: 4px;
background: var(--color-background);
color: var(--color-text);
}

.banner {
Expand Down