tailwindcss-theme-variants
Advanced tools
Comparing version 0.0.5 to 0.1.0
@@ -7,16 +7,9 @@ "use strict"; | ||
const plugin_1 = __importDefault(require("tailwindcss/plugin")); | ||
const nameVariant = (renamedTheme, responsive, variantName) => { | ||
const nameVariant = (renamedTheme, variantName) => { | ||
if (variantName === "") { | ||
if (responsive === "") { | ||
return renamedTheme; | ||
} | ||
return `${renamedTheme}:${responsive}`; | ||
return renamedTheme; | ||
} | ||
if (responsive === "") { | ||
return `${renamedTheme}:${variantName}`; | ||
} | ||
return `${renamedTheme}:${responsive}:${variantName}`; | ||
return `${renamedTheme}:${variantName}`; | ||
}; | ||
const thisPlugin = plugin_1.default.withOptions(({ themes, baseSelector = ":root", fallback = false, rename = (themeName) => themeName, variants = {}, }) => ({ addVariant, e, postcss, theme, }) => { | ||
var _a; | ||
const thisPlugin = plugin_1.default.withOptions(({ themes, baseSelector = ":root", fallback = false, rename = (themeName) => themeName, variants = {}, }) => ({ addVariant, e, postcss, }) => { | ||
const allThemes = Object.entries(themes); | ||
@@ -26,3 +19,2 @@ if (allThemes.length === 0) { | ||
} | ||
const allScreens = Object.entries((_a = theme("screens", {})) !== null && _a !== void 0 ? _a : {}); | ||
if (fallback === true) { | ||
@@ -39,68 +31,48 @@ fallback = allThemes[0][0]; // eslint-disable-line prefer-destructuring,no-param-reassign | ||
} | ||
// Don't even try to add responsive variants if there are no screens, because they won't create any CSS if used | ||
(allScreens.length === 0 ? [false] : [false, true]).forEach((isResponsive) => { | ||
// Use a dummy screen to reduce duplicating logic between the responsive and not responsive versions | ||
const screens = isResponsive ? allScreens : [["", undefined]]; | ||
// Similarly, use a dummy default variant first | ||
Object.entries({ "": (selector) => selector, ...variants }).forEach(([variantName, variantFunction]) => { | ||
allThemes.forEach(([themeName, { mediaQuery, selector }]) => { | ||
const nameThisVariant = (responsive) => nameVariant(rename(themeName), responsive, variantName); | ||
addVariant(nameThisVariant(isResponsive ? "responsive" : ""), ({ container, separator }) => { | ||
const nameSelector = (namedVariant, ruleSelector) => `${variantFunction(`.${e(`${namedVariant.replace(/:/g, separator)}${separator}`)}${ruleSelector.slice(1)}`)}`; | ||
const originalContainer = container.clone(); | ||
// Remove the pre-existing (provided by Tailwind's core) CSS so that we don't duplicate it | ||
container.removeAll(); | ||
screens.forEach(([screen, minWidth]) => { | ||
// Tool to nest the utilities inside the screen media query if it exists | ||
const nestIfNecessaryAndAdd = (source, destination) => { | ||
if (minWidth) { | ||
const screenAtRule = postcss.atRule({ name: "media", params: `(min-width: ${minWidth})` }); | ||
screenAtRule.append(source); | ||
destination.append(screenAtRule); | ||
} | ||
else { | ||
destination.append(source); | ||
} | ||
}; | ||
if (themeName === fallback) { | ||
const containerFallBack = originalContainer.clone(); | ||
containerFallBack.walkRules((rule) => { | ||
const namedSelector = nameSelector(nameThisVariant(""), rule.selector); | ||
const inactiveThemes = selector ? allThemes.map(([_themeName, { selector: otherSelector }]) => `:not(${otherSelector})`) : []; | ||
rule.selector = `${baseSelector}${inactiveThemes.join("")} ${namedSelector}`; | ||
}); | ||
nestIfNecessaryAndAdd(containerFallBack, container); | ||
// Use a dummy default variant first | ||
Object.entries({ "": (selector) => selector, ...variants }).forEach(([variantName, variantFunction]) => { | ||
allThemes.forEach(([themeName, { mediaQuery, selector }]) => { | ||
const namedVariant = nameVariant(rename(themeName), variantName); | ||
addVariant(namedVariant, ({ container, separator }) => { | ||
const nameSelector = (ruleSelector) => `${variantFunction(`.${e(`${namedVariant.replace(/:/g, separator)}${separator}`)}${ruleSelector.slice(1)}`)}`; | ||
const originalContainer = container.clone(); | ||
// Remove the pre-existing (provided by Tailwind's core) CSS so that we don't duplicate it | ||
container.removeAll(); | ||
if (themeName === fallback) { | ||
const containerFallBack = originalContainer.clone(); | ||
containerFallBack.walkRules((rule) => { | ||
const namedSelector = nameSelector(rule.selector); | ||
const inactiveThemes = selector ? allThemes.map(([_themeName, { selector: otherSelector }]) => ((selector === otherSelector) ? "" : `:not(${otherSelector})`)) : []; | ||
rule.selector = `${baseSelector}${inactiveThemes.join("")} ${namedSelector}`; | ||
}); | ||
container.append(containerFallBack); | ||
} | ||
if (mediaQuery) { | ||
const queryAtRule = postcss.parse(mediaQuery).first; // eslint-disable-line @typescript-eslint/no-explicit-any | ||
// Nest the utilities inside the given media query | ||
const queryContainer = originalContainer.clone(); | ||
queryContainer.walkRules((rule) => { | ||
const namedSelector = nameSelector(rule.selector); | ||
if (fallback && baseSelector !== "") { | ||
const inactiveThemes = selector ? allThemes.map(([_themeName, { selector: otherSelector }]) => ((selector === otherSelector) ? "" : `:not(${otherSelector})`)) : []; | ||
rule.selector = `${baseSelector}${inactiveThemes.join("")} ${namedSelector}`; | ||
} | ||
if (mediaQuery) { | ||
const queryAtRule = postcss.parse(mediaQuery).first; // eslint-disable-line @typescript-eslint/no-explicit-any | ||
// Nest the utilities inside the given media query | ||
const queryContainer = originalContainer.clone(); | ||
queryContainer.walkRules((rule) => { | ||
const namedSelector = nameSelector(nameThisVariant(screen), rule.selector); | ||
// Make sure specifity is high enough for media-query-backed themes to overcome their fallbackconst namedSelector = nameSelector(e, nameThisVariant(""), separator, rule.selector, variantFunction); | ||
if (fallback && baseSelector !== "") { | ||
const inactiveThemes = selector ? allThemes.map(([_themeName, { selector: otherSelector }]) => `:not(${otherSelector})`) : []; | ||
rule.selector = `${baseSelector}${inactiveThemes.join("")} ${namedSelector}`; | ||
} | ||
else { | ||
rule.selector = namedSelector; | ||
} | ||
}); | ||
if (queryContainer.nodes) { | ||
queryAtRule.append(queryContainer.nodes); | ||
} | ||
nestIfNecessaryAndAdd(queryAtRule, container); | ||
else { | ||
rule.selector = namedSelector; | ||
} | ||
if (selector) { | ||
const normalScreenContainer = originalContainer.clone(); | ||
normalScreenContainer.walkRules((rule) => { | ||
const namedSelector = nameSelector(nameThisVariant(screen), rule.selector); | ||
const activator = `${baseSelector}${selector}`; | ||
rule.selector = `${activator} ${namedSelector}`; | ||
}); | ||
// Nest the utilities inside the screen media query if it exists | ||
nestIfNecessaryAndAdd(normalScreenContainer, container); | ||
} | ||
}); | ||
}); | ||
if (queryContainer.nodes) { | ||
queryAtRule.append(queryContainer.nodes); | ||
} | ||
container.append(queryAtRule); | ||
} | ||
if (selector) { | ||
const normalScreenContainer = originalContainer.clone(); | ||
normalScreenContainer.walkRules((rule) => { | ||
const namedSelector = nameSelector(rule.selector); | ||
const activator = `${baseSelector}${selector}`; | ||
rule.selector = `${activator} ${namedSelector}`; | ||
}); | ||
container.append(normalScreenContainer); | ||
} | ||
}); | ||
@@ -107,0 +79,0 @@ }); |
{ | ||
"name": "tailwindcss-theme-variants", | ||
"version": "0.0.5", | ||
"version": "0.1.0", | ||
"description": "JavaScript- or media-query-based theme variants with fallback for Tailwind CSS", | ||
@@ -34,2 +34,3 @@ "keywords": [ | ||
"devDependencies": { | ||
"@danestves/tailwindcss-darkmode": "^1.1.5", | ||
"@types/assert": "^1.4.7", | ||
@@ -49,2 +50,4 @@ "@types/lodash": "^4.14.153", | ||
"source-map": "^0.7.3", | ||
"tailwindcss-dark-mode": "^1.1.4", | ||
"tailwindcss-theme-variants": "^0.0.5", | ||
"ts-node": "^8.10.2", | ||
@@ -51,0 +54,0 @@ "typescript": "^3.9.3" |
@@ -155,9 +155,13 @@ This Tailwind CSS plugin registers variants for theming without needing custom properties. It has support for responsive variants, extra stacked variants, media queries, and falling back to a particular theme when none matches. | ||
- `fallback` (default `false`): chooses a theme to fall back to when none of the media queries or selectors are active. You can either manually select a theme by giving a string like `" | ||
- `fallback` (default `false`): chooses a theme to fall back to when none of the media queries or selectors are active. You can either manually select a theme by giving a string like `"solarized-dark"` or implicitly select the first one listed in `themes` by giving `true`. | ||
- `rename` (default is a function that gives back exactly what was passed as in `rename("red") === "red"`, i.e. no renaming actually takes place): a function for renaming every theme, which changes the name of the generated variants. The most usual way to use this is to add a prefix or suffix to reduce duplication. For example, you can ``rename: (themeName) => `${themeName}-theme` `` to make `themes: { red, green, blue }` have corresponding variants `red-theme`, `green-theme`, and `blue-theme`. This also means that their generated class names are like `red-theme\:bg-green-300` instead of just `red\:bg-green-300`. | ||
- `rename` (default is a function that gives back exactly what was passed, as in `rename("red") === "red"`, i.e. no renaming actually takes place): a function for renaming every theme, which changes the name of the generated variants. | ||
- `variants` (default is nothing): an object mapping the name of extra variants to a function that explains what has to be done to the selector for it to be active. For example, the importable `even` variant takes a `selector` and returns `` `${selector}:nth-child(even)` ``. The importable `groupHover` (which you are recommended to name `"group-hover"` for consistency) variant returns `` `.group:hover ${selector}` `` | ||
The most usual way to use this is to add a prefix or suffix to reduce duplication. For example, you can ``rename: (themeName) => `${themeName}-theme` `` to make `themes: { red, green, blue }` have corresponding variants `red-theme`, `green-theme`, and `blue-theme`. This also means that their generated class names are like `red-theme\:bg-green-300` instead of just `red\:bg-green-300`. | ||
- `variants` (default is nothing): an object mapping the name of extra variants to a function that explains what has to be done to the selector for it to be active. | ||
For example, the importable `even` variant takes a `selector` and returns `` `${selector}:nth-child(even)` ``. The importable `groupHover` (which you are recommended to name `"group-hover"` for consistency) variant returns `` `.group:hover ${selector}` `` | ||
## Examples | ||
@@ -193,4 +197,82 @@ š” At the time of writing, this documentation is a work in progress. For all examples, where I've done my best to stretch the plugin to its limits (especially towards the end of the file), see the test suite in [`tests/index.ts`](https://github.com/SirNavith/tailwindcss-theme-variants/blob/master/tests/index.ts#L46). | ||
<table> | ||
<thead> | ||
<tr> | ||
<th></th> | ||
<th><a href="https://tailwindcss.com/docs/breakpoints/#dark-mode">Native screens</a></th> | ||
<th><a href="https://github.com/ChanceArthur/tailwindcss-dark-mode">tailwindcss-dark-mode</a></th> | ||
<th><a href="https://github.com/danestves/tailwindcss-darkmode">tailwindcss-darkmode</a></th> | ||
<th><a href="https://github.com/javifm86/tailwindcss-prefers-dark-mode">tailwindcss-prefers-dark-mode</a></th> | ||
<th><a href="https://github.com/crswll/tailwindcss-theme-swapper">tailwindcss-theme-swapper</a></th> | ||
<th><a href="https://github.com/SirNavith/tailwindcss-theme-variants">tailwindcss-theme-variants</a></th> | ||
<th><a href="https://github.com/innocenzi/tailwindcss-theming">tailwindcss-theming</a></th> | ||
</tr> | ||
</thead> | ||
<tbody> | ||
<tr> | ||
<th>Controllable with selectors (classes or data attributes)</th> | ||
<td>ā</td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
</tr> | ||
<tr> | ||
<th>Responsive</th> | ||
<td>ā</td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td></td> | ||
<td></td> | ||
<td>ā </td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<th>Requires <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*">custom properties</a></th> | ||
<td>ā</td> | ||
<td>ā</td> | ||
<td>ā</td> | ||
<td>ā</td> | ||
<td>ā </td> | ||
<td>ā</td> | ||
<td>ā </td> | ||
</tr> | ||
<tr> | ||
<th>Supports <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"><code>prefers-color-scheme: dark</code></a></th> | ||
<td>ā </td> | ||
<td>With JavaScript</td> | ||
<td>ā</td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<th>Supports <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme"><code>prefers-color-scheme: light</code></a></th> | ||
<td>ā </td> | ||
<td>With JavaScript</td> | ||
<td>ā</td> | ||
<td>ā</td> | ||
<td>ā </td> | ||
<td>ā </td> | ||
<td></td> | ||
</tr> | ||
<tr> | ||
<th>Other thing</th> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
<td></td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
--- | ||
*Repository preview image generated with [GitHub Social Preview](https://social-preview.pqt.dev/)* |
31607
277
19
318