1- import { useForm , getFormProps } from '@conform-to/react'
2- import { parseWithZod } from '@conform-to/zod'
3- import { invariantResponse } from '@epic-web/invariant'
41import {
52 json ,
63 type LoaderFunctionArgs ,
7- type ActionFunctionArgs ,
84 type HeadersFunction ,
95 type LinksFunction ,
106 type MetaFunction ,
@@ -17,16 +13,13 @@ import {
1713 Outlet ,
1814 Scripts ,
1915 ScrollRestoration ,
20- useFetcher ,
21- useFetchers ,
2216 useLoaderData ,
2317 useMatches ,
2418 useSubmit ,
2519} from '@remix-run/react'
2620import { withSentry } from '@sentry/remix'
2721import { useRef } from 'react'
2822import { HoneypotProvider } from 'remix-utils/honeypot/react'
29- import { z } from 'zod'
3023import { GeneralErrorBoundary } from './components/error-boundary.tsx'
3124import { EpicProgress } from './components/progress-bar.tsx'
3225import { SearchBar } from './components/search-bar.tsx'
@@ -41,16 +34,16 @@ import {
4134} from './components/ui/dropdown-menu.tsx'
4235import { Icon , href as iconsHref } from './components/ui/icon.tsx'
4336import { EpicToaster } from './components/ui/sonner.tsx'
37+ import { ThemeSwitch , useTheme } from './routes/resources+/theme-switch.tsx'
4438import tailwindStyleSheetUrl from './styles/tailwind.css?url'
4539import { getUserId , logout } from './utils/auth.server.ts'
46- import { ClientHintCheck , getHints , useHints } from './utils/client-hints.tsx'
40+ import { ClientHintCheck , getHints } from './utils/client-hints.tsx'
4741import { prisma } from './utils/db.server.ts'
4842import { getEnv } from './utils/env.server.ts'
4943import { honeypot } from './utils/honeypot.server.ts'
5044import { combineHeaders , getDomainUrl , getUserImgSrc } from './utils/misc.tsx'
5145import { useNonce } from './utils/nonce-provider.ts'
52- import { useRequestInfo } from './utils/request-info.ts'
53- import { type Theme , setTheme , getTheme } from './utils/theme.server.ts'
46+ import { type Theme , getTheme } from './utils/theme.server.ts'
5447import { makeTimings , time } from './utils/timing.server.ts'
5548import { getToast } from './utils/toast.server.ts'
5649import { useOptionalUser , useUser } from './utils/user.ts'
@@ -156,26 +149,6 @@ export const headers: HeadersFunction = ({ loaderHeaders }) => {
156149 return headers
157150}
158151
159- const ThemeFormSchema = z . object ( {
160- theme : z . enum ( [ 'system' , 'light' , 'dark' ] ) ,
161- } )
162-
163- export async function action ( { request } : ActionFunctionArgs ) {
164- const formData = await request . formData ( )
165- const submission = parseWithZod ( formData , {
166- schema : ThemeFormSchema ,
167- } )
168-
169- invariantResponse ( submission . status === 'success' , 'Invalid theme received' )
170-
171- const { theme } = submission . value
172-
173- const responseInit = {
174- headers : { 'set-cookie' : setTheme ( theme ) } ,
175- }
176- return json ( { result : submission . reply ( ) } , responseInit )
177- }
178-
179152function Document ( {
180153 children,
181154 nonce,
@@ -354,84 +327,6 @@ function UserDropdown() {
354327 )
355328}
356329
357- /**
358- * @returns the user's theme preference, or the client hint theme if the user
359- * has not set a preference.
360- */
361- export function useTheme ( ) {
362- const hints = useHints ( )
363- const requestInfo = useRequestInfo ( )
364- const optimisticMode = useOptimisticThemeMode ( )
365- if ( optimisticMode ) {
366- return optimisticMode === 'system' ? hints . theme : optimisticMode
367- }
368- return requestInfo . userPrefs . theme ?? hints . theme
369- }
370-
371- /**
372- * If the user's changing their theme mode preference, this will return the
373- * value it's being changed to.
374- */
375- export function useOptimisticThemeMode ( ) {
376- const fetchers = useFetchers ( )
377- const themeFetcher = fetchers . find ( f => f . formAction === '/' )
378-
379- if ( themeFetcher && themeFetcher . formData ) {
380- const submission = parseWithZod ( themeFetcher . formData , {
381- schema : ThemeFormSchema ,
382- } )
383-
384- if ( submission . status === 'success' ) {
385- return submission . value . theme
386- }
387- }
388- }
389-
390- function ThemeSwitch ( { userPreference } : { userPreference ?: Theme | null } ) {
391- const fetcher = useFetcher < typeof action > ( )
392-
393- const [ form ] = useForm ( {
394- id : 'theme-switch' ,
395- lastResult : fetcher . data ?. result ,
396- } )
397-
398- const optimisticMode = useOptimisticThemeMode ( )
399- const mode = optimisticMode ?? userPreference ?? 'system'
400- const nextMode =
401- mode === 'system' ? 'light' : mode === 'light' ? 'dark' : 'system'
402- const modeLabel = {
403- light : (
404- < Icon name = "sun" >
405- < span className = "sr-only" > Light</ span >
406- </ Icon >
407- ) ,
408- dark : (
409- < Icon name = "moon" >
410- < span className = "sr-only" > Dark</ span >
411- </ Icon >
412- ) ,
413- system : (
414- < Icon name = "laptop" >
415- < span className = "sr-only" > System</ span >
416- </ Icon >
417- ) ,
418- }
419-
420- return (
421- < fetcher . Form method = "POST" { ...getFormProps ( form ) } >
422- < input type = "hidden" name = "theme" value = { nextMode } />
423- < div className = "flex gap-2" >
424- < button
425- type = "submit"
426- className = "flex h-8 w-8 cursor-pointer items-center justify-center"
427- >
428- { modeLabel [ mode ] }
429- </ button >
430- </ div >
431- </ fetcher . Form >
432- )
433- }
434-
435330export function ErrorBoundary ( ) {
436331 // the nonce doesn't rely on the loader so we can access that
437332 const nonce = useNonce ( )
0 commit comments