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:
		
							parent
							
								
									862a64840e
								
							
						
					
					
						commit
						0f3ab19f6f
					
				
					 9 changed files with 25 additions and 64 deletions
				
			
		|  | @ -1,20 +1,7 @@ | |||
| $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" !default; | ||||
| 
 | ||||
| @import "bootstrap/scss/bootstrap"; | ||||
| @use "bootstrap/scss/bootstrap" with ( | ||||
| 	$color-mode-type: media-query, | ||||
| 	$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"), | ||||
| ); | ||||
| 
 | ||||
| @import "@fontsource/firago/400.css"; | ||||
| @import "@fontsource/firago/400-italic.css"; | ||||
|  |  | |||
|  | @ -3,9 +3,9 @@ import { Nav, Navbar } from "react-bootstrap"; | |||
| import { Link } from "@remix-run/react"; | ||||
| import Logo from "~/components/nav/Logo"; | ||||
| 
 | ||||
| export default function BaseNavbar({ children, theme }: { children?: ReactNode; theme: string }) { | ||||
| export default function BaseNavbar({ children }: { children?: ReactNode; }) { | ||||
| 	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}> | ||||
| 				<Logo /> | ||||
| 			</Navbar.Brand> | ||||
|  |  | |||
|  | @ -1,19 +1,16 @@ | |||
| import { Link, useFetcher } from "@remix-run/react"; | ||||
| 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 { BrightnessHigh, BrightnessHighFill, MoonFill } from "react-bootstrap-icons"; | ||||
| import { useTranslation } from "react-i18next"; | ||||
| import BaseNavbar from "~/components/nav/BaseNavbar"; | ||||
| 
 | ||||
| export default function MainNavbar({ | ||||
| 	user, | ||||
| 	settings, | ||||
| }: { | ||||
| 	meta: Meta; | ||||
| 	user?: User; | ||||
| 	settings: UserSettings; | ||||
| }) { | ||||
| 	const fetcher = useFetcher(); | ||||
| 	const { t } = useTranslation(); | ||||
|  | @ -38,39 +35,10 @@ export default function MainNavbar({ | |||
| 			{t("navbar.log-in")} | ||||
| 		</Nav.Link> | ||||
| 	); | ||||
| 
 | ||||
| 	const ThemeIcon = | ||||
| 		settings.dark_mode === null | ||||
| 			? BrightnessHigh | ||||
| 			: settings.dark_mode | ||||
| 				? MoonFill | ||||
| 				: BrightnessHighFill; | ||||
| 
 | ||||
| 	const theme = settings.dark_mode ? "dark" : "light"; | ||||
| 
 | ||||
| 	 | ||||
| 	return ( | ||||
| 		<BaseNavbar theme={theme}> | ||||
| 		<BaseNavbar> | ||||
| 			{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> | ||||
| 	); | ||||
| } | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { | |||
| }; | ||||
| 
 | ||||
| export function Layout({ children }: { children: ReactNode }) { | ||||
| 	const { locale, settings } = useRouteLoaderData<typeof loader>("root") || { | ||||
| 	const { locale } = useRouteLoaderData<typeof loader>("root") || { | ||||
| 		meta: { | ||||
| 			users: { | ||||
| 				total: 0, | ||||
|  | @ -77,7 +77,6 @@ export function Layout({ children }: { children: ReactNode }) { | |||
| 	return ( | ||||
| 		<html | ||||
| 			lang={locale || "en"} | ||||
| 			data-bs-theme={settings?.dark_mode ? "dark" : "light"} | ||||
| 			dir={i18n.dir()} | ||||
| 		> | ||||
| 			<head> | ||||
|  | @ -119,10 +118,10 @@ export function ErrorBoundary() { | |||
| 				<Links /> | ||||
| 			</head> | ||||
| 			<body> | ||||
| 				{data?.meUser && data?.settings && data?.meta ? ( | ||||
| 					<Navbar meta={data.meta} user={data.meUser} settings={data.settings} /> | ||||
| 				{data?.meUser && data?.meta ? ( | ||||
| 					<Navbar meta={data.meta} user={data.meUser} /> | ||||
| 				) : ( | ||||
| 					<BaseNavbar theme="light" /> | ||||
| 					<BaseNavbar /> | ||||
| 				)} | ||||
| 				<Container>{errorElem}</Container> | ||||
| 				<Scripts /> | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ import PronounLink from "~/components/PronounLink"; | |||
| export const meta: MetaFunction<typeof loader> = ({ data }) => { | ||||
| 	const { user } = data!; | ||||
| 
 | ||||
| 	return [{ title: `@${user.username} - pronouns.cc` }]; | ||||
| 	return [{ title: `@${user.username} • pronouns.cc` }]; | ||||
| }; | ||||
| 
 | ||||
| export const loader = async ({ request, params }: LoaderFunctionArgs) => { | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ import ErrorAlert from "~/components/ErrorAlert"; | |||
| import i18n from "~/i18next.server"; | ||||
| 
 | ||||
| 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 }) => { | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ import ErrorAlert from "~/components/ErrorAlert"; | |||
| import { User } from "~/lib/api/user"; | ||||
| 
 | ||||
| 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 }) => { | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import { Link, useLoaderData } from "@remix-run/react"; | |||
| import { Button } from "react-bootstrap"; | ||||
| 
 | ||||
| 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) => { | ||||
|  |  | |||
|  | @ -4,6 +4,13 @@ import serverRequest, { getCookie, writeCookie } from "~/lib/request.server"; | |||
| 
 | ||||
| // Handles theme switching
 | ||||
| // 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 }) => { | ||||
| 	const body = await request.formData(); | ||||
| 	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", { | ||||
| 			token, | ||||
| 			body: { | ||||
| 				dark_mode: theme === "auto" ? null : theme === "dark" ? true : false, | ||||
| 				dark_mode: theme === "auto" ? null : theme === "dark", | ||||
| 			}, | ||||
| 		}); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue