import * as React from 'react' ; import { useFetcher } from 'remix' ; enum Theme { DARK = 'dark' , LIGHT = 'light' } const themes = Object . values ( Theme ) ; type ThemeContextType = [ Theme | null , React . Dispatch < React . SetStateAction < Theme | null > > ] ; const ThemeContext = React . createContext < ThemeContextType | undefined > ( undefined ) ; ThemeContext . displayName = 'ThemeContext' ; const prefersLightMQ = '(prefers-color-scheme: light)' ; const getPreferredTheme = ( ) => window . matchMedia ( prefersLightMQ ) . matches ? Theme . LIGHT : Theme . DARK ; function ThemeProvider ( { children , specifiedTheme , } ) { const [ theme , setTheme ] = React . useState < Theme | null > ( ( ) => { if ( specifiedTheme ) { if ( themes . includes ( specifiedTheme ) ) return specifiedTheme ; else return null ; } if ( typeof window !== 'object' ) return null ; return getPreferredTheme ( ) ; } ) ; const persistTheme = useFetcher ( ) ; const persistThemeRef = React . useRef ( persistTheme ) ; React . useEffect ( ( ) => { persistThemeRef . current = persistTheme ; } , [ persistTheme ] ) ; const mountRun = React . useRef ( false ) ; React . useEffect ( ( ) => { if ( ! mountRun . current ) { mountRun . current = true ; return ; } if ( ! theme ) return ; persistThemeRef . current . submit ( { theme } , { action : 'action/set-theme' , method : 'post' } ) ; } , [ theme ] ) ; React . useEffect ( ( ) => { const mediaQuery = window . matchMedia ( prefersLightMQ ) ; const handleChange = ( ) => { setTheme ( mediaQuery . matches ? Theme . LIGHT : Theme . DARK ) ; } ; mediaQuery . addEventListener ( 'change' , handleChange ) ; return ( ) => mediaQuery . removeEventListener ( 'change' , handleChange ) ; } , [ ] ) ; return ( < ThemeContext . Provider value = { [ theme , setTheme ] } > { children } < /ThemeContext.Provider>); } const clientThemeCode = `
// hi there dear reader 👋
// this is how I make certain we avoid a flash of the wrong theme. If you select
// a theme, then I'll know what you want in the future and you'll not see this
// script anymore.
;(() => {
  const theme = window.matchMedia(${ JSON . stringify ( prefersLightMQ ) }).matches
    ? 'light'
    : 'dark';
  
  const cl = document.documentElement.classList;
  
  const themeAlreadyApplied = cl.contains('light') || cl.contains('dark');
  if (themeAlreadyApplied) {
    // this script shouldn't exist if the theme is already applied!
    console.warn(
      "Hi there, could you let Kent know you're seeing this message? Thanks!",
    );
  } else {
    cl.add(theme);
  }
  
  // the <dark-mode> and <light-mode> approach won't work for meta tags,
  // so we have to do it manually.
  const meta = document.querySelector('meta[name=color-scheme]');
  if (meta) {
    if (theme === 'dark') {
      meta.content = 'dark light';
    } else if (theme === 'light') {
      meta.content = 'light dark';
    }
  } else {
    console.warn(
      "Heya, could you let Kent know you're seeing this message? Thanks!",
    );
  }
})();
` ; function handleDarkAndLightModeEls ( ) { const theme = getPreferredTheme ( ) ; const darkEls = document . querySelectorAll ( 'dark-mode' ) ; const lightEls = document . querySelectorAll ( 'light-mode' ) ; for ( const darkEl of darkEls ) { if ( theme === 'dark' ) { for ( const child of darkEl . childNodes ) { darkEl . parentElement ?. append ( child ) ; } } darkEl . remove ( ) ; } for ( const lightEl of lightEls ) { if ( theme === 'light' ) { for ( const child of lightEl . childNodes ) { lightEl . parentElement ?. append ( child ) ; } } lightEl . remove ( ) ; } } function NonFlashOfWrongThemeEls ( { ssrTheme } ) { const [ theme ] = useTheme ( ) ; return ( < > { } < meta name = "color-scheme" content = { theme === 'light' ? 'light dark' : 'dark light' } /> { } { ssrTheme ? null : ( < script dangerouslySetInnerHTML = { { __html : clientThemeCode } } />)} < />); } function useTheme ( ) { const context = React . useContext ( ThemeContext ) ; if ( context === undefined ) { throw new Error ( 'useTheme must be used within a ThemeProvider' ) ; } return context ; } /**
 * This allows you to render something that depends on the theme without
 * worrying about whether it'll SSR properly when we don't actually know
 * the user's preferred theme.
 */ function Themed ( { dark , light , initialOnly = false , } ) { const [ theme ] = useTheme ( ) ; const [ initialTheme ] = React . useState ( theme ) ; const themeToReference = initialOnly ? initialTheme : theme ; const serverRenderWithUnknownTheme = ! theme && typeof window !== 'object' ; if ( serverRenderWithUnknownTheme ) { return ( < > { React . createElement ( 'dark-mode' , null , dark ) } { React . createElement ( 'light-mode' , null , light ) } < />); } else { return < > { themeToReference === 'light' ? light : dark } < />; } } function isTheme ( value ) { return typeof value === 'string' && themes . includes ( value as Theme ) ; } export { handleDarkAndLightModeEls , ThemeProvider , useTheme , themes , Theme , isTheme , Themed , NonFlashOfWrongThemeEls , } ;