components · diff
git:20260103.9f62587 to git:20260309.80c3cb2
1 added, 235 removed. Audit A to B.
---
description: Component development patterns - Shadcn first, atomic design, Figma parity
globs: src/components/**/*
alwaysApply: false
---
# Component Rules
- Rules for developing UI components. Based on component-guidelines.md and figma-context.md.
-
- ## Core Principles
-
- ### 1. Shadcn First
-
- **ALWAYS check if Shadcn has an equivalent before creating custom components.**
-
- ```bash
- # Check Shadcn for component
- npx shadcn@latest add button --yes
-
- # Then adapt to project structure
- ```
-
- ### 2. Figma Parity
-
- **100% visual match with Figma designs** - no approximations.
-
- - Use exact sizes, colors, spacing from Figma
- - Verify using screenshots or MCP Figma tools
- - Test all states (hover, focus, disabled, active)
-
- ### 3. Atomic Design
-
- ```
- atoms/ → Basic components (Button, Input, Avatar, Badge)
- molecules/ → Atom combinations (InputField, SearchBar, PostCard)
- organisms/ → Complex features (PostFeed, UserProfile, Forms)
- templates/ → Page layouts
- ```
-
- ## File Structure
-
- ```
- src/components/atoms/Button/
- ├── Button.tsx # Main component
- ├── Button.test.tsx # Unit + snapshot tests
- ├── Button.types.ts # Type definitions
- └── index.ts # Exports
- ```
-
- ## Component Template
-
- ```tsx
- import * as React from 'react';
- import { cva, type VariantProps } from 'class-variance-authority';
- import { cn } from '@/libs';
-
- const buttonVariants = cva(
- 'inline-flex items-center justify-center rounded-md text-sm font-medium',
- {
- variants: {
- variant: {
- default: 'bg-primary text-primary-foreground hover:bg-primary/90',
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
- outline: 'border border-input bg-background hover:bg-accent',
- ghost: 'hover:bg-accent hover:text-accent-foreground',
- },
- size: {
- default: 'h-10 px-4 py-2',
- sm: 'h-8 px-3 text-xs',
- lg: 'h-12 px-6',
- icon: 'h-10 w-10',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- }
- );
-
- export interface ButtonProps
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
- VariantProps<typeof buttonVariants> {}
-
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
- ({ className, variant, size, ...props }, ref) => (
- <button
- ref={ref}
- className={cn(buttonVariants({ variant, size, className }))}
- {...props}
- />
- )
- );
- Button.displayName = 'Button';
-
- export { Button, buttonVariants };
- ```
-
- ## Imports
-
- ```tsx
- // ✅ Correct
- import { cn } from '@/libs';
- import { Button } from '@/components/atoms';
-
- // ❌ Wrong
- import { cn } from 'src/lib/utils';
- import { cn } from '../../libs/utils';
- ```
-
- ## Exports
-
- ```tsx
- // src/components/atoms/index.ts
- export * from './Button';
- export * from './Input';
- export * from './Avatar';
- ```
-
- ## Design System Integration
-
- ### Colors
- ```tsx
- // ✅ Use design tokens
- <div className="bg-primary text-primary-foreground" />
- <div className="bg-muted text-muted-foreground" />
- <div className="border-border" />
-
- // ❌ Don't hardcode
- <div style={{ backgroundColor: '#ffffff' }} />
- <div className="bg-[#1a1a1a]" />
- ```
-
- ### Sizing
- ```tsx
- // ✅ Follow Figma scale
- <Avatar className="h-10 w-10" /> // 40px
- <Avatar className="h-8 w-8" /> // 32px
- <Avatar className="h-6 w-6" /> // 24px
-
- // ❌ Arbitrary sizes
- <Avatar className="h-[37px] w-[37px]" />
- ```
-
- ### Spacing
- ```tsx
- // ✅ Use Tailwind spacing scale
- <div className="p-4 gap-2 space-y-4" />
-
- // ❌ Arbitrary spacing
- <div className="p-[13px]" />
- ```
-
- ## Testing Requirements
-
- Every component needs:
-
- 1. **Test file**: `Component.test.tsx`
- 2. **Sanity test**: Renders without errors
- 3. **Functional tests**: Click, hover, interactions
- 4. **Snapshot tests**: All variants/states
-
- ```tsx
- // See rules/component-testing.mdc for details
- describe('Button', () => {
- it('renders with default props', () => {
- render(<Button>Click me</Button>);
- expect(screen.getByRole('button')).toBeInTheDocument();
- });
- });
-
- describe('Button - Snapshots', () => {
- it('matches snapshot for default variant', () => {
- const { container } = render(<Button>Default</Button>);
- expect(container.firstChild).toMatchSnapshot();
- });
- });
- ```
-
- ## Migration Checklist
-
- When migrating/creating a component:
-
- - [ ] Check if Shadcn has equivalent
- - [ ] Analyze Figma design
- - [ ] Install Shadcn if available: `npx shadcn@latest add [component]`
- - [ ] Move to correct atomic level
- - [ ] Adapt imports to use `@/libs`
- - [ ] Implement all Figma variants
- - [ ] Use CVA for variant management
- - [ ] Create tests (unit + snapshot)
- - [ ] Update exports in `atoms/index.ts`
- - [ ] Test build: `npm run build`
- - [ ] Test in browser
-
- ## Common Pitfalls
-
- ### Ref Forwarding
- ```tsx
- // ✅ Forward refs correctly
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
- (props, ref) => <button ref={ref} {...props} />
- );
-
- // ❌ Missing forwardRef
- const Button = (props: ButtonProps) => <button {...props} />;
- ```
-
- ### Test Queries
- ```tsx
- // ✅ Use data-testid for reliable queries
- <Button data-testid="submit-btn">Submit</Button>
- const button = screen.getByTestId('submit-btn');
-
- // ⚠️ Role queries may not work for all components
- const button = screen.getByRole('button'); // May be fragile
- ```
-
- ### Export Updates
- ```tsx
- // ✅ Remember to update atoms/index.ts
- export * from './NewComponent';
-
- // ❌ Forget to export = import errors
- ```
-
- ## Quick Checklist
-
- When creating/modifying components:
-
- - [ ] Shadcn checked first?
- - [ ] File structure follows pattern?
- - [ ] Using `@/` import aliases?
- - [ ] Design tokens (not hardcoded colors)?
- - [ ] Figma sizing/spacing matched?
- - [ ] Tests created (unit + snapshot)?
- - [ ] Exported in index.ts?
- - [ ] Build passes?
-
- ---
-
- **Reference**: `.cursor/docs/component-guidelines.md`, `.cursor/docs/figma-context.md`
+ @docs/components.md