Skip to content

Commit

Permalink
Implement a colour theme selector 🌑.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmarty committed Apr 24, 2024
1 parent 461214a commit 644c487
Show file tree
Hide file tree
Showing 19 changed files with 190 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/components/ColourSwatch.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const ColourSwatch = ({ colour }) => (
<span
className="inline-block h-6 w-8 rounded border border-slate-700"
className="inline-block h-6 w-8 rounded border border-slate-600 dark:border-slate-400"
style={{ backgroundColor: colour }}
/>
);
Expand Down
6 changes: 3 additions & 3 deletions src/components/DropZone.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
const DropZone = ({ children, isDragActive, isDragReject, errorCode }) => {
let label = 'Drag a PRG or a ROM file here.';
let className =
'text-slate-600 bg-slate-300 outline-slate-400 outline-offset-[-.75rem]';
'text-slate-600 bg-slate-300 dark:text-slate-400 dark:bg-slate-700 outline-slate-400 outline-offset-[-.75rem]';
if (isDragActive) {
label = 'Drop it here!';
className =
'text-blue-600 bg-blue-200 outline-blue-400 outline-offset-[-1.5rem]';
'text-blue-600 bg-blue-200 dark:text-blue-400 dark:bg-blue-800 outline-blue-400 outline-offset-[-1.5rem]';
}
if (isDragReject) {
label = 'Only one file please!';
className =
'text-rose-600 bg-rose-200 outline-rose-400 outline-offset-[-1.5rem]';
'text-rose-600 bg-rose-200 dark:text-red-400 dark:bg-red-800 outline-rose-400 outline-offset-[-1.5rem]';
}

let errorMsg = null;
Expand Down
6 changes: 3 additions & 3 deletions src/components/Footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,17 @@ const navigation = [

export default function Footer() {
return (
<footer className="bg-slate-900">
<footer className="bg-slate-200 dark:bg-black">
<div className="mx-auto flex max-w-7xl items-center justify-between gap-x-2 px-3 py-2 md:px-4">
<p className="font-geohumanist text-balance text-center text-xs text-slate-400">
<p className="text-balance text-center font-geohumanist text-xs text-slate-500">
&copy; 2024 SCUMM NES resource explorer
</p>
<div className="flex gap-x-2 sm:gap-x-4 md:gap-x-6">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="fill-transparent stroke-slate-400 transition-all hover:stroke-slate-200"
className="fill-transparent stroke-slate-500 transition-all hover:stroke-slate-800 hover:dark:stroke-slate-200"
rel="me">
<span className="sr-only">{item.name}</span>
<item.icon
Expand Down
45 changes: 24 additions & 21 deletions src/components/Header.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { Dialog } from '@headlessui/react';
import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline';
import {
Cog8ToothIcon,
Bars3Icon,
XMarkIcon,
} from '@heroicons/react/24/outline';
import meteor from '../assets/meteor.png';

const navigation = [
{ name: 'Rooms', href: '/rooms/1' },
{ name: 'Room Gfx', href: '/roomgfx/0' },
{ name: 'Prepositions', href: '/preps' },
{ name: 'ROM map', href: '/rom-map' },
{ name: 'Settings', href: '/settings', sideBarOnly: true },
];

export default function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);

return (
<header className="bg-slate-900">
<header className="bg-slate-200 dark:bg-black">
<nav
className="mx-auto flex max-w-7xl items-center justify-between px-3 py-2 md:px-4"
aria-label="Global">
Expand All @@ -41,26 +46,24 @@ export default function Header() {
</button>
</div>
<div className="hidden md:flex md:gap-x-12">
{navigation.map((item) => (
<Link
key={item.name}
to={item.href}
className="text-sm font-semibold leading-6 text-white">
{item.name}
</Link>
))}
{navigation
.filter(({ sideBarOnly }) => !sideBarOnly)
.map((item) => (
<Link
key={item.name}
to={item.href}
className="text-sm font-semibold leading-6 text-slate-700 dark:text-slate-300">
{item.name}
</Link>
))}
</div>
<div className="hidden md:flex md:flex-1 md:justify-end">
{process.env.NODE_ENV === 'development' && (
<div className="text-slate-400">
<span className="hidden max-sm:inline">XS</span>
<span className="hidden sm:max-md:inline">SM</span>
<span className="hidden md:max-lg:inline">MD</span>
<span className="hidden lg:max-xl:inline">LG</span>
<span className="hidden xl:max-2xl:inline">XL</span>
<span className="hidden 2xl:inline">2XL</span>
</div>
)}
<Link to="/settings">
<Cog8ToothIcon
strokeWidth="1.5"
className="size-6 text-slate-500 transition hover:text-slate-800 hover:dark:text-slate-200"
/>
</Link>
</div>
</nav>
<Dialog
Expand All @@ -84,7 +87,7 @@ export default function Header() {
<span className="sr-only">Close menu</span>
<XMarkIcon
strokeWidth="2"
className="h-6 w-6"
className="size-6"
aria-hidden="true"
/>
</button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/MainHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const MainHeader = ({ title, children }) => {
return (
<div className="flex justify-between gap-4">
{title && (
<h1 className="whitespace-nowrap text-2xl font-semibold text-slate-700 md:text-3xl">
<h1 className="whitespace-nowrap text-2xl font-semibold text-slate-700 md:text-3xl dark:text-slate-300">
{title}
</h1>
)}
Expand Down
8 changes: 4 additions & 4 deletions src/components/ResourceMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const ResourceMetadata = ({ metadata }) => {
const compressionRow = metadata.decompressedSize && (
<>
<div className="font-bold">unpacked size:</div>
<div className="text-right font-mono">{metadata.decompressedSize}</div>
<div className="font-mono text-right">{metadata.decompressedSize}</div>
<div className="font-mono opacity-50">
({formatPercentage(1 - metadata.size / metadata.decompressedSize)}{' '}
<span className="font-sans"> compr.</span>)
Expand All @@ -14,12 +14,12 @@ const ResourceMetadata = ({ metadata }) => {
);

return (
<div className="font-monocode grid w-max grid-cols-[auto_auto_auto] gap-x-2 gap-y-1 whitespace-nowrap rounded bg-slate-200 p-2 text-xs text-slate-700">
<div className="grid w-max grid-cols-[auto_auto_auto] gap-x-2 gap-y-1 whitespace-nowrap rounded bg-slate-200 p-2 font-monocode text-xs text-slate-700 dark:bg-slate-800 dark:text-slate-300">
<div className="font-bold">payload offset:</div>
<div className="text-right font-mono">0x{hex(metadata.offset)}</div>
<div className="font-mono text-right">0x{hex(metadata.offset)}</div>
<div className="font-mono opacity-50">({metadata.offset})</div>
<div className="font-bold">payload size:</div>
<div className="text-right font-mono">{metadata.size} B</div>
<div className="font-mono text-right">{metadata.size} B</div>
<div className="font-mono opacity-50">{sizeInB}</div>
{compressionRow}
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/components/RomMapTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { formatBytes, formatPercentage } from '../lib/utils';
const RomMapTable = ({ resourceSizes }) => {
return (
<section>
<table className="w-[512] max-w-full table-auto text-xs md:text-sm">
<table className="w-[512] max-w-full table-auto text-xs text-slate-900 md:text-sm dark:text-slate-100">
<thead>
<tr className="bg-slate-500 text-white">
<tr className="bg-slate-300 text-sm md:text-base dark:bg-slate-700">
<th className="px-4 py-1 text-left font-normal">Resource</th>
<th className="px-4 py-1 text-right font-normal">Size</th>
<th className="px-4 py-1 text-right font-normal">%</th>
Expand All @@ -15,7 +15,7 @@ const RomMapTable = ({ resourceSizes }) => {
{resourceSizes.map(({ label, size, percentage }) => (
<tr
key={label}
className="even:bg-slate-100">
className="even:bg-slate-200 even:dark:bg-slate-800">
<td className="px-4 first-letter:capitalize">{label}</td>
<td className="whitespace-nowrap px-4 py-1 text-right">
<span className="font-monocode">{formatBytes(size)}</span>
Expand Down
4 changes: 2 additions & 2 deletions src/components/RoomTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const RoomTabs = ({ currentTab, setCurrentTab }) => {
onClick={() => setCurrentTab(tab.name)}
className={clsx(
tab.current
? 'bg-slate-200 text-slate-700'
: 'text-slate-500 hover:text-slate-700',
? 'bg-slate-200 text-slate-700 dark:bg-slate-800 dark:text-slate-300'
: 'text-slate-500 hover:text-slate-700 hover:dark:text-slate-300',
'rounded px-3 py-2 text-sm font-medium',
)}
aria-current={tab.current ? 'page' : undefined}>
Expand Down
39 changes: 39 additions & 0 deletions src/components/ThemeSwitcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RadioGroup } from '@headlessui/react';
import { clsx } from 'clsx';

const ThemeSwitcher = ({ theme, setTheme, themeOptions }) => {
return (
<>
<h2 className="text-xl font-semibold leading-6 text-slate-700 md:text-2xl dark:text-slate-300">
Colour theme
</h2>

<RadioGroup
value={theme}
onChange={setTheme}>
<RadioGroup.Label className="sr-only">Choose a theme</RadioGroup.Label>
<div className="flex justify-center gap-4 md:gap-5 xl:gap-6">
{themeOptions.map((option) => (
<RadioGroup.Option
key={option.name}
value={option}
className={({ active, checked }) =>
clsx(
'cursor-pointer focus:outline-none',
active && 'ring-2 ring-primary-600 ring-offset-2',
checked
? 'bg-primary-600 text-slate-100 hover:bg-primary-500'
: 'bg-slate-200 text-slate-900 ring-1 ring-inset ring-slate-300 hover:bg-slate-100 dark:bg-slate-800 dark:text-slate-100 dark:ring-slate-700 hover:dark:bg-slate-900',
'flex w-48 max-w-48 flex-initial items-center justify-center rounded-md px-3 py-3 text-sm font-semibold sm:flex-1',
)
}>
<RadioGroup.Label as="span">{option.name}</RadioGroup.Label>
</RadioGroup.Option>
))}
</div>
</RadioGroup>
</>
);
};

export default ThemeSwitcher;
2 changes: 1 addition & 1 deletion src/containers/DropZoneContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const DropZoneContainer = ({ onFile }) => {
return (
<Main>
<div
className="h-full w-full"
className="h-full w-full p-4"
{...getRootProps()}>
<DropZone
isDragActive={isDragActive}
Expand Down
2 changes: 1 addition & 1 deletion src/containers/GfxCanvasContainer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useState, useEffect } from 'react';
import clsx from 'clsx';
import { clsx } from 'clsx';

// See https://colorhunt.co/palette/293462f24c4cec9b3bf7d716
const ROOM_PALETTE = ['#293462', '#F24C4C', '#EC9B3B', '#F7D716'];
Expand Down
5 changes: 5 additions & 0 deletions src/containers/ResourceExplorer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import RoomsContainer from './RoomsContainer';
import RoomGfxContainer from './RoomGfxContainer';
import PrepositionsContainer from './PrepositionsContainer';
import RomMapContainer from './RomMapContainer';
import SettingsContainer from './SettingsContainer';

const ResourceExplorer = ({ rom, res, resources }) => {
if (!resources) {
Expand Down Expand Up @@ -55,6 +56,10 @@ const ResourceExplorer = ({ rom, res, resources }) => {
/>
}
/>
<Route
path="/settings"
element={<SettingsContainer />}
/>
</Routes>
);
};
Expand Down
4 changes: 2 additions & 2 deletions src/containers/RomMapCanvasContainer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useState, useEffect } from 'react';
import clsx from 'clsx';
import { clsx } from 'clsx';
import { getResourceColour } from '../lib/resourceUtils';

const WIDTH = 512;
Expand All @@ -26,7 +26,7 @@ const RomMapCanvasContainer = ({ rom, res, resourceList }) => {
width={WIDTH}
height={HEIGHT}
className={clsx(
'aspect-[4/3] w-full rounded border border-slate-700',
'aspect-[4/3] w-full rounded border border-slate-600 dark:border-slate-400',
isComputing ? 'opacity-0' : 'opacity-100 transition-opacity',
)}
style={{ maxWidth: WIDTH }}
Expand Down
2 changes: 1 addition & 1 deletion src/containers/RoomCanvasContainer.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useState, useEffect } from 'react';
import clsx from 'clsx';
import { clsx } from 'clsx';
import { nesNTSCPalette as nesPalette } from '../lib/palettes';

const RoomCanvasContainer = ({
Expand Down
48 changes: 48 additions & 0 deletions src/containers/SettingsContainer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useState } from 'react';
import Main from '../components/Main';
import MainHeader from '../components/MainHeader';
import ThemeSwitcher from '../components/ThemeSwitcher';
import { setColourTheme } from '../lib/colourThemeUtils';

const themeOptions = [
{ name: 'Dark', value: 'dark' },
{ name: 'System', value: 'system', defaultTheme: true },
{ name: 'Light', value: 'light' },
];

const SettingsContainer = () => {
// Find the selected theme, or the default one.
const selectedThemeValue = localStorage.getItem('theme');
const selectedTheme =
themeOptions.find(({ value }) => value === selectedThemeValue) ||
themeOptions.find(({ defaultTheme }) => defaultTheme);

const [theme, setTheme] = useState(selectedTheme);

// Keep the local storage and the DOM in sync with the state.
const setThemeWrapper = (theme) => {
setTheme(theme);

if (theme.defaultTheme) {
localStorage.removeItem('theme');
setColourTheme();
return;
}

localStorage.setItem('theme', theme.value);
setColourTheme(theme.value);
};

return (
<Main>
<MainHeader title="Settings" />
<ThemeSwitcher
theme={theme}
themeOptions={themeOptions}
setTheme={setThemeWrapper}
/>
</Main>
);
};

export default SettingsContainer;
24 changes: 23 additions & 1 deletion src/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,32 @@
@tailwind components;
@tailwind utilities;

/* With color-scheme the UA will style some elements appropriately. */
:root {
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
}
}
.light {
color-scheme: light;
}
.dark {
color-scheme: dark;
}
/* Transition color scheme change. */
html {
transition-duration: 150ms;
transition-property: color, background-color;
}

html {
@apply font-neogrote;
@apply font-normal;
@apply bg-white;
@apply bg-slate-50;
@apply dark:bg-slate-900;
}

canvas {
Expand Down
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import { setColourTheme } from './lib/colourThemeUtils';

const basename =
process.env.NODE_ENV === 'development' ? undefined : '/scumm-nes';
Expand All @@ -12,6 +13,8 @@ root.render(
</BrowserRouter>,
);

setColourTheme(localStorage.getItem('theme'));

if (process.env.NODE_ENV === 'production' && insights) {
// Load the analytics.
insights.init('OTy7QFoUv4bUuKzo');
Expand Down
Loading

0 comments on commit 644c487

Please sign in to comment.