npm package: @loudpacks/theme-web
Description: Web/Next.js theme picker modal with animations and live theme previews
Author: Noted
Latest version: 0.2.2

# @loudpacks/theme-web

Web/Next.js theme picker modal with smooth animations and live theme previews powered by Framer Motion.

## Installation

```bash
npm install @loudpacks/theme-web @loudpacks/theme-core
# or
pnpm add @loudpacks/theme-web @loudpacks/theme-core
# or
yarn add @loudpacks/theme-web @loudpacks/theme-core
```

## Requirements

- React ≥18.0.0
- React DOM ≥18.0.0
- **Tailwind CSS** ≥3.0.0 (required for styling)

## Features

- 🎨 Live theme previews with light/dark mode toggle
- ✨ Smooth animations powered by Framer Motion
- ⌨️ Keyboard navigation (Escape to close)
- 🔒 Body scroll lock when modal is open
- 🎯 Portal-based rendering for proper z-index handling
- 🌐 SSR-friendly with Next.js App Router support
- 📱 Responsive grid layout
- ♿ Accessible with ARIA attributes
- ✅ Fully tested with 25+ test cases

## Usage

### Basic Setup

```tsx
'use client'; // Required for Next.js App Router

import { ThemePickerModal } from '@loudpacks/theme-web';
import type { ThemeDefinitions } from '@loudpacks/theme-core';
import { useState } from 'react';

const themes: ThemeDefinitions = {
  monochrome: {
    displayName: 'Monochrome',
    description: 'Clean monochrome design',
    light: {
      background: '#fafafa',
      surface: '#ffffff',
      text: '#1a1a1a',
      // ... other color definitions
    },
    dark: {
      background: '#121212',
      surface: '#1e1e1e',
      text: '#f5f5f5',
      // ... other color definitions
    },
  },
  // ... more themes
};

function App() {
  const [showPicker, setShowPicker] = useState(false);
  const [currentTheme, setCurrentTheme] = useState('monochrome');

  return (
    <>
      <button onClick={() => setShowPicker(true)}>
        Change Theme
      </button>

      <ThemePickerModal
        visible={showPicker}
        onClose={() => setShowPicker(false)}
        currentTheme={currentTheme}
        onSelectTheme={setCurrentTheme}
        themes={themes}
        initialPreviewMode="light"
        config={{
          showPreviewToggle: true,
          closeOnSelect: true,
          animationDuration: 300,
        }}
      />
    </>
  );
}
```

### With System Color Scheme Detection

```tsx
import { useSystemColorScheme } from '@loudpacks/theme-core';

function App() {
  const systemScheme = useSystemColorScheme();
  const [colorScheme, setColorScheme] = useState<'light' | 'dark'>(
    systemScheme || 'light'
  );

  return (
    <ThemePickerModal
      // ... other props
      initialPreviewMode={colorScheme}
    />
  );
}
```

### With LocalStorage Persistence

```tsx
import { useEffect } from 'react';

function App() {
  const [currentTheme, setCurrentTheme] = useState('monochrome');

  // Load saved theme on mount
  useEffect(() => {
    const saved = localStorage.getItem('app_theme');
    if (saved) setCurrentTheme(saved);
  }, []);

  const handleSelectTheme = (themeKey: string) => {
    setCurrentTheme(themeKey);
    localStorage.setItem('app_theme', themeKey);
  };

  return (
    <ThemePickerModal
      // ... other props
      onSelectTheme={handleSelectTheme}
    />
  );
}
```

## Components

### ThemePickerModal

The main modal component for selecting themes with animations.

**Props:**
- `visible` (boolean): Controls modal visibility
- `onClose` (() => void): Callback when modal is closed
- `currentTheme` (string): Currently selected theme key
- `onSelectTheme` ((themeKey: string) => void): Callback when theme is selected
- `themes` (ThemeDefinitions): Object containing all available themes
- `initialPreviewMode` ('light' | 'dark', optional): Initial preview mode
- `headerTitle` (string, optional): Modal header text (default: "Choose Theme")
- `className` (string, optional): Additional CSS classes for the modal
- `config` (object, optional): Configuration options

### ThemeCard

Individual theme preview card component with hover effects.

**Props:**
- `themeKey` (string): Theme identifier
- `theme` (ThemeMetadata): Theme data
- `isSelected` (boolean): Whether this theme is currently selected
- `previewMode` ('light' | 'dark'): Which color scheme to preview
- `onSelect` (() => void): Callback when card is clicked

## Configuration Options

```typescript
{
  showPreviewToggle: boolean;     // Show light/dark toggle (default: true)
  closeOnSelect: boolean;         // Close modal after selection (default: false)
  gridColumns: number;            // Number of columns (default: 2)
  animationDuration: number;      // Animation duration in ms (default: 250)
}
```

## Styling

The component uses Tailwind CSS classes for styling. **Tailwind CSS is required** for proper styling.

### Tailwind Setup

If you don't have Tailwind CSS installed, follow the [official installation guide](https://tailwindcss.com/docs/installation).

For Next.js projects:

```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

Ensure your `tailwind.config.js` includes:

```js
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx}',
    './pages/**/*.{js,ts,jsx,tsx}',
    './components/**/*.{js,ts,jsx,tsx}',
    './node_modules/@loudpacks/theme-web/**/*.{js,mjs}', // Include theme-web
  ],
  darkMode: 'class', // or 'media'
  // ... rest of config
}
```

### Styling Features

The modal renders via React Portal and includes:

- Backdrop with opacity fade
- Modal with scale + opacity animation
- Responsive grid layout
- Dark mode support via Tailwind's `dark:` variant
- Hover effects on theme cards
- Focus states for accessibility

## Examples

See the [examples](./examples) directory for complete working examples including:
- Basic Next.js setup
- LocalStorage persistence
- System color scheme detection
- CSS variable integration

## License

MIT