Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add config option to ignore variants/modifiers #440

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,11 @@ const tailwindMergeConfig = {
// Conflicts between class groups are defined here
},
conflictingClassGroupModifiers: {
// Conflicts between postfox modifier of a class group and another class group are defined here
// Conflicts between postfix modifier of a class group and another class group are defined here
dcastil marked this conversation as resolved.
Show resolved Hide resolved
},
ignoredVariants: [
// Variants that should be ignored in classes
],
}
```

Expand Down Expand Up @@ -157,6 +160,12 @@ In the Tailwind config you can modify theme scales. tailwind-merge follows the s

If you modified one of these theme scales in your Tailwind config, you can add all your keys right here and tailwind-merge will take care of the rest. If you modified other theme scales, you need to figure out the class group to modify in the [default config](./api-reference.md#getdefaultconfig).

### Ignored variants

Tailwind supports variants that don't modify the selector, which some third-party plugins use to modify utilities.
If you or one of your Tailwind plugins uses variants like this, you can add them to `ignoredVariants` so
that tailwind-merge ignores them when merging classes.

### Extending the tailwind-merge config

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.
Expand Down
1 change: 1 addition & 0 deletions src/lib/default-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1861,5 +1861,6 @@ export function getDefaultConfig() {
conflictingClassGroupModifiers: {
'font-size': ['leading'],
},
ignoredVariants: [],
} as const satisfies Config<DefaultClassGroupIds, DefaultThemeGroupIds>
}
18 changes: 12 additions & 6 deletions src/lib/merge-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,27 @@ function overrideProperty<T extends object, K extends keyof T>(
}

function overrideConfigProperties(
baseObject: Partial<Record<string, readonly unknown[]>>,
overrideObject: Partial<Record<string, readonly unknown[]>> | undefined,
baseObject: Partial<Record<string, readonly unknown[]>> | unknown[],
overrideObject: Partial<Record<string, readonly unknown[]>> | unknown[] | undefined,
) {
if (overrideObject) {
if (!overrideObject) return
if (Array.isArray(baseObject) && Array.isArray(overrideObject)) {
baseObject.splice(0, Infinity, ...overrideObject)
} else if (!Array.isArray(baseObject) && !Array.isArray(overrideObject)) {
for (const key in overrideObject) {
overrideProperty(baseObject, key, overrideObject[key])
}
}
}

function mergeConfigProperties(
baseObject: Partial<Record<string, readonly unknown[]>>,
mergeObject: Partial<Record<string, readonly unknown[]>> | undefined,
baseObject: Partial<Record<string, readonly unknown[]>> | unknown[],
mergeObject: Partial<Record<string, readonly unknown[]>> | unknown[] | undefined,
) {
if (mergeObject) {
if (!mergeObject) return
if (Array.isArray(baseObject) && Array.isArray(mergeObject)) {
baseObject.push(...mergeObject)
} else if (!Array.isArray(baseObject) && !Array.isArray(mergeObject)) {
for (const key in mergeObject) {
const mergeValue = mergeObject[key]

Expand Down
12 changes: 9 additions & 3 deletions src/lib/modifier-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { GenericConfig } from './types'
export const IMPORTANT_MODIFIER = '!'

export function createSplitModifiers(config: GenericConfig) {
const separator = config.separator
const { separator, ignoredVariants } = config
const isSeparatorSingleCharacter = separator.length === 1
const firstSeparatorCharacter = separator[0]
const separatorLength = separator.length

// splitModifiers inspired by https:/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js
return function splitModifiers(className: string) {
const modifiers = []
const modifiers: string[] = []

let bracketDepth = 0
let modifierStart = 0
Expand All @@ -25,7 +25,13 @@ export function createSplitModifiers(config: GenericConfig) {
(isSeparatorSingleCharacter ||
className.slice(index, index + separatorLength) === separator)
) {
modifiers.push(className.slice(modifierStart, index))
const modifier = className.slice(modifierStart, index)
if (
!ignoredVariants.some((v) =>
typeof v === 'string' ? v === modifier : v(modifier),
)
)
modifiers.push(modifier)
modifierStart = index + separatorLength
continue
}
Expand Down
9 changes: 5 additions & 4 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@ interface ConfigStatic {
* @see https://tailwindcss.com/docs/configuration#separator
*/
separator: string
/**
* Theme scales used in classGroups.
* The keys are the same as in the Tailwind config but the values are sometimes defined more broadly.
*/
dcastil marked this conversation as resolved.
Show resolved Hide resolved
}

interface ConfigGroups<ClassGroupIds extends string, ThemeGroupIds extends string> {
Expand Down Expand Up @@ -57,6 +53,11 @@ interface ConfigGroups<ClassGroupIds extends string, ThemeGroupIds extends strin
conflictingClassGroupModifiers: NoInfer<
Partial<Record<ClassGroupIds, readonly ClassGroupIds[]>>
>
/**
* Variants that should be ignored in classes.
* @example ['no-op', (variant) => variant.startsWith('ignore')]
*/
ignoredVariants: (string | ClassValidator)[]
}

export interface ConfigExtension<ClassGroupIds extends string, ThemeGroupIds extends string>
Expand Down
2 changes: 2 additions & 0 deletions tests/create-tailwind-merge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ test('createTailwindMerge works with single config function', () => {
otherKey: ['fooKey', 'fooKey2'],
},
conflictingClassGroupModifiers: {},
ignoredVariants: [],
}))

expect(tailwindMerge('')).toBe('')
Expand Down Expand Up @@ -56,6 +57,7 @@ test('createTailwindMerge works with multiple config functions', () => {
otherKey: ['fooKey', 'fooKey2'],
},
conflictingClassGroupModifiers: {},
ignoredVariants: [],
}),
(config) => ({
...config,
Expand Down
4 changes: 4 additions & 0 deletions tests/merge-configs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ test('mergeConfigs has correct behavior', () => {
hello: ['world'],
toOverride: ['groupToOverride-2'],
},
ignoredVariants: ['noop-variant'],
},
{
separator: '-',
Expand All @@ -42,6 +43,7 @@ test('mergeConfigs has correct behavior', () => {
conflictingClassGroupModifiers: {
toOverride: ['overridden-2'],
},
ignoredVariants: ['noop-variant2'],
},
extend: {
classGroups: {
Expand All @@ -57,6 +59,7 @@ test('mergeConfigs has correct behavior', () => {
conflictingClassGroupModifiers: {
hello: ['world2'],
},
ignoredVariants: ['noop-variant3'],
},
},
),
Expand Down Expand Up @@ -85,5 +88,6 @@ test('mergeConfigs has correct behavior', () => {
hello: ['world', 'world2'],
toOverride: ['overridden-2'],
},
ignoredVariants: ['noop-variant2', 'noop-variant3'],
})
})
15 changes: 14 additions & 1 deletion tests/modifiers.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createTailwindMerge, twMerge } from '../src'
import { createTailwindMerge, extendTailwindMerge, twMerge } from '../src'

test('conflicts across prefix modifiers', () => {
expect(twMerge('hover:block hover:inline')).toBe('hover:inline')
Expand All @@ -9,6 +9,18 @@ test('conflicts across prefix modifiers', () => {
expect(twMerge('focus-within:inline focus-within:block')).toBe('focus-within:block')
})

test('ignored variants', () => {
const tailwindMerge = extendTailwindMerge<string>({
extend: {
ignoredVariants: ['custom-variant', (variant) => variant.startsWith('ignore')],
},
})
expect(tailwindMerge('hover:block hover:custom-variant:inline')).toBe(
'hover:custom-variant:inline',
)
expect(tailwindMerge('hover:block hover:ignored:inline')).toBe('hover:ignored:inline')
})

test('conflicts across postfix modifiers', () => {
expect(twMerge('text-lg/7 text-lg/8')).toBe('text-lg/8')
expect(twMerge('text-lg/none leading-9')).toBe('text-lg/none leading-9')
Expand All @@ -28,6 +40,7 @@ test('conflicts across postfix modifiers', () => {
conflictingClassGroupModifiers: {
baz: ['bar'],
},
ignoredVariants: [],
}))

expect(customTwMerge('foo-1/2 foo-2/3')).toBe('foo-2/3')
Expand Down
3 changes: 3 additions & 0 deletions tests/public-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ test('createTailwindMerge() has correct inputs and outputs', () => {
classGroups: {},
conflictingClassGroups: {},
conflictingClassGroupModifiers: {},
ignoredVariants: [],
})),
).toStrictEqual(expect.any(Function))

Expand All @@ -111,6 +112,7 @@ test('createTailwindMerge() has correct inputs and outputs', () => {
otherKey: ['fooKey', 'fooKey2'],
},
conflictingClassGroupModifiers: {},
ignoredVariants: [],
}))

expect(tailwindMerge).toStrictEqual(expect.any(Function))
Expand Down Expand Up @@ -173,6 +175,7 @@ test('mergeConfigs has correct inputs and outputs', () => {
},
conflictingClassGroups: {},
conflictingClassGroupModifiers: {},
ignoredVariants: [],
},
{},
),
Expand Down
1 change: 1 addition & 0 deletions tests/type-generics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ test('mergeConfigs type generics work correctly', () => {
hello: ['world'],
toOverride: ['groupToOverride-2'],
},
ignoredVariants: [],
},
{
separator: '-',
Expand Down