Code Monkey home page Code Monkey logo

remix-themes's Introduction

Remix Themes

An abstraction for themes in your Remix app.

Features

  • ✅ Perfect dark mode in a few lines of code
  • ✅ System setting with prefers-color-scheme
  • ✅ Automatically updates the theme when the user changes the system setting
  • ✅ No flash on load
  • ✅ Sync theme across tabs and windows

Check out the Example to see it in action.

Install

$ npm install remix-themes
# or
$ yarn add remix-themes

Getting Started

Create your session storage and create a themeSessionResolver

// sessions.server.tsx

import {createThemeSessionResolver} from 'remix-themes'

const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: '__remix-themes',
    // domain: 'remix.run',
    path: '/',
    httpOnly: true,
    sameSite: 'lax',
    secrets: ['s3cr3t'],
    // secure: true,
  },
})

export const themeSessionResolver = createThemeSessionResolver(sessionStorage)

Note: make sure you have domain and secure parameters set only for your production environment. Otherwise, Safari won't store the cookie if you set these parameters on localhost.

Setup Remix Themes

// root.tsx

import {ThemeProvider, useTheme, PreventFlashOnWrongTheme} from 'remix-themes'
import {themeSessionResolver} from './sessions.server'

// Return the theme from the session storage using the loader
export const loader: LoaderFunction = async ({request}) => {
  const {getTheme} = await themeSessionResolver(request)
  return {
    theme: getTheme(),
  }
}

// Wrap your app with ThemeProvider.
// `specifiedTheme` is the stored theme in the session storage.
// `themeAction` is the action name that's used to change the theme in the session storage.
export default function AppWithProviders() {
  const data = useLoaderData()
  return (
    <ThemeProvider specifiedTheme={data.theme} themeAction="/action/set-theme">
      <App />
    </ThemeProvider>
  )
}

// Use the theme in your app.
// If the theme is missing in session storage, PreventFlashOnWrongTheme will get
// the browser theme before hydration and will prevent a flash in browser.
// The client code runs conditionally, it won't be rendered if we have a theme in session storage.
function App() {
  const data = useLoaderData()
  const [theme] = useTheme()
  return (
    <html lang="en" data-theme={theme ?? ''}>
      <head>
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width,initial-scale=1" />
        <Meta />
        <PreventFlashOnWrongTheme ssrTheme={Boolean(data.theme)} />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        {process.env.NODE_ENV === 'development' && <LiveReload />}
      </body>
    </html>
  )
}

Add the action route

Create a file in /routes/action/set-theme.ts or /routes/action.set-theme.ts when using Route File Naming v2 with the content below. Ensure that you pass the filename to the ThemeProvider component.

Note: You can name the action route whatever you want. Just make sure you pass the correct action name to the ThemeProvider component. Make sure to use absolute path when using nested routing.

This route it's used to store the preferred theme in the session storage when the user changes it.

import {createThemeAction} from 'remix-themes'
import {themeSessionResolver} from './sessions.server'

export const action = createThemeAction(themeSessionResolver)

API

Let's dig into the details.

ThemeProvider

  • specifiedTheme: The theme from the session storage.
  • themeAction: The action name used to change the theme in the session storage.
  • disableTransitionOnThemeChange: Disable CSS transitions on theme change to prevent the flashing effect.

useTheme

useTheme takes no parameters but returns:

  • theme: Active theme name

createThemeSessionResolver

createThemeSessionResolver function takes a cookie session storage and returns

  • resolver: A function that takes a request and returns an object with the following properties:
    • getTheme: A function that returns the theme from the session storage.
    • setTheme: A function that takes a theme name and sets it in the session storage.
    • commit: A function that commits the session storage (Stores all data in the session and returns the Set-Cookie header to use in the HTTP response.)

PreventFlashOnWrongTheme

On the server, "theme" might be null so PreventFlashOnWrongTheme ensures that this is correct before hydration. If the theme is null on the server, this component will set the browser theme on the html element in a data-theme attribute if exists, otherwise it will be set to a class attribute. If both data-theme and class are set, the data-theme will be used.

  • ssrTheme: boolean value that indicates if we have a theme in the session storage.

remix-themes's People

Contributors

abereghici avatar maitkaa avatar mikeatoctoml avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

remix-themes's Issues

use data-themes attribute instead of class on html tag

Describe the bug

Currently, we set a class on html tag, which works, but is not a good practice.

What if we used data-theme attribute instead?

<html data-theme="dark">

Your Example Website or App

n/a

Steps to Reproduce the Bug or Issue

n/a

Expected behavior

n/a

Screenshots or Videos

No response

Platform

all

Additional context

No response

Remix 2.0.0 support

Describe the bug

Remix v2.0.0 not supported

Your Example Website or App

penisland.com

Steps to Reproduce the Bug or Issue

upgrade your remix project to version 2.0.0 while using remix-themes

Expected behavior

I would expect the library to work as in 1.19.X without having to use dependancy workarounds

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS, Windows, Linux]
  • Browser: [e.g. Chrome, Safari, Firefox]
  • Version: [e.g. 91.1]

Additional context

No response

Ability to pass nonce as a prop to the underlying script.

Discussed in #28

Originally posted by Ekthelion January 30, 2024
If the app is using nonce in CSP agains the XSS attacks, script is being ignored/removed and a CSP error is being thrown.
To solve this, the script with the clientThemeCode schould include the nonce attribute passed down from the root.tsx.
//root.tsx

<PreventFlashOnWrongTheme nonce={nonce} ssrTheme={Boolean(ssrTheme)} />

//theme-provider

function PreventFlashOnWrongTheme({ ssrTheme, nonce }) {
    const [theme] = useTheme();
    return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("meta", { name: "color-scheme", content: theme === 'light' ? 'light dark' : 'dark light' }), ssrTheme ? null : ((0, jsx_runtime_1.jsx)("script", { 
                ...
                nonce: nonce,
                dangerouslySetInnerHTML: { __html: clientThemeCode } }))] }));
}
```</div>

Add disableTransitionExclude config

@abereghici Thanks for developing Remix Themes — super helpful! Because my theme switching button needs to use transition, I need to be able to whitelist some components. This can be achieved by using the :not pseudo-class of css, just like this:

document.createTextNode(
    `
	*${disableTransitionExclude.map(s => `:not(${s})`).join('')} {
	  -webkit-transition: none !important;
	  -moz-transition: none !important;
	  -o-transition: none !important;
	  -ms-transition: none !important;
	  transition: none !important;
	}
    `,
),

Originally posted by @mancuoj in #35

Button with transition-colors applied flashing when toggling mode tailwind

Describe the bug

I found a problem that, when switching theme, all elements that have a transition color on them will flash.
The solution of removing the transition color on all elements is not applicabile for stylistic reasons
Will open a pr to fix this

Your Example Website or App

none

Steps to Reproduce the Bug or Issue

  1. Setup a remix project with tailwind
  2. Add a transition color to any element
  3. Watch the flashing

Expected behavior

I expect no strange flashing even with transition applied

Screenshots or Videos

video5881909423554695074.mp4

Platform

  • OS: [macOS]
  • Browser: [Chrome]
  • Version: [121.0]

Additional context

No response

Toggling

Hey!

Thanks for making this!

How do you toggle the theme? I know I can send a POST request to the action but
how do you do it exactly? Using a Form?

Can't get the demo to run

Hi there,

I have this working on a personal app, following the README, but I cannot see any cookie. I therefore tried to get the demo app to run so as to compare and am having some trouble. I

  1. Ran : npm install && npm run build in the repo root
  2. Ran: npm install && npm run dev in the demo directory

The app starts, but I get the following error:

Application Error
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (/home/msmall/dev/projects/github/remix-themes/node_modules/react/cjs/react.development.js:1476:13)
    at Object.useState (/home/msmall/dev/projects/github/remix-themes/node_modules/react/cjs/react.development.js:1507:20)
    at ThemeProvider (/home/msmall/dev/projects/github/remix-themes/build/theme-provider.js:40:37)
    at processChild (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/react-dom/cjs/react-dom-server.node.development.js:3353:14)
    at resolve (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/react-dom/cjs/react-dom-server.node.development.js:3270:5)
    at ReactDOMServerRenderer.render (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/react-dom/cjs/react-dom-server.node.development.js:3753:22)
    at ReactDOMServerRenderer.read (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/react-dom/cjs/react-dom-server.node.development.js:3690:29)
    at renderToString (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/react-dom/cjs/react-dom-server.node.development.js:4298:27)
    at handleRequest (/home/msmall/dev/projects/github/remix-themes/demo/build/index.js:389:51)
    at renderDocumentRequest (/home/msmall/dev/projects/github/remix-themes/demo/node_modules/@remix-run/server-runtime/server.js:404:18)

I looked at the index.tsx file and the hook is called within the body of the function, so I'm a bit confused. Any help would be appreciated.

What I really want to understand is why I cannot see any cookie in my browser, representing the theme, as I want to read this cookie on each subsequent visit to the app and have it's value applied.

Allow a 'System' setting as an option

Discussed in #21

Originally posted by micotodev March 29, 2023
A lot of websites have Light/Dark and then a 'System' preference (see Tailwind CSS website).

Currently, if the user has chosen a specific colour theme on the website, it will be changed irrespective of their choice when they change their system preference.

Maybe having an extra flag of isSystemPreference: boolean and only set the event listener when that is true would be the best option. We could still set the Theme enum (but with the ability to check for this flag when creating a dropdown of the three values).

data-theme attribute sets the wrong theme on it's first use.

Describe the bug

data-theme attribute, sets the wrong theme on it's first use. Theme is being set correctly, on it's second use.
Current behavior: First use: sets 'light' theme (current theme). => Second use: sets 'dark' theme (expected theme).

Also on first page load, I get this common error: Warning: Prop data-theme did not match. Server: "null" Client: "dark"
html.

Additional Info: PreventFlashOnWrongTheme is correctly set on 'head' tags.

Your Example Website or App

http://localhost:3000/

Steps to Reproduce the Bug or Issue

  1. Clear cookies from the website: localhost (in this case).
  2. Set a data-theme attribute on the top html element (root.tsx).
  3. Import useTheme method, and use it for a newly clickable button.
  4. Navigate to the website, and click the newly created button.
  5. Check dev console (Inspector Mode) for the top html element and it's data-attribute values.

Expected behavior

data-theme attribute should set the opposite preferred theme on it's first use.
Example: prefers-color-scheme: light => theme should change to 'dark' on it's first use.

Screenshots or Videos

No response

Platform

  • OS: [e.g. macOS]
  • Browser: [e.g. Chrome, Firefox]

Additional context

  • I noticed that class attribute is set by default with a 'theme value'. Instead data-theme is not being set by default. It only sets after the first useTheme call.

  • After setting a new theme and refreshing the page, class attribute is removed, and only remains data-theme attribute. (This is good I guess)


You guys are doing an amazing job with the module.
Hope it keeps getting better and better, because for sure it will be used by a lot of people soon or later.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.