feat: remove dark mode toggle, switch to prefers-color-scheme

This means it's not possible to manually change the theme, but all major operating systems
support global dark mode now, so it shouldn't be a huge problem.
Will re-add the dark mode toggle if the Sec-CH-Prefers-Color-Scheme header gets added to Firefox and Safari.
This commit is contained in:
sam 2024-09-25 15:14:48 +02:00
parent 862a64840e
commit 0f3ab19f6f
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
9 changed files with 25 additions and 64 deletions

View file

@ -1,20 +1,7 @@
$font-family-sans-serif: @use "bootstrap/scss/bootstrap" with (
"FiraGO", $color-mode-type: media-query,
system-ui, $font-family-sans-serif: ("FiraGO", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"),
-apple-system, );
"Segoe UI",
Roboto,
"Helvetica Neue",
"Noto Sans",
"Liberation Sans",
Arial,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji" !default;
@import "bootstrap/scss/bootstrap";
@import "@fontsource/firago/400.css"; @import "@fontsource/firago/400.css";
@import "@fontsource/firago/400-italic.css"; @import "@fontsource/firago/400-italic.css";

View file

@ -3,9 +3,9 @@ import { Nav, Navbar } from "react-bootstrap";
import { Link } from "@remix-run/react"; import { Link } from "@remix-run/react";
import Logo from "~/components/nav/Logo"; import Logo from "~/components/nav/Logo";
export default function BaseNavbar({ children, theme }: { children?: ReactNode; theme: string }) { export default function BaseNavbar({ children }: { children?: ReactNode; }) {
return ( return (
<Navbar expand="lg" className={`mb-4 mx-2 bg-${theme}`} color={theme} variant={theme}> <Navbar expand="lg" className={`mb-4 mx-2`}>
<Navbar.Brand to="/" as={Link}> <Navbar.Brand to="/" as={Link}>
<Logo /> <Logo />
</Navbar.Brand> </Navbar.Brand>

View file

@ -1,19 +1,16 @@
import { Link, useFetcher } from "@remix-run/react"; import { Link, useFetcher } from "@remix-run/react";
import Meta from "~/lib/api/meta"; import Meta from "~/lib/api/meta";
import { User, UserSettings } from "~/lib/api/user"; import { User } from "~/lib/api/user";
import { Nav, NavDropdown } from "react-bootstrap"; import { Nav, NavDropdown } from "react-bootstrap";
import { BrightnessHigh, BrightnessHighFill, MoonFill } from "react-bootstrap-icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import BaseNavbar from "~/components/nav/BaseNavbar"; import BaseNavbar from "~/components/nav/BaseNavbar";
export default function MainNavbar({ export default function MainNavbar({
user, user,
settings,
}: { }: {
meta: Meta; meta: Meta;
user?: User; user?: User;
settings: UserSettings;
}) { }) {
const fetcher = useFetcher(); const fetcher = useFetcher();
const { t } = useTranslation(); const { t } = useTranslation();
@ -39,38 +36,9 @@ export default function MainNavbar({
</Nav.Link> </Nav.Link>
); );
const ThemeIcon =
settings.dark_mode === null
? BrightnessHigh
: settings.dark_mode
? MoonFill
: BrightnessHighFill;
const theme = settings.dark_mode ? "dark" : "light";
return ( return (
<BaseNavbar theme={theme}> <BaseNavbar>
{userMenu} {userMenu}
<fetcher.Form method="POST" action="/dark-mode">
<NavDropdown
title={
<>
<ThemeIcon /> {t("navbar.theme")}
</>
}
align="end"
>
<NavDropdown.Item as="button" name="theme" value="auto" type="submit">
{t("navbar.theme-auto")}
</NavDropdown.Item>
<NavDropdown.Item as="button" name="theme" value="dark" type="submit">
{t("navbar.theme-dark")}
</NavDropdown.Item>
<NavDropdown.Item as="button" name="theme" value="light" type="submit">
{t("navbar.theme-light")}
</NavDropdown.Item>
</NavDropdown>
</fetcher.Form>
</BaseNavbar> </BaseNavbar>
); );
} }

View file

@ -57,7 +57,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
}; };
export function Layout({ children }: { children: ReactNode }) { export function Layout({ children }: { children: ReactNode }) {
const { locale, settings } = useRouteLoaderData<typeof loader>("root") || { const { locale } = useRouteLoaderData<typeof loader>("root") || {
meta: { meta: {
users: { users: {
total: 0, total: 0,
@ -77,7 +77,6 @@ export function Layout({ children }: { children: ReactNode }) {
return ( return (
<html <html
lang={locale || "en"} lang={locale || "en"}
data-bs-theme={settings?.dark_mode ? "dark" : "light"}
dir={i18n.dir()} dir={i18n.dir()}
> >
<head> <head>
@ -119,10 +118,10 @@ export function ErrorBoundary() {
<Links /> <Links />
</head> </head>
<body> <body>
{data?.meUser && data?.settings && data?.meta ? ( {data?.meUser && data?.meta ? (
<Navbar meta={data.meta} user={data.meUser} settings={data.settings} /> <Navbar meta={data.meta} user={data.meUser} />
) : ( ) : (
<BaseNavbar theme="light" /> <BaseNavbar />
)} )}
<Container>{errorElem}</Container> <Container>{errorElem}</Container>
<Scripts /> <Scripts />

View file

@ -13,7 +13,7 @@ import PronounLink from "~/components/PronounLink";
export const meta: MetaFunction<typeof loader> = ({ data }) => { export const meta: MetaFunction<typeof loader> = ({ data }) => {
const { user } = data!; const { user } = data!;
return [{ title: `@${user.username} - pronouns.cc` }]; return [{ title: `@${user.username} pronouns.cc` }];
}; };
export const loader = async ({ request, params }: LoaderFunctionArgs) => { export const loader = async ({ request, params }: LoaderFunctionArgs) => {

View file

@ -21,7 +21,7 @@ import ErrorAlert from "~/components/ErrorAlert";
import i18n from "~/i18next.server"; import i18n from "~/i18next.server";
export const meta: MetaFunction<typeof loader> = ({ data }) => { export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [{ title: `${data?.meta.title || "Log in"} - pronouns.cc` }]; return [{ title: `${data?.meta.title || "Log in"} pronouns.cc` }];
}; };
export const shouldRevalidate: ShouldRevalidateFunction = ({ actionResult }) => { export const shouldRevalidate: ShouldRevalidateFunction = ({ actionResult }) => {

View file

@ -21,7 +21,7 @@ import ErrorAlert from "~/components/ErrorAlert";
import { User } from "~/lib/api/user"; import { User } from "~/lib/api/user";
export const meta: MetaFunction<typeof loader> = ({ data }) => { export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [{ title: `${data?.meta.title || "Log in"} - pronouns.cc` }]; return [{ title: `${data?.meta.title || "Log in"} pronouns.cc` }];
}; };
export const shouldRevalidate: ShouldRevalidateFunction = ({ actionResult }) => { export const shouldRevalidate: ShouldRevalidateFunction = ({ actionResult }) => {

View file

@ -7,7 +7,7 @@ import { Link, useLoaderData } from "@remix-run/react";
import { Button } from "react-bootstrap"; import { Button } from "react-bootstrap";
export const meta: MetaFunction<typeof loader> = ({ data }) => { export const meta: MetaFunction<typeof loader> = ({ data }) => {
return [{ title: `${data?.meta.title || "Welcome"} - pronouns.cc` }]; return [{ title: `${data?.meta.title || "Welcome"} pronouns.cc` }];
}; };
export const loader = async ({ request }: LoaderFunctionArgs) => { export const loader = async ({ request }: LoaderFunctionArgs) => {

View file

@ -4,6 +4,13 @@ import serverRequest, { getCookie, writeCookie } from "~/lib/request.server";
// Handles theme switching // Handles theme switching
// Remix itself handles redirecting back to the original page after the setting is set // Remix itself handles redirecting back to the original page after the setting is set
//
// Note: this function is currently unused. Bootstrap only lets us switch themes with either prefers-color-scheme
// *or* a programmatic switch using data-bs-theme, not both.
// If the Sec-CH-Prefers-Color-Scheme header (https://caniuse.com/mdn-http_headers_sec-ch-prefers-color-scheme)
// is added to Firefox and Safari, the dark mode setting should be reworked to use it instead.
// As it stands, using prefers-color-scheme is the only way
// to respect the operating system's dark mode setting without using JavaScript.
export const action: ActionFunction = async ({ request }) => { export const action: ActionFunction = async ({ request }) => {
const body = await request.formData(); const body = await request.formData();
const theme = (body.get("theme") as string | null) || "auto"; const theme = (body.get("theme") as string | null) || "auto";
@ -13,7 +20,7 @@ export const action: ActionFunction = async ({ request }) => {
await serverRequest<UserSettings>("PATCH", "/users/@me/settings", { await serverRequest<UserSettings>("PATCH", "/users/@me/settings", {
token, token,
body: { body: {
dark_mode: theme === "auto" ? null : theme === "dark" ? true : false, dark_mode: theme === "auto" ? null : theme === "dark",
}, },
}); });