refactor: Migrate to monorepo
BREAKING CHANGE: Complete structure overhaul
This commit is contained in:
496 files changed
+45388
-7531
No files matched your search
@@ -0,0 +1,95 @@
|
||||
import { cn } from './cn';
|
||||
|
||||
/**
|
||||
* Test suite for the cn utility function
|
||||
*/
|
||||
describe('shared/helpers/cn/cn', () => {
|
||||
it('combines simple class names', () => {
|
||||
expect(cn('p-4', 'bg-red-500')).toBe('p-4 bg-red-500');
|
||||
expect(cn('flex', 'items-center', 'justify-between')).toBe('flex items-center justify-between');
|
||||
});
|
||||
|
||||
it('resolves conflicting Tailwind classes using tailwind-merge', () => {
|
||||
expect(cn('p-4 bg-red-500', 'bg-blue-500')).toBe('p-4 bg-blue-500');
|
||||
expect(cn('text-sm font-normal', 'font-bold')).toBe('text-sm font-bold');
|
||||
expect(cn('px-2 py-1', 'p-4')).toBe('p-4');
|
||||
});
|
||||
|
||||
it('handles conditional classes using clsx', () => {
|
||||
// Test with actual conditional values (simulating real usage)
|
||||
const activeCondition = Math.random() > -1; // Always true but not constant
|
||||
const inactiveCondition = Math.random() < -1; // Always false but not constant
|
||||
|
||||
expect(cn('p-4', activeCondition && 'bg-red-500')).toBe('p-4 bg-red-500');
|
||||
expect(cn('p-4', inactiveCondition && 'bg-red-500')).toBe('p-4');
|
||||
|
||||
// Test null and undefined conditions
|
||||
const nullCondition: string | null = null;
|
||||
const undefinedCondition: string | undefined = undefined;
|
||||
expect(cn('p-4', nullCondition)).toBe('p-4');
|
||||
expect(cn('p-4', undefinedCondition)).toBe('p-4');
|
||||
});
|
||||
|
||||
it('handles object-based conditional classes', () => {
|
||||
expect(cn('p-4', { 'bg-red-500': true, 'text-white': false })).toBe('p-4 bg-red-500');
|
||||
expect(cn('p-4', { 'bg-red-500': false, 'text-white': true })).toBe('p-4 text-white');
|
||||
});
|
||||
|
||||
it('handles array inputs', () => {
|
||||
expect(cn(['flex', 'items-center'], 'justify-between')).toBe('flex items-center justify-between');
|
||||
expect(cn(['p-4', null, 'bg-red-500'])).toBe('p-4 bg-red-500');
|
||||
});
|
||||
|
||||
it('handles empty inputs', () => {
|
||||
expect(cn()).toBe('');
|
||||
expect(cn('', null, undefined, false)).toBe('');
|
||||
});
|
||||
|
||||
it('combines complex scenarios', () => {
|
||||
// Simulate dynamic conditions and variant selection
|
||||
const getIsActive = (): boolean => true;
|
||||
const getPrimaryVariant = (): 'primary' | 'secondary' => 'primary';
|
||||
const getSecondaryVariant = (): 'primary' | 'secondary' => 'secondary';
|
||||
|
||||
const isActive = getIsActive();
|
||||
const primaryVariant = getPrimaryVariant();
|
||||
const secondaryVariant = getSecondaryVariant();
|
||||
|
||||
// Test primary variant
|
||||
expect(
|
||||
cn(
|
||||
'base-class',
|
||||
isActive && 'active-class',
|
||||
{
|
||||
'primary-class': primaryVariant === 'primary',
|
||||
'secondary-class': primaryVariant === 'secondary',
|
||||
},
|
||||
['flex', 'items-center'],
|
||||
'bg-red-500 bg-blue-500' // Should resolve to bg-blue-500
|
||||
)
|
||||
).toBe('base-class active-class primary-class flex items-center bg-blue-500');
|
||||
|
||||
// Test secondary variant
|
||||
expect(
|
||||
cn(
|
||||
'base-class',
|
||||
isActive && 'active-class',
|
||||
{
|
||||
'primary-class': secondaryVariant === 'primary',
|
||||
'secondary-class': secondaryVariant === 'secondary',
|
||||
},
|
||||
['flex', 'items-center']
|
||||
)
|
||||
).toBe('base-class active-class secondary-class flex items-center');
|
||||
});
|
||||
|
||||
it('properly handles spacing and responsive classes', () => {
|
||||
expect(
|
||||
cn('p-2 p-4', 'sm:p-6', 'md:p-8')
|
||||
).toBe('p-4 sm:p-6 md:p-8');
|
||||
|
||||
expect(
|
||||
cn('m-2', 'mx-4', 'ml-6')
|
||||
).toBe('m-2 mx-4 ml-6');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/**
|
||||
* Combines and merges CSS class names using clsx and tailwind-merge.
|
||||
*
|
||||
* This utility function combines the power of clsx for conditional class handling
|
||||
* with tailwind-merge for proper Tailwind CSS class deduplication and conflict resolution.
|
||||
*
|
||||
* @param inputs - Class values to combine (strings, objects, arrays, etc.)
|
||||
* @returns Merged class string with conflicts resolved
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* cn('px-4 py-2', 'bg-blue-500') // 'px-4 py-2 bg-blue-500'
|
||||
* cn('px-4', { 'px-8': true }) // 'px-8' (px-8 overrides px-4)
|
||||
* cn('text-red-500', undefined, 'text-blue-500') // 'text-blue-500'
|
||||
* ```
|
||||
*/
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Shared helper utilities for common functionality.
|
||||
*/
|
||||
|
||||
export { cn } from './cn/cn';
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './types';
|
||||
export * from './functions';
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Props interface for components that accept a className prop.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* interface ButtonProps extends ClassNameProps {
|
||||
* children: string;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface ClassNameProps {
|
||||
/** Optional CSS class name */
|
||||
className?: string;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Shared type definitions for common patterns.
|
||||
*/
|
||||
|
||||
export type { ClassNameProps } from './classNameProps';
|
||||
export type { TestIdProps } from './testIdProps';
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Props interface for components that accept a test ID prop for React Testing Library.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* interface ButtonProps extends TestIdProps {
|
||||
* children: string;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface TestIdProps {
|
||||
/** Test identifier for React Testing Library queries */
|
||||
'data-testid'?: string;
|
||||
}
|
||||
Reference in new issue
Block a user