├── .gitignore ├── .npmignore ├── .prettierrc ├── README.md ├── example ├── .eslintrc.json ├── .gitignore ├── README.md ├── components │ └── ColorSwitcher.js ├── next.config.js ├── package.json ├── pages │ ├── _app.js │ └── index.js ├── public │ ├── favicon.ico │ └── vercel.svg └── yarn.lock ├── global.d.ts ├── package.json ├── renovate.json ├── src ├── ColorModeScript.tsx ├── ColorModeStyles.tsx ├── index.ts ├── useColorModeValue.tsx └── useColorSwitcher.tsx ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # build 107 | lib/ 108 | 109 | .vscode/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # sources 2 | preview 3 | src 4 | 5 | # locks 6 | package-lock.json 7 | yarn-lock.json 8 | 9 | # configs 10 | tslint.json 11 | tsconfig.json 12 | .prettierrc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Next.js color-mode

3 |

A helper for creating non-flickering and accessible themed applications

4 |
5 |
6 | 7 |
8 | npm version badge 9 | npm bundle size 10 | license badge 11 |
12 | 13 |
14 | 15 | ## Features 16 | 17 | - [x] 🙉 Non-flickering 18 | - [x] ♿ Accessible (supports `prefers-color-scheme`) 19 | - [x] 🐱 Dynamic theme values 20 | - [x] 🐄 No additional dependencies 21 | - [x] 🧠 Agnostic to the way you style your app 22 | 23 | ## Installation 24 | 25 | ``` 26 | $ npm i --save nextjs-color-mode 27 | 28 | # or 29 | 30 | $ yarn add nextjs-color-mode 31 | ``` 32 | 33 | ## Setup 34 | First, you need to import `ColorModeScript` from `nextjs-color-mode` and place it somewhere in the `_app.js` file. 35 | > If you're using styled-components or emotion, you can put the contents of `criticalThemeCss` to GlobalStyles. Just make sure it's critical css, and at the top of your global styles. 36 | 37 | ```jsx 38 | // _app.js 39 | 40 | import Head from 'next/head' 41 | import { ColorModeScript } from 'nextjs-color-mode' 42 | 43 | const criticalThemeCss = ` 44 | .next-light-theme { 45 | --background: #fff; 46 | --text: #000; 47 | } 48 | 49 | .next-dark-theme { 50 | --background: #000; 51 | --text: #fff; 52 | } 53 | 54 | body { 55 | background: var(--background); 56 | color: var(--text); 57 | } 58 | ` 59 | 60 | function MyApp({ Component, pageProps }) { 61 | return ( 62 | <> 63 | 64 | 14 | 15 | ) 16 | } 17 | 18 | function transformColorModeStyles(...styles: ColoredValue[]) { 19 | const lightStyles = styles.map((entry) => entry.light) 20 | const darkStyles = styles.map((entry) => entry.dark) 21 | return ['.next-light-theme {', lightStyles.join(''), '}', '.next-dark-theme {', darkStyles.join(''), '}'] 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ColorModeScript' 2 | export * from './ColorModeStyles' 3 | export * from './useColorModeValue' 4 | export * from './useColorSwitcher' 5 | -------------------------------------------------------------------------------- /src/useColorModeValue.tsx: -------------------------------------------------------------------------------- 1 | export function useColorModeValue( 2 | name: string, 3 | lightThemeValue: string, 4 | darkThemeValue: string, 5 | ): [string, { light: string; dark: string }] { 6 | return [`var(--${name})`, { light: `--${name}: ${lightThemeValue};`, dark: `--${name}: ${darkThemeValue};` }] 7 | } 8 | -------------------------------------------------------------------------------- /src/useColorSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback } from 'react' 2 | 3 | export function useColorSwitcher() { 4 | const [isLightMode, { toggle }] = useBoolean(() => { 5 | const colorMode = localStorage.getItem('nextColorMode') 6 | if (!colorMode) { 7 | return window.prefersDarkMode ? false : true 8 | } 9 | return colorMode === 'light' 10 | }) 11 | 12 | function changeTheme(colorMode: 'light' | 'dark') { 13 | toggle() 14 | localStorage.setItem('nextColorMode', colorMode) 15 | const bodyElement = window.document.querySelector('body') 16 | bodyElement?.classList.remove('next-light-theme') 17 | bodyElement?.classList.remove('next-dark-theme') 18 | bodyElement?.classList.add('next-' + colorMode + '-theme') 19 | } 20 | 21 | function toggleTheme() { 22 | return changeTheme(isLightMode ? 'dark' : 'light') 23 | } 24 | 25 | return { colorMode: isLightMode ? 'light' : 'dark', changeTheme, toggleTheme } 26 | } 27 | 28 | function useBoolean(initialState: boolean | (() => boolean) = false) { 29 | const [value, setValue] = useState(initialState) 30 | 31 | const on = useCallback(() => { 32 | setValue(true) 33 | }, []) 34 | 35 | const off = useCallback(() => { 36 | setValue(false) 37 | }, []) 38 | 39 | const toggle = useCallback(() => { 40 | setValue((prev: boolean) => !prev) 41 | }, []) 42 | 43 | return [value, { on, off, toggle }] as const 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "react", 6 | "declaration": true, 7 | "outDir": "lib", 8 | "strict": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "allowSyntheticDefaultImports": true, 14 | "esModuleInterop": true, 15 | "moduleResolution": "node", 16 | "skipLibCheck": true, 17 | "baseUrl": "." 18 | }, 19 | "include": ["src", "node_modules/next/types/global.d.ts", "global.d.ts"], 20 | "exclude": ["node_modules", "lib", "**/*.spec.ts", "example"] 21 | } 22 | --------------------------------------------------------------------------------