From 889f9aa65a76f7693eb318d876c3f1b99eed5e9b Mon Sep 17 00:00:00 2001 From: Ben Rogerson Date: Sat, 5 Sep 2020 15:17:45 +0930 Subject: [PATCH] add dark and light mode variants --- __fixtures__/!variants.js | 8 +++ __snapshots__/plugin.test.js.snap | 53 +++++++++++++++++++ package-lock.json | 12 +++-- package.json | 2 +- src/config/variantConfig.js | 86 ++++++++++++++++++++++++++++--- src/variants.js | 33 ++++++++++-- tailwind.config.js | 1 + 7 files changed, 182 insertions(+), 13 deletions(-) diff --git a/__fixtures__/!variants.js b/__fixtures__/!variants.js index 5fc027fc..11d65610 100644 --- a/__fixtures__/!variants.js +++ b/__fixtures__/!variants.js @@ -60,4 +60,12 @@ tw`focus-within:flex` tw`motion-safe:flex` tw`motion-reduce:flex` +// Dark/Light themes +tw`dark:bg-black` +tw`light:bg-black` +tw`dark:sm:bg-black` +tw`light:sm:bg-black` +tw`dark:group-hover:sm:bg-black` +tw`light:group-hocus:sm:bg-black` + const multiVariants = tw`xl:placeholder-red-500! first:md:block sm:disabled:flex` diff --git a/__snapshots__/plugin.test.js.snap b/__snapshots__/plugin.test.js.snap index f608470b..74af9a28 100644 --- a/__snapshots__/plugin.test.js.snap +++ b/__snapshots__/plugin.test.js.snap @@ -1892,6 +1892,14 @@ tw\`focus-within:flex\` tw\`motion-safe:flex\` tw\`motion-reduce:flex\` +// Dark/Light themes +tw\`dark:bg-black\` +tw\`light:bg-black\` +tw\`dark:sm:bg-black\` +tw\`light:sm:bg-black\` +tw\`dark:group-hover:sm:bg-black\` +tw\`light:group-hocus:sm:bg-black\` + const multiVariants = tw\`xl:placeholder-red-500! first:md:block sm:disabled:flex\` ↓ ↓ ↓ ↓ ↓ ↓ @@ -2137,6 +2145,51 @@ const multiVariants = tw\`xl:placeholder-red-500! first:md:block sm:disabled:fle '@media (prefers-reduced-motion: reduce)': { display: 'flex', }, +}) // Dark/Light themes + +;({ + '.dark &': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, +}) +;({ + '.light &': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, +}) +;({ + '.dark &': { + '@media (min-width: 640px)': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, + }, +}) +;({ + '.light &': { + '@media (min-width: 640px)': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, + }, +}) +;({ + '.dark .group:hover &, .dark.group:hover &': { + '@media (min-width: 640px)': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, + }, +}) +;({ + '.light .group:hover &, .light .group:focus &, .light.group:hover &, .light.group:focus &': { + '@media (min-width: 640px)': { + '--bg-opacity': '1', + backgroundColor: 'rgba(0, 0, 0, var(--bg-opacity))', + }, + }, }) const multiVariants = { ':first-child': { diff --git a/package-lock.json b/package-lock.json index 2da9fb3a..2e8e2dc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5202,6 +5202,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==" + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -13972,9 +13977,9 @@ } }, "tailwindcss": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.7.5.tgz", - "integrity": "sha512-thDHLkRioJh0/62EFcEfQCCBEsZXpluehymrPzn8Hkycy8uI9svvtOqyWtcfkBPB0s5yb6R2tY9zPzh5mIr0Wg==", + "version": "1.8.8", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-1.8.8.tgz", + "integrity": "sha512-MCaTFA+ae278rYeB0UTJAkWJMW5eYMMO6/XXBL0oo+SKuZCM4uCFskroHbMFvQSoA96sslFX2+tCPDOv1T7Tbw==", "requires": { "@fullhuman/postcss-purgecss": "^2.1.2", "autoprefixer": "^9.4.5", @@ -13984,6 +13989,7 @@ "color": "^3.1.2", "detective": "^5.2.0", "fs-extra": "^8.0.0", + "html-tags": "^3.1.0", "lodash": "^4.17.20", "node-emoji": "^1.8.1", "normalize.css": "^8.0.1", diff --git a/package.json b/package.json index c494ca3c..4ebc7ec7 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "dset": "^2.0.1", "lodash.merge": "^4.6.2", "string-similarity": "^4.0.1", - "tailwindcss": "^1.7.5", + "tailwindcss": "^1.8.8", "timsort": "^0.3.0" }, "peerDependencies": { diff --git a/src/config/variantConfig.js b/src/config/variantConfig.js index ed9e695d..4bb88357 100644 --- a/src/config/variantConfig.js +++ b/src/config/variantConfig.js @@ -57,16 +57,90 @@ export default { 'odd-of-type': ':nth-of-type(odd)', // Group states - // Add className="group" to an ancestor and add these on the children + // You'll need to add className="group" to an ancestor to make these work // https://github.com/ben-rogerson/twin.macro/blob/master/docs/group.md - 'group-hover': '.group:hover &', // Tailwind - 'group-focus': '.group:focus &', // Tailwind - 'group-hocus': '.group:hover &, .group:focus &', - 'group-active': '.group:active &', - 'group-visited': '.group:visited &', + 'group-hover': variantData => + generateGroupSelector('.group:hover &', variantData), // Tailwind + 'group-focus': variantData => + generateGroupSelector('.group:focus &', variantData), // Tailwind + 'group-hocus': variantData => + generateGroupSelector('.group:hover &, .group:focus &', variantData), + 'group-active': variantData => + generateGroupSelector('.group:active &', variantData), + 'group-visited': variantData => + generateGroupSelector('.group:visited &', variantData), // Motion control // https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion 'motion-safe': '@media (prefers-reduced-motion: no-preference)', 'motion-reduce': '@media (prefers-reduced-motion: reduce)', + + // Dark theme + dark: ({ hasGroupVariant, config, errorCustom }) => { + const styles = + { + // Media strategy: The default when you prepend with dark, tw`dark:block` + media: '@media (prefers-color-scheme: dark)', + // Class strategy: In your tailwind.config.js, add `{ dark: 'class' } + // then add a `className="dark"` on a parent element. + class: !hasGroupVariant && '.dark &', + }[config('dark') || 'media'] || null + + if (!styles && !hasGroupVariant) { + errorCustom( + "The `dark` config option must be either `{ dark: 'media' }` (default) or `{ dark: 'class' }`" + ) + } + + return styles + }, + + // Light theme + light: ({ hasGroupVariant, config, errorCustom }) => { + const styles = + { + // Media strategy: The default when you prepend with light, tw`light:block` + media: '@media (prefers-color-scheme: light)', + // Class strategy: In your tailwind.config.js, add `{ light: 'class' } + // then add a `className="light"` on a parent element. + class: !hasGroupVariant && '.light &', + }[config('light') || config('dark') || 'media'] || null + + if (!styles && !hasGroupVariant) { + if (config('light')) { + errorCustom( + "The `light` config option must be either `{ light: 'media' }` (default) or `{ light: 'class' }`" + ) + } + + errorCustom( + "The `dark` config option must be either `{ dark: 'media' }` (default) or `{ dark: 'class' }`" + ) + } + + return styles + }, +} + +const generateGroupSelector = ( + className, + { hasDarkVariant, hasLightVariant, config } +) => { + const themeVariant = + (hasDarkVariant && config('dark') === 'class' && ['dark ', 'dark']) || + (hasLightVariant && + (config('light') === 'class' || config('dark') === 'class') && [ + 'light ', + 'light', + ]) + return themeVariant + ? themeVariant + .map(v => + className + .split(', ') + .map(cn => `.${v}${cn}`) + .join(', ') + ) + .join(', ') + : className } diff --git a/src/variants.js b/src/variants.js index e860f2b8..d86d7d79 100644 --- a/src/variants.js +++ b/src/variants.js @@ -2,13 +2,13 @@ import { MacroError } from 'babel-plugin-macros' import dlv from 'dlv' import cleanSet from 'clean-set' import { stringifyScreen } from './screens' -import { logNoVariant } from './logging' +import { logNoVariant, logGeneralError } from './logging' import { variantConfig } from './config' /** * Validate variants against the variants config key */ -const validateVariants = ({ variants, state }) => { +const validateVariants = ({ variants, state, ...rest }) => { if (!variants) return [] const screens = dlv(state.config, ['theme', 'screens']) @@ -22,7 +22,18 @@ const validateVariants = ({ variants, state }) => { } if (variantConfig[variant]) { - const foundVariant = variantConfig[variant] + let foundVariant = variantConfig[variant] + + if (typeof foundVariant === 'function') { + const context = { + ...rest, + config: item => state.config[item] || null, + errorCustom: message => { + throw new MacroError(logGeneralError(message)) + }, + } + foundVariant = foundVariant(context) + } if (state.sassyPseudo) { return foundVariant.replace(/(?<= ):|^:/g, '&:') @@ -55,10 +66,26 @@ const splitVariants = ({ classNameRaw, state }) => { } } + // dark: and light: variants + const hasDarkVariant = variantsList.some(v => v === 'dark') + const hasLightVariant = variantsList.some(v => v === 'light') + if (hasDarkVariant && hasLightVariant) { + throw new MacroError( + logGeneralError( + 'The light: and dark: variants can’t be used on the same element' + ) + ) + } + + const hasGroupVariant = variantsList.some(v => v.startsWith('group-')) + // Match the filtered variants const variants = validateVariants({ variants: variantsList, state, + hasDarkVariant, + hasLightVariant, + hasGroupVariant, }) const hasVariants = variants.length > 0 diff --git a/tailwind.config.js b/tailwind.config.js index 1675f74f..5327b7ae 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -99,6 +99,7 @@ const animations = { } module.exports = { + dark: 'class', theme: { ...animations, container: {