@nuxtjs/color-mode
Advanced tools
Comparing version 1.1.1 to 2.0.0
@@ -5,2 +5,13 @@ # Changelog | ||
## [2.0.0](https://github.com/nuxt-community/color-mode-module/compare/v1.1.1...v2.0.0) (2020-10-13) | ||
### â BREAKING CHANGES | ||
* Version 2 (#39) | ||
### Features | ||
* Version 2 ([#39](https://github.com/nuxt-community/color-mode-module/issues/39)) ([47664d7](https://github.com/nuxt-community/color-mode-module/commit/47664d76dc76a44ff270a6cd8569f512e5b004f3)) | ||
### [1.1.1](https://github.com/nuxt-community/color-mode-module/compare/v1.1.0...v1.1.1) (2020-09-15) | ||
@@ -7,0 +18,0 @@ |
@@ -16,9 +16,3 @@ import { resolve } from 'path' | ||
classSuffix: '-mode', | ||
cookie: { | ||
key: 'nuxt-color-mode', | ||
options: { | ||
path: this.options.router.base, | ||
sameSite: 'lax' | ||
} | ||
} | ||
storageKey: 'nuxt-color-mode' | ||
} | ||
@@ -25,0 +19,0 @@ // defu(object, defaults) |
@@ -10,4 +10,9 @@ // Add dark / light detection that runs before loading Nuxt.js | ||
const preference = getCookie('<%= options.cookie.key %>') || '<%= options.preference %>' | ||
const value = preference === 'system' ? getColorScheme() : preference | ||
const preference = window.localStorage.getItem('<%= options.storageKey %>') || '<%= options.preference %>' | ||
let value = preference === 'system' ? getColorScheme() : preference | ||
// Applied forced color mode | ||
const forcedColorMode = d.body.getAttribute('data-color-mode-forced') | ||
if (forcedColorMode) { | ||
value = forcedColorMode | ||
} | ||
@@ -24,18 +29,2 @@ addClass(value) | ||
function getCookie (name) { | ||
const nameEQ = name + '=' | ||
const cookies = d.cookie.split(';') | ||
for (let i = 0; i < cookies.length; i++) { | ||
let cookie = cookies[i] | ||
while (cookie.charAt(0) === ' ') { | ||
cookie = cookie.substring(1, cookie.length) | ||
} | ||
if (cookie.indexOf(nameEQ) === 0) { | ||
return cookie.substring(nameEQ.length, cookie.length) | ||
} | ||
} | ||
return null | ||
} | ||
function addClass (value) { | ||
@@ -42,0 +31,0 @@ const className = '<%= options.classPrefix %>' + value + '<%= options.classSuffix %>' |
@@ -1,1 +0,1 @@ | ||
!function(){"use strict";var e=window,s=document,t=s.documentElement,n=["dark","light"],o=function(e){for(var t=e+"=",n=s.cookie.split(";"),o=0;o<n.length;o++){for(var a=n[o];" "===a.charAt(0);)a=a.substring(1,a.length);if(0===a.indexOf(t))return a.substring(t.length,a.length)}return null}("<%= options.cookie.key %>")||"<%= options.preference %>",a="system"===o?c():o;function i(e){var s="<%= options.classPrefix %>"+e+"<%= options.classSuffix %>";t.classList?t.classList.add(s):t.className+=" "+s}function r(s){return e.matchMedia("(prefers-color-scheme"+s+")")}function c(){if(e.matchMedia&&"not all"!==r("").media)for(var s of n)if(r(":"+s).matches)return s;return"<%= options.fallback %>"}i(a),e["<%= options.globalName %>"]={preference:o,value:a,getColorScheme:c,addClass:i,removeClass:function(e){var s="<%= options.classPrefix %>"+e+"<%= options.classSuffix %>";t.classList?t.classList.remove(s):t.className=t.className.replace(new RegExp(s,"g"),"")}}}(); | ||
!function(){"use strict";var e=window,s=document,o=s.documentElement,a=["dark","light"],t=window.localStorage.getItem("<%= options.storageKey %>")||"<%= options.preference %>",c="system"===t?l():t,i=s.body.getAttribute("data-color-mode-forced");function r(e){var s="<%= options.classPrefix %>"+e+"<%= options.classSuffix %>";o.classList?o.classList.add(s):o.className+=" "+s}function n(s){return e.matchMedia("(prefers-color-scheme"+s+")")}function l(){if(e.matchMedia&&"not all"!==n("").media)for(var s of a)if(n(":"+s).matches)return s;return"<%= options.fallback %>"}i&&(c=i),r(c),e["<%= options.globalName %>"]={preference:t,value:c,getColorScheme:l,addClass:r,removeClass:function(e){var s="<%= options.classPrefix %>"+e+"<%= options.classSuffix %>";o.classList?o.classList.remove(s):o.className=o.className.replace(new RegExp(s,"g"),"")}}}(); |
import Vue from 'vue' | ||
import { serialize } from 'cookie' | ||
import colorSchemeComponent from './color-scheme' | ||
@@ -7,5 +6,5 @@ | ||
const cookieKey = '<%= options.cookie.key %>' | ||
const cookieOptions = JSON.parse('<%= JSON.stringify(options.cookie.options) %>') | ||
const storageKey = '<%= options.storageKey %>' | ||
const colorMode = window['<%= options.globalName %>'] | ||
const getForcedColorMode = route => route.matched[0] && route.matched[0].components.default.options.colorMode | ||
@@ -19,5 +18,12 @@ export default function (ctx, inject) { | ||
value: colorMode.value, | ||
unknown: colorMode.preference === 'system' | ||
unknown: false | ||
} | ||
const pageColorMode = getForcedColorMode(ctx.route) | ||
if (pageColorMode) { | ||
data.value = pageColorMode | ||
data.forced = true | ||
colorMode.addClass(pageColorMode) | ||
} | ||
} | ||
// Get current page component | ||
const $colorMode = new Vue({ | ||
@@ -27,2 +33,5 @@ data, | ||
preference (preference) { | ||
if (this.forced) { | ||
return | ||
} | ||
if (preference === 'system') { | ||
@@ -52,3 +61,3 @@ this.value = colorMode.getColorScheme() | ||
_watchMedia () { | ||
if (this._mediaWatcher || !window.matchMedia) { | ||
if (this._darkWatcher || !window.matchMedia) { | ||
return | ||
@@ -59,3 +68,3 @@ } | ||
this._darkWatcher.addListener((e) => { | ||
if (this.preference === 'system') { | ||
if (!this.forced && this.preference === 'system') { | ||
this.value = colorMode.getColorScheme() | ||
@@ -67,3 +76,3 @@ } | ||
window.addEventListener('storage', (e) => { | ||
if (e.key === cookieKey) { | ||
if (e.key === storageKey) { | ||
this.preference = e.newValue | ||
@@ -74,9 +83,4 @@ } | ||
_storePreference (preference) { | ||
// Cookies for SSR | ||
document.cookie = serialize(cookieKey, preference, cookieOptions) | ||
// Local storage to sync with other tabs | ||
if (window.localStorage) { | ||
window.localStorage.setItem(cookieKey, preference) | ||
} | ||
window.localStorage.setItem(storageKey, preference) | ||
} | ||
@@ -86,10 +90,25 @@ } | ||
if ($colorMode.unknown) { | ||
window.onNuxtReady(() => { | ||
window.onNuxtReady(() => { | ||
if ($colorMode.unknown) { | ||
$colorMode.preference = colorMode.preference | ||
$colorMode.value = colorMode.value | ||
$colorMode.unknown = false | ||
} | ||
ctx.app.router.beforeEach((route, from, next) => { | ||
const forcedColorMode = getForcedColorMode(route) | ||
if (forcedColorMode && forcedColorMode !== 'system') { | ||
$colorMode.value = forcedColorMode | ||
$colorMode.forced = true | ||
} else { | ||
if (forcedColorMode === 'system') { | ||
console.warn('You cannot force the colorMode to system at the page level.') | ||
} | ||
$colorMode.forced = false | ||
$colorMode.value = $colorMode.preference === 'system' ? colorMode.getColorScheme() : $colorMode.preference | ||
} | ||
next() | ||
}) | ||
} | ||
}) | ||
inject('colorMode', $colorMode) | ||
} |
import Vue from 'vue' | ||
import { parse } from 'cookie' | ||
import colorSchemeComponent from './color-scheme' | ||
const cookieKey = '<%= options.cookie.key %>' | ||
@@ -9,19 +7,24 @@ Vue.component('<%= options.componentName %>', colorSchemeComponent) | ||
export default function (ctx, inject) { | ||
let preference = '<%= options.preference %>' | ||
const preference = '<%= options.preference %>' | ||
// Try to read from cookies | ||
if (ctx.req) { | ||
// Check if cookie exist, otherwise TypeError: argument str must be a string | ||
const cookies = parse(ctx.req.headers.cookie || '') | ||
if (cookies[cookieKey]) { | ||
preference = cookies[cookieKey] | ||
} | ||
} | ||
const colorMode = { | ||
preference, | ||
value: preference, | ||
unknown: process.static || !ctx.req || preference === 'system' | ||
unknown: true, | ||
forced: false | ||
} | ||
if (ctx.route.matched[0]) { | ||
const pageColorMode = ctx.route.matched[0].components.default.options.colorMode | ||
if (pageColorMode && pageColorMode !== 'system') { | ||
colorMode.value = pageColorMode | ||
colorMode.forced = true | ||
ctx.app.head.bodyAttrs = ctx.app.head.bodyAttrs || {} | ||
ctx.app.head.bodyAttrs['data-color-mode-forced'] = pageColorMode | ||
} else if (pageColorMode === 'system') { | ||
console.warn('You cannot force the colorMode to system at the page level.') | ||
} | ||
} | ||
ctx.beforeNuxtRender(({ nuxtState }) => { | ||
@@ -28,0 +31,0 @@ nuxtState.colorMode = colorMode |
{ | ||
"name": "@nuxtjs/color-mode", | ||
"version": "1.1.1", | ||
"version": "2.0.0", | ||
"description": "Dark and Light mode for NuxtJS with auto detection", | ||
@@ -34,3 +34,3 @@ "repository": "nuxt-community/color-mode-module", | ||
"@commitlint/config-conventional": "^9.1.2", | ||
"@nuxt/types": "^2.14.5", | ||
"@nuxt/types": "^2.14.6", | ||
"@nuxtjs/eslint-config": "^3.1.0", | ||
@@ -40,8 +40,8 @@ "@nuxtjs/module-test-utils": "^1.6.3", | ||
"babel-eslint": "latest", | ||
"babel-jest": "^26.3.0", | ||
"eslint": "^7.9.0", | ||
"babel-jest": "^26.5.2", | ||
"eslint": "^7.10.0", | ||
"husky": "latest", | ||
"jest": "^26.4.2", | ||
"nuxt": "^2.14.5", | ||
"rollup": "^2.26.11", | ||
"jest": "^26.5.2", | ||
"nuxt": "^2.14.6", | ||
"rollup": "^2.29.0", | ||
"rollup-plugin-babel": "latest", | ||
@@ -48,0 +48,0 @@ "rollup-plugin-terser": "^6.1.0", |
175
README.md
@@ -0,1 +1,3 @@ | ||
[![@nuxtjs/color-mode](https://color-mode.nuxtjs.org/preview.png)](https://color-mode.nuxtjs.org) | ||
# @nuxtjs/color-mode | ||
@@ -16,3 +18,4 @@ | ||
[đ Release Notes](./CHANGELOG.md) | ||
- [⨠Release Notes](https://color-mode.nuxtjs.org/releases) | ||
- [đ Documentation](https://color-mode.nuxtjs.org) | ||
@@ -22,172 +25,10 @@ ## Features | ||
- Add `.${color}-mode` class to `<html>` for easy CSS theming | ||
- Force a page to a specific color mode (perfect for incremental development) | ||
- Works with any NuxtJS target (`static` or `server`) and rendering (`universal` and `spa`) | ||
- Auto detect the system [color-mode](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode) | ||
- Sync between tabs đ | ||
- Sync dark mode across tabs and windows đ | ||
- Supports IE9+ đ´ | ||
âšī¸ This module is using a cookie to support server-side rendering, if your visitors are located in Europe, make sure to follow the [EU cookie directive](https://en.wikipedia.org/wiki/HTTP_cookie#EU_cookie_directive) | ||
[đ Read more](https://color-mode.nuxtjs.org) | ||
## Setup | ||
1. Add `@nuxtjs/color-mode` dependency to your project | ||
```bash | ||
yarn add --dev @nuxtjs/color-mode | ||
# OR npm install --save-dev @nuxtjs/color-mode | ||
``` | ||
2. Add `@nuxtjs/color-mode` to the `buildModules` section of your `nuxt.config.js` | ||
```js | ||
{ | ||
buildModules: [ | ||
// Simple usage | ||
'@nuxtjs/color-mode' | ||
] | ||
} | ||
``` | ||
âšī¸ Use the `modules` property instead of `buildModules` if: | ||
- you are using `ssr: false` and `nuxt start`, see [#25](https://github.com/nuxt-community/color-mode-module/issues/25#issuecomment-692567237) | ||
- you are using `nuxt < 2.9.0` | ||
3. Start theming your CSS with `.dark-mode` and `.light-mode` classes | ||
## Usage | ||
It injects `$colorMode` helper with: | ||
- `preference`: Actual color-mode selected (can be `'system'`), update it to change the user preferred color mode | ||
- `value`: Useful to know what color mode has been detected when `$colorMode === 'system'`, you should not update it | ||
- `unknown`: Useful to know if during SSR or Generate, we need to render a placeholder | ||
```vue | ||
<template> | ||
<div> | ||
<h1>Color mode: {{ $colorMode.value }}</h1> | ||
<select v-model="$colorMode.preference"> | ||
<option value="system">System</option> | ||
<option value="light">Light</option> | ||
<option value="dark">Dark</option> | ||
<option value="sepia">Sepia</option> | ||
</select> | ||
</div> | ||
</template> | ||
<style> | ||
body { | ||
background-color: #fff; | ||
color: rgba(0,0,0,0.8); | ||
} | ||
.dark-mode body { | ||
background-color: #091a28; | ||
color: #ebf4f1; | ||
} | ||
.sepia-mode body { | ||
background-color: #f1e7d0; | ||
color: #433422; | ||
} | ||
</style> | ||
``` | ||
You can see a more advanced example in the [example/ directory](./example) or [play online on CodeSandBox](https://codesandbox.io/s/github/nuxt-community/color-mode-module/tree/master/?fontsize=14&hidenavigation=1&theme=dark&file=/example/pages/index.vue). | ||
## Configuration | ||
You can configure the module by providing the `colorMode` property in your `nuxt.config.js`, here are the default options: | ||
```js | ||
colorMode: { | ||
preference: 'system', // default value of $colorMode.preference | ||
fallback: 'light', // fallback value if not system preference found | ||
hid: 'nuxt-color-mode-script', | ||
globalName: '__NUXT_COLOR_MODE__', | ||
componentName: 'ColorScheme', | ||
classPrefix: '', | ||
classSufix: '-mode', | ||
cookie: { | ||
key: 'nuxt-color-mode', | ||
options: { | ||
path: nuxt.options.router.base, // https://nuxtjs.org/api/configuration-router#base | ||
sameSite: 'lax' // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite | ||
} | ||
} | ||
} | ||
``` | ||
Notes: | ||
- `'system'` is a special value, it will automatically detect the color mode based on the system preferences (see [prefers-color-mode spec](https://drafts.csswg.org/mediaqueries-5/#descdef-media-prefers-color-mode)). The value injected will be either `'light'` or `'dark'`. If `no-preference` is detected or the browser does not handle color-mode, it will set the `fallback` value. | ||
- `cookie` are the options where to store the chosen color mode (to make it work universally), the `cookie.options` are available on the [cookie serialize options](https://www.npmjs.com/package/cookie#options-1) documentation. Each option will recursively overwrite the default property (by using [defu](https://github.com/nuxt-contrib/defu)). | ||
## Caveats | ||
If you are doing SSR (`nuxt start` or `nuxt generate`) and if `$colorMode.preference` is set to `'system'`, using `$colorMode` in your Vue template will lead to a flash. This is due to the fact that we cannot know the user preferences when pre-rendering the page since they are detected on client-side. | ||
To avoid the flash, you have to guard any rendering path which depends on `$colorMode` with `$colorMode.unknown` to render a placeholder or use our `<ColorScheme>` component. | ||
**Example:** | ||
```vue | ||
<template> | ||
<ColorScheme placeholder="..." tag="span"> | ||
Color mode: <b>{{ $colorMode.preference }}</b> | ||
<span v-if="$colorMode.preference === 'system'">(<i>{{ $colorMode.value }}</i> mode detected)</span> | ||
</ColorScheme> | ||
</template> | ||
``` | ||
Props: | ||
- `placeholder`: `String` | ||
- `tag`: `String`, default: `'span'` | ||
## TailwindCSS Dark Mode | ||
### Tailwind v1.8+ | ||
Tailwind v1.8 introduced [dark mode support](https://github.com/tailwindlabs/tailwindcss/pull/2279), in order to work with @nuxtjs/color-mode, you need to set `dark: 'class'` in your `tailwind.config.js`: | ||
```js | ||
// tailwind.config.js | ||
module.exports = { | ||
experimental: { | ||
darkModeVariant: true | ||
}, | ||
dark: 'class' | ||
} | ||
``` | ||
Then in your `nuxt.config.js`, set the `classSuffix` option to an empty string: | ||
```js | ||
// nuxt.config.js | ||
export default { | ||
// ... | ||
colorMode: { | ||
classSuffix: '' | ||
} | ||
} | ||
``` | ||
### Tailwindcss Dark Mode Plugin | ||
You can easily integrate this module with [tailwindcss-dark-mode](https://github.com/ChanceArthur/tailwindcss-dark-mode) by just setting `darkSelector: '.dark-mode'`, see [changing the selector documentation](https://github.com/ChanceArthur/tailwindcss-dark-mode#changing-the-selector). | ||
```js | ||
// tailwind.config.js | ||
module.exports = { | ||
theme: { | ||
darkSelector: '.dark-mode' | ||
}, | ||
variants: { | ||
backgroundColor: ["dark", "dark-hover", "dark-group-hover", "dark-even", "dark-odd"], | ||
borderColor: ["dark", "dark-focus", "dark-focus-within"], | ||
textColor: ["dark", "dark-hover", "dark-active"] | ||
}, | ||
plugins: [ | ||
require('tailwindcss-dark-mode')() | ||
] | ||
} | ||
``` | ||
Checkout a [live example on CodeSandBox](https://codesandbox.io/s/nuxt-dark-tailwindcss-17g2j?file=/pages/index.vue) as well as [@nuxtjs/tailwindcss](https://github.com/nuxt-community/tailwindcss-module) module. | ||
## Contributing | ||
@@ -197,3 +38,3 @@ | ||
[![Edit @nuxtjs/tailwindcss](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxt-community/color-mode-module/tree/master/?fontsize=14&hidenavigation=1&theme=dark) | ||
[![Edit @nuxtjs/color-mode](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/nuxt-community/color-mode-module/tree/master/?fontsize=14&hidenavigation=1&theme=dark) | ||
@@ -200,0 +41,0 @@ Or locally: |
@@ -1,18 +0,1 @@ | ||
export interface ColorModeCookieOptions { | ||
/** | ||
* Default: `nuxt-color-mode` | ||
*/ | ||
key: string, | ||
options: { | ||
/** | ||
* Default: `nuxt.options.router.base` | ||
*/ | ||
path: string, | ||
/** | ||
* Default: `lax` | ||
*/ | ||
sameSite: string | ||
} | ||
} | ||
export interface ColorModeOptions { | ||
@@ -47,3 +30,6 @@ /** | ||
classSuffix: string, | ||
cookie: ColorModeCookieOptions | ||
/** | ||
* Default: 'nuxt-color-mode' | ||
*/ | ||
storageKey: string | ||
} | ||
@@ -54,3 +40,4 @@ | ||
value: string, | ||
unknown: boolean | ||
unknown: boolean, | ||
forced: boolean | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18676
320
65