Skip to content

Commit

Permalink
Merge pull request #294 from dcastil/breaking-feature/199/make-it-eas…
Browse files Browse the repository at this point in the history
…ier-to-override-elements-in-config

Make it possible to override elements with `extendTailwindMerge`
  • Loading branch information
dcastil authored Aug 20, 2023
2 parents 01a1c48 + 941ac19 commit 650405d
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 153 deletions.
138 changes: 94 additions & 44 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,29 @@ It can be used like this:

```ts
extendTailwindMerge({
theme: {
'my-scale': ['foo', 'bar']
extend: {
theme: {
'my-scale': ['foo', 'bar'],
},
classGroups: {
'my-group': [{ 'my-group': [fromTheme('my-scale'), fromTheme('spacing')] }],
'my-group-x': [{ 'my-group-x': [fromTheme('my-scale')] }],
},
},
classGroups: {
'my-group': [{ 'my-group': [fromTheme('my-scale'), fromTheme('spacing')] }]
'my-group-x': [{ 'my-group-x': [fromTheme('my-scale')] }]
}
})
```
## `extendTailwindMerge`
```ts
function extendTailwindMerge(
configExtension: Partial<Config>,
...createConfig: Array<(config: Config) => Config>
configExtension: ConfigExtension,
...createConfig: ((config: Config) => Config)[]
): TailwindMerge
function extendTailwindMerge(...createConfig: Array<(config: Config) => Config>): TailwindMerge
function extendTailwindMerge(...createConfig: ((config: Config) => Config)[]): TailwindMerge
```

Function to create merge function with custom config which extends the default config. Use this if you use the default Tailwind config and just extend it in some places.
Function to create merge function with custom config which extends the default config. Use this if you use the default Tailwind config and just modified it in some places.

> **Note**
> The function `extendTailwindMerge` computes a large data structure based on the config passed to it. I recommend to call it only once and store the result in a top-level variable instead of calling it inline within another repeatedly called function.
Expand All @@ -88,40 +90,82 @@ You provide it a `configExtension` object which gets [merged](#mergeconfigs) wit

```ts
const customTwMerge = extendTailwindMerge({
cacheSize: 0, // ← Disabling cache
// ↓ Optional cache size
// Here we're disabling the cache
cacheSize: 0,
// ↓ Optional prefix from TaiLwind config
prefix: 'tw-',
// ↓ Optional separator from TaiLwind config
separator: '_',
// ↓ Add values to existing theme scale or create a new one
// Not all theme keys form the Tailwind config are supported by default.
theme: {
spacing: ['sm', 'md', 'lg'],
},
// ↓ Here you define class groups
classGroups: {
// ↓ The `foo` key here is the class group ID
// ↓ Creates group of classes which have conflicting styles
// Classes here: foo, foo-2, bar-baz, bar-baz-1, bar-baz-2
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
// ↓ Functions can also be used to match classes.
// Classes here: qux-auto, qux-1000, qux-1001,…
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
baz: ['baz-sm', 'baz-md', 'baz-lg'],
},
// ↓ Here you can define additional conflicts across different groups
conflictingClassGroups: {
// ↓ ID of class group which creates a conflict with…
// ↓ …classes from groups with these IDs
// In this case `twMerge('qux-auto foo') → 'foo'`
foo: ['bar'],

// ↓ Optional config overrides
// Only elements from the second level onwards are overridden
override: {
// ↓ Theme scales to override
// Not all theme keys from the Tailwind config are supported by default.
theme: {
colors: ['black', 'white', 'yellow-500'],
},
// ↓ Class groups to override
classGroups: {
// ↓ The `shadow` key here is the class group ID
// ↓ Creates group of classes which have conflicting styles
// Classes here: shadow-100, shadow-200, shadow-300, shadow-400, shadow-500
shadow: [{ shadow: ['100', '200', '300', '400', '500'] }],
},
// ↓ Conflicts across different groups to override
conflictingClassGroups: {
// ↓ ID of class group which creates a conflict with…
// ↓ …classes from groups with these IDs
// Here we remove the default conflict between the font-size and leading class
// groups.
'font-size': [],
},
// ↓ Conflicts between the postfix modifier of a group and a different class group to
// override
conflictingClassGroupModifiers: {
// You probably won't need this, but it follows the same shape as
// `conflictingClassGroups`.
},
},
// ↓ Here you can define conflicts between the postfix modifier of a group and a different class group.
conflictingClassGroupModifiers: {
// ↓ ID of class group whose postfix modifier creates a conflict with…
// ↓ …classes from groups with these IDs
// In this case `twMerge('qux-auto baz-sm/1000') → 'baz-sm/1000'`
baz: ['bar'],

// ↓ Optional config extensions
// Follows same shape as the `override` object.
extend: {
// ↓ Theme scales to extend or create
// Not all theme keys from the Tailwind config are supported by default.
theme: {
spacing: ['sm', 'md', 'lg'],
},
// ↓ Class groups to extend or create
classGroups: {
// ↓ The `animate` key here is the class group ID
// ↓ Adds class animate-shimmer to existing group with ID `animate` or creates
// new class group if it doesn't exist.
animate: ['animate-shimmer'],
// ↓ Functions can also be used to match classes
// They take the class part value as argument and return a boolean defining whether
// it is a match.
// Here we accept all string classes starting with `aspec-w-` followed by a number.
'aspect-w': [{ 'aspect-w': [(value) => Boolean(value) && !isNaN(value)] }],
'aspect-h': [{ 'aspect-h': [(value) => Boolean(value) && !isNaN(value)] }],
'aspect-reset': ['aspect-none'],
// ↓ You can also use validators exported by tailwind-merge
'prose-size': [{ prose: ['base', validators.isTshirtSize] }],
},
// ↓ Conflicts across different groups to extend or create
conflictingClassGroups: {
// ↓ ID of class group which creates a conflict with…
// ↓ …classes from groups with these IDs
// In this case `twMerge('aspect-w-5 aspect-none') → 'aspect-none'`
'aspect-reset': ['aspect-w', 'aspect-h'],
},
// ↓ Conflicts between the postfix modifier of a group and a different class group to
// extend or create
conflictingClassGroupModifiers: {
// You probably won't need this, but it follows the same shape as
// `conflictingClassGroups`.
},
},
})
```
Expand Down Expand Up @@ -200,16 +244,22 @@ But don't merge configs like that. Use [`mergeConfigs`](#mergeconfigs) instead.
function mergeConfigs(baseConfig: Config, configExtension: Partial<Config>): Config
```

Helper function to merge multiple config objects. Objects are merged, arrays are concatenated, scalar values are overridden and `undefined` does nothing. The function assumes that both parameters are tailwind-merge config objects and shouldn't be used as a generic merge function.
Helper function to merge multiple tailwind-merge configs. Properties with the value `undefined` are skipped.

```ts
const customTwMerge = createTailwindMerge(getDefaultConfig, (config) =>
mergeConfigs(config, {
classGroups: {
// ↓ Adding new class group
mySpecialClassGroup: [{ special: ['1', '2'] }],
override: {
classGroups: {
// ↓ Overriding existing class group
shadow: [{ shadow: ['100', '200', '300', '400', '500'] }],
},
}
extend: {
// ↓ Adding value to existing class group
animate: ['animate-magic'],
animate: ['animate-shimmer'],
// ↓ Adding new class group
prose: [{ prose: ['', validators.isTshirtSize] }],
},
}),
)
Expand Down
42 changes: 24 additions & 18 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,29 +160,35 @@ If you modified one of these theme scales in your Tailwind config, you can add a

### Extending the tailwind-merge config

If you only need to extend the default tailwind-merge config, [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge) is the easiest way to extend the config. You provide it a `configExtension` object which gets [merged](./api-reference.md#mergeconfigs) with the default config. Therefore, all keys here are optional.
If you only need to slightly modify the default tailwind-merge config, [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge) is the easiest way to extend the config. You provide it a `configExtension` object which gets [merged](./api-reference.md#mergeconfigs) with the default config. Therefore, all keys here are optional.

```ts
import { extendTailwindMerge } from 'tailwind-merge'

const customTwMerge = extendTailwindMerge({
// ↓ Add values to existing theme scale or create a new one
theme: {
spacing: ['sm', 'md', 'lg'],
},
// ↓ Add values to existing class groups or define new ones
classGroups: {
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
baz: ['baz-sm', 'baz-md', 'baz-lg'],
},
// ↓ Here you can define additional conflicts across class groups
conflictingClassGroups: {
foo: ['bar'],
},
// ↓ Define conflicts between postfix modifiers and class groups
conflictingClassGroupModifiers: {
baz: ['bar'],
// ↓ Override eleemnts from the default config
// It has the same shape as the `extend` object, so we're going to skip it here.
override: {},
// ↓ Extend values from the default config
extend: {
// ↓ Add values to existing theme scale or create a new one
theme: {
spacing: ['sm', 'md', 'lg'],
},
// ↓ Add values to existing class groups or define new ones
classGroups: {
foo: ['foo', 'foo-2', { 'bar-baz': ['', '1', '2'] }],
bar: [{ qux: ['auto', (value) => Number(value) >= 1000] }],
baz: ['baz-sm', 'baz-md', 'baz-lg'],
},
// ↓ Here you can define additional conflicts across class groups
conflictingClassGroups: {
foo: ['bar'],
},
// ↓ Define conflicts between postfix modifiers and class groups
conflictingClassGroupModifiers: {
baz: ['bar'],
},
},
})
```
Expand Down
32 changes: 14 additions & 18 deletions docs/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ How to configure tailwind-merge with some common patterns.

> I have a custom shadow scale with the keys 100, 200 and 300 configured in Tailwind. How do I make tailwind-merge resolve conflicts among those?
You'll be able to do this by creating a custom `twMerge` functon with [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge).
We'll be able to do this by creating a custom `twMerge` functon with [`extendTailwindMerge`](./api-reference.md#extendtailwindmerge).

First, check whether your particular theme scale is included in tailwind-merge's theme config object [here](./configuration.md#theme). In the hypothetical case that tailwind-merge supported Tailwind's `boxShadow` theme scale, you could add it to the tailwind-merge config like this:
First, we need to know whether we want to override or extend the default scale. Let's say we extended the default config by putting the scale into the `extend` key in the Tailwind config.

Then we check whether our particular theme scale is included in tailwind-merge's theme config object [here](./configuration.md#theme). In the hypothetical case that tailwind-merge supported Tailwind's `boxShadow` theme scale, we could add it to the tailwind-merge config like this:

```js
const customTwMerge = extendTailwindMerge({
theme: {
// The `boxShadow` key isn't actually supported
boxShadow: [{ shadow: ['100', '200', '300'] }],
extend: {
theme: {
// The `boxShadow` key isn't actually supported
boxShadow: [{ shadow: ['100', '200', '300'] }],
},
},
})
```
Expand All @@ -23,23 +27,15 @@ In the case of the `boxShadow` scale, tailwind-merge doesn't include it in the t

```js
const customTwMerge = extendTailwindMerge({
classGroups: {
shadow: [{ shadow: ['100', '200', '300'] }],
extend: {
classGroups: {
shadow: [{ shadow: ['100', '200', '300'] }],
},
},
})
```

Note that by using `extendTailwindMerge` we're only adding our custom classes to the existing ones in the config, so `twMerge('shadow-200 shadow-lg')` will return the string `shadow-lg`. In most cases that's fine because you won't use that class in your project.

If you expect classes like `shadow-lg` to be input in `twMerge` and don't want the class to cause incorrect merges, you can explicitly override the class group with [`createTailwindMerge`](./api-reference.md#createtailwindmerge), removing the default classes.

```js
const customTwMerge = createTailwindMerge(() => {
const config = getDefaultConfig()
config.classGroups.shadow = [{ shadow: ['100', '200', '300'] }]
return config
})
```
Note that by using the `extend` object we're only adding our custom classes to the existing ones in the config, so `twMerge('shadow-200 shadow-lg')` will return the string `shadow-lg`. If we want to override the class instead, we need to use the `override` object instead.

## Extracting classes with Tailwind's [`@apply`](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply)

Expand Down
6 changes: 4 additions & 2 deletions docs/writing-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import { mergeConfigs, validators, Config } from 'tailwind-merge'

export function withMagic(config: Config): Config {
return mergeConfigs(config, {
classGroups: {
'magic.my-group': [{ magic: [validators.isLength, 'wow'] }],
extend: {
classGroups: {
'magic.my-group': [{ magic: [validators.isLength, 'wow'] }],
},
},
})
}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/extend-tailwind-merge.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { createTailwindMerge } from './create-tailwind-merge'
import { getDefaultConfig } from './default-config'
import { mergeConfigs } from './merge-configs'
import { Config } from './types'
import { Config, ConfigExtension } from './types'

type CreateConfigSubsequent = (config: Config) => Config

export function extendTailwindMerge(
configExtension: Partial<Config> | CreateConfigSubsequent,
configExtension: ConfigExtension | CreateConfigSubsequent,
...createConfig: CreateConfigSubsequent[]
) {
return typeof configExtension === 'function'
Expand Down
Loading

0 comments on commit 650405d

Please sign in to comment.