feat(shared): add cn utility for tailwind-aware class merging

This commit is contained in:
Ilia Mashkov
2026-04-23 09:38:30 +03:00
parent fe0d4e7daa
commit 2c579a3336
4 changed files with 45 additions and 0 deletions
+1
View File
@@ -39,6 +39,7 @@ export {
export { export {
buildQueryString, buildQueryString,
clampNumber, clampNumber,
cn,
debounce, debounce,
getDecimalPlaces, getDecimalPlaces,
roundToStepPrecision, roundToStepPrecision,
+30
View File
@@ -0,0 +1,30 @@
import {
describe,
expect,
it,
} from 'vitest';
import { cn } from './cn';
describe('cn utility', () => {
it('should merge classes with clsx', () => {
expect(cn('class1', 'class2')).toBe('class1 class2');
expect(cn('class1', { class2: true, class3: false })).toBe('class1 class2');
});
it('should resolve tailwind specificity conflicts', () => {
// text-neutral-400 vs text-brand (text-brand should win)
expect(cn('text-neutral-400', 'text-brand')).toBe('text-brand');
// p-4 vs p-2
expect(cn('p-4', 'p-2')).toBe('p-2');
// dark mode classes should be handled correctly too
expect(cn('text-neutral-400 dark:text-neutral-400', 'text-brand dark:text-brand')).toBe(
'text-brand dark:text-brand',
);
});
it('should handle undefined and null inputs', () => {
expect(cn('class1', undefined, null, 'class2')).toBe('class1 class2');
});
});
+13
View File
@@ -0,0 +1,13 @@
import {
type ClassValue,
clsx,
} from 'clsx';
import { twMerge } from 'tailwind-merge';
/**
* Utility for merging Tailwind classes with clsx and tailwind-merge.
* This resolves specificity conflicts between Tailwind classes.
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+1
View File
@@ -15,6 +15,7 @@ export {
type QueryParamValue, type QueryParamValue,
} from './buildQueryString/buildQueryString'; } from './buildQueryString/buildQueryString';
export { clampNumber } from './clampNumber/clampNumber'; export { clampNumber } from './clampNumber/clampNumber';
export { cn } from './cn';
export { debounce } from './debounce/debounce'; export { debounce } from './debounce/debounce';
export { getDecimalPlaces } from './getDecimalPlaces/getDecimalPlaces'; export { getDecimalPlaces } from './getDecimalPlaces/getDecimalPlaces';
export { getSkeletonWidth } from './getSkeletonWidth/getSkeletonWidth'; export { getSkeletonWidth } from './getSkeletonWidth/getSkeletonWidth';