From 4ea7caa526418712a8567e7bd38ea028be64ec0f Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 12:10:55 +0200 Subject: [PATCH 01/19] fix contributing link in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 61aa108b..9359963a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') - [API reference](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/api-reference.md) - [Writing plugins](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/writing-plugins.md) - [Versioning](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/versioning.md) -- [Contributing](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/.github/CONTRIBUTING.md) +- [Contributing](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/contributing.md) - [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/similar-packages.md) From 504ab606b7e906537028515e8b033ed5ed6a0d9b Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 12:11:26 +0200 Subject: [PATCH 02/19] add overview to docs and link to it from every docs page --- docs/README.md | 34 ++++++++++++++++++++++++++++++++++ docs/api-reference.md | 2 ++ docs/configuration.md | 2 ++ docs/contributing.md | 2 ++ docs/features.md | 2 ++ docs/recipes.md | 2 ++ docs/similar-packages.md | 2 ++ docs/versioning.md | 2 ++ docs/what-is-it-for.md | 2 +- docs/writing-plugins.md | 2 ++ 10 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..ccb23789 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,34 @@ +
+
+ + tailwind-merge + +
+ +# tailwind-merge + +Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com) classes in JS without style conflicts. + +```ts +import { twMerge } from 'tailwind-merge' + +twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') +// → 'hover:bg-dark-red p-3 bg-[#B91C1C]' +``` + +- Supports Tailwind v3.0 up to v3.3 (if you use Tailwind v2, use [tailwind-merge v0.9.0](https://github.com/dcastil/tailwind-merge/tree/v0.9.0)) +- Works in all modern browsers and Node >=12 +- Fully typed +- [Check bundle size on Bundlephobia](https://bundlephobia.com/package/tailwind-merge) + +## Get started + +- [What is it for](./what-is-it-for.md) +- [Features](./features.md) +- [Configuration](./configuration.md) +- [Recipes](./recipes.md) +- [API reference](./api-reference.md) +- [Writing plugins](./writing-plugins.md) +- [Versioning](./versioning.md) +- [Contributing](./contributing.md) +- [Similar packages](./similar-packages.md) diff --git a/docs/api-reference.md b/docs/api-reference.md index 979bbe9c..4c77b6aa 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -262,3 +262,5 @@ TypeScript type for config object. Useful if you want to build a `createConfig` Next: [Writing plugins](./writing-plugins.md) Previous: [Recipes](./recipes.md) + +[Back to overview](./README.md) diff --git a/docs/configuration.md b/docs/configuration.md index 5016bdf1..b453005c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -239,3 +239,5 @@ const twMerge3 = createTailwindMerge(() => ({ … }), withMagic, withMoreMagic) Next: [Recipes](./recipes.md) Previous: [Features](./features.md) + +[Back to overview](./README.md) diff --git a/docs/contributing.md b/docs/contributing.md index fbb48c0b..f970f76b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -7,3 +7,5 @@ Please see [CONTRIBUTING](../.github/CONTRIBUTING.md) for details. Next: [Similar packages](./similar-packages.md) Previous: [Versioning](./versioning.md) + +[Back to overview](./README.md) diff --git a/docs/features.md b/docs/features.md index b89ce84b..96e7997c 100644 --- a/docs/features.md +++ b/docs/features.md @@ -150,3 +150,5 @@ The initial computations are called lazily on the first call to `twMerge` to pre Next: [Configuration](./configuration.md) Previous: [What is it for](./what-is-it-for.md) + +[Back to overview](./README.md) diff --git a/docs/recipes.md b/docs/recipes.md index 58a8cb24..bf0f5d41 100644 --- a/docs/recipes.md +++ b/docs/recipes.md @@ -85,3 +85,5 @@ function customTwMerge(...inputs) { Next: [API reference](./api-reference.md) Previous: [Configuration](./configuration.md) + +[Back to overview](./README.md) diff --git a/docs/similar-packages.md b/docs/similar-packages.md index fa97f14c..43dad4c4 100644 --- a/docs/similar-packages.md +++ b/docs/similar-packages.md @@ -15,3 +15,5 @@ --- Previous: [Contributing](./contributing.md) + +[Back to overview](./README.md) diff --git a/docs/versioning.md b/docs/versioning.md index 506bafee..d4a9ae89 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -21,3 +21,5 @@ This package follows the [SemVer](https://semver.org) versioning rules. More spe Next: [Contributing](./contributing.md) Previous: [Writing plugins](./writing-plugins.md) + +[Back to overview](./README.md) diff --git a/docs/what-is-it-for.md b/docs/what-is-it-for.md index 3343ff2f..b7c02d33 100644 --- a/docs/what-is-it-for.md +++ b/docs/what-is-it-for.md @@ -38,4 +38,4 @@ tailwind-merge overrides conflicting classes and keeps everything else untouched Next: [Features](./features.md) -Previous: [Overview](../README.md) +[Back to overview](./README.md) diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index 38ac7882..1d00dae5 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -37,3 +37,5 @@ Also, feel free to check out [tailwind-merge-rtl-plugin](https://www.npmjs.com/p Next: [Versioning](./versioning.md) Previous: [API reference](./api-reference.md) + +[Back to overview](./README.md) From 160ca3692ebdc636506634175cf2f67b2728fdab Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 13:27:37 +0200 Subject: [PATCH 03/19] update README from docs overview file instead of README itself --- package.json | 1 - scripts/helpers/apply-versioned-text.js | 23 ---------- scripts/update-readme.js | 35 --------------- scripts/update-readme.mjs | 57 +++++++++++++++++++++++++ yarn.lock | 5 --- 5 files changed, 57 insertions(+), 64 deletions(-) delete mode 100644 scripts/helpers/apply-versioned-text.js delete mode 100644 scripts/update-readme.js create mode 100644 scripts/update-readme.mjs diff --git a/package.json b/package.json index 1fb7b820..fddae3a2 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "eslint": "^8.39.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-jest": "^27.2.1", - "fp-ts": "^2.14.0", "globby": "^11.1.0", "prettier": "^2.8.8", "size-limit": "^8.2.4", diff --git a/scripts/helpers/apply-versioned-text.js b/scripts/helpers/apply-versioned-text.js deleted file mode 100644 index 8dd2c8ba..00000000 --- a/scripts/helpers/apply-versioned-text.js +++ /dev/null @@ -1,23 +0,0 @@ -const AUTOGENERATED_VERSION_REGEX = - /.*?/gs -const VERSION_REGEX = /v\d+\.\d+\.\d+|[\da-f]{40}/g -const DEV_VERSION_REGEX = /^\d+.\d+.\d+-dev\.(?\w+)$/ - -function applyVersionedText(text, packageJson) { - const versionFromPackageJson = JSON.parse(packageJson).version - const resolvedVersion = - versionFromPackageJson.match(DEV_VERSION_REGEX)?.groups.gitRef || - 'v' + versionFromPackageJson - const hasPartsToUpdate = !resolvedVersion || AUTOGENERATED_VERSION_REGEX.test(text) - - const updatedText = text.replace(AUTOGENERATED_VERSION_REGEX, (match) => - match.replace(VERSION_REGEX, resolvedVersion), - ) - - return { - hasPartsToUpdate, - updatedText, - } -} - -module.exports = { applyVersionedText } diff --git a/scripts/update-readme.js b/scripts/update-readme.js deleted file mode 100644 index 212d3a6e..00000000 --- a/scripts/update-readme.js +++ /dev/null @@ -1,35 +0,0 @@ -// File can only be executed with zx -const { pipe } = require('fp-ts/lib/function') - -const { applyVersionedText } = require('./helpers/apply-versioned-text') - -async function run() { - const ROOT_PATH = `${__dirname}/..` - const README_PATH = `${ROOT_PATH}/README.md` - const PACKAGE_PATH = `${ROOT_PATH}/package.json` - - const [readme, packageJson] = await Promise.all([ - fs.readFile(README_PATH, { encoding: 'utf-8' }), - fs.readFile(PACKAGE_PATH, { encoding: 'utf-8' }), - ]) - - const nextReadme = pipe(readme, (readme) => { - const { hasPartsToUpdate, updatedText } = applyVersionedText(readme, packageJson) - - if (!hasPartsToUpdate) { - throw Error( - `${chalk.red('[ERROR]')} Could not find versioned logo image to update in README`, - ) - } - - return updatedText - }) - - if (nextReadme !== readme) { - await fs.writeFile(README_PATH, nextReadme) - } - - await $`git add README.md` -} - -run() diff --git a/scripts/update-readme.mjs b/scripts/update-readme.mjs new file mode 100644 index 00000000..83fda27e --- /dev/null +++ b/scripts/update-readme.mjs @@ -0,0 +1,57 @@ +// This file can only be executed with zx. More info: https://github.com/google/zx + +// @ts-check + +import 'zx/globals' + +import packageJson from '../package.json' assert { type: 'json' } + +updateReadme() + +async function updateReadme() { + const ROOT_PATH = `${__dirname}/..` + const README_PATH = `${ROOT_PATH}/README.md` + const DOCS_OVERVIEW_PATH = `${ROOT_PATH}/docs/README.md` + + const [readmeContent, docsOverviewContent] = await Promise.all([ + fs.readFile(README_PATH, { encoding: 'utf-8' }), + fs.readFile(DOCS_OVERVIEW_PATH, { encoding: 'utf-8' }), + ]) + + const nextReadme = applyStaticLinks(docsOverviewContent) + + if (nextReadme === docsOverviewContent) { + throw Error(`${chalk.red('[ERROR]')} Did not update anything in docs overview file.`) + } + + if (nextReadme !== readmeContent) { + await fs.writeFile(README_PATH, nextReadme) + } + + await $`git add README.md` +} + +/** + * @param {string} docsOverviewContent + */ +function applyStaticLinks(docsOverviewContent) { + const version = getVersion() + + return docsOverviewContent + .replace( + /(["'`(])\.\.\/(assets\/[^\s]+)(["'`)])/g, + `$1https://github.com/dcastil/tailwind-merge/raw/${version}/$2$3`, + ) + .replace( + /(["'`(])\.\/([^\s]+\.md)(["'`)])/g, + `$1https://github.com/dcastil/tailwind-merge/tree/${version}/docs/$2$3`, + ) +} + +function getVersion() { + const gitRefVersionRegex = /^\d+.\d+.\d+-[^.]+\.(?[\da-f]+)$/ + + return ( + packageJson.version.match(gitRefVersionRegex)?.groups?.gitRef || 'v' + packageJson.version + ) +} diff --git a/yarn.lock b/yarn.lock index 643304c3..35fa11b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4298,11 +4298,6 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" -fp-ts@^2.14.0: - version "2.14.0" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.14.0.tgz#97ac3d9f002dcd02f93ca1269ae14a7fcb805582" - integrity sha512-QLagLSYAgMA00pZzUzeksH/78Sd14y7+Gc2A8Yaja3/IpGOFMdm/gYBuDMxYqLsJ58iT5lz+bJb953RAeFfp1A== - from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" From f8e4c2c7a1fb967569d0bd68522679df1f9d56e7 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 13:27:57 +0200 Subject: [PATCH 04/19] run update readme script --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 9359963a..54a7f6b8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,7 @@

- tailwind-merge -
@@ -25,8 +23,6 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') ## Get started - - - [What is it for](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/what-is-it-for.md) - [Features](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/features.md) - [Configuration](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/configuration.md) @@ -36,5 +32,3 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') - [Versioning](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/versioning.md) - [Contributing](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/contributing.md) - [Similar packages](https://github.com/dcastil/tailwind-merge/tree/v1.12.0/docs/similar-packages.md) - - From b04fadc41a33296ebc86f68ae137d905e36ebcb7 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 16:04:42 +0200 Subject: [PATCH 05/19] add comment informing about autogenerated README --- README.md | 2 ++ scripts/update-readme.mjs | 39 +++++++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 54a7f6b8..5016813b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + +

diff --git a/scripts/update-readme.mjs b/scripts/update-readme.mjs index 83fda27e..d6390405 100644 --- a/scripts/update-readme.mjs +++ b/scripts/update-readme.mjs @@ -13,18 +13,18 @@ async function updateReadme() { const README_PATH = `${ROOT_PATH}/README.md` const DOCS_OVERVIEW_PATH = `${ROOT_PATH}/docs/README.md` - const [readmeContent, docsOverviewContent] = await Promise.all([ + const [currentReadmeContent, currentDocsOverviewContent] = await Promise.all([ fs.readFile(README_PATH, { encoding: 'utf-8' }), fs.readFile(DOCS_OVERVIEW_PATH, { encoding: 'utf-8' }), ]) - const nextReadme = applyStaticLinks(docsOverviewContent) - - if (nextReadme === docsOverviewContent) { - throw Error(`${chalk.red('[ERROR]')} Did not update anything in docs overview file.`) - } + const nextReadme = pipe( + currentDocsOverviewContent, + applyStaticLinks, + addAutoGeneratedInfoComment, + ) - if (nextReadme !== readmeContent) { + if (nextReadme !== currentReadmeContent) { await fs.writeFile(README_PATH, nextReadme) } @@ -33,11 +33,12 @@ async function updateReadme() { /** * @param {string} docsOverviewContent + * @returns {string} */ function applyStaticLinks(docsOverviewContent) { const version = getVersion() - return docsOverviewContent + const nextDocsOverviewContent = docsOverviewContent .replace( /(["'`(])\.\.\/(assets\/[^\s]+)(["'`)])/g, `$1https://github.com/dcastil/tailwind-merge/raw/${version}/$2$3`, @@ -46,6 +47,12 @@ function applyStaticLinks(docsOverviewContent) { /(["'`(])\.\/([^\s]+\.md)(["'`)])/g, `$1https://github.com/dcastil/tailwind-merge/tree/${version}/docs/$2$3`, ) + + if (nextDocsOverviewContent === docsOverviewContent) { + throw Error(`${chalk.red('[ERROR]')} Did not update anything in docs overview file.`) + } + + return nextDocsOverviewContent } function getVersion() { @@ -55,3 +62,19 @@ function getVersion() { packageJson.version.match(gitRefVersionRegex)?.groups?.gitRef || 'v' + packageJson.version ) } + +function addAutoGeneratedInfoComment(content) { + return ( + '\n\n' + + content + ) +} + +/** + * @param {string} argument + * @param {((arg: string) => string)[]} functions + * @returns {string} + */ +function pipe(argument, ...functions) { + return functions.reduce((argument, fn) => fn(argument), argument) +} From 2c6a40c5340e6e000f587b9a1a842a5a4e7742d0 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 16:07:06 +0200 Subject: [PATCH 06/19] use cjs for CommonJS exports test --- .eslintrc.json | 2 +- package.json | 2 +- ...-built-package-exports.js => test-built-package-exports.cjs} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename scripts/{test-built-package-exports.js => test-built-package-exports.cjs} (87%) diff --git a/.eslintrc.json b/.eslintrc.json index 2a3a5553..0c6fd0e0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -161,7 +161,7 @@ "extends": ["plugin:jest/recommended", "plugin:jest/style"] }, { - "files": ["scripts/**/*.?(m)js"], + "files": ["scripts/**/*.?(m|c)js"], "rules": { "no-console": "off" } diff --git a/package.json b/package.json index fddae3a2..9cbbab5e 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "scripts": { "build": "dts build", "test": "dts test", - "test:exports": "node scripts/test-built-package-exports.js && node scripts/test-built-package-exports.mjs", + "test:exports": "node scripts/test-built-package-exports.cjs && node scripts/test-built-package-exports.mjs", "lint": "eslint --max-warnings 0 '**'", "size": "size-limit", "preversion": "if [ -n \"$DANYS_MACHINE\" ]; then git checkout main && git pull; fi", diff --git a/scripts/test-built-package-exports.js b/scripts/test-built-package-exports.cjs similarity index 87% rename from scripts/test-built-package-exports.js rename to scripts/test-built-package-exports.cjs index d01a07ba..c106b0d0 100644 --- a/scripts/test-built-package-exports.js +++ b/scripts/test-built-package-exports.cjs @@ -1,6 +1,6 @@ const assert = require('assert') -const { twMerge } = require('..') +const { twMerge } = require('../dist') assert(twMerge() === '') assert( From 8aedc55d1b6fd404b6fbf225a1cf890ba30da2fc Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 16:11:52 +0200 Subject: [PATCH 07/19] fix tests --- tests/docs-examples.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docs-examples.test.ts b/tests/docs-examples.test.ts index 039b8efe..f0224aae 100644 --- a/tests/docs-examples.test.ts +++ b/tests/docs-examples.test.ts @@ -8,7 +8,7 @@ const twMergeExampleRegex = /twMerge\((?[\w\s\-:[\]#(),!&\n'"]+?)\)(?!.*(?.+)['"]/g test('docs examples', () => { - expect.assertions(28) + expect.assertions(29) return forEachFile(['README.md', 'docs/**/*.md'], (fileContent) => { Array.from(fileContent.matchAll(twMergeExampleRegex)).forEach((match) => { From 2e040882848916eb1641049f16ace38e14848f90 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 16:36:34 +0200 Subject: [PATCH 08/19] improve "what is it for" page a bit --- docs/what-is-it-for.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/what-is-it-for.md b/docs/what-is-it-for.md index b7c02d33..904a6be8 100644 --- a/docs/what-is-it-for.md +++ b/docs/what-is-it-for.md @@ -1,6 +1,6 @@ # What is it for -If you use Tailwind with a component-based UI renderer like [React](https://reactjs.org) or [Vue](https://vuejs.org), you're probably familiar with the situation that you want to change some styles of a component, but only in one place. +If you use Tailwind CSS with a component-based UI renderer like [React](https://reactjs.org) or [Vue](https://vuejs.org), you're probably familiar with the situation that you want to change some styles of a component, but only in a one-off case. ```jsx // React components with JSX syntax used in this example @@ -10,7 +10,7 @@ function MyGenericInput(props) { return } -function MySlightlyModifiedInput(props) { +function MyOneOffInput(props) { return ( Date: Sun, 21 May 2023 16:55:14 +0200 Subject: [PATCH 09/19] add empty "when and how to use it" page --- docs/README.md | 1 + docs/what-is-it-for.md | 2 +- docs/when-and-how-to-use-it.md | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 docs/when-and-how-to-use-it.md diff --git a/docs/README.md b/docs/README.md index ccb23789..fd9ca9ee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,7 @@ twMerge('px-2 py-1 bg-red hover:bg-dark-red', 'p-3 bg-[#B91C1C]') ## Get started - [What is it for](./what-is-it-for.md) +- [When and how to use it](./when-and-how-to-use-it.md) - [Features](./features.md) - [Configuration](./configuration.md) - [Recipes](./recipes.md) diff --git a/docs/what-is-it-for.md b/docs/what-is-it-for.md index 904a6be8..bb018dd7 100644 --- a/docs/what-is-it-for.md +++ b/docs/what-is-it-for.md @@ -36,6 +36,6 @@ tailwind-merge overrides conflicting classes and keeps everything else untouched --- -Next: [Features](./features.md) +Next: [When and how to use it](./when-and-how-to-use-it.md) [Back to overview](./README.md) diff --git a/docs/when-and-how-to-use-it.md b/docs/when-and-how-to-use-it.md new file mode 100644 index 00000000..e7d81858 --- /dev/null +++ b/docs/when-and-how-to-use-it.md @@ -0,0 +1,19 @@ +# When and how to use it + + + +## When to use it + + + +## How to use it + + + +--- + +Next: [Features](./features.md) + +Previous: [What is it for](./what-is-it-for.md) + +[Back to overview](./README.md) From 8d5f9a0da7d8f4b4e2cf7d8da5e9a7a1d7d7405a Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sun, 21 May 2023 20:36:13 +0200 Subject: [PATCH 10/19] add content for "when to use it" and "when not to use it" --- docs/when-and-how-to-use-it.md | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/docs/when-and-how-to-use-it.md b/docs/when-and-how-to-use-it.md index e7d81858..ccf45da3 100644 --- a/docs/when-and-how-to-use-it.md +++ b/docs/when-and-how-to-use-it.md @@ -1,15 +1,48 @@ # When and how to use it - +Like any other package, tailwind-merge comes with opportunities and trade-offs. This document tries to help you decide if tailwind-merge is the right tool for your use case based on my own experience and the feedback I got from the community. + +> **Note** +> If you're thinking of a major argument that is not covered here, please [let me know](https://github.com/dcastil/tailwind-merge/discussions/new?category=ideas)! ## When to use it - +### Using Tailwind CSS and component composition + +I hope this is self-explanatory, but tailwind-merge is probably only useful if you use Tailwind CSS and compose components together in some form. If you have a use case for tailwind-merge outside of thosse boundaries, please [let me know](https://github.com/dcastil/tailwind-merge/discussions/new?category=show-and-tell), I'm curious about it! + +### Using highly reusable components + +tailwind-merge is a great fit for highly reusable components like in design systems or UI component libraries. If you expect that styles of a component will be modified on multiple levels, e.g. ContextMenuOption -> MenuOption -> BaseOption, with each component passing some modifications to the component it renders, tailwind-merge can help you to keep the API surface between components small. + +### Fast development velocity and iteration speed wanted + +tailwind-merge allows you to support a wide range of styling use cases without having to explicitly define each of them separately within a component. E.g. you can pass a custom width to a button component, change its text color or position it absolutely with a single `className` prop without the need to define support for custom widths, text colors or positioning within the button component explicitly. + +### Preventing premature abstractions + +Let's say you have a Button component that you already use in many places. You have a place in your app in which you want to make its background red to signal that the action of the button is destructive. You could modify the Button component to deal with the concept of destructiveness, but then you'd need to make sure that those styles work with all the other permutations of the component which you don't need in the place where the destructive button is used. And maybe you're not even sure whether you'll keep the Button red in this one place, so the time investment of making the Button understand destructiveness doesn't seem worth it. + +tailwind-merge allows you to defer the creation of abstractions like destructiveness to the point where you're sure that you need them. You can just pass a `className` prop to the Button component in which you define the red background and be done with it for now. If you later decide that you want to make the Button red in more places, you can still define the logic inside the Button component later. + +## When not to use it + +### Bundle size constraints + +tailwind-merge relies on a large config (~5 kB out of the ~7 kB minified and gzipped bundle size) to understand which classes are conflicting. This might be limiting if you have tight bundle size constraints. + +### API surface constraints + +With large teams or components that are made available publicly, API surface of components becomes an issue as it will be used and misused in any way the API allows. With this in mind tailwind-merge might give too much freedom to users of a component which could make it harder to maintain and evolve the component over time. If you need full control over styling in your components, tailwind-merge is probably not be the right tool for you. ## How to use it +## Alternatives + + + --- Next: [Features](./features.md) From 4a839ddabbe2a5f3c2bfeedc517bbc098f08f35d Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Sat, 27 May 2023 14:22:03 +0200 Subject: [PATCH 11/19] add "start discussion" template to issue template config --- .github/ISSUE_TEMPLATE/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9b9aa179..74a16528 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -9,3 +9,6 @@ contact_links: - name: Bug Report url: https://github.com/dcastil/tailwind-merge/issues/new about: If something is broken with tailwind-merge itself, create a bug report. + - name: Start discussion + url: https://github.com/dcastil/tailwind-merge/discussions + about: Anything else on your mind? Check out the discussions forum. From 5ad44516ecb09348b07e22eb5493671a28f7a29d Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Wed, 31 May 2023 22:44:25 +0200 Subject: [PATCH 12/19] add note about object support to `twJoin` API reference docs --- docs/api-reference.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-reference.md b/docs/api-reference.md index 4c77b6aa..c0d9eb5a 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -35,6 +35,8 @@ twJoin( It is used internally within `twMerge` and a direct subset of [`clsx`](https://www.npmjs.com/package/clsx). If you use `clsx` or [`classnames`](https://www.npmjs.com/package/classnames) to apply Tailwind classes conditionally and don't need support for object arguments, you can use `twJoin` instead, it is a little faster and will save you a few hundred bytes in bundle size. +Why no object support? [Read here](https://github.com/dcastil/tailwind-merge/discussions/137#discussioncomment-3481605). + ## `getDefaultConfig` ```ts From 5901e85306371b486f914e1dcc8728439e63407b Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Wed, 31 May 2023 23:04:22 +0200 Subject: [PATCH 13/19] write docs about when and how to use tailwind-merge --- docs/when-and-how-to-use-it.md | 97 ++++++++++++++++++++++++++++------ 1 file changed, 82 insertions(+), 15 deletions(-) diff --git a/docs/when-and-how-to-use-it.md b/docs/when-and-how-to-use-it.md index ccf45da3..9ba56988 100644 --- a/docs/when-and-how-to-use-it.md +++ b/docs/when-and-how-to-use-it.md @@ -1,21 +1,39 @@ # When and how to use it -Like any other package, tailwind-merge comes with opportunities and trade-offs. This document tries to help you decide if tailwind-merge is the right tool for your use case based on my own experience and the feedback I got from the community. +Like any other package, tailwind-merge comes with opportunities and trade-offs. This document tries to help you decide whether tailwind-merge is the right tool for your use case based on my own experience and the feedback I got from the community. > **Note** > If you're thinking of a major argument that is not covered here, please [let me know](https://github.com/dcastil/tailwind-merge/discussions/new?category=ideas)! -## When to use it +## Why not to use it -### Using Tailwind CSS and component composition +Generally speaking, there are situations where you _could_ use tailwind-merge but probably shouldn't. Think of tailwind-merge as an escape hatch rather than the primary tool to handle style variants.[^simonswiss-quote] -I hope this is self-explanatory, but tailwind-merge is probably only useful if you use Tailwind CSS and compose components together in some form. If you have a use case for tailwind-merge outside of thosse boundaries, please [let me know](https://github.com/dcastil/tailwind-merge/discussions/new?category=show-and-tell), I'm curious about it! +[^simonswiss-quote]: Don't just take my word for it, [Simon Vrachliotis thinks so too](https://twitter.com/simonswiss/status/1663721037949984768). -### Using highly reusable components +### Increases bundle size -tailwind-merge is a great fit for highly reusable components like in design systems or UI component libraries. If you expect that styles of a component will be modified on multiple levels, e.g. ContextMenuOption -> MenuOption -> BaseOption, with each component passing some modifications to the component it renders, tailwind-merge can help you to keep the API surface between components small. +tailwind-merge relies on a large config (~5 kB out of the ~7 kB minified and gzipped bundle size) to understand which classes are conflicting. This might be limiting if you have tight bundle size constraints. + +### Might give too much freedom to users of a component + +With large teams or components that are made available publicly you can expect users of components to use and misuse the component's API in any way the component allows. With this in mind tailwind-merge might give too much freedom to users of a component which could make it harder to maintain and evolve the component over time. With tailwind-merge you give up full control over styling in your components. + +### More difficult to refactor highly reusable components + +When you allow arbitrary classes to be passed into a component, you can break the styles of the component's users when you refactor the component's internal styles. If you need to be able to refactor a component's styles often, those styles shouldn't be merged with styles from props unless you're willing to refactor the component's uses as well. + +### Not using Tailwind CSS or component composition -### Fast development velocity and iteration speed wanted +tailwind-merge is probably only useful if you use Tailwind CSS and compose components together in some form. If you have a use case for tailwind-merge outside of thosse boundaries, please [let me know](https://github.com/dcastil/tailwind-merge/discussions/new?category=show-and-tell), I'm curious about it! + +## Why to use it + +### Easy to compose components through multiple levels + +tailwind-merge is a great fit for highly composed components like in design systems or UI component libraries. If you expect that styles of a component will be modified on multiple levels, e.g. ContextMenuOption -> MenuOption -> BaseOption, with each component passing some modifications to the component it renders, tailwind-merge can help you to keep the API surface between components small. + +### Enables fast development velocity and iteration speed tailwind-merge allows you to support a wide range of styling use cases without having to explicitly define each of them separately within a component. E.g. you can pass a custom width to a button component, change its text color or position it absolutely with a single `className` prop without the need to define support for custom widths, text colors or positioning within the button component explicitly. @@ -25,23 +43,72 @@ Let's say you have a Button component that you already use in many places. You h tailwind-merge allows you to defer the creation of abstractions like destructiveness to the point where you're sure that you need them. You can just pass a `className` prop to the Button component in which you define the red background and be done with it for now. If you later decide that you want to make the Button red in more places, you can still define the logic inside the Button component later. -## When not to use it +## How to use it -### Bundle size constraints +### Joining internal classes -tailwind-merge relies on a large config (~5 kB out of the ~7 kB minified and gzipped bundle size) to understand which classes are conflicting. This might be limiting if you have tight bundle size constraints. +If you want to merge classes that are all defined within a component, prefer using the [`twJoin`](./api-reference.md#twjoin) function over `twMerge`. As the name suggests, `twJoin` only joins the class strings together and doesn't deal with conflicting classes. -### API surface constraints +```jsx +// React components with JSX syntax used in this example -With large teams or components that are made available publicly, API surface of components becomes an issue as it will be used and misused in any way the API allows. With this in mind tailwind-merge might give too much freedom to users of a component which could make it harder to maintain and evolve the component over time. If you need full control over styling in your components, tailwind-merge is probably not be the right tool for you. +function MyComponent({ forceHover, disbaled, isMuted }) { + return ( +
+ {/* More code… */} +
+ ) +} +``` -## How to use it +Joining classes instead of merging forces you to write your code in a way so that no merge conflicts appear which seems like more work at first. But it has two big advantages: + +1. It's much more performant because no conflict resolution is computed. `twJoin` has the same performance characteristics as other class joining libraries like [`clsx`](https://www.npmjs.com/package/clsx). + +2. It's usually easier to reason about. When you can't override classes, you naturally start to put classes that are in conflict with each other closer together through conditionals like ternaries. Also when a condition within the `twJoin` call is truthy, you can be sure that this class will be applied without the need to check whether conflicting classes appear in a later argument. Not relying on overrides makes it easier to understand which classes are in conflict with each other and which classes are applied in which cases. + +But there are also exceptions to (2) in which using `twMerge` for purely internally defined classes is preferable, especially in some complicated cases. So just take this as a rule of thumb. - +### Merging internal classes with `className` prop + +The primary purpose of tailwind-merge is to merge a `className` prop with the default classes of a component. + +```jsx +// React components with JSX syntax used in this example + +function MyComponent({ forceHover, disbaled, isMuted, className }) { + return ( +
+ {/* More code… */} +
+ ) +} +``` + +You don't need to worry about potentially expensive re-renders here because tailwind-merge [caches results](./features.md#results-are-cached) so that a re-render with the same props and state becomes computationally lightweight as far as the call to `twMerge` goes. ## Alternatives - +TODO: + +- Write about important modifier +- Adding prop dealing with one-off style to component +- Adding more granular className props like `paddingClassNames` to component --- From 52ce30e69becaf011d907e9dd911af3b8398dea6 Mon Sep 17 00:00:00 2001 From: Dany Castillo <31006608+dcastil@users.noreply.github.com> Date: Wed, 31 May 2023 23:50:58 +0200 Subject: [PATCH 14/19] add alternatives to using tailwind-merge to docs --- docs/when-and-how-to-use-it.md | 56 +++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/docs/when-and-how-to-use-it.md b/docs/when-and-how-to-use-it.md index 9ba56988..94a35648 100644 --- a/docs/when-and-how-to-use-it.md +++ b/docs/when-and-how-to-use-it.md @@ -104,11 +104,55 @@ You don't need to worry about potentially expensive re-renders here because tail ## Alternatives -TODO: +In case the disadvantages of tailwind-merge weigh in too much for your use case, here are some alternatives that might be a better fit. -- Write about important modifier -- Adding prop dealing with one-off style to component -- Adding more granular className props like `paddingClassNames` to component +### Adding props that toggle internal styles + +This is the goold-old way of styling components and is also probably your default. E.g. think of a variant prop that toggles between a primary and a secondary styles of a button. The `variant` prop is already toggling between internal styles of the component. If you have a one-off use case to give the button a full width, you can add a `isFullWidth` prop to the button component which toggles the `w-full` class internally. + +```jsx +// React components with JSX syntax used in this example + +function Button({ variant = 'primary', isFullWidth, ...props }) { + return + + + ) +} + +function Button({ className ...props }) { + return