From 1f286e56ab8465d97af65c6c12e37a155ba474a9 Mon Sep 17 00:00:00 2001 From: Victor Li Date: Tue, 20 Oct 2020 16:39:18 -0700 Subject: [PATCH] feat: 1.0.0 (#1) * chore: Add .gitignore * chore: Add .prettierrc * chore: npm init * feat: * chore: Update README.md * chore: Update LICENSE * fix: Filter defaults not applied when partial filter object is given * chore: Update README.md * feat: Replaced `defaultMode` with `appearance` * chore: Update README.md * chore: Update LICENSE * chore: `package.json` changes for release * chore: Update README.md --- .gitignore | 1 + .prettierrc | 13 ++++++ LICENSE | 3 +- README.md | 113 +++++++++++++++++++++++++++++++++++++++++++++- index.js | 1 + interpolator.jsx | 57 +++++++++++++++++++++++ package-lock.json | 13 ++++++ package.json | 30 ++++++++++++ 8 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 index.js create mode 100644 interpolator.jsx create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..fa22fec --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "printWidth": 80, + "tabWidth": 2, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": true, + "bracketSpacing": false, + "jsxBracketSameLine": true, + "proseWrap": "always", + "trailingComma": "none", + "quoteProps": "consistent", + "arrowParens": "avoid" +} diff --git a/LICENSE b/LICENSE index 651d99a..c84a03a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License -Copyright (c) 2020 VALISOFT +Copyright (c) 2019 Alexander Shutau +Copyright (c) 2020 Victor Li Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d120b66..dcf68ae 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,113 @@ # react-apply-darkmode -Apply dark mode interpolation with @darkreader/darkreader directly to your React app with zero manual theming. + +Apply dark mode directly to your React website or web app in one step; no manual +theming required! Reduce component complexity and CSS while still delivering a +high-quality dark mode experience that your users will greatly appreciate. + +`react-apply-darkmode` is a wrapper around +[@darkreader/darkreader](https://github.com/darkreader/darkreader)'s ES6 API, +allowing you to control the dark mode functionality it provides with React +bindings. Your users **do NOT** need to have the Dark Reader extension installed +for dark mode to work. + +## Installation + +`npm i react-apply-darkmode` + +`yarn add react-apply-darkmode` + +## Usage + +Use the `Interpolator` component to wrap your app at the top level: + +##### `App.js` + +```javascript +import {Interpolator} from 'react-apply-darkmode'; + +export default function App() { + return ( + + + + ); +} +``` + +## Props + +| Prop | Values | Purpose | +| ----------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| appearance | `'dark'`, `'light'`, or `undefined` | `Interpolator` will apply this theme to your site; this is your manual control for dark mode. `appearance` is `undefined` by default; if `undefined`, no theme will be applied on mount (you can still use `watchSystem` to control dark mode). | +| watchSystem | `true` or `false` | Apply dark mode based on the device's color scheme. This will override `appearance` if you've set it. `watchSystem` defaults to `false`. Not all browsers are supported; see Notes for more. | +| filter | `{brightness: number, contrast: number, sepia: number}` | Dark mode's appearance filter. You can supply any number of these values or none at all; default is 100 brightness, 90 contrast, and 10 sepia. | + +## Notes + +- `Interpolator` (or a component that utilizes it) should always your top-level + component, such as in `App` in `App.js` if using Create React App or via + `wrapRootElement` if using Gatsby. This will ensure that dark mode is ready + before your components render, preventing undesirable flashes. + + You will only need to define `wrapRootElement` for Gatsby's browser API in + `gatsby-browser.js`; you do not need do this in `gatsby-ssr.js`. + +- `watchSystem` relies on the `prefers-color-scheme` CSS media feature, which + some browsers may not support; privacy settings can also influence the value + of `prefers-color-scheme`. + + Mozilla has compiled a list of compatible browsers + [here](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme#Browser_compatibility). + +## Tips + +### Persistence + +You will need to implement your own wrapper component or container to persist +dark mode across browser sessions by changing values supplied to the +`appearance` prop. `localStorage` and `redux` (with `redux-persist`) are both +good solutions. + +### Colors + +`react-apply-darkmode` will tone down bright colors and reduce contrast. Don't +assume that a color is going to work optimally in light and dark mode; always do +a visual test by switching between both modes! + +### Images + +Maximize transparency of image assets! `react-apply-darkmode` will not invert +images (to preserve your sanity), so make sure your images have transparent +backgrounds and transparency where color isn't needed. + +Also, try to use colors that have good contrast in both light and dark mode. + +### Theming Conflicts + +Don't use another dark mode theming solution alongside `react-apply-darkmode` +(i.e. with React context/providers, CSS classes, or another package). This can +cause undesirable flickering effects when solutions try to compensate for each +other's changes. + +### Component Library Issues + +Certain UI component libraries don't work well with `react-apply-darkmode`. +Audit a library before choosing it! Installing the Dark Reader browser extension +(Chrome or Firefox, for best results) and exploring a component library's site +will give you a good idea of how well it works. + +You can view a list of issue libraries +[here](https://github.com/valisoftpdx/react-apply-darkmode/wiki/Issue-Component-Libraries). + +## Credits + +This package was created by [Victor Li](https://github.com/victorli08), an avid +and longtime user of the Dark Reader extension. + +`react-apply-darkmode` is made possible by open source code from +[@darkreader](https://github.com/darkreader/darkreader). If you like this +package, please give a shoutout to Dark Reader's developers and consider +sponsoring their project! diff --git a/index.js b/index.js new file mode 100644 index 0000000..9c4949b --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +export {default as Interpolator} from './interpolator'; diff --git a/interpolator.jsx b/interpolator.jsx new file mode 100644 index 0000000..34eb34e --- /dev/null +++ b/interpolator.jsx @@ -0,0 +1,57 @@ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import {enable as enableDarkMode, disable as disableDarkMode} from 'darkreader'; + +const defaultFilter = { + brightness: 100, + contrast: 90, + sepia: 10 +}; + +export default function Interpolator({ + appearance, + watchSystem = false, + filter, + children +}) { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const [watchIsDark, setWatchIsDark] = useState(mq.matches); + + function updateWatch(update) { + setWatchIsDark(update.matches); + } + + useEffect(() => { + mq.addEventListener('change', updateWatch); + return () => mq.removeEventListener('change', updateWatch); + }, [mq]); + + const filterProps = filter ? filter : defaultFilter; + const { + brightness = defaultFilter.brightness, + contrast = defaultFilter.contrast, + sepia = defaultFilter.sepia + } = filterProps; + + if ((watchSystem && watchIsDark) || (!watchSystem && appearance === 'dark')) { + enableDarkMode({brightness, contrast, sepia}); + } else if ( + (watchSystem && !watchIsDark) || + (!watchSystem && appearance !== 'dark') + ) { + disableDarkMode(); + } + + return <>{children}; +} + +Interpolator.propTypes = { + appearance: PropTypes.string, + watchSystem: PropTypes.bool, + filter: PropTypes.shape({ + brightness: PropTypes.number, + contrast: PropTypes.number, + sepia: PropTypes.number + }), + children: PropTypes.node +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1ad69ea --- /dev/null +++ b/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "react-apply-darkmode", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "darkreader": { + "version": "4.9.21", + "resolved": "https://registry.npmjs.org/darkreader/-/darkreader-4.9.21.tgz", + "integrity": "sha512-7j0T8EoWoUXY32HsRkX/JQeSZ4MovWUH95f916CkPkLUAsPFG/ZaZBQLYU6k11aIGym8TR1S+D+bMA1VMXTTlg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cc2a8f1 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "react-apply-darkmode", + "version": "1.0.0", + "description": "Apply dark mode to your React app with zero manual theming.", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/valisoftworks/react-apply-darkmode.git" + }, + "keywords": [ + "react", + "darkreader", + "dark-theme", + "dark-mode", + "react-component", + "component" + ], + "author": { + "name": "Victor Li", + "email": "victorli08@outlook.com" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/valisoftworks/react-apply-darkmode/issues" + }, + "homepage": "https://github.com/valisoftworks/react-apply-darkmode#readme", + "dependencies": { + "darkreader": "^4.9.21" + } +}