1
- import { useForm , getFormProps } from '@conform-to/react'
2
- import { parseWithZod } from '@conform-to/zod'
3
- import { invariantResponse } from '@epic-web/invariant'
4
1
import {
5
2
json ,
6
3
type LoaderFunctionArgs ,
7
- type ActionFunctionArgs ,
8
4
type HeadersFunction ,
9
5
type LinksFunction ,
10
6
type MetaFunction ,
@@ -17,16 +13,13 @@ import {
17
13
Outlet ,
18
14
Scripts ,
19
15
ScrollRestoration ,
20
- useFetcher ,
21
- useFetchers ,
22
16
useLoaderData ,
23
17
useMatches ,
24
18
useSubmit ,
25
19
} from '@remix-run/react'
26
20
import { withSentry } from '@sentry/remix'
27
21
import { useRef } from 'react'
28
22
import { HoneypotProvider } from 'remix-utils/honeypot/react'
29
- import { z } from 'zod'
30
23
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
31
24
import { EpicProgress } from './components/progress-bar.tsx'
32
25
import { SearchBar } from './components/search-bar.tsx'
@@ -41,16 +34,16 @@ import {
41
34
} from './components/ui/dropdown-menu.tsx'
42
35
import { Icon , href as iconsHref } from './components/ui/icon.tsx'
43
36
import { EpicToaster } from './components/ui/sonner.tsx'
37
+ import { ThemeSwitch , useTheme } from './routes/resources+/theme-switch.tsx'
44
38
import tailwindStyleSheetUrl from './styles/tailwind.css?url'
45
39
import { 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'
47
41
import { prisma } from './utils/db.server.ts'
48
42
import { getEnv } from './utils/env.server.ts'
49
43
import { honeypot } from './utils/honeypot.server.ts'
50
44
import { combineHeaders , getDomainUrl , getUserImgSrc } from './utils/misc.tsx'
51
45
import { 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'
54
47
import { makeTimings , time } from './utils/timing.server.ts'
55
48
import { getToast } from './utils/toast.server.ts'
56
49
import { useOptionalUser , useUser } from './utils/user.ts'
@@ -156,26 +149,6 @@ export const headers: HeadersFunction = ({ loaderHeaders }) => {
156
149
return headers
157
150
}
158
151
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
-
179
152
function Document ( {
180
153
children,
181
154
nonce,
@@ -354,84 +327,6 @@ function UserDropdown() {
354
327
)
355
328
}
356
329
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
-
435
330
export function ErrorBoundary ( ) {
436
331
// the nonce doesn't rely on the loader so we can access that
437
332
const nonce = useNonce ( )
0 commit comments