# Component Development Guide

This guide provides comprehensive instructions for developing reusable UI components for the component library. It covers creating accessible, responsive, **SSR-compatible**, and testable components using Radix UI primitives with Tailwind CSS. All components must work across mobile and desktop browsers, support server-side rendering (SSR), and have accessibility as a core requirement.

**Purpose**: This guide is specifically for developers creating components that will be used throughout the application. It is not for generating screens or application features - for that, refer to the [App Framework Documentation](../../README.md).

## Core Principles

### 1. Simplicity First

- Keep components simple and predictable
- Prefer uncontrolled components over controlled ones
- Avoid premature optimization
- Let the browser handle what it does best

### 2. Accessibility Always

- Every component MUST meet WCAG 2.1 AA standards
- Use semantic HTML
- Implement proper ARIA attributes
- Support keyboard navigation
- Provide clear focus indicators
- Test with screen readers

### 3. Mobile-First Responsive

- Design for mobile screens first
- Use Tailwind's responsive utilities
- Ensure 44x44px minimum touch targets
- Test on real devices

### 4. SSR Compatible

- Components MUST render on the server without errors
- Guard all browser API usage with `typeof window !== 'undefined'`
- Use `useEffect` instead of `useLayoutEffect`
- Avoid non-deterministic rendering (no `Math.random()` or `Date.now()` in render)
- Test with `renderToString` to ensure SSR safety

### 5. Testable by Design

- Each component must have clear test cases
- Props should be predictable
- Avoid hidden complexity
- Test accessibility, SSR compatibility, and functionality

## Tailwind CSS Guidelines

### Typography

For typography classes and usage, see the **[SCREENS.md](../../../SCREENS.md)** documentation which contains the complete typography system with semantic classes like `headline-lg` and `text-base`.

**IMPORTANT Typography Guidelines for Components:**

1. **Prefer the default text size** - The responsive typography system automatically handles text sizing (16px mobile → 14px desktop)
2. **Almost NEVER use `text-sm`** - Only use text size classes when they truly improve the design
3. **Use spacing instead of smaller text** to create visual hierarchy

**Appropriate uses of `text-sm` in components:**

- Timestamps and dates ("Updated 2 hours ago")
- Status badges and pills
- Small metadata that's truly secondary

### Accent Color System

The component library uses a customizable accent color system defined in `app.manifest.ts`. This provides a consistent theming approach across all components with automatic utility class generation.

#### Available Accent Utilities

**Backgrounds**:

- `bg-accent` - Full accent color
- `bg-accent/10` - 10% opacity
- `bg-accent/20` - 20% opacity
- `bg-accent/90` - 90% opacity

**Text Colors**:

- `text-accent` - Full accent color
- `text-accent/60` - 60% opacity
- `text-accent/80` - 80% opacity

**Hover States**:

- `hover:bg-accent/20` - Background on hover
- `hover:bg-accent/90` - Darker background on hover
- `hover:text-accent/80` - Text color on hover

**Focus States**:

- `focus:ring-accent` - Focus ring color
- `focus:ring-accent/50` - Semi-transparent focus ring

### Design Tokens via Tailwind Config

```js
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      spacing: {
        // Consistent spacing scale
        xs: "0.25rem",
        sm: "0.5rem",
        md: "1rem",
        lg: "1.5rem",
        xl: "2rem",
      },
      borderRadius: {
        // Consistent radius scale
        sm: "0.25rem",
        md: "0.5rem",
        lg: "0.75rem",
      },
    },
  },
};
```

### Component Class Organization

```tsx
// Order: Layout → Spacing → Typography → Colors → States
className =
  "flex items-center gap-4 p-4 text-base text-gray-900 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2";
```

## Radix UI Integration

### Basic Component Structure

```tsx
import * as RadixPrimitive from "@radix-ui/react-[primitive]";
import { cn } from "../utils/cn";

interface ComponentProps
  extends React.ComponentPropsWithoutRef<typeof RadixPrimitive.Root> {
  // Additional props if needed
}

export const Component = React.forwardRef<
  React.ElementRef<typeof RadixPrimitive.Root>,
  ComponentProps
>(({ className, ...props }, ref) => (
  <RadixPrimitive.Root
    ref={ref}
    className={cn("base-tailwind-classes", className)}
    {...props}
  />
));

Component.displayName = "Component";
```

### Utility Function for Class Names

```tsx
// utils/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
```

### Interactive Elements and Cursor Styling

**IMPORTANT: All interactive elements MUST have proper cursor styles:**

#### Cursor Guidelines:

- ✅ **Always add `cursor-pointer`** to clickable elements (buttons, links, tabs, etc.)
- ✅ **Use `disabled:cursor-not-allowed`** for disabled interactive elements
- ✅ **Apply `cursor-move`** for draggable elements
- ✅ **Use `cursor-text`** for text input areas (usually handled by browser defaults)

## Accessibility Checklist

### Every Component Must:

- [ ] Be keyboard navigable (Tab, Arrow keys, Enter, Escape)
- [ ] Have proper ARIA labels and roles
- [ ] Show clear focus indicators
- [ ] Support screen readers
- [ ] Have sufficient color contrast (4.5:1 for normal text)
- [ ] Provide error feedback accessibly
- [ ] Work without JavaScript where possible

### Testing Accessibility:

```tsx
// Example accessibility test
it("meets accessibility standards", async () => {
  const { container } = render(<Component />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});
```

## Responsive Design Patterns

### Mobile-First Classes

```tsx
// Stack on mobile, side-by-side on larger screens
<div className="flex flex-col md:flex-row gap-4">

// Smaller touch targets on desktop
<button className="p-4 md:p-2">

// Full width on mobile, constrained on desktop
<div className="w-full md:w-auto">

// Hide on mobile, show on desktop
<span className="hidden md:inline">

// Different spacing for emphasis
<div className="space-y-2 md:space-y-4 lg:space-y-6">
```

### Touch-Friendly Sizing

```tsx
// Minimum 44x44px touch targets
<button className="min-h-[44px] min-w-[44px] p-3">

// Larger click areas on mobile
<a className="block py-3 md:py-2">
```

## Testing Guidelines

### Component Test Structure

```tsx
describe("ComponentName", () => {
  // Rendering tests
  it("renders with default props", () => {});
  it("renders with custom className", () => {});

  // Interaction tests
  it("handles user interactions", () => {});
  it("supports keyboard navigation", () => {});

  // State tests
  it("manages internal state correctly", () => {});

  // Accessibility tests
  it("has proper ARIA attributes", () => {});
  it("announces changes to screen readers", () => {});

  // Edge cases
  it("handles empty/null data gracefully", () => {});
  it("works when disabled", () => {});
});
```

## Visual Testing with Chromatic

### MANDATORY: Visual Story Requirements

Every visual story (@src/templates) MUST have exactly ONE story for visual regression testing:

- **Tags**: MUST include `tags: ['visual-test']` for easy filtering
- **Parameters**: MUST include `parameters: { chromatic: { disableSnapshot: false } }`

Note: Visual snapshots are disabled globally by default in `.storybook/preview.tsx`. Only stories with `disableSnapshot: false` will be captured for visual regression testing.

### What to Include in Visual Story

- All variants/moods
- All sizes
- All interactive states (disabled, loading, error)
- Common prop combinations
- With/without optional elements (icons, labels)

### What NOT to Do

❌ Create multiple visual test stories
❌ Forget to add the `visual-test` tag
❌ Show empty/minimal states
❌ Test animations or transitions

### Chromatic Testing Modes

Visual stories are automatically tested in 4 modes:

- **light-desktop**: Light theme on desktop viewport
- **dark-desktop**: Dark theme on desktop viewport
- **light-mobile**: Light theme on mobile viewport
- **dark-mobile**: Dark theme on mobile viewport

No additional configuration needed - these modes are set globally in `.storybook/preview.tsx`.

### Testing Utilities

```tsx
// Custom render with providers if needed
function renderWithProviders(ui: React.ReactElement) {
  return render(ui);
}

// Accessibility testing helper
async function expectNoA11yViolations(container: HTMLElement) {
  const results = await axe(container);
  expect(results).toHaveNoViolations();
}
```

## SSR (Server-Side Rendering) Requirements

### All Components MUST Be SSR-Safe

Every component in this library must work in server-side rendering environments like Next.js, Remix, and other SSR frameworks.

#### SSR Guidelines:

1. **Guard Browser APIs**
```tsx
// ❌ Bad - Will crash on server
const width = window.innerWidth;

// ✅ Good - Safe for SSR
const width = typeof window !== 'undefined' ? window.innerWidth : 0;
```

2. **Use useEffect, Not useLayoutEffect**
```tsx
// ❌ Bad - useLayoutEffect runs before paint, not available on server
useLayoutEffect(() => {
  // ...
}, []);

// ✅ Good - useEffect is SSR-safe
useEffect(() => {
  // ...
}, []);
```

3. **Avoid Non-Deterministic Rendering**
```tsx
// ❌ Bad - Different output on server vs client
<div key={Math.random()}>...</div>

// ✅ Good - Consistent between server and client
<div key={item.id}>...</div>
```

4. **Handle Client-Only Features Gracefully**
```tsx
// For features that only work on client
if (typeof window !== 'undefined') {
  // Client-only code here
}

// Or provide a server-side fallback
const MapComponent = typeof window !== 'undefined'
  ? ActualMap
  : () => <div>Map loads on client</div>;
```

### SSR Testing is MANDATORY

Every component MUST have a separate `.ssr.test.tsx` file that runs in Node environment. SSR tests must verify two things:

1. **The component doesn't throw errors during server-side rendering**
2. **The component actually renders the expected content**

```tsx
// component-name.ssr.test.tsx
import { describe, it, expect } from 'vitest';
import React from 'react';
import { renderSSR } from '@/test/ssr/ssr-render';
import { ComponentName } from './component-name';

describe('ComponentName - SSR', () => {
  it('renders without throwing in Node environment', () => {
    // This test runs in Node where window/document are undefined
    // If the component accesses browser APIs, this will throw
    expect(() => {
      renderSSR(<ComponentName />);
    }).not.toThrow();
  });

  it('renders actual content', () => {
    // IMPORTANT: Verify the component actually renders expected content
    // Don't just check that it doesn't throw - verify it produces HTML
    const html = renderSSR(<ComponentName>Test Content</ComponentName>);

    // Check that the expected content is in the rendered HTML
    expect(html).toContain('Test Content');

    // For components without children, check for key attributes or text
    // Example for a button with label:
    // const html = renderSSR(<Button label="Click me" />);
    // expect(html).toContain('Click me');
  });
});
```

**Important SSR Test Requirements**:

1. **Always verify content rendering** - Don't just test that components don't throw. Verify they actually render the expected HTML content.
2. **Test with realistic props** - Use props that would be used in production to ensure proper SSR behavior.
3. **Check for key content** - Verify that important text, labels, or attributes are present in the rendered HTML.
4. **These tests run in pure Node environment** where browser globals don't exist. Any component that accesses `window`, `document`, etc. will fail immediately.

## Browser Compatibility

### Required Support:

- Chrome/Edge (latest 2 versions)
- Firefox (latest 2 versions)
- Safari (latest 2 versions)
- Chrome Mobile
- Safari iOS
- **Server-side Node.js environments** (for SSR)

### Progressive Enhancement:

- Use CSS features with fallbacks
- Test without JavaScript
- Ensure forms work with browser autofill
- Support browser zoom (up to 200%)
- **Components must render meaningful content on server**

## Component Import Structure

### File Structure Requirements

**⚠️ CRITICAL: ALL FILES MUST USE LOWERCASE KEBAB-CASE ⚠️**

- **NEVER use uppercase in file names** (❌ `Card.tsx`, ✅ `card.tsx`)
- Component folder name and main file name MUST match exactly
- **All component files MUST use kebab-case** (e.g., `button.tsx`, `input.tsx`, `dialog.tsx`)
- Component folder names MUST use kebab-case (e.g., `button/`, `input/`, `dialog/`)
- Test files MUST use kebab-case (e.g., `button.test.tsx`, ❌ NOT `Button.test.tsx`)
- Story files MUST use kebab-case (e.g., `button.stories.tsx`, ❌ NOT `Button.stories.tsx`)
- Example: `button` folder contains `button.tsx` (❌ NOT `Button.tsx`)
- No index.ts re-export files - components export directly from their main file
- Each component has exactly one export from its main file

## Component Documentation Template

````markdown
## ComponentName

Brief description of what the component does.

### Usage

\```tsx
import { ComponentName } from '@/components/component-name';

// Basic usage
<ComponentName />

// With props
<ComponentName prop="value" />
\```

### Props

| Prop | Type | Default | Description |
| ---- | ---- | ------- | ----------- |
| ...  | ...  | ...     | ...         |

### Accessibility

- Keyboard support: [describe keys]
- Screen reader: [describe announcements]
- ARIA: [list ARIA attributes used]

### Examples

[Show 2-3 common use cases]
````

## Accent Color Usage Guidelines

### Primary Actions

- Use `bg-accent` with white text for primary buttons and CTAs
- Apply `hover:bg-accent/90` for hover states
- Use `focus:ring-accent` for focus indicators

### Secondary Actions

- Use `bg-accent/10` with `text-accent` for secondary buttons
- Apply `hover:bg-accent/20` for hover states
- Use `focus:ring-accent/50` for subtle focus rings

### Links and Navigation

- Use `text-accent` for links
- Apply `hover:text-accent/80` for hover states
- Consider `bg-accent/10` for active navigation items

### Status Indicators

- Active/Selected: `bg-accent/10 text-accent`
- Important badges: `bg-accent text-white`
- Subtle indicators: `text-accent/60`

### Form Elements

- Focus states: `focus:border-accent focus:ring-accent`
- Selected options: `bg-accent/10`
- Active checkboxes/radios: `bg-accent`

## Best Practices Summary

0. **Test Driven Development**: Always start with failing test and then do the actual implementation.
1. **Test Everything**: Write tests for all use cases, not just the happy path
2. **Keep It Simple**: Prefer uncontrolled components, let the browser handle native behavior
3. **Accessibility First**: Every decision should consider keyboard and screen reader users
4. **Mobile-First**: Design for touch and small screens, then enhance
5. **Use Tailwind**: Leverage utility classes for consistent, maintainable styles
6. **Consistent Theming**: Use accent colors consistently across all components
7. **Document Clearly**: Help other developers understand and use your components. Write a .md file explaining how to use it
8. **Update `src/components/ALL_COMPONENTS`**: Add or update the component reference to that file once you're done.
9. **Dialog Compliance**: ALL dialogs MUST use the Dialog component from `@/components/dialog` for consistent mobile experience

## Limitations & notes

You can ONLY add and edit files in `src/components`.
Use `vitest` as test runner and `vitest-axe` for a11y.
Always create a new directory inside `src/components` for every new component.

### Component Creation Checklist

When creating a new component:

1. [ ] Create folder with **lowercase** kebab-case component name (e.g., `modal/`)
2. [ ] Create main file with **lowercase** kebab-case matching folder name (e.g., `modal.tsx`, ❌ NOT `Modal.tsx`)
3. [ ] Ensure ALL component files use **lowercase** kebab-case (component, test, story, types files)
4. [ ] Write component tests with **lowercase** kebab-case filename (e.g., `modal.test.tsx`)
5. [ ] **Create separate SSR test file** (e.g., `modal.ssr.test.tsx`)
6. [ ] Write story files with **lowercase** kebab-case filename (e.g., `modal.stories.tsx`)
7. [ ] **Verify SSR tests pass** (`npm run test:ssr`)
8. [ ] Create README.md documentation
9. [ ] Update `src/components/ALL_COMPONENTS.md`
