diff --git a/src/shared/lib/index.ts b/src/shared/lib/index.ts index 33c077c..42fccfb 100644 --- a/src/shared/lib/index.ts +++ b/src/shared/lib/index.ts @@ -39,6 +39,7 @@ export { export { buildQueryString, clampNumber, + cn, debounce, getDecimalPlaces, roundToStepPrecision, diff --git a/src/shared/lib/utils/cn.test.ts b/src/shared/lib/utils/cn.test.ts new file mode 100644 index 0000000..34475b0 --- /dev/null +++ b/src/shared/lib/utils/cn.test.ts @@ -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'); + }); +}); diff --git a/src/shared/lib/utils/cn.ts b/src/shared/lib/utils/cn.ts new file mode 100644 index 0000000..1e1842f --- /dev/null +++ b/src/shared/lib/utils/cn.ts @@ -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)); +} diff --git a/src/shared/lib/utils/index.ts b/src/shared/lib/utils/index.ts index b4ae529..21eef79 100644 --- a/src/shared/lib/utils/index.ts +++ b/src/shared/lib/utils/index.ts @@ -15,6 +15,7 @@ export { type QueryParamValue, } from './buildQueryString/buildQueryString'; export { clampNumber } from './clampNumber/clampNumber'; +export { cn } from './cn'; export { debounce } from './debounce/debounce'; export { getDecimalPlaces } from './getDecimalPlaces/getDecimalPlaces'; export { getSkeletonWidth } from './getSkeletonWidth/getSkeletonWidth';