From 98dd008613155ff7a9f8f5cb89be90ddd7ddd122 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 20:10:39 +0000 Subject: [PATCH 1/5] Add comprehensive cursor rules for React/TypeScript form components - Add form-component-patterns.mdc: Remix Hook Form + Zod integration patterns - Add react-typescript-patterns.mdc: Component architecture and TypeScript best practices - Add ui-component-patterns.mdc: Radix UI + Tailwind CSS + CVA patterns - Add monorepo-organization.mdc: Import conventions and package structure These rules complement the existing Storybook testing rules and provide comprehensive guidance for consistent development patterns across the lambda-curry/forms component library. --- .cursor/rules/form-component-patterns.mdc | 218 ++++++++++ .cursor/rules/monorepo-organization.mdc | 363 ++++++++++++++++ .cursor/rules/react-typescript-patterns.mdc | 386 +++++++++++++++++ .cursor/rules/ui-component-patterns.mdc | 448 ++++++++++++++++++++ 4 files changed, 1415 insertions(+) create mode 100644 .cursor/rules/form-component-patterns.mdc create mode 100644 .cursor/rules/monorepo-organization.mdc create mode 100644 .cursor/rules/react-typescript-patterns.mdc create mode 100644 .cursor/rules/ui-component-patterns.mdc diff --git a/.cursor/rules/form-component-patterns.mdc b/.cursor/rules/form-component-patterns.mdc new file mode 100644 index 00000000..75c41cb5 --- /dev/null +++ b/.cursor/rules/form-component-patterns.mdc @@ -0,0 +1,218 @@ +--- +type: Always +description: Rules for form component integration patterns in the lambda-curry/forms repository +--- + +You are an expert in React Hook Form, Remix Hook Form, Zod validation, and form component architecture for the lambda-curry/forms monorepo. + +# Form Component Integration Patterns + +## Core Principles +- All form components must integrate seamlessly with Remix Hook Form +- Use Zod schemas for validation with proper TypeScript inference +- Follow the wrapper pattern for consistent component composition +- Maintain separation between UI components and form-aware components +- Ensure proper error handling and validation feedback + +## Required Imports for Form Components +```typescript +// Remix Hook Form integration +import { useRemixFormContext } from 'remix-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; + +// Form components +import { FormControl, FormDescription, FormLabel, FormMessage } from './form'; + +// Base UI components +import { ComponentName as BaseComponentName } from '../ui/component-name'; +``` + +## Form Schema Pattern +Always define Zod schemas with proper error messages: +```typescript +const formSchema = z.object({ + fieldName: z.string().min(1, 'Field is required'), + email: z.string().email('Invalid email address'), + price: z.string().min(1, 'Price is required'), +}); + +type FormData = z.infer; +``` + +## Wrapper Component Pattern +Follow this pattern for all form-aware components: +```typescript +export type ComponentNameProps = Omit; + +export function ComponentName(props: ComponentNameProps) { + const { control } = useRemixFormContext(); + + // Merge provided components with default form components + const defaultComponents = { + FormControl, + FormLabel, + FormDescription, + FormMessage, + }; + + const components = { + ...defaultComponents, + ...props.components, + }; + + return ; +} +``` + +## Component Composition Pattern +For UI components that accept form integration: +```typescript +export interface ComponentNameProps extends Omit { + control?: Control; + name: FieldPath; + label?: string; + description?: string; + components?: Partial & { + Input?: React.ComponentType; + }; + className?: string; +} + +export const ComponentName = ({ + control, + name, + label, + description, + className, + components, + ...props +}: ComponentNameProps) => { + const InputComponent = components?.Input || DefaultInput; + + return ( + ( + + {label && {label}} + + + + {description && {description}} + {fieldState.error && ( + {fieldState.error.message} + )} + + )} + /> + ); +}; +``` + +## Form Setup Pattern +Use this pattern for form initialization: +```typescript +const ControlledComponentExample = () => { + const fetcher = useFetcher<{ message: string }>(); + const methods = useRemixForm({ + resolver: zodResolver(formSchema), + defaultValues: { + // Provide sensible defaults + }, + fetcher, + submitConfig: { action: '/', method: 'post' }, + }); + + return ( + + + {/* Form components */} + + + ); +}; +``` + +## Validation Patterns + +### Client-Side Validation +- Use Zod schemas for all form validation +- Provide clear, user-friendly error messages +- Validate on blur and submit, not on every keystroke + +### Server-Side Validation +```typescript +export const action = async ({ request }: ActionFunctionArgs) => { + const { data, errors } = await getValidatedFormData( + request, + zodResolver(formSchema) + ); + + if (errors) return { errors }; + + // Additional server-side validation + if (data.username === 'taken') { + return { + errors: { + username: { message: 'Username is already taken' } + } + }; + } + + return { message: 'Success!' }; +}; +``` + +## Error Handling Best Practices +- Always display field-level errors using FormMessage +- Handle both client-side and server-side validation errors +- Provide loading states during form submission +- Clear errors appropriately when fields are corrected + +## Component Naming Conventions +- Form-aware components: `ComponentName` (e.g., `TextField`, `Checkbox`) +- Base UI components: `ComponentName` in ui/ directory +- Props interfaces: `ComponentNameProps` +- Form schemas: `formSchema` or `componentNameSchema` + +## File Organization +``` +packages/components/src/ +├── remix-hook-form/ # Form-aware wrapper components +│ ├── text-field.tsx +│ ├── checkbox.tsx +│ └── index.ts +└── ui/ # Base UI components + ├── text-field.tsx + ├── checkbox.tsx + └── index.ts +``` + +## Required Exports +Always export both the component and its props type: +```typescript +export { ComponentName }; +export type { ComponentNameProps }; +``` + +## Performance Considerations +- Use React.memo for expensive form components +- Avoid unnecessary re-renders by properly structuring form state +- Consider field-level subscriptions for large forms + +## Accessibility Requirements +- All form fields must have proper labels +- Use ARIA attributes for complex form interactions +- Ensure keyboard navigation works correctly +- Provide clear error announcements for screen readers + +## Testing Integration +- Form components should work with the existing Storybook testing patterns +- Test both valid and invalid form states +- Verify server-side validation integration +- Test component composition and customization + +Remember: Form components are the core of this library. Every form component should be intuitive, accessible, and integrate seamlessly with the Remix Hook Form + Zod validation pattern. + diff --git a/.cursor/rules/monorepo-organization.mdc b/.cursor/rules/monorepo-organization.mdc new file mode 100644 index 00000000..84ec5a7b --- /dev/null +++ b/.cursor/rules/monorepo-organization.mdc @@ -0,0 +1,363 @@ +--- +type: Always +description: Monorepo structure and import conventions for the lambda-curry/forms repository +--- + +You are an expert in monorepo architecture, package management, and TypeScript module organization. + +# Monorepo Organization & Import Conventions + +## Core Principles +- Maintain clear separation between packages and apps +- Use consistent import patterns across the monorepo +- Follow package naming conventions +- Organize exports for optimal tree-shaking +- Ensure proper dependency management + +## Project Structure +``` +lambda-curry/forms/ +├── apps/ +│ └── docs/ # Storybook documentation app +│ ├── src/ +│ │ ├── remix-hook-form/ # Story files +│ │ └── lib/ # Storybook utilities +│ └── package.json +├── packages/ +│ └── components/ # Main component library +│ ├── src/ +│ │ ├── remix-hook-form/ # Form-aware components +│ │ ├── ui/ # Base UI components +│ │ └── index.ts # Main export file +│ └── package.json +├── package.json # Root package.json +└── turbo.json # Turbo configuration +``` + +## Package Import Patterns + +### External Package Imports +```typescript +// ✅ Use package name imports for published packages +import { TextField } from '@lambdacurry/forms/remix-hook-form'; +import { Button } from '@lambdacurry/forms/ui'; + +// ✅ Import from specific entry points +import { TextField } from '@lambdacurry/forms/remix-hook-form/text-field'; +import { Button } from '@lambdacurry/forms/ui/button'; +``` + +### Internal Package Imports +```typescript +// ✅ Use relative imports within the same package +import { FormControl } from './form'; +import { cn } from './utils'; + +// ✅ Use relative imports for sibling directories +import { Button } from '../ui/button'; +import { TextField as BaseTextField } from '../ui/text-field'; +``` + +### Cross-Package Dependencies +```typescript +// ✅ In apps/docs - import from package name +import { TextField } from '@lambdacurry/forms/remix-hook-form'; +import { Button } from '@lambdacurry/forms/ui'; + +// ❌ Don't use relative imports across packages +import { TextField } from '../../packages/components/src/remix-hook-form/text-field'; +``` + +## Export Conventions + +### Package Entry Points +```typescript +// packages/components/src/index.ts +export * from './ui'; +export * from './remix-hook-form'; + +// packages/components/src/ui/index.ts +export { Button } from './button'; +export type { ButtonProps } from './button'; +export { Input } from './input'; +export type { InputProps } from './input'; + +// packages/components/src/remix-hook-form/index.ts +export { TextField } from './text-field'; +export type { TextFieldProps } from './text-field'; +export { Checkbox } from './checkbox'; +export type { CheckboxProps } from './checkbox'; +``` + +### Specific Entry Points (package.json) +```json +{ + "name": "@lambdacurry/forms", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./remix-hook-form": { + "import": { + "types": "./dist/remix-hook-form/index.d.ts", + "default": "./dist/remix-hook-form/index.js" + } + }, + "./ui": { + "import": { + "types": "./dist/ui/index.d.ts", + "default": "./dist/ui/index.js" + } + } + } +} +``` + +### Component Exports +```typescript +// ✅ Always export both component and props type +export { ComponentName }; +export type { ComponentNameProps }; + +// ✅ Use named exports for better tree-shaking +export const ComponentName = () => { /* ... */ }; + +// ❌ Avoid default exports for components +export default ComponentName; +``` + +## File Naming Conventions + +### Component Files +``` +// ✅ Use kebab-case for file names +text-field.tsx +data-table.tsx +dropdown-menu.tsx + +// ✅ Match component name in PascalCase +// text-field.tsx exports TextField +// data-table.tsx exports DataTable +``` + +### Directory Structure +``` +src/ +├── remix-hook-form/ # Form-aware wrapper components +│ ├── text-field.tsx # TextField component +│ ├── checkbox.tsx # Checkbox component +│ ├── form.tsx # Form utilities +│ └── index.ts # Package exports +├── ui/ # Base UI components +│ ├── button.tsx # Button component +│ ├── input.tsx # Input component +│ ├── utils/ # Utility functions +│ │ ├── cn.ts # Class name utility +│ │ └── index.ts # Utility exports +│ └── index.ts # Package exports +└── index.ts # Main package export +``` + +## Dependency Management + +### Package Dependencies +```json +// packages/components/package.json +{ + "peerDependencies": { + "react": "^19.0.0", + "remix-hook-form": "7.0.0" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "typescript": "^5.7.2" + } +} +``` + +### Workspace Dependencies +```json +// apps/docs/package.json +{ + "dependencies": { + "@lambdacurry/forms": "*" // Use workspace version + } +} +``` + +## TypeScript Configuration + +### Path Mapping +```json +// tsconfig.json +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@lambdacurry/forms": ["./packages/components/src"], + "@lambdacurry/forms/*": ["./packages/components/src/*"] + } + } +} +``` + +### Package TypeScript Config +```json +// packages/components/tsconfig.json +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules"] +} +``` + +## Build Configuration + +### Turbo Configuration +```json +// turbo.json +{ + "pipeline": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "test": { + "dependsOn": ["build"] + } + } +} +``` + +### Vite Build Config +```typescript +// packages/components/vite.config.ts +import { defineConfig } from 'vite'; +import { resolve } from 'path'; +import dts from 'vite-plugin-dts'; + +export default defineConfig({ + plugins: [dts()], + build: { + lib: { + entry: { + index: resolve(__dirname, 'src/index.ts'), + 'remix-hook-form/index': resolve(__dirname, 'src/remix-hook-form/index.ts'), + 'ui/index': resolve(__dirname, 'src/ui/index.ts'), + }, + formats: ['es'], + }, + rollupOptions: { + external: ['react', 'react-dom', 'remix-hook-form'], + }, + }, +}); +``` + +## Import Organization + +### Import Order +```typescript +// 1. External library imports +import * as React from 'react'; +import { type VariantProps, cva } from 'class-variance-authority'; +import { Slot } from '@radix-ui/react-slot'; + +// 2. Internal package imports (same package) +import { cn } from './utils'; +import { FormControl } from './form'; + +// 3. Cross-package imports +import { Button } from '@lambdacurry/forms/ui'; + +// 4. Type-only imports (grouped separately) +import type { ComponentProps } from 'react'; +import type { FormFieldProps } from './types'; +``` + +### Re-export Patterns +```typescript +// ✅ Re-export with explicit names +export { Button } from './button'; +export { Input } from './input'; + +// ✅ Re-export types explicitly +export type { ButtonProps } from './button'; +export type { InputProps } from './input'; + +// ✅ Barrel exports for convenience +export * from './components'; + +// ❌ Avoid mixing default and named exports +export { default as Button } from './button'; +``` + +## Versioning Strategy + +### Package Versioning +```json +// Use consistent versioning across packages +{ + "name": "@lambdacurry/forms", + "version": "0.15.1" +} +``` + +### Changesets Integration +```markdown +--- +"@lambdacurry/forms": minor +--- + +Add new TextField component with validation support +``` + +## Documentation Structure +``` +docs/ +├── components/ # Component documentation +│ ├── text-field.mdx +│ └── button.mdx +├── guides/ # Usage guides +│ ├── getting-started.mdx +│ └── form-validation.mdx +└── examples/ # Code examples + ├── basic-form.tsx + └── advanced-form.tsx +``` + +## Best Practices + +### Package Boundaries +- Keep form-aware components separate from base UI components +- Don't import from `remix-hook-form` package in `ui` components +- Use composition over inheritance for cross-package functionality + +### Performance +- Use tree-shaking friendly exports +- Avoid circular dependencies +- Keep bundle sizes minimal with proper externals + +### Maintenance +- Use consistent naming across packages +- Document breaking changes in changelogs +- Maintain backward compatibility when possible + +Remember: A well-organized monorepo makes development faster and more predictable. Every import should be intentional and follow the established patterns to maintain consistency across the codebase. + diff --git a/.cursor/rules/react-typescript-patterns.mdc b/.cursor/rules/react-typescript-patterns.mdc new file mode 100644 index 00000000..7270ef86 --- /dev/null +++ b/.cursor/rules/react-typescript-patterns.mdc @@ -0,0 +1,386 @@ +--- +type: Always +description: React and TypeScript component architecture patterns for the lambda-curry/forms repository +--- + +You are an expert in React 19, TypeScript, modern React patterns, and component library architecture. + +# React + TypeScript Component Architecture + +## Core Principles +- Use functional components with TypeScript interfaces +- Prefer composition over inheritance +- Write self-documenting code with descriptive names +- Follow React 19 best practices and modern patterns +- Maintain consistent component structure and naming + +## Component Structure Pattern +Organize components with this consistent structure: +```typescript +// 1. Imports (external libraries first, then internal) +import * as React from 'react'; +import { type VariantProps, cva } from 'class-variance-authority'; +import { Slot } from '@radix-ui/react-slot'; + +// 2. Internal imports +import { cn } from './utils'; + +// 3. Type definitions and interfaces +export interface ComponentProps extends React.HTMLAttributes { + variant?: 'default' | 'secondary'; + size?: 'sm' | 'md' | 'lg'; + asChild?: boolean; +} + +// 4. Component variants (if using CVA) +const componentVariants = cva( + 'base-classes', + { + variants: { + variant: { + default: 'default-styles', + secondary: 'secondary-styles', + }, + size: { + sm: 'small-styles', + md: 'medium-styles', + lg: 'large-styles', + }, + }, + defaultVariants: { + variant: 'default', + size: 'md', + }, + } +); + +// 5. Component implementation +export const Component = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'div'; + + return ( + + ); + } +); + +// 6. Display name +Component.displayName = 'Component'; +``` + +## TypeScript Interface Conventions + +### Props Interfaces +```typescript +// Extend HTML attributes when appropriate +export interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'default' | 'destructive' | 'outline'; + size?: 'default' | 'sm' | 'lg'; + asChild?: boolean; +} + +// Use descriptive names with auxiliary verbs +export interface FormFieldProps { + isLoading?: boolean; + hasError?: boolean; + isRequired?: boolean; + isDisabled?: boolean; +} + +// Prefer interfaces over types for component props +export interface ComponentProps { + // Use specific types instead of any + onValueChange?: (value: string) => void; + // Use union types for controlled options + variant: 'primary' | 'secondary' | 'ghost'; +} +``` + +### Generic Type Patterns +```typescript +// For form components that work with any field type +export interface FieldProps { + value?: T; + onChange?: (value: T) => void; + defaultValue?: T; +} + +// For components that accept custom renderers +export interface ListProps { + items: T[]; + renderItem: (item: T, index: number) => React.ReactNode; + keyExtractor: (item: T) => string; +} +``` + +## Component Composition Patterns + +### Compound Components +```typescript +// Main component +export const Card = ({ className, ...props }: CardProps) => ( +
+); + +// Sub-components +export const CardHeader = ({ className, ...props }: CardHeaderProps) => ( +
+); + +export const CardContent = ({ className, ...props }: CardContentProps) => ( +
+); + +// Attach sub-components +Card.Header = CardHeader; +Card.Content = CardContent; +``` + +### Polymorphic Components +```typescript +type AsChildProps = { + asChild?: boolean; + as?: T; +} & React.ComponentPropsWithoutRef; + +export const Polymorphic = ({ + asChild, + as, + ...props +}: AsChildProps) => { + const Component = asChild ? Slot : (as || 'div'); + return ; +}; +``` + +## Hook Patterns + +### Custom Hook Structure +```typescript +// Use descriptive names starting with 'use' +export function useFormValidation(schema: z.ZodSchema) { + const [errors, setErrors] = React.useState>({}); + const [isValidating, setIsValidating] = React.useState(false); + + const validate = React.useCallback(async (data: T) => { + setIsValidating(true); + try { + await schema.parseAsync(data); + setErrors({}); + return true; + } catch (error) { + if (error instanceof z.ZodError) { + const fieldErrors = error.errors.reduce((acc, err) => { + const path = err.path.join('.'); + acc[path] = err.message; + return acc; + }, {} as Record); + setErrors(fieldErrors); + } + return false; + } finally { + setIsValidating(false); + } + }, [schema]); + + return { errors, isValidating, validate }; +} +``` + +### Context Patterns +```typescript +// Create strongly typed context +interface FormContextValue { + isSubmitting: boolean; + errors: Record; + setValue: (name: string, value: unknown) => void; +} + +const FormContext = React.createContext(null); + +// Custom hook for context consumption +export function useFormContext() { + const context = React.useContext(FormContext); + if (!context) { + throw new Error('useFormContext must be used within a FormProvider'); + } + return context; +} +``` + +## Event Handler Patterns +```typescript +// Use descriptive event handler names +interface ComponentProps { + onValueChange?: (value: string) => void; + onSelectionChange?: (selectedIds: string[]) => void; + onSubmit?: (data: FormData) => void | Promise; + onError?: (error: Error) => void; +} + +// Handle async operations properly +const handleSubmit = async (data: FormData) => { + try { + setIsLoading(true); + await onSubmit?.(data); + } catch (error) { + onError?.(error as Error); + } finally { + setIsLoading(false); + } +}; +``` + +## Performance Optimization Patterns + +### Memoization +```typescript +// Memoize expensive calculations +const expensiveValue = React.useMemo(() => { + return computeExpensiveValue(props.data); +}, [props.data]); + +// Memoize callback functions +const handleClick = React.useCallback((id: string) => { + onItemClick?.(id); +}, [onItemClick]); + +// Memoize components when necessary +export const ExpensiveComponent = React.memo(({ data }: Props) => { + return
{/* expensive rendering */}
; +}); +``` + +### Ref Patterns +```typescript +// Forward refs for component library components +export const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); + +// Use refs for imperative operations +export function useImperativeHandle() { + const inputRef = React.useRef(null); + + const focus = React.useCallback(() => { + inputRef.current?.focus(); + }, []); + + return { inputRef, focus }; +} +``` + +## Error Boundary Patterns +```typescript +interface ErrorBoundaryState { + hasError: boolean; + error?: Error; +} + +export class ErrorBoundary extends React.Component< + React.PropsWithChildren<{}>, + ErrorBoundaryState +> { + constructor(props: React.PropsWithChildren<{}>) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Error caught by boundary:', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return
Something went wrong.
; + } + + return this.props.children; + } +} +``` + +## Naming Conventions +- **Components**: PascalCase (`TextField`, `DataTable`) +- **Props interfaces**: ComponentName + Props (`TextFieldProps`) +- **Hooks**: camelCase starting with 'use' (`useFormValidation`) +- **Event handlers**: camelCase starting with 'handle' (`handleSubmit`) +- **Boolean props**: Use auxiliary verbs (`isLoading`, `hasError`, `canEdit`) +- **Files**: kebab-case (`text-field.tsx`, `data-table.tsx`) + +## File Organization +``` +src/ +├── components/ +│ ├── ui/ # Base UI components +│ │ ├── button.tsx +│ │ └── index.ts +│ └── remix-hook-form/ # Form-aware components +│ ├── text-field.tsx +│ └── index.ts +├── hooks/ # Custom hooks +│ ├── use-form-validation.ts +│ └── index.ts +└── utils/ # Utility functions + ├── cn.ts + └── index.ts +``` + +## Export Patterns +```typescript +// Named exports for components and types +export { Button } from './button'; +export type { ButtonProps } from './button'; + +// Re-export from index files +export * from './button'; +export * from './input'; + +// Default exports only for single-purpose modules +export default function utilityFunction() { + // ... +} +``` + +## Documentation Requirements +```typescript +/** + * A flexible button component that supports multiple variants and sizes. + * + * @example + * ```tsx + * + * ``` + */ +export interface ButtonProps { + /** The visual style variant */ + variant?: 'primary' | 'secondary' | 'ghost'; + /** The size of the button */ + size?: 'sm' | 'md' | 'lg'; + /** Click event handler */ + onClick?: (event: React.MouseEvent) => void; +} +``` + +Remember: Write components that are predictable, composable, and maintainable. Every component should have a clear purpose and follow consistent patterns that make the codebase easy to understand and extend. + diff --git a/.cursor/rules/ui-component-patterns.mdc b/.cursor/rules/ui-component-patterns.mdc new file mode 100644 index 00000000..7eeac6cb --- /dev/null +++ b/.cursor/rules/ui-component-patterns.mdc @@ -0,0 +1,448 @@ +--- +type: Always +description: UI component patterns for Radix UI, Tailwind CSS, and class-variance-authority integration +--- + +You are an expert in Radix UI, Tailwind CSS, class-variance-authority (CVA), and accessible component design. + +# UI Component Patterns + +## Core Principles +- Build accessible-first components using Radix UI primitives +- Use Tailwind CSS with a mobile-first responsive approach +- Implement consistent variant patterns with class-variance-authority +- Follow the Slot pattern for polymorphic components +- Maintain design system consistency across all components + +## Class Variance Authority (CVA) Patterns + +### Basic CVA Setup +```typescript +import { type VariantProps, cva } from 'class-variance-authority'; + +const buttonVariants = cva( + // Base classes - always applied + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); + +// Extract variant props type +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} +``` + +### Complex Variant Patterns +```typescript +const inputVariants = cva( + 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', + { + variants: { + size: { + default: 'h-10', + sm: 'h-9', + lg: 'h-11', + }, + variant: { + default: 'border-input', + error: 'border-destructive focus-visible:ring-destructive', + success: 'border-green-500 focus-visible:ring-green-500', + }, + }, + compoundVariants: [ + { + variant: 'error', + size: 'sm', + class: 'text-xs', + }, + ], + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +); +``` + +## Radix UI Integration Patterns + +### Basic Radix Component +```typescript +import * as React from 'react'; +import * as RadixPrimitive from '@radix-ui/react-primitive'; +import { cn } from './utils'; + +const Component = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Component.displayName = RadixPrimitive.Root.displayName; +``` + +### Compound Radix Components +```typescript +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; + +const DropdownMenu = DropdownMenuPrimitive.Root; +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +// Export compound component +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, +}; +``` + +## Slot Pattern for Polymorphic Components +```typescript +import { Slot } from '@radix-ui/react-slot'; + +interface ButtonProps extends React.ButtonHTMLAttributes { + asChild?: boolean; + variant?: 'default' | 'destructive' | 'outline'; +} + +const Button = React.forwardRef( + ({ className, variant, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); + } +); + +// Usage examples: +// +// +``` + +## Tailwind CSS Organization Patterns + +### Responsive Design +```typescript +// Mobile-first responsive classes +const responsiveClasses = cn( + 'text-sm', // Mobile default + 'md:text-base', // Tablet and up + 'lg:text-lg', // Desktop and up + 'xl:text-xl' // Large desktop +); + +// Container patterns +const containerClasses = cn( + 'w-full', + 'max-w-sm', // Mobile + 'sm:max-w-md', // Small tablet + 'md:max-w-lg', // Tablet + 'lg:max-w-xl', // Desktop + 'xl:max-w-2xl' // Large desktop +); +``` + +### Component State Classes +```typescript +const stateClasses = cn( + // Base state + 'bg-background text-foreground', + // Hover state + 'hover:bg-accent hover:text-accent-foreground', + // Focus state + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', + // Active state + 'active:scale-95', + // Disabled state + 'disabled:pointer-events-none disabled:opacity-50', + // Data states (for Radix components) + 'data-[state=open]:bg-accent', + 'data-[state=closed]:bg-background' +); +``` + +### Animation Classes +```typescript +const animationClasses = cn( + // Transitions + 'transition-all duration-200 ease-in-out', + // Enter animations + 'data-[state=open]:animate-in', + 'data-[state=open]:fade-in-0', + 'data-[state=open]:zoom-in-95', + // Exit animations + 'data-[state=closed]:animate-out', + 'data-[state=closed]:fade-out-0', + 'data-[state=closed]:zoom-out-95', + // Slide animations + 'data-[side=bottom]:slide-in-from-top-2', + 'data-[side=left]:slide-in-from-right-2', + 'data-[side=right]:slide-in-from-left-2', + 'data-[side=top]:slide-in-from-bottom-2' +); +``` + +## Accessibility Patterns + +### ARIA Attributes +```typescript +interface AccessibleComponentProps { + 'aria-label'?: string; + 'aria-labelledby'?: string; + 'aria-describedby'?: string; + 'aria-expanded'?: boolean; + 'aria-selected'?: boolean; + 'aria-disabled'?: boolean; +} + +// Example implementation +const AccessibleButton = ({ + children, + 'aria-label': ariaLabel, + disabled, + ...props +}: ButtonProps & AccessibleComponentProps) => ( + +); +``` + +### Keyboard Navigation +```typescript +const handleKeyDown = (event: React.KeyboardEvent) => { + switch (event.key) { + case 'Enter': + case ' ': + event.preventDefault(); + onClick?.(event as any); + break; + case 'Escape': + onClose?.(); + break; + case 'ArrowDown': + event.preventDefault(); + focusNext(); + break; + case 'ArrowUp': + event.preventDefault(); + focusPrevious(); + break; + } +}; +``` + +### Focus Management +```typescript +const useFocusManagement = () => { + const containerRef = React.useRef(null); + + const focusFirst = React.useCallback(() => { + const firstFocusable = containerRef.current?.querySelector( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ) as HTMLElement; + firstFocusable?.focus(); + }, []); + + const trapFocus = React.useCallback((event: KeyboardEvent) => { + if (event.key !== 'Tab') return; + + const focusableElements = containerRef.current?.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + + if (!focusableElements?.length) return; + + const firstElement = focusableElements[0] as HTMLElement; + const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement; + + if (event.shiftKey) { + if (document.activeElement === firstElement) { + event.preventDefault(); + lastElement.focus(); + } + } else { + if (document.activeElement === lastElement) { + event.preventDefault(); + firstElement.focus(); + } + } + }, []); + + return { containerRef, focusFirst, trapFocus }; +}; +``` + +## Form Field Integration Patterns +```typescript +interface FieldWrapperProps { + label?: string; + description?: string; + error?: string; + required?: boolean; + children: React.ReactNode; +} + +const FieldWrapper = ({ + label, + description, + error, + required, + children +}: FieldWrapperProps) => ( +
+ {label && ( + + )} +
+ {children} +
+ {description && ( +

+ {description} +

+ )} + {error && ( +

+ {error} +

+ )} +
+); +``` + +## Component Composition Patterns +```typescript +// Prefix/Suffix pattern for inputs +interface InputWithAddonsProps extends InputProps { + prefix?: React.ReactNode; + suffix?: React.ReactNode; +} + +const InputWithAddons = ({ prefix, suffix, className, ...props }: InputWithAddonsProps) => ( +
+ {prefix && ( +
+ {prefix} +
+ )} + + {suffix && ( +
+ {suffix} +
+ )} +
+); +``` + +## Design Token Integration +```typescript +// Use CSS custom properties for consistent theming +const themeClasses = { + colors: { + primary: 'bg-primary text-primary-foreground', + secondary: 'bg-secondary text-secondary-foreground', + destructive: 'bg-destructive text-destructive-foreground', + muted: 'bg-muted text-muted-foreground', + }, + spacing: { + xs: 'p-1', + sm: 'p-2', + md: 'p-4', + lg: 'p-6', + xl: 'p-8', + }, + radius: { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded-md', + lg: 'rounded-lg', + full: 'rounded-full', + }, +}; +``` + +## Performance Considerations +- Use `React.memo` for components that receive stable props +- Implement proper key props for list items +- Avoid inline object/function creation in render +- Use CSS classes instead of inline styles +- Optimize Tailwind CSS bundle size with purging + +## Testing Integration +- Components should work with Storybook testing patterns +- Test all variant combinations +- Verify accessibility with screen readers +- Test keyboard navigation +- Validate responsive behavior + +Remember: UI components should be accessible, performant, and consistent. Every component should follow the design system and provide a great user experience across all devices and interaction methods. + From 0a954e9bd4fa160381719072a6f744f65e57d353 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 20:38:56 +0000 Subject: [PATCH 2/5] Update cursor rules with React 19, Tailwind 4, and ShadCN UI best practices - Update react-typescript-patterns.mdc: Remove forwardRef patterns, add React 19 ref handling, use() hook for async operations, and ref cleanup functions - Update ui-component-patterns.mdc: Add Tailwind 4 features (CSS custom properties, container queries, modern CSS functions), ShadCN UI component architecture, and performance optimizations These updates ensure the cursor rules reflect the latest best practices for React 19, Tailwind CSS 4, and ShadCN UI patterns. --- .cursor/rules/react-typescript-patterns.mdc | 214 +++++-- .cursor/rules/ui-component-patterns.mdc | 639 ++++++++++---------- 2 files changed, 496 insertions(+), 357 deletions(-) diff --git a/.cursor/rules/react-typescript-patterns.mdc b/.cursor/rules/react-typescript-patterns.mdc index 7270ef86..d187167f 100644 --- a/.cursor/rules/react-typescript-patterns.mdc +++ b/.cursor/rules/react-typescript-patterns.mdc @@ -54,25 +54,124 @@ const componentVariants = cva( } ); -// 5. Component implementation -export const Component = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'div'; - - return ( - - ); - } -); +// 5. Component implementation (React 19 - no forwardRef needed!) +export const Component = ({ className, variant, size, asChild = false, ref, ...props }: ComponentProps) => { + const Comp = asChild ? Slot : 'div'; + + return ( + + ); +}; // 6. Display name Component.displayName = 'Component'; ``` +## React 19 Ref Patterns + +### Direct Ref Props (No More forwardRef!) +```typescript +// ✅ React 19 - ref as a direct prop +export interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'default' | 'destructive' | 'outline'; + size?: 'default' | 'sm' | 'lg'; + asChild?: boolean; +} + +export const Button = ({ className, variant, size, asChild = false, ref, ...props }: ButtonProps) => { + const Comp = asChild ? Slot : 'button'; + + return ( + + ); +}; + +// Usage - ref works directly! +const MyComponent = () => { + const buttonRef = React.useRef(null); + + return ; +}; +``` + +### Ref Cleanup Functions (React 19 Feature) +```typescript +// ✅ React 19 - ref cleanup functions +export const AutoFocusInput = ({ ref, ...props }: InputProps) => { + return ( + { + // Set the ref + if (typeof ref === 'function') { + ref(node); + } else if (ref) { + ref.current = node; + } + + // Auto-focus when mounted + if (node) { + node.focus(); + } + + // Return cleanup function (React 19 feature) + return () => { + if (node) { + node.blur(); + } + }; + }} + {...props} + /> + ); +}; +``` + +### useRef Patterns for React 19 +```typescript +// ✅ Modern useRef patterns +export function useImperativeActions() { + const inputRef = React.useRef(null); + + const focus = React.useCallback(() => { + inputRef.current?.focus(); + }, []); + + const clear = React.useCallback(() => { + if (inputRef.current) { + inputRef.current.value = ''; + inputRef.current.focus(); + } + }, []); + + const selectAll = React.useCallback(() => { + inputRef.current?.select(); + }, []); + + return { inputRef, focus, clear, selectAll }; +} + +// Usage +const SearchInput = () => { + const { inputRef, focus, clear } = useImperativeActions(); + + return ( +
+ + + +
+ ); +}; +``` + ## TypeScript Interface Conventions ### Props Interfaces @@ -123,17 +222,17 @@ export interface ListProps { ### Compound Components ```typescript // Main component -export const Card = ({ className, ...props }: CardProps) => ( -
+export const Card = ({ className, ref, ...props }: CardProps) => ( +
); // Sub-components -export const CardHeader = ({ className, ...props }: CardHeaderProps) => ( -
+export const CardHeader = ({ className, ref, ...props }: CardHeaderProps) => ( +
); -export const CardContent = ({ className, ...props }: CardContentProps) => ( -
+export const CardContent = ({ className, ref, ...props }: CardContentProps) => ( +
); // Attach sub-components @@ -141,7 +240,7 @@ Card.Header = CardHeader; Card.Content = CardContent; ``` -### Polymorphic Components +### Polymorphic Components (React 19 Style) ```typescript type AsChildProps = { asChild?: boolean; @@ -151,10 +250,11 @@ type AsChildProps = { export const Polymorphic = ({ asChild, as, + ref, ...props }: AsChildProps) => { const Component = asChild ? Slot : (as || 'div'); - return ; + return ; }; ``` @@ -192,6 +292,43 @@ export function useFormValidation(schema: z.ZodSchema) { } ``` +### React 19 use() Hook for Async Operations +```typescript +// ✅ React 19 - use() hook for promises +import { use } from 'react'; + +interface User { + id: string; + name: string; + email: string; +} + +async function fetchUser(id: string): Promise { + const response = await fetch(`/api/users/${id}`); + return response.json(); +} + +// Component using the new use() hook +export const UserProfile = ({ userId }: { userId: string }) => { + // React 19 - use() hook handles promises directly + const user = use(fetchUser(userId)); + + return ( +
+

{user.name}

+

{user.email}

+
+ ); +}; + +// Wrap with Suspense for loading states +export const UserProfileWithSuspense = ({ userId }: { userId: string }) => ( + Loading user...
}> + + +); +``` + ### Context Patterns ```typescript // Create strongly typed context @@ -256,35 +393,7 @@ export const ExpensiveComponent = React.memo(({ data }: Props) => { }); ``` -### Ref Patterns -```typescript -// Forward refs for component library components -export const Input = React.forwardRef( - ({ className, type, ...props }, ref) => { - return ( - - ); - } -); - -// Use refs for imperative operations -export function useImperativeHandle() { - const inputRef = React.useRef(null); - - const focus = React.useCallback(() => { - inputRef.current?.focus(); - }, []); - - return { inputRef, focus }; -} -``` - -## Error Boundary Patterns +## Error Boundary Patterns (React 19 Compatible) ```typescript interface ErrorBoundaryState { hasError: boolean; @@ -382,5 +491,4 @@ export interface ButtonProps { } ``` -Remember: Write components that are predictable, composable, and maintainable. Every component should have a clear purpose and follow consistent patterns that make the codebase easy to understand and extend. - +Remember: Write components that are predictable, composable, and maintainable. Every component should have a clear purpose and follow consistent patterns that make the codebase easy to understand and extend. React 19 simplifies ref handling significantly - embrace the new patterns! diff --git a/.cursor/rules/ui-component-patterns.mdc b/.cursor/rules/ui-component-patterns.mdc index 7eeac6cb..0400f4cd 100644 --- a/.cursor/rules/ui-component-patterns.mdc +++ b/.cursor/rules/ui-component-patterns.mdc @@ -1,43 +1,92 @@ --- type: Always -description: UI component patterns for Radix UI, Tailwind CSS, and class-variance-authority integration +description: UI component patterns for Radix UI, Tailwind CSS 4, and ShadCN UI integration --- -You are an expert in Radix UI, Tailwind CSS, class-variance-authority (CVA), and accessible component design. +You are an expert in Radix UI, Tailwind CSS 4, ShadCN UI, class-variance-authority (CVA), and accessible component design. # UI Component Patterns ## Core Principles - Build accessible-first components using Radix UI primitives -- Use Tailwind CSS with a mobile-first responsive approach +- Use Tailwind CSS 4 with modern CSS features and performance optimizations +- Follow ShadCN UI patterns for consistent component architecture - Implement consistent variant patterns with class-variance-authority - Follow the Slot pattern for polymorphic components - Maintain design system consistency across all components -## Class Variance Authority (CVA) Patterns +## Tailwind CSS 4 Features & Patterns -### Basic CVA Setup +### Modern CSS Custom Properties ```typescript +// ✅ Tailwind 4 - CSS custom properties for theming +const themeClasses = { + // Use CSS custom properties directly + colors: { + primary: 'bg-[--color-primary] text-[--color-primary-foreground]', + secondary: 'bg-[--color-secondary] text-[--color-secondary-foreground]', + destructive: 'bg-[--color-destructive] text-[--color-destructive-foreground]', + muted: 'bg-[--color-muted] text-[--color-muted-foreground]', + }, + // Modern container queries + container: 'container @container', + // CSS Grid improvements + grid: 'grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))]', +}; +``` + +### Tailwind 4 Performance Optimizations +```typescript +// ✅ Use Tailwind 4's improved arbitrary value syntax +const modernClasses = cn( + // Improved arbitrary values + 'w-[clamp(200px,50vw,400px)]', + 'bg-[oklch(0.7_0.15_200)]', + // Container queries + '@md:grid-cols-2 @lg:grid-cols-3', + // Modern CSS functions + 'h-[max(200px,20vh)]', + 'gap-[max(1rem,2vw)]' +); + +// ✅ Tailwind 4 - Better performance with CSS layers +const layeredClasses = cn( + // Base layer + 'layer-base:bg-background', + // Component layer + 'layer-components:rounded-md', + // Utilities layer + 'layer-utilities:hover:bg-accent' +); +``` + +### ShadCN UI Component Architecture +```typescript +// ✅ ShadCN UI pattern - consistent component structure +import * as React from 'react'; +import { Slot } from '@radix-ui/react-slot'; import { type VariantProps, cva } from 'class-variance-authority'; +import { cn } from '@/lib/utils'; +// ShadCN UI variant pattern const buttonVariants = cva( - // Base classes - always applied - 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + // Base classes optimized for Tailwind 4 + 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', { variants: { variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', - destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', - outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', - secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', ghost: 'hover:bg-accent hover:text-accent-foreground', link: 'text-primary underline-offset-4 hover:underline', }, size: { - default: 'h-10 px-4 py-2', - sm: 'h-9 rounded-md px-3', - lg: 'h-11 rounded-md px-8', - icon: 'h-10 w-10', + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', }, }, defaultVariants: { @@ -47,74 +96,101 @@ const buttonVariants = cva( } ); -// Extract variant props type -export interface ButtonProps +// React 19 + ShadCN UI pattern (no forwardRef needed) +export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; } + +export const Button = ({ className, variant, size, asChild = false, ...props }: ButtonProps) => { + const Comp = asChild ? Slot : 'button'; + return ( + + ); +}; +Button.displayName = 'Button'; ``` -### Complex Variant Patterns +## Class Variance Authority (CVA) Patterns + +### Advanced CVA with Tailwind 4 ```typescript -const inputVariants = cva( - 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50', +import { type VariantProps, cva } from 'class-variance-authority'; + +// ✅ Complex variant patterns with Tailwind 4 features +const cardVariants = cva( + // Base classes with modern CSS + 'rounded-lg border bg-card text-card-foreground shadow-sm transition-all duration-200', { variants: { + variant: { + default: 'border-border', + elevated: 'border-border shadow-lg', + outlined: 'border-2 border-primary', + ghost: 'border-transparent shadow-none', + }, size: { - default: 'h-10', - sm: 'h-9', - lg: 'h-11', + sm: 'p-4', + default: 'p-6', + lg: 'p-8', }, - variant: { - default: 'border-input', - error: 'border-destructive focus-visible:ring-destructive', - success: 'border-green-500 focus-visible:ring-green-500', + interactive: { + true: 'cursor-pointer hover:shadow-md hover:scale-[1.02] active:scale-[0.98]', + false: '', }, }, compoundVariants: [ { - variant: 'error', - size: 'sm', - class: 'text-xs', + variant: 'elevated', + interactive: true, + class: 'hover:shadow-xl', + }, + { + variant: 'ghost', + interactive: true, + class: 'hover:bg-accent/50', }, ], defaultVariants: { variant: 'default', size: 'default', + interactive: false, }, } ); + +// Usage with proper TypeScript inference +export interface CardProps + extends React.HTMLAttributes, + VariantProps {} + +export const Card = ({ className, variant, size, interactive, ...props }: CardProps) => ( +
+); ``` ## Radix UI Integration Patterns -### Basic Radix Component +### ShadCN UI + Radix Pattern ```typescript import * as React from 'react'; -import * as RadixPrimitive from '@radix-ui/react-primitive'; -import { cn } from './utils'; - -const Component = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); -Component.displayName = RadixPrimitive.Root.displayName; -``` - -### Compound Radix Components -```typescript import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { Check, ChevronRight, Circle } from 'lucide-react'; +import { cn } from '@/lib/utils'; +// ShadCN UI pattern for Radix components const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; +const DropdownMenuGroup = DropdownMenuPrimitive.Group; +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; +const DropdownMenuSub = DropdownMenuPrimitive.Sub; +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; +// Content with Tailwind 4 optimizations const DropdownMenuContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef @@ -124,7 +200,14 @@ const DropdownMenuContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + // Tailwind 4 optimized classes + 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md', + // Modern animations with better performance + 'data-[state=open]:animate-in data-[state=closed]:animate-out', + 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', + 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', + 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2', + 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className )} {...props} @@ -133,108 +216,125 @@ const DropdownMenuContent = React.forwardRef< )); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; -// Export compound component +// Export following ShadCN UI pattern export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, + DropdownMenuGroup, + // ... other exports }; ``` -## Slot Pattern for Polymorphic Components -```typescript -import { Slot } from '@radix-ui/react-slot'; +## Tailwind 4 Responsive & Container Patterns -interface ButtonProps extends React.ButtonHTMLAttributes { - asChild?: boolean; - variant?: 'default' | 'destructive' | 'outline'; -} +### Modern Responsive Design +```typescript +// ✅ Tailwind 4 - Container queries and modern responsive +const responsiveClasses = cn( + // Traditional breakpoints + 'text-sm md:text-base lg:text-lg', + // Container queries (Tailwind 4) + '@sm:grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3', + // Fluid typography + 'text-[clamp(0.875rem,2.5vw,1.125rem)]', + // Modern CSS functions + 'gap-[max(1rem,3vw)]', + 'p-[clamp(1rem,5vw,2rem)]' +); -const Button = React.forwardRef( - ({ className, variant, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'button'; - return ( - - ); - } +// ✅ Container component with Tailwind 4 +export const Container = ({ className, ...props }: React.HTMLAttributes) => ( +
); +``` -// Usage examples: -// -// +### Advanced Animation Patterns +```typescript +// ✅ Tailwind 4 - Enhanced animations and transitions +const animationClasses = cn( + // Base transition with better performance + 'transition-all duration-200 ease-out', + // Modern transform patterns + 'hover:scale-[1.02] active:scale-[0.98]', + // CSS custom properties for dynamic animations + 'hover:translate-y-[--hover-offset:-2px]', + // View transitions (Tailwind 4) + 'view-transition-name-[card-animation]', + // Scroll-driven animations + 'animate-[fade-in_linear_both] animation-timeline-[view()]', + // Modern focus styles + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2' +); ``` -## Tailwind CSS Organization Patterns +## ShadCN UI Form Patterns -### Responsive Design +### Modern Form Component Architecture ```typescript -// Mobile-first responsive classes -const responsiveClasses = cn( - 'text-sm', // Mobile default - 'md:text-base', // Tablet and up - 'lg:text-lg', // Desktop and up - 'xl:text-xl' // Large desktop +// ✅ ShadCN UI form pattern with React 19 +import * as React from 'react'; +import * as LabelPrimitive from '@radix-ui/react-label'; +import { Slot } from '@radix-ui/react-slot'; +import { cn } from '@/lib/utils'; + +// Form components following ShadCN UI patterns +const Form = ({ ...props }) =>
; + +const FormField = ({ ...props }) =>
; + +const FormItem = ({ className, ...props }: React.HTMLAttributes) => ( +
); -// Container patterns -const containerClasses = cn( - 'w-full', - 'max-w-sm', // Mobile - 'sm:max-w-md', // Small tablet - 'md:max-w-lg', // Tablet - 'lg:max-w-xl', // Desktop - 'xl:max-w-2xl' // Large desktop +const FormLabel = ({ className, ...props }: React.ComponentPropsWithoutRef) => ( + ); -``` -### Component State Classes -```typescript -const stateClasses = cn( - // Base state - 'bg-background text-foreground', - // Hover state - 'hover:bg-accent hover:text-accent-foreground', - // Focus state - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', - // Active state - 'active:scale-95', - // Disabled state - 'disabled:pointer-events-none disabled:opacity-50', - // Data states (for Radix components) - 'data-[state=open]:bg-accent', - 'data-[state=closed]:bg-background' +const FormControl = ({ ...props }: React.ComponentPropsWithoutRef) => ( + ); -``` -### Animation Classes -```typescript -const animationClasses = cn( - // Transitions - 'transition-all duration-200 ease-in-out', - // Enter animations - 'data-[state=open]:animate-in', - 'data-[state=open]:fade-in-0', - 'data-[state=open]:zoom-in-95', - // Exit animations - 'data-[state=closed]:animate-out', - 'data-[state=closed]:fade-out-0', - 'data-[state=closed]:zoom-out-95', - // Slide animations - 'data-[side=bottom]:slide-in-from-top-2', - 'data-[side=left]:slide-in-from-right-2', - 'data-[side=right]:slide-in-from-left-2', - 'data-[side=top]:slide-in-from-bottom-2' +const FormDescription = ({ className, ...props }: React.HTMLAttributes) => ( +

); + +const FormMessage = ({ className, children, ...props }: React.HTMLAttributes) => { + if (!children) return null; + + return ( +

+ {children} +

+ ); +}; + +export { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage }; ``` -## Accessibility Patterns +## Accessibility Patterns (ShadCN UI Style) -### ARIA Attributes +### ARIA and Keyboard Navigation ```typescript +// ✅ ShadCN UI accessibility patterns interface AccessibleComponentProps { 'aria-label'?: string; 'aria-labelledby'?: string; @@ -244,205 +344,136 @@ interface AccessibleComponentProps { 'aria-disabled'?: boolean; } -// Example implementation -const AccessibleButton = ({ - children, - 'aria-label': ariaLabel, - disabled, - ...props -}: ButtonProps & AccessibleComponentProps) => ( - -); -``` - -### Keyboard Navigation -```typescript -const handleKeyDown = (event: React.KeyboardEvent) => { - switch (event.key) { - case 'Enter': - case ' ': - event.preventDefault(); - onClick?.(event as any); - break; - case 'Escape': - onClose?.(); - break; - case 'ArrowDown': - event.preventDefault(); - focusNext(); - break; - case 'ArrowUp': - event.preventDefault(); - focusPrevious(); - break; - } -}; -``` - -### Focus Management -```typescript -const useFocusManagement = () => { - const containerRef = React.useRef(null); - - const focusFirst = React.useCallback(() => { - const firstFocusable = containerRef.current?.querySelector( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ) as HTMLElement; - firstFocusable?.focus(); - }, []); - - const trapFocus = React.useCallback((event: KeyboardEvent) => { - if (event.key !== 'Tab') return; - - const focusableElements = containerRef.current?.querySelectorAll( - 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' - ); - - if (!focusableElements?.length) return; - - const firstElement = focusableElements[0] as HTMLElement; - const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement; - - if (event.shiftKey) { - if (document.activeElement === firstElement) { +// Keyboard navigation hook +export function useKeyboardNavigation() { + const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { + switch (event.key) { + case 'Enter': + case ' ': event.preventDefault(); - lastElement.focus(); - } - } else { - if (document.activeElement === lastElement) { + // Handle activation + break; + case 'Escape': + // Handle escape + break; + case 'ArrowDown': event.preventDefault(); - firstElement.focus(); - } + // Focus next item + break; + case 'ArrowUp': + event.preventDefault(); + // Focus previous item + break; + case 'Home': + event.preventDefault(); + // Focus first item + break; + case 'End': + event.preventDefault(); + // Focus last item + break; } }, []); - return { containerRef, focusFirst, trapFocus }; -}; -``` - -## Form Field Integration Patterns -```typescript -interface FieldWrapperProps { - label?: string; - description?: string; - error?: string; - required?: boolean; - children: React.ReactNode; + return { handleKeyDown }; } - -const FieldWrapper = ({ - label, - description, - error, - required, - children -}: FieldWrapperProps) => ( -
- {label && ( - - )} -
- {children} -
- {description && ( -

- {description} -

- )} - {error && ( -

- {error} -

- )} -
-); ``` -## Component Composition Patterns +## Performance Optimization Patterns + +### Tailwind 4 Performance Best Practices ```typescript -// Prefix/Suffix pattern for inputs -interface InputWithAddonsProps extends InputProps { - prefix?: React.ReactNode; - suffix?: React.ReactNode; -} +// ✅ Optimized class patterns for Tailwind 4 +const optimizedClasses = { + // Use CSS custom properties for dynamic values + dynamic: 'bg-[--dynamic-color] text-[--dynamic-text]', + + // Prefer CSS Grid over Flexbox for complex layouts + layout: 'grid grid-cols-[auto_1fr_auto] gap-4', + + // Use container queries for responsive components + responsive: '@container @sm:grid-cols-2 @md:grid-cols-3', + + // Optimize animations for performance + animation: 'transform-gpu will-change-transform', + + // Use modern CSS features + modern: 'backdrop-blur-sm supports-[backdrop-filter]:bg-background/60', +}; -const InputWithAddons = ({ prefix, suffix, className, ...props }: InputWithAddonsProps) => ( -
- {prefix && ( -
- {prefix} -
- )} - - {suffix && ( -
- {suffix} -
- )} -
-); +// ✅ Memoized component with optimized classes +export const OptimizedCard = React.memo(({ children, variant }: CardProps) => { + const classes = React.useMemo(() => + cn(cardVariants({ variant })), + [variant] + ); + + return
{children}
; +}); ``` -## Design Token Integration +## Design Token Integration (Tailwind 4) + +### CSS Custom Properties Pattern ```typescript -// Use CSS custom properties for consistent theming -const themeClasses = { +// ✅ Tailwind 4 - CSS custom properties for design tokens +const designTokens = { + // Color tokens colors: { - primary: 'bg-primary text-primary-foreground', - secondary: 'bg-secondary text-secondary-foreground', - destructive: 'bg-destructive text-destructive-foreground', - muted: 'bg-muted text-muted-foreground', + primary: 'hsl(var(--primary))', + 'primary-foreground': 'hsl(var(--primary-foreground))', + secondary: 'hsl(var(--secondary))', + 'secondary-foreground': 'hsl(var(--secondary-foreground))', }, + + // Spacing tokens with fluid values spacing: { - xs: 'p-1', - sm: 'p-2', - md: 'p-4', - lg: 'p-6', - xl: 'p-8', + xs: 'clamp(0.25rem, 1vw, 0.5rem)', + sm: 'clamp(0.5rem, 2vw, 1rem)', + md: 'clamp(1rem, 3vw, 1.5rem)', + lg: 'clamp(1.5rem, 4vw, 2rem)', + xl: 'clamp(2rem, 5vw, 3rem)', }, - radius: { - none: 'rounded-none', - sm: 'rounded-sm', - md: 'rounded-md', - lg: 'rounded-lg', - full: 'rounded-full', + + // Typography tokens + typography: { + 'heading-1': 'clamp(1.75rem, 4vw, 2.5rem)', + 'heading-2': 'clamp(1.5rem, 3.5vw, 2rem)', + 'body': 'clamp(0.875rem, 2.5vw, 1rem)', }, }; ``` -## Performance Considerations -- Use `React.memo` for components that receive stable props -- Implement proper key props for list items -- Avoid inline object/function creation in render -- Use CSS classes instead of inline styles -- Optimize Tailwind CSS bundle size with purging - ## Testing Integration -- Components should work with Storybook testing patterns -- Test all variant combinations -- Verify accessibility with screen readers -- Test keyboard navigation -- Validate responsive behavior -Remember: UI components should be accessible, performant, and consistent. Every component should follow the design system and provide a great user experience across all devices and interaction methods. +### Component Testing Patterns +```typescript +// ✅ Testing patterns for ShadCN UI components +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Button } from './button'; + +describe('Button Component', () => { + it('should render with correct variant classes', () => { + render(); + const button = screen.getByRole('button'); + expect(button).toHaveClass('bg-destructive', 'text-destructive-foreground'); + }); + + it('should handle keyboard navigation', async () => { + const user = userEvent.setup(); + const handleClick = jest.fn(); + + render(); + const button = screen.getByRole('button'); + + await user.tab(); + expect(button).toHaveFocus(); + + await user.keyboard('{Enter}'); + expect(handleClick).toHaveBeenCalled(); + }); +}); +``` +Remember: UI components should be accessible, performant, and consistent. Follow ShadCN UI patterns for component architecture, leverage Tailwind 4's modern CSS features, and ensure every component provides a great user experience across all devices and interaction methods. React 19's simplified ref handling makes component composition even more elegant! From 644f914f5c8a72e18d11f22236138edb6d39e7ea Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:21:34 +0000 Subject: [PATCH 3/5] Remove unnecessary context and focus on project-specific components - Remove framer-motion references (not used in project) - Focus UI patterns on actual Radix UI components in use - Base examples on existing components (button.tsx, form.tsx, table.tsx, etc.) - Remove generic ShadCN references, focus on lambda-curry/forms architecture - Streamline patterns to match actual project dependencies and structure The cursor rules now provide focused guidance only for components and patterns that exist in the lambda-curry/forms repository. --- .cursor/rules/ui-component-patterns.mdc | 343 ++++++++++-------------- 1 file changed, 139 insertions(+), 204 deletions(-) diff --git a/.cursor/rules/ui-component-patterns.mdc b/.cursor/rules/ui-component-patterns.mdc index 0400f4cd..dc524ea8 100644 --- a/.cursor/rules/ui-component-patterns.mdc +++ b/.cursor/rules/ui-component-patterns.mdc @@ -1,19 +1,47 @@ --- type: Always -description: UI component patterns for Radix UI, Tailwind CSS 4, and ShadCN UI integration +description: UI component patterns for Radix UI, Tailwind CSS 4, and component architecture specific to lambda-curry/forms --- -You are an expert in Radix UI, Tailwind CSS 4, ShadCN UI, class-variance-authority (CVA), and accessible component design. +You are an expert in Radix UI, Tailwind CSS 4, class-variance-authority (CVA), and accessible component design for the lambda-curry/forms component library. # UI Component Patterns ## Core Principles - Build accessible-first components using Radix UI primitives - Use Tailwind CSS 4 with modern CSS features and performance optimizations -- Follow ShadCN UI patterns for consistent component architecture -- Implement consistent variant patterns with class-variance-authority +- Follow consistent component architecture patterns +- Implement variant patterns with class-variance-authority - Follow the Slot pattern for polymorphic components -- Maintain design system consistency across all components +- Focus only on components that exist in the lambda-curry/forms library + +## Project-Specific Component Stack +Based on your actual dependencies and components: + +### Radix UI Components Used +- `@radix-ui/react-alert-dialog` +- `@radix-ui/react-avatar` +- `@radix-ui/react-checkbox` +- `@radix-ui/react-dialog` +- `@radix-ui/react-dropdown-menu` +- `@radix-ui/react-label` +- `@radix-ui/react-popover` +- `@radix-ui/react-radio-group` +- `@radix-ui/react-scroll-area` +- `@radix-ui/react-separator` +- `@radix-ui/react-slot` +- `@radix-ui/react-switch` +- `@radix-ui/react-tooltip` + +### Existing UI Components +- Badge, Button, Checkbox Field +- Command, Data Table +- Date Picker, Dropdown Menu +- Form components, Label +- OTP Input, Popover +- Radio Group, Select +- Separator, Switch +- Table, Text Field, Textarea ## Tailwind CSS 4 Features & Patterns @@ -23,10 +51,10 @@ You are an expert in Radix UI, Tailwind CSS 4, ShadCN UI, class-variance-authori const themeClasses = { // Use CSS custom properties directly colors: { - primary: 'bg-[--color-primary] text-[--color-primary-foreground]', - secondary: 'bg-[--color-secondary] text-[--color-secondary-foreground]', - destructive: 'bg-[--color-destructive] text-[--color-destructive-foreground]', - muted: 'bg-[--color-muted] text-[--color-muted-foreground]', + primary: 'bg-primary text-primary-foreground', + secondary: 'bg-secondary text-secondary-foreground', + destructive: 'bg-destructive text-destructive-foreground', + muted: 'bg-muted text-muted-foreground', }, // Modern container queries container: 'container @container', @@ -41,34 +69,25 @@ const themeClasses = { const modernClasses = cn( // Improved arbitrary values 'w-[clamp(200px,50vw,400px)]', - 'bg-[oklch(0.7_0.15_200)]', // Container queries '@md:grid-cols-2 @lg:grid-cols-3', // Modern CSS functions 'h-[max(200px,20vh)]', 'gap-[max(1rem,2vw)]' ); - -// ✅ Tailwind 4 - Better performance with CSS layers -const layeredClasses = cn( - // Base layer - 'layer-base:bg-background', - // Component layer - 'layer-components:rounded-md', - // Utilities layer - 'layer-utilities:hover:bg-accent' -); ``` -### ShadCN UI Component Architecture +## Component Architecture Pattern + +### Standard Component Structure ```typescript -// ✅ ShadCN UI pattern - consistent component structure +// ✅ Consistent pattern used in lambda-curry/forms import * as React from 'react'; import { Slot } from '@radix-ui/react-slot'; import { type VariantProps, cva } from 'class-variance-authority'; import { cn } from '@/lib/utils'; -// ShadCN UI variant pattern +// Variant pattern following your existing components const buttonVariants = cva( // Base classes optimized for Tailwind 4 'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', @@ -96,7 +115,7 @@ const buttonVariants = cva( } ); -// React 19 + ShadCN UI pattern (no forwardRef needed) +// React 19 pattern (no forwardRef needed) export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { @@ -115,74 +134,16 @@ export const Button = ({ className, variant, size, asChild = false, ...props }: Button.displayName = 'Button'; ``` -## Class Variance Authority (CVA) Patterns - -### Advanced CVA with Tailwind 4 -```typescript -import { type VariantProps, cva } from 'class-variance-authority'; - -// ✅ Complex variant patterns with Tailwind 4 features -const cardVariants = cva( - // Base classes with modern CSS - 'rounded-lg border bg-card text-card-foreground shadow-sm transition-all duration-200', - { - variants: { - variant: { - default: 'border-border', - elevated: 'border-border shadow-lg', - outlined: 'border-2 border-primary', - ghost: 'border-transparent shadow-none', - }, - size: { - sm: 'p-4', - default: 'p-6', - lg: 'p-8', - }, - interactive: { - true: 'cursor-pointer hover:shadow-md hover:scale-[1.02] active:scale-[0.98]', - false: '', - }, - }, - compoundVariants: [ - { - variant: 'elevated', - interactive: true, - class: 'hover:shadow-xl', - }, - { - variant: 'ghost', - interactive: true, - class: 'hover:bg-accent/50', - }, - ], - defaultVariants: { - variant: 'default', - size: 'default', - interactive: false, - }, - } -); - -// Usage with proper TypeScript inference -export interface CardProps - extends React.HTMLAttributes, - VariantProps {} - -export const Card = ({ className, variant, size, interactive, ...props }: CardProps) => ( -
-); -``` - ## Radix UI Integration Patterns -### ShadCN UI + Radix Pattern +### Dropdown Menu Pattern (Based on Your Components) ```typescript import * as React from 'react'; import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; import { Check, ChevronRight, Circle } from 'lucide-react'; import { cn } from '@/lib/utils'; -// ShadCN UI pattern for Radix components +// Following your existing dropdown-menu.tsx pattern const DropdownMenu = DropdownMenuPrimitive.Root; const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; const DropdownMenuGroup = DropdownMenuPrimitive.Group; @@ -216,7 +177,6 @@ const DropdownMenuContent = React.forwardRef< )); DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; -// Export following ShadCN UI pattern export { DropdownMenu, DropdownMenuTrigger, @@ -226,68 +186,17 @@ export { }; ``` -## Tailwind 4 Responsive & Container Patterns +## Form Component Patterns (Based on Your form.tsx) -### Modern Responsive Design +### Form Components Following Your Architecture ```typescript -// ✅ Tailwind 4 - Container queries and modern responsive -const responsiveClasses = cn( - // Traditional breakpoints - 'text-sm md:text-base lg:text-lg', - // Container queries (Tailwind 4) - '@sm:grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3', - // Fluid typography - 'text-[clamp(0.875rem,2.5vw,1.125rem)]', - // Modern CSS functions - 'gap-[max(1rem,3vw)]', - 'p-[clamp(1rem,5vw,2rem)]' -); - -// ✅ Container component with Tailwind 4 -export const Container = ({ className, ...props }: React.HTMLAttributes) => ( -
-); -``` - -### Advanced Animation Patterns -```typescript -// ✅ Tailwind 4 - Enhanced animations and transitions -const animationClasses = cn( - // Base transition with better performance - 'transition-all duration-200 ease-out', - // Modern transform patterns - 'hover:scale-[1.02] active:scale-[0.98]', - // CSS custom properties for dynamic animations - 'hover:translate-y-[--hover-offset:-2px]', - // View transitions (Tailwind 4) - 'view-transition-name-[card-animation]', - // Scroll-driven animations - 'animate-[fade-in_linear_both] animation-timeline-[view()]', - // Modern focus styles - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2' -); -``` - -## ShadCN UI Form Patterns - -### Modern Form Component Architecture -```typescript -// ✅ ShadCN UI form pattern with React 19 +// ✅ Based on your existing form.tsx patterns import * as React from 'react'; import * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; import { cn } from '@/lib/utils'; -// Form components following ShadCN UI patterns +// Form components following your existing patterns const Form = ({ ...props }) => ; const FormField = ({ ...props }) =>
; @@ -330,21 +239,90 @@ const FormMessage = ({ className, children, ...props }: React.HTMLAttributes) => ( +
+ + +); + +const TableHeader = ({ className, ...props }: React.HTMLAttributes) => ( + +); + +const TableBody = ({ className, ...props }: React.HTMLAttributes) => ( + +); + +const TableRow = ({ className, ...props }: React.HTMLAttributes) => ( + +); + +export { Table, TableHeader, TableBody, TableRow }; +``` + +## Responsive Design Patterns + +### Container Queries with Tailwind 4 +```typescript +// ✅ Modern responsive design for your components +const responsiveClasses = cn( + // Traditional breakpoints + 'text-sm md:text-base lg:text-lg', + // Container queries (Tailwind 4) + '@sm:grid-cols-1 @md:grid-cols-2 @lg:grid-cols-3', + // Fluid typography + 'text-[clamp(0.875rem,2.5vw,1.125rem)]', + // Modern CSS functions + 'gap-[max(1rem,3vw)]', + 'p-[clamp(1rem,5vw,2rem)]' +); +``` + +## Animation Patterns (CSS-Only) + +### Tailwind CSS Animations +```typescript +// ✅ CSS-only animations (no external animation libraries) +const animationClasses = cn( + // Base transition with better performance + 'transition-all duration-200 ease-out', + // Modern transform patterns + 'hover:scale-[1.02] active:scale-[0.98]', + // Modern focus styles + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2', + // Data state animations for Radix components + 'data-[state=open]:animate-in data-[state=closed]:animate-out', + 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0' +); +``` + +## Accessibility Patterns + +### Keyboard Navigation (Based on Your Components) +```typescript +// ✅ Accessibility patterns for your existing components export function useKeyboardNavigation() { const handleKeyDown = React.useCallback((event: React.KeyboardEvent) => { switch (event.key) { @@ -364,14 +342,6 @@ export function useKeyboardNavigation() { event.preventDefault(); // Focus previous item break; - case 'Home': - event.preventDefault(); - // Focus first item - break; - case 'End': - event.preventDefault(); - // Focus last item - break; } }, []); @@ -379,16 +349,16 @@ export function useKeyboardNavigation() { } ``` -## Performance Optimization Patterns +## Performance Optimization -### Tailwind 4 Performance Best Practices +### Optimized Class Patterns ```typescript -// ✅ Optimized class patterns for Tailwind 4 +// ✅ Optimized for your component library const optimizedClasses = { // Use CSS custom properties for dynamic values dynamic: 'bg-[--dynamic-color] text-[--dynamic-text]', - // Prefer CSS Grid over Flexbox for complex layouts + // Prefer CSS Grid for complex layouts (like your data-table) layout: 'grid grid-cols-[auto_1fr_auto] gap-4', // Use container queries for responsive components @@ -396,15 +366,12 @@ const optimizedClasses = { // Optimize animations for performance animation: 'transform-gpu will-change-transform', - - // Use modern CSS features - modern: 'backdrop-blur-sm supports-[backdrop-filter]:bg-background/60', }; -// ✅ Memoized component with optimized classes -export const OptimizedCard = React.memo(({ children, variant }: CardProps) => { +// ✅ Memoized component pattern +export const OptimizedComponent = React.memo(({ children, variant }: ComponentProps) => { const classes = React.useMemo(() => - cn(cardVariants({ variant })), + cn(componentVariants({ variant })), [variant] ); @@ -412,43 +379,11 @@ export const OptimizedCard = React.memo(({ children, variant }: CardProps) => { }); ``` -## Design Token Integration (Tailwind 4) - -### CSS Custom Properties Pattern -```typescript -// ✅ Tailwind 4 - CSS custom properties for design tokens -const designTokens = { - // Color tokens - colors: { - primary: 'hsl(var(--primary))', - 'primary-foreground': 'hsl(var(--primary-foreground))', - secondary: 'hsl(var(--secondary))', - 'secondary-foreground': 'hsl(var(--secondary-foreground))', - }, - - // Spacing tokens with fluid values - spacing: { - xs: 'clamp(0.25rem, 1vw, 0.5rem)', - sm: 'clamp(0.5rem, 2vw, 1rem)', - md: 'clamp(1rem, 3vw, 1.5rem)', - lg: 'clamp(1.5rem, 4vw, 2rem)', - xl: 'clamp(2rem, 5vw, 3rem)', - }, - - // Typography tokens - typography: { - 'heading-1': 'clamp(1.75rem, 4vw, 2.5rem)', - 'heading-2': 'clamp(1.5rem, 3.5vw, 2rem)', - 'body': 'clamp(0.875rem, 2.5vw, 1rem)', - }, -}; -``` - ## Testing Integration ### Component Testing Patterns ```typescript -// ✅ Testing patterns for ShadCN UI components +// ✅ Testing patterns for your components import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Button } from './button'; @@ -476,4 +411,4 @@ describe('Button Component', () => { }); ``` -Remember: UI components should be accessible, performant, and consistent. Follow ShadCN UI patterns for component architecture, leverage Tailwind 4's modern CSS features, and ensure every component provides a great user experience across all devices and interaction methods. React 19's simplified ref handling makes component composition even more elegant! +Remember: Focus only on the components and patterns that exist in the lambda-curry/forms library. Use Radix UI primitives with Tailwind CSS 4, follow the established component architecture, and ensure accessibility and performance in all implementations. From 16a12ac45be38a04ce12eaea5e48384e9fd26c66 Mon Sep 17 00:00:00 2001 From: "codegen-sh[bot]" <131295404+codegen-sh[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 02:32:18 +0000 Subject: [PATCH 4/5] Remove duplicate sections from UI component patterns - Remove Form Component Patterns section (already covered in form-component-patterns.mdc) - Remove Data Table Patterns section (not needed in cursor rules) - Remove Testing Integration section (already covered in storybook-testing.mdc) The UI component patterns file now focuses solely on UI-specific patterns: - Radix UI integration patterns - Tailwind CSS 4 features - Component architecture - Responsive design - Animations - Accessibility - Performance optimization This eliminates duplication and keeps each cursor rule file focused on its specific domain. --- .cursor/rules/ui-component-patterns.mdc | 131 +----------------------- 1 file changed, 2 insertions(+), 129 deletions(-) diff --git a/.cursor/rules/ui-component-patterns.mdc b/.cursor/rules/ui-component-patterns.mdc index dc524ea8..06fb49e2 100644 --- a/.cursor/rules/ui-component-patterns.mdc +++ b/.cursor/rules/ui-component-patterns.mdc @@ -186,102 +186,6 @@ export { }; ``` -## Form Component Patterns (Based on Your form.tsx) - -### Form Components Following Your Architecture -```typescript -// ✅ Based on your existing form.tsx patterns -import * as React from 'react'; -import * as LabelPrimitive from '@radix-ui/react-label'; -import { Slot } from '@radix-ui/react-slot'; -import { cn } from '@/lib/utils'; - -// Form components following your existing patterns -const Form = ({ ...props }) => ; - -const FormField = ({ ...props }) =>
; - -const FormItem = ({ className, ...props }: React.HTMLAttributes) => ( -
-); - -const FormLabel = ({ className, ...props }: React.ComponentPropsWithoutRef) => ( - -); - -const FormControl = ({ ...props }: React.ComponentPropsWithoutRef) => ( - -); - -const FormDescription = ({ className, ...props }: React.HTMLAttributes) => ( -

-); - -const FormMessage = ({ className, children, ...props }: React.HTMLAttributes) => { - if (!children) return null; - - return ( -

- {children} -

- ); -}; - -export { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage }; -``` - -## Data Table Patterns (Based on Your data-table Components) - -### Data Table Architecture -```typescript -// ✅ Following your existing data-table patterns -import * as React from 'react'; -import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; -import { cn } from '@/lib/utils'; - -// Table components following your existing table.tsx -const Table = ({ className, ...props }: React.HTMLAttributes) => ( -
-
- -); - -const TableHeader = ({ className, ...props }: React.HTMLAttributes) => ( - -); - -const TableBody = ({ className, ...props }: React.HTMLAttributes) => ( - -); - -const TableRow = ({ className, ...props }: React.HTMLAttributes) => ( - -); - -export { Table, TableHeader, TableBody, TableRow }; -``` - ## Responsive Design Patterns ### Container Queries with Tailwind 4 @@ -358,7 +262,7 @@ const optimizedClasses = { // Use CSS custom properties for dynamic values dynamic: 'bg-[--dynamic-color] text-[--dynamic-text]', - // Prefer CSS Grid for complex layouts (like your data-table) + // Prefer CSS Grid for complex layouts layout: 'grid grid-cols-[auto_1fr_auto] gap-4', // Use container queries for responsive components @@ -379,36 +283,5 @@ export const OptimizedComponent = React.memo(({ children, variant }: ComponentPr }); ``` -## Testing Integration - -### Component Testing Patterns -```typescript -// ✅ Testing patterns for your components -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { Button } from './button'; - -describe('Button Component', () => { - it('should render with correct variant classes', () => { - render(); - const button = screen.getByRole('button'); - expect(button).toHaveClass('bg-destructive', 'text-destructive-foreground'); - }); - - it('should handle keyboard navigation', async () => { - const user = userEvent.setup(); - const handleClick = jest.fn(); - - render(); - const button = screen.getByRole('button'); - - await user.tab(); - expect(button).toHaveFocus(); - - await user.keyboard('{Enter}'); - expect(handleClick).toHaveBeenCalled(); - }); -}); -``` - Remember: Focus only on the components and patterns that exist in the lambda-curry/forms library. Use Radix UI primitives with Tailwind CSS 4, follow the established component architecture, and ensure accessibility and performance in all implementations. + From 91c9ed5cf7a09b70d75e2b86ef06f6bfb2bee78f Mon Sep 17 00:00:00 2001 From: Jake Ruesink Date: Sun, 25 May 2025 21:38:01 -0500 Subject: [PATCH 5/5] Update react-typescript-patterns.mdc --- .cursor/rules/react-typescript-patterns.mdc | 99 --------------------- 1 file changed, 99 deletions(-) diff --git a/.cursor/rules/react-typescript-patterns.mdc b/.cursor/rules/react-typescript-patterns.mdc index d187167f..bf89a4d4 100644 --- a/.cursor/rules/react-typescript-patterns.mdc +++ b/.cursor/rules/react-typescript-patterns.mdc @@ -260,38 +260,6 @@ export const Polymorphic = ({ ## Hook Patterns -### Custom Hook Structure -```typescript -// Use descriptive names starting with 'use' -export function useFormValidation(schema: z.ZodSchema) { - const [errors, setErrors] = React.useState>({}); - const [isValidating, setIsValidating] = React.useState(false); - - const validate = React.useCallback(async (data: T) => { - setIsValidating(true); - try { - await schema.parseAsync(data); - setErrors({}); - return true; - } catch (error) { - if (error instanceof z.ZodError) { - const fieldErrors = error.errors.reduce((acc, err) => { - const path = err.path.join('.'); - acc[path] = err.message; - return acc; - }, {} as Record); - setErrors(fieldErrors); - } - return false; - } finally { - setIsValidating(false); - } - }, [schema]); - - return { errors, isValidating, validate }; -} -``` - ### React 19 use() Hook for Async Operations ```typescript // ✅ React 19 - use() hook for promises @@ -360,73 +328,6 @@ interface ComponentProps { onError?: (error: Error) => void; } -// Handle async operations properly -const handleSubmit = async (data: FormData) => { - try { - setIsLoading(true); - await onSubmit?.(data); - } catch (error) { - onError?.(error as Error); - } finally { - setIsLoading(false); - } -}; -``` - -## Performance Optimization Patterns - -### Memoization -```typescript -// Memoize expensive calculations -const expensiveValue = React.useMemo(() => { - return computeExpensiveValue(props.data); -}, [props.data]); - -// Memoize callback functions -const handleClick = React.useCallback((id: string) => { - onItemClick?.(id); -}, [onItemClick]); - -// Memoize components when necessary -export const ExpensiveComponent = React.memo(({ data }: Props) => { - return
{/* expensive rendering */}
; -}); -``` - -## Error Boundary Patterns (React 19 Compatible) -```typescript -interface ErrorBoundaryState { - hasError: boolean; - error?: Error; -} - -export class ErrorBoundary extends React.Component< - React.PropsWithChildren<{}>, - ErrorBoundaryState -> { - constructor(props: React.PropsWithChildren<{}>) { - super(props); - this.state = { hasError: false }; - } - - static getDerivedStateFromError(error: Error): ErrorBoundaryState { - return { hasError: true, error }; - } - - componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - console.error('Error caught by boundary:', error, errorInfo); - } - - render() { - if (this.state.hasError) { - return
Something went wrong.
; - } - - return this.props.children; - } -} -``` - ## Naming Conventions - **Components**: PascalCase (`TextField`, `DataTable`) - **Props interfaces**: ComponentName + Props (`TextFieldProps`)