1
mirror of https://github.com/jakejarvis/dark-mode.git synced 2025-06-30 20:46:38 -04:00

Compare commits

..

31 Commits
0.1.1 ... 0.7.1

Author SHA1 Message Date
20aaf287b2 Release 0.7.1 2021-08-23 09:15:31 -04:00
a84de1b0e5 📦 npm: Bump rollup from 2.56.2 to 2.56.3
Bumps [rollup](https://github.com/rollup/rollup) from 2.56.2 to 2.56.3.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.56.2...v2.56.3)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-23 09:14:48 -04:00
d00e3d54b2 add version number to banner comment 2021-08-23 09:11:27 -04:00
80153bce73 Release 0.7.0 2021-08-22 10:41:07 -04:00
fa128cb222 remove super-duper edge case testing of localStorage support
use the following methods externally to test on older browsers if needed:
- https://www.npmjs.com/package/storage-available
- https://www.npmjs.com/package/local-storage-fallback
- https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#testing_for_availability
2021-08-22 10:38:26 -04:00
582e64ae13 📦 npm: Bump eslint-plugin-compat from 3.12.0 to 3.13.0
Bumps [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) from 3.12.0 to 3.13.0.
- [Release notes](https://github.com/amilajack/eslint-plugin-compat/releases)
- [Changelog](https://github.com/amilajack/eslint-plugin-compat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/amilajack/eslint-plugin-compat/compare/v3.12.0...v3.13.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-compat
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-22 10:37:54 -04:00
eb6d058b5b set "sideEffects": false 2021-08-16 23:06:22 -04:00
46ade562c4 Release 0.6.3 2021-08-11 09:48:05 -04:00
6ef857154b publish via CI to NPM and GH packages 2021-08-11 09:47:30 -04:00
df981e17b3 add dependabot.yml 2021-08-11 09:39:52 -04:00
710e94d7dc Release 0.6.2 2021-08-07 10:04:01 -04:00
1ad8cf373e add exports field to package.json to comply with skypack.dev 2021-08-07 10:03:16 -04:00
01ff103e08 Update README 2021-08-05 12:02:56 -04:00
29728eb5cf Release 0.6.1 2021-08-05 08:47:05 -04:00
755ac19dba small cleanup 2021-08-05 08:41:45 -04:00
7e2e8948ec Release 0.6.0 2021-08-05 08:17:30 -04:00
6bf29bca6f Update README 2021-08-05 08:12:49 -04:00
cfa1f575e3 add onInit and onChange callback function options 2021-08-05 07:54:47 -04:00
8b40221791 Release 0.5.1 2021-08-04 21:00:06 -04:00
f87f43c5c5 clean up type defs, update example HTML 2021-08-04 20:59:02 -04:00
c7a304caa4 Release 0.5.0 2021-08-04 19:18:58 -04:00
eedd0a96e0 much simpler bundling with rollup, also generate separate ESM and CJS files 2021-08-04 19:16:33 -04:00
1c2b15d70f Release 0.4.0 2021-08-04 15:37:02 -04:00
a91d67cfeb a bit more cleanup 2021-08-04 15:34:22 -04:00
e4d7e4f61b remove unnecessary deps (now zero 😎) 2021-08-04 15:19:54 -04:00
4881b908e9 expose an init function instead of exporting as module default 2021-08-04 15:00:26 -04:00
092c833d97 Release 0.3.0 2021-08-04 11:44:30 -04:00
2e862538c0 Update README.md 2021-08-04 11:42:41 -04:00
df3b462de0 bundle into a UMD script via webpack for both browser & node 2021-08-04 11:31:37 -04:00
0d48f8e8af Release 0.2.0 2021-08-04 09:02:04 -04:00
2a1d634637 Check if localStorage is available before accessing 2021-08-04 09:01:27 -04:00
15 changed files with 2988 additions and 199 deletions

View File

@ -1,11 +1,7 @@
{
"extends": [
"eslint:recommended",
"plugin:compat/recommended",
"plugin:prettier/recommended"
],
"plugins": [
"prettier"
"plugin:compat/recommended"
],
"env": {
"browser": true,
@ -15,5 +11,8 @@
"ecmaVersion": 2015,
"sourceType": "module"
},
"rules": {}
"rules": {
"quotes": ["error", "double"],
"semi": ["error", "always"]
}
}

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
versioning-strategy: increase
schedule:
interval: "daily"
commit-message:
prefix: "📦 npm:"

View File

@ -16,3 +16,4 @@ jobs:
node-version: '14.x'
- run: yarn install --frozen-lockfile
- run: yarn lint
- run: yarn build

38
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Release
on:
push:
tags:
- '*'
jobs:
npm:
name: Publish to NPM
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org/
- env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
yarn install --frozen-lockfile
yarn publish
gpr:
name: Publish to GitHub
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://npm.pkg.github.com/
scope: '@jakejarvis'
- env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
yarn install --frozen-lockfile
yarn publish

11
.gitignore vendored
View File

@ -1,10 +1,5 @@
# Dependency directories
.DS_Store
node_modules/
.npm/
# Logs
*.log
npm-debug.log*
# VSCode config
dist/
.npmrc
.vscode/

View File

@ -1,6 +0,0 @@
{
"printWidth": 70,
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

View File

@ -1,39 +1,86 @@
# 🌓 Dark mode switcheroo
# 🌓 Dark Mode Switcheroo
[![CI](https://github.com/jakejarvis/dark-mode.js/actions/workflows/ci.yml/badge.svg)](https://github.com/jakejarvis/dark-mode.js/actions/workflows/ci.yml) [![npm (scoped)](https://img.shields.io/npm/v/jakejarvis/dark-mode)](https://www.npmjs.com/package/@jakejarvis/dark-mode)
[![CI](https://github.com/jakejarvis/dark-mode.js/actions/workflows/ci.yml/badge.svg)](https://github.com/jakejarvis/dark-mode.js/actions/workflows/ci.yml)
[![npm (scoped)](https://img.shields.io/npm/v/@jakejarvis/dark-mode)](https://www.npmjs.com/package/@jakejarvis/dark-mode)
[![MIT License](https://img.shields.io/github/license/jakejarvis/dark-mode?color=violet)](LICENSE)
Simple dark mode switching with saved preference via local storage & dynamic OS setting detection!
Very simple CSS dark/light mode toggler with saved preference via local storage & dynamic OS setting detection. Zero dependencies and [only ~500 bytes gzipped!](https://bundlephobia.com/package/@jakejarvis/dark-mode)
- [View the example.](https://jakejarvis.github.io/dark-mode-example/)
- [View the example.](https://jakejarvis.github.io/dark-mode/)
- [Read the blog post.](https://jarv.is/notes/dark-mode/)
- [See it in action.](https://jarv.is/)
## Usage
### `darkMode([...options])`
### Options
`darkMode.init([...options])`
- **`toggle`**: The clickable [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) used to toggle between the two themes. (optional, default: `null`)
- **`classes`**: An object containing the `<body>` class names for the light and dark themes. (optional, default: `{ light: "light", dark: "dark" }`)
- **`default`**: The initial `<body>` class hard-coded into the HTML template. (optional, default: `"light"`)
- **`storageKey`**: Name of the `localStorage` key holding the user's preference. (optional, default: `"dark_mode_pref"`)
- **`onInit([toggle])`**: Callback function executed at the end of initialization. The toggle above is passed in if set. (optional, default: `null`)
- **`onChange([theme, toggle])`**: Callback function executed when theme is switched. The new theme and the toggle above (if set) are passed in. (optional, default: `null`)
### Browser
```html
<button class="dark-mode-toggle" style="visibility: hidden;">💡 Click to see the light... or not.</button>
<script src="https://unpkg.com/@jakejarvis/dark-mode/dist/dark-mode.min.js"></script>
<script>
window.darkMode.init({
toggle: document.querySelector(".dark-mode-toggle"),
classes: {
light: "light",
dark: "dark",
},
default: "light",
storageKey: "dark_mode_pref",
onInit: function (toggle) {
toggle.style.visibility = "visible"; // toggle appears now that we know JS is enabled
},
onChange: function (theme, toggle) {
console.log("Theme is now " + theme);
},
});
</script>
```
### Node
```bash
npm install @jakejarvis/dark-mode
# or...
yarn add @jakejarvis/dark-mode
```
#### Module via `import`
```js
import darkMode from "@jakejarvis/dark-mode";
import { init } from "@jakejarvis/dark-mode";
darkMode({
toggle: document.querySelector(".dark-mode-toggle"),
classes: {
light: "light",
dark: "dark",
},
default: "light",
storageKey: "dark_mode_pref",
init({
// ...same as browser.
});
```
#### Options
#### CommonJS via `require()`
All optional.
```js
const darkMode = require("@jakejarvis/dark-mode");
- **toggle:** The clickable HTML element used to toggle between the two themes. (optional)
- **classes:** An object containing the `<body>` class names for the light and dark themes. (optional, default: `{ light: "light", dark: "dark" }`)
- **default:** The initial `<body>` class hard-coded into the HTML template. (optional, default: `light`)
- **storageKey:** Name of the `localStorage` key holding the user's preference. (optional, default: `dark_mode_pref`)
darkMode.init({
// ...same as browser.
});
```
## To-Do
- [ ] Support more than two themes
- [ ] Better readme docs
- [x] Add callback function `onChange` (or `onToggle` etc.) passed in as an option
## License

View File

@ -4,15 +4,61 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Welcome to the dark side 🌓</title>
<style>/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}body{font-family:system-ui,-apple-system,sans-serif;text-align:center}a{text-decoration:none}h1{font-size:1.8em}.dark-mode-toggle{cursor:pointer;padding:1em;visibility:hidden}body.light{background-color:#fff;color:#222}body.light a{color:#06f}body.dark{background-color:#222;color:#fff}body.dark a{color:#fe0}</style>
<style>/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}</style>
<style>
/* Global styles */
body {
font-family: system-ui, -apple-system, sans-serif;
text-align: center;
}
/* Light theme */
body.light {
background-color: #fff;
color: #222;
}
body.light a {
color: #06f;
}
/* Dark theme */
body.dark {
background-color: #222;
color: #fff;
}
body.dark a {
color: #fe0;
}
/* The Toggle (TM) */
.dark-mode-toggle {
padding: 1em;
cursor: pointer;
/* Hide the toggle initially if the user's JS is disabled: */
visibility: hidden;
}
</style>
</head>
<body class="light">
<body class="light"> <!-- default to light theme -->
<h1>Welcome to the dark side 🌓</h1>
<button class="dark-mode-toggle">💡 Click to see the light... or not.</button>
<p><a href="https://github.com/jakejarvis/dark-mode-example" target="_blank" rel="noopener">View the source code</a> or <a href="https://jarv.is/notes/dark-mode/" target="_blank" rel="noopener">read the post</a>.</p>
<p><a href="https://github.com/jakejarvis/dark-mode" target="_blank" rel="noopener">View the source code</a> or <a href="https://jarv.is/notes/dark-mode/" target="_blank" rel="noopener">read the post</a>.</p>
<script src="main.js"></script>>
<script src="../dist/dark-mode.min.js"></script> <!-- or use CDN: https://unpkg.com/@jakejarvis/dark-mode/dist/dark-mode.min.js -->
<script>
window.darkMode.init({
toggle: document.querySelector(".dark-mode-toggle"),
onInit: function (e) {
e.style.visibility = "visible";
console.log("Toggle is visible now that we know user has JS enabled.");
},
onChange: function (t) {
console.log("Set theme to " + t);
}
});
</script>
</body>
</html>

View File

@ -1,5 +0,0 @@
import darkMode from "../index.js";
darkMode({
toggle: document.querySelector(".dark-mode-toggle"),
});

8
index.d.ts vendored
View File

@ -1,8 +0,0 @@
interface DarkModeOptions {
toggle?: HTMLElement;
classes?: { dark: string, light: string };
default?: string;
storageKey?: string;
}
export default function (options?: Partial<DarkModeOptions>): void;

View File

@ -1,40 +1,56 @@
{
"name": "@jakejarvis/dark-mode",
"version": "0.1.1",
"description": "Super simple CSS theme switching with saved preferences and automatic OS setting detection",
"version": "0.7.1",
"description": "🌓 Simple CSS theme switching with saved preferences and automatic OS setting detection",
"license": "MIT",
"author": {
"name": "Jake Jarvis",
"email": "jake@jarv.is",
"url": "https://jarv.is/"
},
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/jakejarvis/dark-mode.git"
},
"exports": "./index.js",
"types": "./index.d.ts",
"node": "^12.20.0 || ^14.13.1 || >=16.0.0",
"type": "module",
"sideEffects": false,
"files": [
"dist"
],
"main": "./dist/dark-mode.cjs.js",
"module": "./dist/dark-mode.esm.js",
"unpkg": "./dist/dark-mode.min.js",
"types": "./dist/dark-mode.d.ts",
"exports": {
"require": "./dist/dark-mode.cjs.js",
"import": "./dist/dark-mode.esm.js",
"browser": "./dist/dark-mode.min.js"
},
"scripts": {
"lint": "eslint **/*.js"
"build": "rollup -c",
"lint": "eslint src/**/*.js",
"prepublishOnly": "rm -rf dist && yarn build"
},
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.15.0",
"@babel/preset-env": "^7.15.0",
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-eslint": "^8.0.1",
"@rollup/plugin-node-resolve": "^13.0.4",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-compat": "^3.11.1",
"eslint-plugin-prettier": "^3.4.0",
"prettier": "^2.3.2"
"eslint-plugin-compat": "^3.13.0",
"rollup": "^2.56.3",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-filesize": "^9.1.1",
"rollup-plugin-terser": "^7.0.2"
},
"files": [
"index.js",
"index.d.ts",
"LICENSE"
],
"keywords": [
"frontend",
"dark mode",
"theme",
"appearance",
"design",
"css"
]
}

88
rollup.config.js Normal file
View File

@ -0,0 +1,88 @@
import pkg from "./package.json";
import resolve from "@rollup/plugin-node-resolve";
import { babel } from "@rollup/plugin-babel";
import { terser } from "rollup-plugin-terser";
import eslint from "@rollup/plugin-eslint";
import filesize from "rollup-plugin-filesize";
import copy from "rollup-plugin-copy";
const banner = `/*! Dark Mode Switcheroo v${pkg.version} | MIT License | jrvs.io/darkmode */`;
export default [
{
// universal (browser and node)
input: "src/index.js",
output: [
{
name: "darkMode",
file: "dist/dark-mode.js",
format: "umd",
exports: "named",
esModule: false,
banner: banner,
},
{
name: "darkMode",
file: "dist/dark-mode.min.js",
format: "umd",
exports: "named",
esModule: false,
plugins: [
terser({
output: {
preamble: banner,
},
}),
],
},
],
plugins: [
copy({
// clearly this isn't really typescript, so we need to manually copy the type definition file
targets: [
{
src: "src/index.d.ts",
dest: "dist",
rename: "dark-mode.d.ts",
},
],
}),
resolve(),
eslint(),
babel({
babelHelpers: "bundled",
presets: [["@babel/preset-env"]],
exclude: ["node_modules/**"],
}),
filesize(),
],
},
{
// modules
input: "src/index.js",
output: [
{
// ES6 module (import)
file: "dist/dark-mode.esm.js",
format: "esm",
exports: "named",
banner: banner,
},
{
// commonjs (require)
file: "dist/dark-mode.cjs.js",
format: "cjs",
exports: "named",
banner: banner,
},
],
plugins: [
resolve(),
babel({
babelHelpers: "bundled",
exclude: ["node_modules/**"],
}),
filesize(),
],
},
];

8
src/index.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
export function init(options?: {
toggle?: HTMLElement;
classes?: { dark: string, light: string };
default?: string;
storageKey?: string;
onInit?: (toggle?: HTMLElement) => unknown;
onChange?: (theme?: string, toggle?: HTMLElement) => unknown;
}): void;

View File

@ -1,27 +1,16 @@
/*! Dark mode switcheroo | MIT License | jrvs.io/darkmode */
export default function (options) {
// { toggle, classes: { light, dark }, default, storageKey }
const init = function (options) {
options = options || {};
console.log(options);
// use a specified element(s) to trigger swap when clicked
const toggle = options.toggle || null;
console.log(toggle);
// check for preset `dark_mode_pref` preference in local storage
const storageKey = options.storageKey || "dark_mode_pref";
const pref = localStorage.getItem(storageKey);
// change CSS via these <body> classes:
let dark = "dark";
let light = "light";
if (options.classes) {
dark = options.classes.dark;
light = options.classes.light;
}
const dark = options.classes ? options.classes.dark : "dark";
const light = options.classes ? options.classes.light : "light";
// which class is <body> set to initially?
const defaultTheme = options.default || "light";
@ -30,10 +19,19 @@ export default function (options) {
let active = defaultTheme === dark;
// receives a class name and switches <body> to it
const activateTheme = function (theme) {
const activateTheme = function (theme, remember) {
document.body.classList.remove(dark, light);
document.body.classList.add(theme);
active = theme === dark;
if (remember) {
localStorage.setItem(storageKey, theme);
}
// optional onChange callback function passed as option
if (typeof options.onChange === "function") {
options.onChange(theme, toggle);
}
};
// user has never clicked the button, so go by their OS preference until/if they do so
@ -55,20 +53,16 @@ export default function (options) {
}
// real-time switching if supported by OS/browser
window
.matchMedia(prefers("dark"))
.addEventListener("change", function (e) {
if (e.matches) {
activateTheme(dark);
}
});
window
.matchMedia(prefers("light"))
.addEventListener("change", function (e) {
if (e.matches) {
activateTheme(light);
}
});
window.matchMedia(prefers("dark")).addEventListener("change", function (e) {
if (e.matches) {
activateTheme(dark);
}
});
window.matchMedia(prefers("light")).addEventListener("change", function (e) {
if (e.matches) {
activateTheme(light);
}
});
} else if (pref === dark || pref === light) {
// if user already explicitly toggled in the past, restore their preference
activateTheme(pref);
@ -79,19 +73,21 @@ export default function (options) {
// don't freak out if page happens not to have a toggle
if (toggle !== null) {
// toggle re-appears now that we know user has JS enabled
toggle.style.display = "block";
// handle toggle click
toggle.addEventListener("click", function () {
// switch to the opposite theme & save preference in local storage
if (active) {
activateTheme(light);
localStorage.setItem(storageKey, light);
activateTheme(light, true);
} else {
activateTheme(dark);
localStorage.setItem(storageKey, dark);
activateTheme(dark, true);
}
});
}
}
// optional onInit callback function passed as option
if (typeof options.onInit === "function") {
options.onInit(toggle);
}
};
export { init };

2742
yarn.lock

File diff suppressed because it is too large Load Diff